From 5d7de2f6c259f9db674d23d67e946d24b23efdc2 Mon Sep 17 00:00:00 2001 From: Jordan <51442161+JordanSh@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:49:09 +0300 Subject: [PATCH 01/25] [Cloud Security] Adding unprivileged state to benchmark page (#195172) --- .../pages/benchmarks/benchmarks.test.tsx | 22 +++++++++++++++++++ .../public/pages/benchmarks/benchmarks.tsx | 13 +++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx index e11095bb622f0..4e52484dffbea 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx @@ -18,6 +18,7 @@ import { useCspSetupStatusApi } from '@kbn/cloud-security-posture/src/hooks/use_ import { useCspIntegrationLink } from '../../common/navigation/use_csp_integration_link'; import { ERROR_STATE_TEST_SUBJECT } from './benchmarks_table'; import { useLicenseManagementLocatorApi } from '../../common/api/use_license_management_locator_api'; +import { NO_FINDINGS_STATUS_TEST_SUBJ } from '../../components/test_subjects'; jest.mock('./use_csp_benchmark_integrations'); jest.mock('@kbn/cloud-security-posture/src/hooks/use_csp_setup_status_api'); @@ -85,6 +86,27 @@ describe('', () => { expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument(); }); + it('renders unprivileged state ', () => { + (useCspSetupStatusApi as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: { + cspm: { status: 'unprivileged' }, + kspm: { status: 'unprivileged' }, + }, + }) + ); + + renderBenchmarks( + createReactQueryResponse({ + status: 'success', + data: { total: 1, items: [createCspBenchmarkIntegrationFixture()] }, + }) + ); + + expect(screen.getByTestId(NO_FINDINGS_STATUS_TEST_SUBJ.UNPRIVILEGED)).toBeInTheDocument(); + }); + it('renders the benchmarks table', () => { renderBenchmarks( createReactQueryResponse({ diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx index dd6b8ce45318b..e3e4efc0db371 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx @@ -163,9 +163,14 @@ export const Benchmarks = () => { const getSetupStatus = useCspSetupStatusApi({ refetchInterval: NO_FINDINGS_STATUS_REFRESH_INTERVAL_MS, }); - const showConfigurationInstallPrompt = - getSetupStatus.data?.kspm?.status === 'not-installed' && - getSetupStatus.data?.cspm?.status === 'not-installed'; + + const kspmStatus = getSetupStatus.data?.kspm?.status; + const cspmStatus = getSetupStatus.data?.cspm?.status; + + const showNoFindingsStates = + (kspmStatus === 'not-installed' && cspmStatus === 'not-installed') || + cspmStatus === 'unprivileged' || + kspmStatus === 'unprivileged'; return ( @@ -182,7 +187,7 @@ export const Benchmarks = () => { bottomBorder /> - {showConfigurationInstallPrompt ? ( + {showNoFindingsStates ? ( ) : ( <> From 10271a2e1fb5860a8a6d3d3e3f072d5b67a3f63f Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Mon, 7 Oct 2024 11:36:27 -0600 Subject: [PATCH 02/25] [Dashboard] Await new services in exported listing table (#195277) Closes https://github.com/elastic/kibana/issues/194733 ## Summary In https://github.com/elastic/kibana/pull/193644, I forgot to remove references to the old `servicesReady` promise - this caused an issue where it never resolved `true`, so anywhere that depended on this would be stuck in a loading state. This PR fixes this by replacing all instances of `servicesReady` with the new `untilPluginStartServicesReady` promise. Specifically, this fixes the exported `DashboardListingTable` that the Security page uses: - **Before:** https://github.com/user-attachments/assets/78fc8ad8-7bff-43bf-95ec-d52f4da91371 - **After:** https://github.com/user-attachments/assets/af1be9d3-9af5-4a30-9b5d-bc4352214a97 ### 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) --- src/plugins/dashboard/public/dashboard_listing/index.tsx | 6 +++--- src/plugins/dashboard/public/plugin.tsx | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/plugins/dashboard/public/dashboard_listing/index.tsx b/src/plugins/dashboard/public/dashboard_listing/index.tsx index a6996008f9b35..0fa1df0be5220 100644 --- a/src/plugins/dashboard/public/dashboard_listing/index.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/index.tsx @@ -7,10 +7,10 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { Suspense } from 'react'; import { EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; +import React, { Suspense } from 'react'; -import { servicesReady } from '../plugin'; +import { untilPluginStartServicesReady } from '../services/kibana_services'; import { DashboardListingProps } from './types'; const ListingTableLoadingIndicator = () => { @@ -20,7 +20,7 @@ const ListingTableLoadingIndicator = () => { const LazyDashboardListing = React.lazy(() => (async () => { const modulePromise = import('./dashboard_listing_table'); - const [module] = await Promise.all([modulePromise, servicesReady]); + const [module] = await Promise.all([modulePromise, untilPluginStartServicesReady()]); return { default: module.DashboardListingTable, diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index c46dcbc3e4139..b1d60adc84d0f 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -138,7 +138,6 @@ export interface DashboardStart { } export let resolveServicesReady: () => void; -export const servicesReady = new Promise((resolve) => (resolveServicesReady = resolve)); export class DashboardPlugin implements From 484f95e7335a5b8d8df0d8c321d2b2e74db668a8 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:56:12 -0400 Subject: [PATCH 03/25] [Security Solution] Makes `rule_source` a required field in `RuleResponse` (#193636) **Resolves https://github.com/elastic/kibana/issues/180270** ## Summary Sets `rule_source` to be a required field in the `RuleResponse` type ### Checklist Delete any items that are not applicable to this PR. - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### 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: Elastic Machine Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- oas_docs/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 + .../model/rule_schema/rule_response_schema.mock.ts | 1 + .../model/rule_schema/rule_response_schema.test.ts | 9 +++++---- .../model/rule_schema/rule_schemas.gen.ts | 2 +- .../model/rule_schema/rule_schemas.schema.yaml | 1 + .../rule_management/bulk_crud/response_schema.test.ts | 2 +- ...olution_detections_api_2023_10_31.bundled.schema.yaml | 1 + ...olution_detections_api_2023_10_31.bundled.schema.yaml | 1 + .../rule_details/legacy_url_conflict_callout.test.tsx | 1 + .../pages/rule_details/use_redirect_legacy_url.test.ts | 1 + .../pages/rule_details/use_rule_details_tabs.test.tsx | 1 + .../components/rule_details/json_diff/json_diff.test.tsx | 4 +++- .../components/rule_details/rule_diff_tab.tsx | 4 ++++ .../detection_engine/rule_management/logic/mock.ts | 3 +++ .../rule_management/logic/use_rule_with_fallback.test.ts | 1 + .../components/rules_table/__mocks__/mock.ts | 1 + .../converters/common_params_camel_to_snake.ts | 9 +++++++++ .../converters/internal_rule_to_api_response.ts | 4 ++-- .../converters/normalize_rule_params.ts | 6 +++++- .../detection_engine/rule_types/__mocks__/es_results.ts | 1 + 23 files changed, 47 insertions(+), 10 deletions(-) diff --git a/oas_docs/output/kibana.serverless.staging.yaml b/oas_docs/output/kibana.serverless.staging.yaml index 4ddb7c4876eaf..8bd9bd198e5e1 100644 --- a/oas_docs/output/kibana.serverless.staging.yaml +++ b/oas_docs/output/kibana.serverless.staging.yaml @@ -44211,6 +44211,7 @@ components: - id - rule_id - immutable + - rule_source - updated_at - updated_by - created_at diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 4ddb7c4876eaf..8bd9bd198e5e1 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -44211,6 +44211,7 @@ components: - id - rule_id - immutable + - rule_source - updated_at - updated_by - created_at diff --git a/oas_docs/output/kibana.staging.yaml b/oas_docs/output/kibana.staging.yaml index 42a30dca9cb91..aba85f8c82ca9 100644 --- a/oas_docs/output/kibana.staging.yaml +++ b/oas_docs/output/kibana.staging.yaml @@ -52949,6 +52949,7 @@ components: - id - rule_id - immutable + - rule_source - updated_at - updated_by - created_at diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 42a30dca9cb91..aba85f8c82ca9 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -52949,6 +52949,7 @@ components: - id - rule_id - immutable + - rule_source - updated_at - updated_by - created_at diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.mock.ts index c605436576995..59b09533a6e14 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.mock.ts @@ -47,6 +47,7 @@ const getResponseBaseParams = (anchorDate: string = ANCHOR_DATE): SharedResponse risk_score: 55, risk_score_mapping: [], rule_id: 'query-rule-id', + rule_source: { type: 'internal' }, interval: '5m', exceptions_list: getListArrayMock(), related_integrations: [], diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts index 30fe84514e05a..9546ab3a59b09 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts @@ -266,12 +266,13 @@ describe('rule_source', () => { expect(result.data).toEqual(payload); }); - test('it should validate a rule with "rule_source" set to undefined', () => { + test('it should not validate a rule with "rule_source" set to undefined', () => { const payload = getRulesSchemaMock(); - payload.rule_source = undefined; + // @ts-expect-error + delete payload.rule_source; const result = RuleResponse.safeParse(payload); - expectParseSuccess(result); - expect(result.data).toEqual(payload); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"rule_source: Required"`); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts index a723eb8e7da89..da4661ae8464c 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts @@ -160,7 +160,7 @@ export const ResponseFields = z.object({ id: RuleObjectId, rule_id: RuleSignatureId, immutable: IsRuleImmutable, - rule_source: RuleSource.optional(), + rule_source: RuleSource, updated_at: z.string().datetime(), updated_by: z.string(), created_at: z.string().datetime(), diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml index ca2f325c8f713..d8aba232c26f9 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml @@ -203,6 +203,7 @@ components: - id - rule_id - immutable + - rule_source - updated_at - updated_by - created_at diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/response_schema.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/response_schema.test.ts index 2d4af1c18f6d1..fb03c9c4b18ee 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/response_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/response_schema.test.ts @@ -46,7 +46,7 @@ describe('Bulk CRUD rules response schema', () => { const result = BulkCrudRulesResponse.safeParse(payload); expectParseError(result); expect(stringifyZodError(result.error)).toMatchInlineSnapshot( - `"0.name: Required, 0.error: Required, 0: Unrecognized key(s) in object: 'author', 'created_at', 'updated_at', 'created_by', 'description', 'enabled', 'false_positives', 'from', 'immutable', 'references', 'revision', 'severity', 'severity_mapping', 'updated_by', 'tags', 'to', 'threat', 'version', 'output_index', 'max_signals', 'risk_score', 'risk_score_mapping', 'interval', 'exceptions_list', 'related_integrations', 'required_fields', 'setup', 'throttle', 'actions', 'building_block_type', 'note', 'license', 'outcome', 'alias_target_id', 'alias_purpose', 'timeline_id', 'timeline_title', 'meta', 'rule_name_override', 'timestamp_override', 'timestamp_override_fallback_disabled', 'namespace', 'investigation_fields', 'query', 'type', 'language', 'index', 'data_view_id', 'filters', 'saved_id', 'response_actions', 'alert_suppression'"` + `"0.name: Required, 0.error: Required, 0: Unrecognized key(s) in object: 'author', 'created_at', 'updated_at', 'created_by', 'description', 'enabled', 'false_positives', 'from', 'immutable', 'references', 'revision', 'severity', 'severity_mapping', 'updated_by', 'tags', 'to', 'threat', 'version', 'output_index', 'max_signals', 'risk_score', 'risk_score_mapping', 'rule_source', 'interval', 'exceptions_list', 'related_integrations', 'required_fields', 'setup', 'throttle', 'actions', 'building_block_type', 'note', 'license', 'outcome', 'alias_target_id', 'alias_purpose', 'timeline_id', 'timeline_title', 'meta', 'rule_name_override', 'timestamp_override', 'timestamp_override_fallback_disabled', 'namespace', 'investigation_fields', 'query', 'type', 'language', 'index', 'data_view_id', 'filters', 'saved_id', 'response_actions', 'alert_suppression'"` ); }); diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml index cdff061d94f22..8fca765f4fb3f 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml @@ -4890,6 +4890,7 @@ components: - id - rule_id - immutable + - rule_source - updated_at - updated_by - created_at diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml index 4b7d9bed6e416..38b419972a681 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml @@ -4043,6 +4043,7 @@ components: - id - rule_id - immutable + - rule_source - updated_at - updated_by - created_at diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/legacy_url_conflict_callout.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/legacy_url_conflict_callout.test.tsx index 5334143f27047..879a4570e6d03 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/legacy_url_conflict_callout.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/legacy_url_conflict_callout.test.tsx @@ -114,4 +114,5 @@ const mockRule: Rule = { related_integrations: [], required_fields: [], setup: '', + rule_source: { type: 'internal' }, }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_redirect_legacy_url.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_redirect_legacy_url.test.ts index e706f5fda4b39..2e005cd7ca03b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_redirect_legacy_url.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_redirect_legacy_url.test.ts @@ -116,4 +116,5 @@ const mockRule: Rule = { related_integrations: [], required_fields: [], setup: '', + rule_source: { type: 'internal' }, }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.test.tsx index cf4446c7fea42..1dec9aa36d21a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.test.tsx @@ -53,6 +53,7 @@ const mockRule: Rule = { related_integrations: [], required_fields: [], setup: '', + rule_source: { type: 'internal' }, }; describe('useRuleDetailsTabs', () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/json_diff.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/json_diff.test.tsx index 2a769e4be87d4..58ee6b56528a5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/json_diff.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/json_diff.test.tsx @@ -179,7 +179,7 @@ describe('Rule upgrade workflow: viewing rule changes in JSON diff view', () => }); describe('Technical properties should not be included in preview', () => { - it.each(['revision', 'created_at', 'created_by', 'updated_at', 'updated_by'])( + it.each(['revision', 'created_at', 'created_by', 'updated_at', 'updated_by', 'rule_source'])( 'Should not include "%s" in preview', (property) => { const oldRule: RuleResponse = { @@ -190,6 +190,7 @@ describe('Rule upgrade workflow: viewing rule changes in JSON diff view', () => created_by: 'mockUserOne', updated_at: '01/01/2024T00:00:000z', updated_by: 'mockUserTwo', + rule_source: { type: 'internal' }, }; const newRule: RuleResponse = { @@ -200,6 +201,7 @@ describe('Rule upgrade workflow: viewing rule changes in JSON diff view', () => created_by: 'mockUserOne', updated_at: '02/02/2024T00:00:001z', updated_by: 'mockUserThree', + rule_source: { type: 'external', is_customized: true }, }; render(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_diff_tab.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_diff_tab.tsx index dd6d0417d3bb6..c2bf9ffa29098 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_diff_tab.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_diff_tab.tsx @@ -57,6 +57,10 @@ const HIDDEN_PROPERTIES: Array = [ 'updated_by', 'created_at', 'created_by', + /* + * Another technical property that is used for logic under the hood the user doesn't need to be aware of + */ + 'rule_source', ]; const sortAndStringifyJson = (jsObject: Record): string => diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/mock.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/mock.ts index b8aba73818ac3..616469f03c1ba 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/mock.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/mock.ts @@ -41,6 +41,7 @@ export const savedRuleMock: RuleResponse = { references: [], related_integrations: [], required_fields: [], + rule_source: { type: 'internal' }, setup: '', severity: 'high', severity_mapping: [], @@ -99,6 +100,7 @@ export const rulesMock: FetchRulesResponse = { version: 1, revision: 1, exceptions_list: [], + rule_source: { type: 'internal' }, }, { actions: [], @@ -138,6 +140,7 @@ export const rulesMock: FetchRulesResponse = { version: 1, revision: 1, exceptions_list: [], + rule_source: { type: 'internal' }, }, ], }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_with_fallback.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_with_fallback.test.ts index 81438f3708623..666a8e9fddf3e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_with_fallback.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_with_fallback.test.ts @@ -118,6 +118,7 @@ const getMockRule = (overwrites: Pick): Rule => ({ updated_by: 'elastic', related_integrations: [], required_fields: [], + rule_source: { type: 'internal' }, setup: '', ...overwrites, }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts index 0de6e5d1e0844..70d5ee6b6038f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts @@ -94,6 +94,7 @@ export const mockRule = (id: string): SavedQueryRule => ({ version: 1, revision: 1, exceptions_list: [], + rule_source: { type: 'internal' }, }); export const mockRuleWithEverything = (id: string): RuleResponse => ({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/converters/common_params_camel_to_snake.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/converters/common_params_camel_to_snake.ts index 6f98230043e74..f86abd4f08d8d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/converters/common_params_camel_to_snake.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/converters/common_params_camel_to_snake.ts @@ -5,9 +5,11 @@ * 2.0. */ +import snakecaseKeys from 'snakecase-keys'; import { convertObjectKeysToSnakeCase } from '../../../../../../utils/object_case_converters'; import type { BaseRuleParams } from '../../../../rule_schema'; import { migrateLegacyInvestigationFields } from '../../../utils/utils'; +import type { NormalizedRuleParams } from './normalize_rule_params'; export const commonParamsCamelToSnake = (params: BaseRuleParams) => { return { @@ -45,3 +47,10 @@ export const commonParamsCamelToSnake = (params: BaseRuleParams) => { setup: params.setup ?? '', }; }; + +export const normalizedCommonParamsCamelToSnake = (params: NormalizedRuleParams) => { + return { + ...commonParamsCamelToSnake(params), + rule_source: snakecaseKeys(params.ruleSource, { deep: true }), + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/converters/internal_rule_to_api_response.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/converters/internal_rule_to_api_response.ts index 336cd8fae0405..5c53eb2c951a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/converters/internal_rule_to_api_response.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/converters/internal_rule_to_api_response.ts @@ -19,7 +19,7 @@ import { transformToActionFrequency, } from '../../../normalization/rule_actions'; import { typeSpecificCamelToSnake } from './type_specific_camel_to_snake'; -import { commonParamsCamelToSnake } from './common_params_camel_to_snake'; +import { normalizedCommonParamsCamelToSnake } from './common_params_camel_to_snake'; import { normalizeRuleParams } from './normalize_rule_params'; export const internalRuleToAPIResponse = ( @@ -58,7 +58,7 @@ export const internalRuleToAPIResponse = ( enabled: rule.enabled, revision: rule.revision, // Security solution shared rule params - ...commonParamsCamelToSnake(normalizedRuleParams), + ...normalizedCommonParamsCamelToSnake(normalizedRuleParams), // Type specific security solution rule params ...typeSpecificCamelToSnake(rule.params), // Actions diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/converters/normalize_rule_params.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/converters/normalize_rule_params.ts index eddd8b0434ba0..8d5793c04f22b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/converters/normalize_rule_params.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/converters/normalize_rule_params.ts @@ -11,6 +11,10 @@ interface NormalizeRuleSourceParams { ruleSource: BaseRuleParams['ruleSource']; } +export interface NormalizedRuleParams extends BaseRuleParams { + ruleSource: RuleSourceCamelCased; +} + /* * Since there's no mechanism to migrate all rules at the same time, * we cannot guarantee that the ruleSource params is present in all rules. @@ -36,7 +40,7 @@ export const normalizeRuleSource = ({ return ruleSource; }; -export const normalizeRuleParams = (params: BaseRuleParams) => { +export const normalizeRuleParams = (params: BaseRuleParams): NormalizedRuleParams => { return { ...params, // Fields to normalize diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/es_results.ts index 0867245a40933..a31ea28e0972a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/es_results.ts @@ -556,6 +556,7 @@ export const sampleSignalHit = (): SignalHit => ({ saved_id: undefined, alert_suppression: undefined, investigation_fields: undefined, + rule_source: { type: 'internal' }, }, depth: 1, }, From 149c514e8384232c501aad02ef989e00917ac6f8 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 7 Oct 2024 13:00:12 -0500 Subject: [PATCH 04/25] Re-enable org wide PR bot (#195131) https://github.com/elastic/kibana/pull/194768 without the merge conflicts. Switches over to the org wide PR bot, with backwards compatibility for both versions. Updating the pipeline definition here is a global set for environment variables on all branches, so I intend on merging the backports first to support both versions and then proceeding with this. --- .../pipeline-resource-definitions/kibana-on-merge.yml | 2 +- .buildkite/pipeline-resource-definitions/kibana-pr.yml | 6 +++--- .buildkite/pipeline-utils/test-failures/annotate.ts | 5 ++++- .buildkite/scripts/lifecycle/post_build.sh | 2 +- .buildkite/scripts/lifecycle/pre_build.sh | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.buildkite/pipeline-resource-definitions/kibana-on-merge.yml b/.buildkite/pipeline-resource-definitions/kibana-on-merge.yml index 97008d1ce78f7..6fe305979652e 100644 --- a/.buildkite/pipeline-resource-definitions/kibana-on-merge.yml +++ b/.buildkite/pipeline-resource-definitions/kibana-on-merge.yml @@ -20,7 +20,7 @@ spec: spec: env: SLACK_NOTIFICATIONS_CHANNEL: '#kibana-operations-alerts' - GITHUB_BUILD_COMMIT_STATUS_ENABLED: 'true' + ELASTIC_GITHUB_BUILD_COMMIT_STATUS_ENABLED: 'true' GITHUB_COMMIT_STATUS_CONTEXT: buildkite/on-merge REPORT_FAILED_TESTS_TO_GITHUB: 'true' ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true' diff --git a/.buildkite/pipeline-resource-definitions/kibana-pr.yml b/.buildkite/pipeline-resource-definitions/kibana-pr.yml index 8d2a6c8bf9e99..4d6275843327e 100644 --- a/.buildkite/pipeline-resource-definitions/kibana-pr.yml +++ b/.buildkite/pipeline-resource-definitions/kibana-pr.yml @@ -19,10 +19,10 @@ spec: description: Runs manually for pull requests spec: env: - PR_COMMENTS_ENABLED: 'true' - GITHUB_BUILD_COMMIT_STATUS_ENABLED: 'true' + ELASTIC_PR_COMMENTS_ENABLED: 'true' + ELASTIC_GITHUB_BUILD_COMMIT_STATUS_ENABLED: 'true' + ELASTIC_GITHUB_STEP_COMMIT_STATUS_ENABLED: 'true' GITHUB_BUILD_COMMIT_STATUS_CONTEXT: kibana-ci - GITHUB_STEP_COMMIT_STATUS_ENABLED: 'true' allow_rebuilds: true branch_configuration: '' cancel_intermediate_builds: true diff --git a/.buildkite/pipeline-utils/test-failures/annotate.ts b/.buildkite/pipeline-utils/test-failures/annotate.ts index 7327aa82c065c..43f3b733ebcd7 100644 --- a/.buildkite/pipeline-utils/test-failures/annotate.ts +++ b/.buildkite/pipeline-utils/test-failures/annotate.ts @@ -171,7 +171,10 @@ export const annotateTestFailures = async () => { buildkite.setAnnotation('test_failures', 'error', getAnnotation(failures, failureHtmlArtifacts)); - if (process.env.PR_COMMENTS_ENABLED === 'true') { + if ( + process.env.PR_COMMENTS_ENABLED === 'true' || + process.env.ELASTIC_PR_COMMENTS_ENABLED === 'true' + ) { buildkite.setMetadata( 'pr_comment:test_failures:body', getPrComment(failures, failureHtmlArtifacts) diff --git a/.buildkite/scripts/lifecycle/post_build.sh b/.buildkite/scripts/lifecycle/post_build.sh index 3ca36e9d04b78..f35e0b97447f5 100755 --- a/.buildkite/scripts/lifecycle/post_build.sh +++ b/.buildkite/scripts/lifecycle/post_build.sh @@ -5,7 +5,7 @@ set -euo pipefail BUILD_SUCCESSFUL=$(ts-node "$(dirname "${0}")/build_status.ts") export BUILD_SUCCESSFUL -if [[ "${GITHUB_BUILD_COMMIT_STATUS_ENABLED:-}" != "true" ]]; then +if [[ "${GITHUB_BUILD_COMMIT_STATUS_ENABLED:-}" != "true" ]] && [[ "${ELASTIC_GITHUB_BUILD_COMMIT_STATUS_ENABLED:-}" != "true" ]]; then "$(dirname "${0}")/commit_status_complete.sh" fi diff --git a/.buildkite/scripts/lifecycle/pre_build.sh b/.buildkite/scripts/lifecycle/pre_build.sh index b8ccaf04f9bb9..31e569b10ca59 100755 --- a/.buildkite/scripts/lifecycle/pre_build.sh +++ b/.buildkite/scripts/lifecycle/pre_build.sh @@ -4,7 +4,7 @@ set -euo pipefail source .buildkite/scripts/common/util.sh -if [[ "${GITHUB_BUILD_COMMIT_STATUS_ENABLED:-}" != "true" ]]; then +if [[ "${GITHUB_BUILD_COMMIT_STATUS_ENABLED:-}" != "true" ]] && [[ "${ELASTIC_GITHUB_BUILD_COMMIT_STATUS_ENABLED:-}" != "true" ]]; then "$(dirname "${0}")/commit_status_start.sh" fi From 36fe809ba9958d67235ee0d99d80d3d7a1defa56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Mon, 7 Oct 2024 21:00:42 +0200 Subject: [PATCH 05/25] docs(): fix dynamic config url (#195271) --- packages/core/plugins/core-plugins-server/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/plugins/core-plugins-server/src/types.ts b/packages/core/plugins/core-plugins-server/src/types.ts index 785959e62b897..6da8b2727733e 100644 --- a/packages/core/plugins/core-plugins-server/src/types.ts +++ b/packages/core/plugins/core-plugins-server/src/types.ts @@ -107,7 +107,7 @@ export interface PluginConfigDescriptor { */ exposeToBrowser?: ExposedToBrowserDescriptor; /** - * List of configuration properties that can be dynamically changed via the PUT /_settings API. + * List of configuration properties that can be dynamically changed via the PUT /internal/core/_settings API. */ dynamicConfig?: DynamicConfigDescriptor; /** From 99970dca41d472d9202caaf2b091a0df2b4e69ab Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Mon, 7 Oct 2024 15:13:03 -0400 Subject: [PATCH 06/25] [Obs AI Assistant] Avoid AI assistant overlaying AI conversations (#194722) Closes https://github.com/elastic/kibana/issues/190430 ## Summary ### Problem After navigating to AI conversations, from the AI assistant overlay via the "Navigate to Conversations" button, the AI assistant overlay doesn't close. Instead, it remains open and the same content is shown on the overlay and the conversations page. ### Solution Close the AI Assistant overlay when navigating to the AI conversations page. https://github.com/user-attachments/assets/db1b9a30-c073-4867-a2aa-520e1b5c3fa1 --------- Co-authored-by: Elastic Machine --- .../public/components/chat/chat_body.tsx | 3 +++ .../public/components/chat/chat_flyout.tsx | 1 + .../public/components/chat/chat_header.tsx | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.tsx index f54dc021938d5..0bf5a8009b635 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.tsx @@ -110,6 +110,7 @@ export function ChatBody({ showLinkToConversationsApp, onConversationUpdate, onToggleFlyoutPositionMode, + onClose, }: { connectors: ReturnType; currentUser?: Pick; @@ -121,6 +122,7 @@ export function ChatBody({ showLinkToConversationsApp: boolean; onConversationUpdate: (conversation: { conversation: Conversation['conversation'] }) => void; onToggleFlyoutPositionMode?: (flyoutPositionMode: FlyoutPositionMode) => void; + onClose?: () => void; }) { const license = useLicense(); const hasCorrectLicense = license?.hasAtLeast('enterprise'); @@ -498,6 +500,7 @@ export function ChatBody({ saveTitle(newTitle); }} onToggleFlyoutPositionMode={onToggleFlyoutPositionMode} + onClose={onClose} /> diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.tsx index 4194f9a2ca0c4..67ac37a88d724 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.tsx @@ -274,6 +274,7 @@ export function ChatFlyout({ conversationList.conversations.refresh(); }} onToggleFlyoutPositionMode={handleToggleFlyoutPositionMode} + onClose={onClose} /> diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.tsx index d4ede58040391..c67596fbafd5e 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.tsx @@ -50,6 +50,7 @@ export function ChatHeader({ onCopyConversation, onSaveTitle, onToggleFlyoutPositionMode, + onClose, }: { connectors: UseGenAIConnectorsResult; conversationId?: string; @@ -60,6 +61,7 @@ export function ChatHeader({ onCopyConversation: () => void; onSaveTitle: (title: string) => void; onToggleFlyoutPositionMode?: (newFlyoutPositionMode: FlyoutPositionMode) => void; + onClose?: () => void; }) { const theme = useEuiTheme(); const breakpoint = useCurrentEuiBreakpoint(); @@ -73,6 +75,10 @@ export function ChatHeader({ }, [title]); const handleNavigateToConversations = () => { + if (onClose) { + onClose(); + } + if (conversationId) { router.push('/conversations/{conversationId}', { path: { From 1599e4f61b3e5caf60730be5a2def73f6d603148 Mon Sep 17 00:00:00 2001 From: Ilya Nikokoshev Date: Mon, 7 Oct 2024 22:47:43 +0300 Subject: [PATCH 07/25] [Auto Import] Make sure the committed schema is generated (#195302) ## Summary Previously our schema and `.gen` files were not in sync, in the sense that ``` git checkout main && yarn kbn bootstrap && node scripts/generate_openapi --rootDir ./x-pack/plugins/integration_assistant && git diff ``` returned a non-trivial diff, despite the implementation in https://github.com/elastic/kibana/issues/193243. In this PR we - add missing additionalProperties to the schema; - generate and commit exactly the generated files. There don't seem to be any effects from the change from `passthrough` to `catchall`. Tested with Teleport (see GitHub). --- .../common/api/model/common_attributes.gen.ts | 4 ++-- .../common/api/model/common_attributes.schema.yaml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts b/x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts index 59fe7d461698f..49e6f12691429 100644 --- a/x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts +++ b/x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts @@ -46,7 +46,7 @@ export const RawSamples = z.array(z.string()); * mapping object to ECS Mapping Request. */ export type Mapping = z.infer; -export const Mapping = z.object({}).passthrough(); +export const Mapping = z.object({}).catchall(z.unknown()); /** * LLM Connector to be used in each API request. @@ -58,7 +58,7 @@ export const Connector = z.string(); * An array of processed documents. */ export type Docs = z.infer; -export const Docs = z.array(z.object({}).passthrough()); +export const Docs = z.array(z.object({}).catchall(z.unknown())); /** * The name of the log samples format. diff --git a/x-pack/plugins/integration_assistant/common/api/model/common_attributes.schema.yaml b/x-pack/plugins/integration_assistant/common/api/model/common_attributes.schema.yaml index 0af01834970c7..073b485b1cb3d 100644 --- a/x-pack/plugins/integration_assistant/common/api/model/common_attributes.schema.yaml +++ b/x-pack/plugins/integration_assistant/common/api/model/common_attributes.schema.yaml @@ -31,6 +31,7 @@ components: Mapping: type: object description: mapping object to ECS Mapping Request. + additionalProperties: true Connector: type: string @@ -41,6 +42,7 @@ components: description: An array of processed documents. items: type: object + additionalProperties: true SamplesFormatName: type: string From e923dca1f4086baff2043791b925c3c2bb8d0bc8 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Mon, 7 Oct 2024 13:09:08 -0700 Subject: [PATCH 08/25] [OpenAPI][DOCS] Edit role and space tags (#194888) --- .../overlays/kibana.overlays.serverless.yaml | 5 +++++ oas_docs/overlays/kibana.overlays.yaml | 22 ++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/oas_docs/overlays/kibana.overlays.serverless.yaml b/oas_docs/overlays/kibana.overlays.serverless.yaml index b4a37c1aec846..64040383ae38c 100644 --- a/oas_docs/overlays/kibana.overlays.serverless.yaml +++ b/oas_docs/overlays/kibana.overlays.serverless.yaml @@ -54,6 +54,11 @@ actions: description: Change displayName update: x-displayName: "Service level objectives" + - target: '$.tags[?(@.name=="spaces")]' + description: Change displayName + update: + x-displayName: "Spaces" + description: Manage your Kibana spaces. - target: '$.tags[?(@.name=="system")]' description: Change displayName and description update: diff --git a/oas_docs/overlays/kibana.overlays.yaml b/oas_docs/overlays/kibana.overlays.yaml index 7464f1899d8f2..c4747d7d13a0a 100644 --- a/oas_docs/overlays/kibana.overlays.yaml +++ b/oas_docs/overlays/kibana.overlays.yaml @@ -23,7 +23,7 @@ actions: If you use the Kibana console to send API requests, it automatically adds the appropriate space identifier. - To learn more, check out [Spaces](https://www.elastic.co/guide/en/kibana/current/xpack-spaces.html). + To learn more, check out [Spaces](https://www.elastic.co/guide/en/kibana/master/xpack-spaces.html). # Add some tag descriptions and displayNames - target: '$.tags[?(@.name=="alerting")]' description: Change tag description and displayName @@ -34,7 +34,7 @@ actions: Actions typically involve the use of connectors to interact with Kibana services or third party integrations. externalDocs: description: Alerting documentation - url: https://www.elastic.co/guide/en/kibana/current/alerting-getting-started.html + url: https://www.elastic.co/guide/en/kibana/master/alerting-getting-started.html x-displayName: "Alerting" - target: '$.tags[?(@.name=="cases")]' description: Change tag description and displayName @@ -45,7 +45,7 @@ actions: You can also send cases to external incident management systems by configuring connectors. externalDocs: description: Cases documentation - url: https://www.elastic.co/guide/en/kibana/current/cases.html + url: https://www.elastic.co/guide/en/kibana/master/cases.html x-displayName: "Cases" - target: '$.tags[?(@.name=="connectors")]' description: Change tag description and displayName @@ -65,10 +65,26 @@ actions: description: Change displayName update: x-displayName: "Machine learning" + - target: '$.tags[?(@.name=="roles")]' + description: Change displayName and description + update: + x-displayName: "Roles" + description: Manage the roles that grant Elasticsearch and Kibana privileges. + externalDocs: + description: Kibana role management + url: https://www.elastic.co/guide/en/kibana/master/kibana-role-management.html - target: '$.tags[?(@.name=="slo")]' description: Change displayName update: x-displayName: "Service level objectives" + - target: '$.tags[?(@.name=="spaces")]' + description: Change displayName + update: + x-displayName: "Spaces" + description: Manage your Kibana spaces. + externalDocs: + url: https://www.elastic.co/guide/en/kibana/master/xpack-spaces.html + description: Space overview - target: '$.tags[?(@.name=="system")]' description: Change displayName and description update: From 94aa9151695ec4d7a001195aa10cc25ea6e2d282 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 7 Oct 2024 22:19:08 +0200 Subject: [PATCH 09/25] github-actions: notify github commands for all the observability code (#195219) --- .github/workflows/oblt-github-commands.yml | 30 +++++----------------- renovate.json | 1 + 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/.github/workflows/oblt-github-commands.yml b/.github/workflows/oblt-github-commands.yml index d3f4bd61b817e..443c0fa5f9071 100644 --- a/.github/workflows/oblt-github-commands.yml +++ b/.github/workflows/oblt-github-commands.yml @@ -8,39 +8,21 @@ name: oblt-github-commands on: - pull_request_target: + pull_request: types: - - opened + - labeled permissions: contents: read + pull-requests: write jobs: - comment-if-oblt-member: + comment: + if: ${{ github.event.label.name == 'ci:project-deploy-observability' }} runs-on: ubuntu-latest steps: - - uses: elastic/apm-pipeline-library/.github/actions/github-token@current + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: - url: ${{ secrets.OBLT_VAULT_ADDR }} - roleId: ${{ secrets.OBLT_VAULT_ROLE_ID }} - secretId: ${{ secrets.OBLT_VAULT_SECRET_ID }} - - - id: is_team_member - name: Check if user is member of the Elastic org and Observability team - run: | - if gh api -H "Accept: application/vnd.github+json" \ - /orgs/elastic/teams/observability/memberships/${{ github.actor }} ; then - echo "result=true" >> $GITHUB_OUTPUT - else - echo "result=false" >> $GITHUB_OUTPUT - fi - env: - GH_TOKEN: ${{ env.GITHUB_TOKEN }} - - - if: ${{ steps.is_team_member.outputs.result == 'true' }} - uses: actions/github-script@v6 - with: - github-token: ${{ env.GITHUB_TOKEN }} script: | const body = ` ### :robot: GitHub comments diff --git a/renovate.json b/renovate.json index 029dab13f394f..b66d29c13ca0d 100644 --- a/renovate.json +++ b/renovate.json @@ -41,6 +41,7 @@ "matchManagers": ["github-actions"], "matchPackageNames": [ "actions/checkout", + "actions/github-script", "elastic/github-actions/project-assigner", "hmarr/auto-approve-action", "octokit/graphql-action", From afb671f5619b5439255233599a246a2714cbda25 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 7 Oct 2024 14:36:33 -0600 Subject: [PATCH 10/25] [embeddable] avoid uncaught error when embeddable factory is not found (#195306) Small clean-up of ReactEmbeddableRenderer 1. avoid uncaught error when embeddable factory is not found. Move `getReactEmbeddableFactory` into `buildEmbeddable` so that if factory is not found and `getReactEmbeddableFactory` throws, its caught by try/catch wrapping `buildEmbeddable`. 2. unsubscribe from `subscriptions` --- .../react_embeddable_renderer.tsx | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx index 054949650c343..0f9ae361bbf93 100644 --- a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx +++ b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx @@ -102,22 +102,10 @@ export const ReactEmbeddableRenderer = < */ return (async () => { const parentApi = getParentApi(); - const factory = await getReactEmbeddableFactory(type); const subscriptions = new Subscription(); - const setApi = ( - apiRegistration: SetReactEmbeddableApiRegistration - ) => { - return { - ...apiRegistration, - uuid, - phase$, - parentApi, - type: factory.type, - } as unknown as Api; - }; - const buildEmbeddable = async () => { + const factory = await getReactEmbeddableFactory(type); const serializedState = parentApi.getSerializedStateForChild(uuid); const lastSavedRuntimeState = serializedState ? await factory.deserializeState(serializedState) @@ -131,6 +119,18 @@ export const ReactEmbeddableRenderer = < const initialRuntimeState = { ...lastSavedRuntimeState, ...partialRuntimeState }; + const setApi = ( + apiRegistration: SetReactEmbeddableApiRegistration + ) => { + return { + ...apiRegistration, + uuid, + phase$, + parentApi, + type: factory.type, + } as unknown as Api; + }; + const buildApi = ( apiRegistration: BuildReactEmbeddableApiRegistration< SerializedState, @@ -179,7 +179,10 @@ export const ReactEmbeddableRenderer = < ...unsavedChanges.api, } as unknown as SetReactEmbeddableApiRegistration); - cleanupFunction.current = () => unsavedChanges.cleanup(); + cleanupFunction.current = () => { + subscriptions.unsubscribe(); + unsavedChanges.cleanup(); + }; return fullApi as Api & HasSnapshottableState; }; From ecc7d4e2e6099beca9028c6aacec978ad0e7e992 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 7 Oct 2024 14:43:12 -0600 Subject: [PATCH 11/25] [dashboard] decouple DashboardCreationOptions from DashboardContainer (#194875) PR decouples `DashboardCreationOptions` type from `DashboardContainer` and moves `DashboardCreationOptions` type into dashboard_api folder. `useControlGroupIntegration` removed from `DashboardCreationOptions` type since its no longer used. --------- Co-authored-by: Elastic Machine --- .../dashboard_with_controls_example.tsx | 1 - .../dashboard/public/dashboard_api/types.ts | 43 ++++++++++++++++++- .../public/dashboard_app/dashboard_app.tsx | 4 +- .../url/search_sessions_integration.ts | 33 +++++++------- .../create/create_dashboard.test.ts | 2 +- .../embeddable/create/create_dashboard.ts | 2 +- ...rt_dashboard_search_session_integration.ts | 23 +++++----- .../embeddable/dashboard_container.tsx | 2 +- .../dashboard_container_factory.tsx | 40 +---------------- .../external_api/dashboard_renderer.test.tsx | 4 +- .../external_api/dashboard_renderer.tsx | 2 +- .../public/dashboard_container/index.ts | 1 - .../diffing/dashboard_diffing_integration.ts | 3 +- src/plugins/dashboard/public/index.ts | 3 +- .../app/metrics/static_dashboard/index.tsx | 1 - .../app/service_dashboards/index.tsx | 1 - .../tabs/dashboards/dashboards.tsx | 1 - .../components/dashboard_renderer.test.tsx | 1 - .../components/dashboard_renderer.tsx | 1 - 19 files changed, 80 insertions(+), 88 deletions(-) diff --git a/examples/portable_dashboards_example/public/dashboard_with_controls_example.tsx b/examples/portable_dashboards_example/public/dashboard_with_controls_example.tsx index 316ed8e47fb28..ca6c21538f3dd 100644 --- a/examples/portable_dashboards_example/public/dashboard_with_controls_example.tsx +++ b/examples/portable_dashboards_example/public/dashboard_with_controls_example.tsx @@ -68,7 +68,6 @@ export const DashboardWithControlsExample = ({ dataView }: { dataView: DataView }); return { - useControlGroupIntegration: true, getInitialInput: () => ({ timeRange: { from: 'now-30d', to: 'now' }, viewMode: ViewMode.VIEW, diff --git a/src/plugins/dashboard/public/dashboard_api/types.ts b/src/plugins/dashboard/public/dashboard_api/types.ts index c874ff9e67241..e0b6b8d3e4824 100644 --- a/src/plugins/dashboard/public/dashboard_api/types.ts +++ b/src/plugins/dashboard/public/dashboard_api/types.ts @@ -16,6 +16,7 @@ import { TracksOverlays, } from '@kbn/presentation-containers'; import { + EmbeddableAppContext, HasAppContext, HasType, PublishesDataViews, @@ -29,11 +30,49 @@ import { } from '@kbn/presentation-publishing'; import { ControlGroupApi, ControlGroupSerializedState } from '@kbn/controls-plugin/public'; import { Filter, Query, TimeRange } from '@kbn/es-query'; -import { DefaultEmbeddableApi, ErrorEmbeddable, IEmbeddable } from '@kbn/embeddable-plugin/public'; +import { + DefaultEmbeddableApi, + EmbeddablePackageState, + ErrorEmbeddable, + IEmbeddable, +} from '@kbn/embeddable-plugin/public'; +import { Observable } from 'rxjs'; +import { SearchSessionInfoProvider } from '@kbn/data-plugin/public'; +import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { DashboardPanelMap, DashboardPanelState } from '../../common'; -import { SaveDashboardReturn } from '../services/dashboard_content_management_service/types'; +import { + LoadDashboardReturn, + SaveDashboardReturn, + SavedDashboardInput, +} from '../services/dashboard_content_management_service/types'; import { DashboardStateFromSettingsFlyout, UnsavedPanelState } from '../dashboard_container/types'; +export interface DashboardCreationOptions { + getInitialInput?: () => Partial; + + getIncomingEmbeddable?: () => EmbeddablePackageState | undefined; + + useSearchSessionsIntegration?: boolean; + searchSessionSettings?: { + sessionIdToRestore?: string; + sessionIdUrlChangeObservable?: Observable; + getSearchSessionIdFromURL: () => string | undefined; + removeSessionIdFromUrl: () => void; + createSessionRestorationDataProvider: (dashboardApi: DashboardApi) => SearchSessionInfoProvider; + }; + + useSessionStorageIntegration?: boolean; + + useUnifiedSearchIntegration?: boolean; + unifiedSearchSettings?: { kbnUrlStateStorage: IKbnUrlStateStorage }; + + validateLoadedSavedObject?: (result: LoadDashboardReturn) => 'valid' | 'invalid' | 'redirected'; + + isEmbeddedExternally?: boolean; + + getEmbeddableAppContext?: (dashboardId?: string) => EmbeddableAppContext; +} + export type DashboardApi = CanExpandPanels & HasAppContext & HasRuntimeChildState & diff --git a/src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx b/src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx index ecb62bf8fc2b3..f7ca6b552893b 100644 --- a/src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx +++ b/src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx @@ -19,14 +19,13 @@ import { ViewMode } from '@kbn/embeddable-plugin/public'; import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public'; -import { DashboardApi, DashboardRenderer } from '..'; +import { DashboardApi, DashboardCreationOptions, DashboardRenderer } from '..'; import { SharedDashboardState } from '../../common'; import { DASHBOARD_APP_ID, DASHBOARD_STATE_STORAGE_KEY, createDashboardEditUrl, } from '../dashboard_constants'; -import type { DashboardCreationOptions } from '../dashboard_container/embeddable/dashboard_container_factory'; import { DashboardRedirect } from '../dashboard_container/types'; import { DashboardTopNav } from '../dashboard_top_nav'; import { @@ -143,7 +142,6 @@ export function DashboardApp({ embeddableService.getStateTransfer().getIncomingEmbeddablePackage(DASHBOARD_APP_ID, true), // integrations - useControlGroupIntegration: true, useSessionStorageIntegration: true, useUnifiedSearchIntegration: true, unifiedSearchSettings: { diff --git a/src/plugins/dashboard/public/dashboard_app/url/search_sessions_integration.ts b/src/plugins/dashboard/public/dashboard_app/url/search_sessions_integration.ts index 2845808f723c3..e9ae3d6a15050 100644 --- a/src/plugins/dashboard/public/dashboard_app/url/search_sessions_integration.ts +++ b/src/plugins/dashboard/public/dashboard_app/url/search_sessions_integration.ts @@ -18,12 +18,13 @@ import { import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/common'; import type { Query } from '@kbn/es-query'; import { SearchSessionInfoProvider } from '@kbn/data-plugin/public'; - +import type { ViewMode } from '@kbn/embeddable-plugin/common'; import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics'; import { SEARCH_SESSION_ID } from '../../dashboard_constants'; -import { DashboardContainer, DashboardLocatorParams } from '../../dashboard_container'; +import { DashboardLocatorParams } from '../../dashboard_container'; import { convertPanelMapToSavedPanels } from '../../../common'; import { dataService } from '../../services/kibana_services'; +import { DashboardApi } from '../../dashboard_api/types'; export const removeSearchSessionIdFromURL = (kbnUrlStateStorage: IKbnUrlStateStorage) => { kbnUrlStateStorage.kbnUrlControls.updateAsync((nextUrl) => { @@ -46,14 +47,15 @@ export const getSessionURLObservable = (history: History) => ); export function createSessionRestorationDataProvider( - container: DashboardContainer + dashboardApi: DashboardApi ): SearchSessionInfoProvider { return { - getName: async () => container.getTitle(), + getName: async () => + dashboardApi.panelTitle.value ?? dashboardApi.savedObjectId.value ?? dashboardApi.uuid$.value, getLocatorData: async () => ({ id: DASHBOARD_APP_LOCATOR, - initialState: getLocatorParams({ container, shouldRestoreSearchSession: false }), - restoreState: getLocatorParams({ container, shouldRestoreSearchSession: true }), + initialState: getLocatorParams({ dashboardApi, shouldRestoreSearchSession: false }), + restoreState: getLocatorParams({ dashboardApi, shouldRestoreSearchSession: true }), }), }; } @@ -63,24 +65,19 @@ export function createSessionRestorationDataProvider( * as it was. */ function getLocatorParams({ - container, + dashboardApi, shouldRestoreSearchSession, }: { - container: DashboardContainer; + dashboardApi: DashboardApi; shouldRestoreSearchSession: boolean; }): DashboardLocatorParams { - const { - explicitInput: { panels, query, viewMode }, - } = container.getState(); - - const savedObjectId = container.savedObjectId.value; - + const savedObjectId = dashboardApi.savedObjectId.value; return { - viewMode, + viewMode: (dashboardApi.viewMode.value as ViewMode) ?? 'view', useHash: false, preserveSavedFilters: false, filters: dataService.query.filterManager.getFilters(), - query: dataService.query.queryString.formatQuery(query) as Query, + query: dataService.query.queryString.formatQuery(dashboardApi.query$.value) as Query, dashboardId: savedObjectId, searchSessionId: shouldRestoreSearchSession ? dataService.search.session.getSessionId() @@ -96,6 +93,8 @@ function getLocatorParams({ : undefined, panels: savedObjectId ? undefined - : (convertPanelMapToSavedPanels(panels) as DashboardLocatorParams['panels']), + : (convertPanelMapToSavedPanels( + dashboardApi.panels$.value + ) as DashboardLocatorParams['panels']), }; } diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts index ea5508dff23da..ddcdeff25ea4e 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts @@ -21,7 +21,7 @@ import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { DEFAULT_DASHBOARD_INPUT } from '../../../dashboard_constants'; import { getSampleDashboardPanel, mockControlGroupApi } from '../../../mocks'; import { dataService, embeddableService } from '../../../services/kibana_services'; -import { DashboardCreationOptions } from '../dashboard_container_factory'; +import { DashboardCreationOptions } from '../../..'; import { createDashboard } from './create_dashboard'; import { getDashboardContentManagementService } from '../../../services/dashboard_content_management_service'; import { getDashboardBackupService } from '../../../services/dashboard_backup_service'; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index 0264e834bbd07..3eab6c641ee87 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -44,7 +44,7 @@ import { runPanelPlacementStrategy } from '../../panel_placement/place_new_panel import { startDiffingDashboardState } from '../../state/diffing/dashboard_diffing_integration'; import { UnsavedPanelState } from '../../types'; import { DashboardContainer } from '../dashboard_container'; -import { DashboardCreationOptions } from '../dashboard_container_factory'; +import type { DashboardCreationOptions } from '../../..'; import { startSyncingDashboardDataViews } from './data_views/sync_dashboard_data_views'; import { startQueryPerformanceTracking } from './performance/query_performance_tracking'; import { startDashboardSearchSessionIntegration } from './search_sessions/start_dashboard_search_session_integration'; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts index 8150ee7a72381..70f841db869a5 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts @@ -13,7 +13,7 @@ import { noSearchSessionStorageCapabilityMessage } from '@kbn/data-plugin/public import { dataService } from '../../../../services/kibana_services'; import { DashboardContainer } from '../../dashboard_container'; -import { DashboardCreationOptions } from '../../dashboard_container_factory'; +import type { DashboardApi, DashboardCreationOptions } from '../../../..'; import { newSession$ } from './new_session'; import { getDashboardCapabilities } from '../../../../utils/get_dashboard_capabilities'; @@ -33,15 +33,18 @@ export function startDashboardSearchSessionIntegration( createSessionRestorationDataProvider, } = searchSessionSettings; - dataService.search.session.enableStorage(createSessionRestorationDataProvider(this), { - isDisabled: () => - getDashboardCapabilities().storeSearchSession - ? { disabled: false } - : { - disabled: true, - reasonText: noSearchSessionStorageCapabilityMessage, - }, - }); + dataService.search.session.enableStorage( + createSessionRestorationDataProvider(this as DashboardApi), + { + isDisabled: () => + getDashboardCapabilities().storeSearchSession + ? { disabled: false } + : { + disabled: true, + reasonText: noSearchSessionStorageCapabilityMessage, + }, + } + ); // force refresh when the session id in the URL changes. This will also fire off the "handle search session change" below. const searchSessionIdChangeSubscription = sessionIdUrlChangeObservable diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index c6fed0998cfde..e508e511d41cb 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -112,11 +112,11 @@ import { } from './create/controls/dashboard_control_group_integration'; import { initializeDashboard } from './create/create_dashboard'; import { - DashboardCreationOptions, dashboardTypeDisplayLowercase, dashboardTypeDisplayName, } from './dashboard_container_factory'; import { InitialComponentState, getDashboardApi } from '../../dashboard_api/get_dashboard_api'; +import type { DashboardCreationOptions } from '../..'; export interface InheritedChildInput { filters: Filter[]; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx index ecccc49a60f12..52d7d84f67490 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx @@ -8,29 +8,20 @@ */ import { i18n } from '@kbn/i18n'; -import { Observable } from 'rxjs'; - -import { SearchSessionInfoProvider } from '@kbn/data-plugin/public'; import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common'; import { Container, ContainerOutput, EmbeddableFactory, EmbeddableFactoryDefinition, - EmbeddablePackageState, ErrorEmbeddable, } from '@kbn/embeddable-plugin/public'; -import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; -import { EmbeddableAppContext } from '@kbn/presentation-publishing'; import { DASHBOARD_CONTAINER_TYPE } from '..'; import { createExtract, createInject, DashboardContainerInput } from '../../../common'; import { DEFAULT_DASHBOARD_INPUT } from '../../dashboard_constants'; -import { - LoadDashboardReturn, - SavedDashboardInput, -} from '../../services/dashboard_content_management_service/types'; import type { DashboardContainer } from './dashboard_container'; +import type { DashboardCreationOptions } from '../..'; export type DashboardContainerFactory = EmbeddableFactory< DashboardContainerInput, @@ -38,35 +29,6 @@ export type DashboardContainerFactory = EmbeddableFactory< DashboardContainer >; -export interface DashboardCreationOptions { - getInitialInput?: () => Partial; - - getIncomingEmbeddable?: () => EmbeddablePackageState | undefined; - - useSearchSessionsIntegration?: boolean; - searchSessionSettings?: { - sessionIdToRestore?: string; - sessionIdUrlChangeObservable?: Observable; - getSearchSessionIdFromURL: () => string | undefined; - removeSessionIdFromUrl: () => void; - createSessionRestorationDataProvider: ( - container: DashboardContainer - ) => SearchSessionInfoProvider; - }; - - useControlGroupIntegration?: boolean; - useSessionStorageIntegration?: boolean; - - useUnifiedSearchIntegration?: boolean; - unifiedSearchSettings?: { kbnUrlStateStorage: IKbnUrlStateStorage }; - - validateLoadedSavedObject?: (result: LoadDashboardReturn) => 'valid' | 'invalid' | 'redirected'; - - isEmbeddedExternally?: boolean; - - getEmbeddableAppContext?: (dashboardId?: string) => EmbeddableAppContext; -} - export const dashboardTypeDisplayName = i18n.translate('dashboard.factory.displayName', { defaultMessage: 'Dashboard', }); diff --git a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.test.tsx b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.test.tsx index 153324f5a2bf3..fd41fdd5e764d 100644 --- a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.test.tsx +++ b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.test.tsx @@ -18,10 +18,9 @@ import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/common'; import { setStubKibanaServices as setPresentationPanelMocks } from '@kbn/presentation-panel-plugin/public/mocks'; import { BehaviorSubject } from 'rxjs'; import { DashboardContainerFactory } from '..'; -import { DASHBOARD_CONTAINER_TYPE } from '../..'; +import { DASHBOARD_CONTAINER_TYPE, DashboardCreationOptions } from '../..'; import { embeddableService } from '../../services/kibana_services'; import { DashboardContainer } from '../embeddable/dashboard_container'; -import { DashboardCreationOptions } from '../embeddable/dashboard_container_factory'; import { DashboardRenderer } from './dashboard_renderer'; describe('dashboard renderer', () => { @@ -53,7 +52,6 @@ describe('dashboard renderer', () => { test('saved object id & creation options are passed to dashboard factory', async () => { const options: DashboardCreationOptions = { - useControlGroupIntegration: true, useSessionStorageIntegration: true, useUnifiedSearchIntegration: true, }; diff --git a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx index e8ef4faef724d..a43bd6ddbc75b 100644 --- a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx +++ b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx @@ -28,8 +28,8 @@ import type { DashboardContainer } from '../embeddable/dashboard_container'; import { DashboardContainerFactory, DashboardContainerFactoryDefinition, - DashboardCreationOptions, } from '../embeddable/dashboard_container_factory'; +import type { DashboardCreationOptions } from '../..'; import { DashboardLocatorParams, DashboardRedirect } from '../types'; import { Dashboard404Page } from './dashboard_404'; diff --git a/src/plugins/dashboard/public/dashboard_container/index.ts b/src/plugins/dashboard/public/dashboard_container/index.ts index d8103f1e3f9bc..16314f52d38f8 100644 --- a/src/plugins/dashboard/public/dashboard_container/index.ts +++ b/src/plugins/dashboard/public/dashboard_container/index.ts @@ -17,7 +17,6 @@ export const LATEST_DASHBOARD_CONTAINER_VERSION = convertNumberToDashboardVersio export type { DashboardContainer } from './embeddable/dashboard_container'; export { type DashboardContainerFactory, - type DashboardCreationOptions, DashboardContainerFactoryDefinition, } from './embeddable/dashboard_container_factory'; diff --git a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts index 8c014d0ec92c6..ad33b2e5fb117 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts @@ -10,7 +10,8 @@ import { childrenUnsavedChanges$ } from '@kbn/presentation-containers'; import { omit } from 'lodash'; import { AnyAction, Middleware } from 'redux'; import { combineLatest, debounceTime, skipWhile, startWith, switchMap } from 'rxjs'; -import { DashboardContainer, DashboardCreationOptions } from '../..'; +import { DashboardContainer } from '../..'; +import { DashboardCreationOptions } from '../../..'; import { DashboardContainerInput } from '../../../../common'; import { CHANGE_CHECK_DEBOUNCE } from '../../../dashboard_constants'; import { diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index 612cdc5517bbe..109be5bc0eaf0 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -17,11 +17,10 @@ export { DASHBOARD_GRID_COLUMN_COUNT, PanelPlacementStrategy, } from './dashboard_constants'; -export type { DashboardApi } from './dashboard_api/types'; +export type { DashboardApi, DashboardCreationOptions } from './dashboard_api/types'; export { LazyDashboardRenderer as DashboardRenderer, DASHBOARD_CONTAINER_TYPE, - type DashboardCreationOptions, type DashboardLocatorParams, type IProvidesLegacyPanelPlacementSettings, } from './dashboard_container'; diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/metrics/static_dashboard/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/metrics/static_dashboard/index.tsx index 15a4b62a7efe7..cc9c12b97a6d2 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/metrics/static_dashboard/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/metrics/static_dashboard/index.tsx @@ -82,7 +82,6 @@ async function getCreationOptions( } return { - useControlGroupIntegration: true, getInitialInput: () => ({ viewMode: ViewMode.VIEW, panels, diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/index.tsx index bf864e134a698..627c314bf72e6 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/index.tsx @@ -103,7 +103,6 @@ export function ServiceDashboards({ checkForEntities = false }: { checkForEntiti }); return Promise.resolve({ getInitialInput, - useControlGroupIntegration: true, }); }, [rangeFrom, rangeTo]); diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/dashboards/dashboards.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/dashboards/dashboards.tsx index ae5251a5c17b9..f90aa500c9266 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/dashboards/dashboards.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/dashboards/dashboards.tsx @@ -137,7 +137,6 @@ export function Dashboards() { }); return Promise.resolve({ getInitialInput, - useControlGroupIntegration: true, }); }, [dateRange.from, dateRange.to]); diff --git a/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.test.tsx b/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.test.tsx index 9c6df7bb6e395..6897f59af6ffd 100644 --- a/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.test.tsx @@ -62,7 +62,6 @@ describe('DashboardRenderer', () => { filters: undefined, }) ); - expect(options.useControlGroupIntegration).toEqual(true); }); it('does not render when No Read Permission', () => { diff --git a/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.tsx b/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.tsx index cad24fe3f494c..183f62f6cb923 100644 --- a/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.tsx +++ b/x-pack/plugins/security_solution/public/dashboards/components/dashboard_renderer.tsx @@ -108,7 +108,6 @@ const DashboardRendererComponent = ({ const getCreationOptions: () => Promise = useCallback(() => { return Promise.resolve({ useSessionStorageIntegration: true, - useControlGroupIntegration: true, getInitialInput: () => { return initialInput.value; }, From b4d52e440ef6f7c68675893cde4994a2b3d45247 Mon Sep 17 00:00:00 2001 From: Rachel Shen Date: Mon, 7 Oct 2024 14:43:40 -0600 Subject: [PATCH 12/25] [Dashboard] [Usability] Add scroll margin to panels (#193430) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Closes [[Bug] Upper part of the chart is hidden when user edits a chart inline · Issue #192437 · elastic/kibana · GitHub](https://github.com/elastic/kibana/issues/192437) This PR disables the overflow-y hidden css and the panel is set to the top. Users are also able to scroll to the top of the visualization in inline editing mode. ### Now: https://github.com/user-attachments/assets/a68a3bdc-5452-40f3-84a6-4950e2fc7893 --------- Co-authored-by: Hannah Mudge --- src/plugins/dashboard/public/dashboard_api/track_panel.ts | 3 +-- .../component/grid/_dashboard_panel.scss | 7 +++++++ .../component/grid/dashboard_grid_item.tsx | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/plugins/dashboard/public/dashboard_api/track_panel.ts b/src/plugins/dashboard/public/dashboard_api/track_panel.ts index 3f3a9b4aaad4b..42345f38d614f 100644 --- a/src/plugins/dashboard/public/dashboard_api/track_panel.ts +++ b/src/plugins/dashboard/public/dashboard_api/track_panel.ts @@ -73,8 +73,7 @@ export function initializeTrackPanel(untilEmbeddableLoaded: (id: string) => Prom }; return; } - - panelRef.scrollIntoView({ block: 'center' }); + panelRef.scrollIntoView({ block: 'nearest' }); }); }, scrollToTop: () => { diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_panel.scss b/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_panel.scss index 5a252f9bf1630..d54f513a207a4 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_panel.scss +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_panel.scss @@ -4,6 +4,11 @@ * .embPanel--editing doesn't get updating without a hard refresh */ +.dshDashboardGrid__item { + scroll-margin-top: calc((var(--euiFixedHeadersOffset, 100) * 2) + $euiSizeS); + scroll-margin-bottom: $euiSizeS; +} + // LAYOUT MODES // Adjust borders/etc... for non-spaced out and expanded panels .dshLayout-withoutMargins { @@ -36,9 +41,11 @@ 0% { outline: solid $euiSizeXS transparentize($euiColorSuccess, 1); } + 25% { outline: solid $euiSizeXS transparentize($euiColorSuccess, .5); } + 100% { outline: solid $euiSizeXS transparentize($euiColorSuccess, 1); } diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx index 518b009571e27..7b21db4ea3f84 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx @@ -60,7 +60,7 @@ export const Item = React.forwardRef( const hidePanel = expandedPanelId !== undefined && expandedPanelId !== id; const focusPanel = focusedPanelId !== undefined && focusedPanelId === id; const blurPanel = focusedPanelId !== undefined && focusedPanelId !== id; - const classes = classNames({ + const classes = classNames('dshDashboardGrid__item', { 'dshDashboardGrid__item--expanded': expandPanel, 'dshDashboardGrid__item--hidden': hidePanel, 'dshDashboardGrid__item--focused': focusPanel, From 1ee648d672f0ed5322183d5abd4ebbe4e13f0a93 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Mon, 7 Oct 2024 23:41:20 +0200 Subject: [PATCH 13/25] [Security Assistant] AI Assistant - Better Solution for OSS models (#10416) (#194166) --- .../impl/assistant/use_send_message/index.tsx | 16 ++ .../use_send_message/translations.ts | 15 ++ .../server/lib/langchain/executors/types.ts | 1 + .../graphs/default_assistant_graph/graph.ts | 4 + .../graphs/default_assistant_graph/helpers.ts | 11 +- .../graphs/default_assistant_graph/index.ts | 7 +- .../nodes/model_input.ts | 4 +- .../nodes/translations.ts | 56 ++++++ .../graphs/default_assistant_graph/prompts.ts | 57 +----- .../graphs/default_assistant_graph/types.ts | 2 + .../server/routes/chat/chat_complete_route.ts | 6 +- .../server/routes/evaluate/post_evaluate.ts | 22 ++- .../server/routes/helpers.ts | 3 + .../routes/post_actions_connector_execute.ts | 5 + .../server/routes/utils.test.ts | 69 ++++++++ .../elastic_assistant/server/routes/utils.ts | 28 +++ .../plugins/elastic_assistant/server/types.ts | 1 + .../esql_language_knowledge_base/common.ts | 15 ++ .../esql_language_knowledge_base_tool.test.ts | 23 +++ .../esql_language_knowledge_base_tool.ts | 12 +- .../nl_to_esql_tool.test.ts | 162 ++++++++++++++++++ .../nl_to_esql_tool.ts | 7 +- 22 files changed, 452 insertions(+), 74 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/translations.ts create mode 100644 x-pack/plugins/elastic_assistant/server/routes/utils.test.ts create mode 100644 x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/common.ts create mode 100644 x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.test.ts diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/index.tsx index 93bd03607e71f..438b2282371d9 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/index.tsx @@ -10,6 +10,16 @@ import { useCallback, useRef, useState } from 'react'; import { ApiConfig, Replacements } from '@kbn/elastic-assistant-common'; import { useAssistantContext } from '../../assistant_context'; import { fetchConnectorExecuteAction, FetchConnectorExecuteResponse } from '../api'; +import * as i18n from './translations'; + +/** + * TODO: This is a workaround to solve the issue with the long standing server tasks while cahtting with the assistant. + * Some models (like Llama 3.1 70B) can perform poorly and be slow which leads to a long time to handle the request. + * The `core-http-browser` has a timeout of two minutes after which it will re-try the request. In combination with the slow model it can lead to + * a situation where core http client will initiate same request again and again. + * To avoid this, we abort http request after timeout which is slightly below two minutes. + */ +const EXECUTE_ACTION_TIMEOUT = 110 * 1000; // in milliseconds interface SendMessageProps { apiConfig: ApiConfig; @@ -38,6 +48,11 @@ export const useSendMessage = (): UseSendMessage => { async ({ apiConfig, http, message, conversationId, replacements }: SendMessageProps) => { setIsLoading(true); + const timeoutId = setTimeout(() => { + abortController.current.abort(i18n.FETCH_MESSAGE_TIMEOUT_ERROR); + abortController.current = new AbortController(); + }, EXECUTE_ACTION_TIMEOUT); + try { return await fetchConnectorExecuteAction({ conversationId, @@ -52,6 +67,7 @@ export const useSendMessage = (): UseSendMessage => { traceOptions, }); } finally { + clearTimeout(timeoutId); setIsLoading(false); } }, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/translations.ts new file mode 100644 index 0000000000000..1185d8cfdbc65 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_message/translations.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const FETCH_MESSAGE_TIMEOUT_ERROR = i18n.translate( + 'xpack.elasticAssistant.assistant.useSendMessage.fetchMessageTimeoutError', + { + defaultMessage: 'Assistant could not respond in time. Please try again later.', + } +); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts index 2d86d05447916..5761201849c09 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts @@ -45,6 +45,7 @@ export interface AgentExecutorParams { esClient: ElasticsearchClient; langChainMessages: BaseMessage[]; llmType?: string; + isOssModel?: boolean; logger: Logger; inference: InferenceServerStart; onNewReplacements?: (newReplacements: Replacements) => void; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts index 8395076ad62ee..8f2f713c170ed 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts @@ -94,6 +94,10 @@ export const getDefaultAssistantGraph = ({ value: (x: boolean, y?: boolean) => y ?? x, default: () => false, }, + isOssModel: { + value: (x: boolean, y?: boolean) => y ?? x, + default: () => false, + }, conversation: { value: (x: ConversationResponse | undefined, y?: ConversationResponse | undefined) => y ?? x, diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts index 93890f9dfb121..840b2a9ac8ce0 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts @@ -24,6 +24,7 @@ interface StreamGraphParams { assistantGraph: DefaultAssistantGraph; inputs: GraphInputs; logger: Logger; + isOssModel?: boolean; onLlmResponse?: OnLlmResponse; request: KibanaRequest; traceOptions?: TraceOptions; @@ -36,6 +37,7 @@ interface StreamGraphParams { * @param assistantGraph * @param inputs * @param logger + * @param isOssModel * @param onLlmResponse * @param request * @param traceOptions @@ -45,6 +47,7 @@ export const streamGraph = async ({ assistantGraph, inputs, logger, + isOssModel, onLlmResponse, request, traceOptions, @@ -80,8 +83,8 @@ export const streamGraph = async ({ }; if ( - (inputs?.llmType === 'bedrock' || inputs?.llmType === 'gemini') && - inputs?.bedrockChatEnabled + inputs.isOssModel || + ((inputs?.llmType === 'bedrock' || inputs?.llmType === 'gemini') && inputs?.bedrockChatEnabled) ) { const stream = await assistantGraph.streamEvents( inputs, @@ -92,7 +95,9 @@ export const streamGraph = async ({ version: 'v2', streamMode: 'values', }, - inputs?.llmType === 'bedrock' ? { includeNames: ['Summarizer'] } : undefined + inputs.isOssModel || inputs?.llmType === 'bedrock' + ? { includeNames: ['Summarizer'] } + : undefined ); for await (const { event, data, tags } of stream) { diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts index dee23f202b3d4..daec22b436474 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts @@ -36,6 +36,7 @@ export const callAssistantGraph: AgentExecutor = async ({ inference, langChainMessages, llmType, + isOssModel, logger: parentLogger, isStream = false, onLlmResponse, @@ -48,7 +49,7 @@ export const callAssistantGraph: AgentExecutor = async ({ responseLanguage = 'English', }) => { const logger = parentLogger.get('defaultAssistantGraph'); - const isOpenAI = llmType === 'openai'; + const isOpenAI = llmType === 'openai' && !isOssModel; const llmClass = getLlmClass(llmType, bedrockChatEnabled); /** @@ -111,7 +112,7 @@ export const callAssistantGraph: AgentExecutor = async ({ }; const tools: StructuredTool[] = assistantTools.flatMap( - (tool) => tool.getTool({ ...assistantToolParams, llm: createLlmInstance() }) ?? [] + (tool) => tool.getTool({ ...assistantToolParams, llm: createLlmInstance(), isOssModel }) ?? [] ); // If KB enabled, fetch for any KB IndexEntries and generate a tool for each @@ -166,6 +167,7 @@ export const callAssistantGraph: AgentExecutor = async ({ conversationId, llmType, isStream, + isOssModel, input: latestMessage[0]?.content as string, }; @@ -175,6 +177,7 @@ export const callAssistantGraph: AgentExecutor = async ({ assistantGraph, inputs, logger, + isOssModel, onLlmResponse, request, traceOptions, diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/model_input.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/model_input.ts index f634d10f5cd4a..5f46e1ad2a741 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/model_input.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/model_input.ts @@ -22,7 +22,9 @@ interface ModelInputParams extends NodeParamsBase { export function modelInput({ logger, state }: ModelInputParams): Partial { logger.debug(() => `${NodeType.MODEL_INPUT}: Node state:\n${JSON.stringify(state, null, 2)}`); - const hasRespondStep = state.isStream && state.bedrockChatEnabled && state.llmType === 'bedrock'; + const hasRespondStep = + state.isStream && + (state.isOssModel || (state.bedrockChatEnabled && state.llmType === 'bedrock')); return { hasRespondStep, diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/translations.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/translations.ts index 9eedce48ba69d..e55e1081e6474 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/translations.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/translations.ts @@ -18,3 +18,59 @@ const KB_CATCH = export const GEMINI_SYSTEM_PROMPT = `${BASE_GEMINI_PROMPT} ${KB_CATCH}`; export const BEDROCK_SYSTEM_PROMPT = `Use tools as often as possible, as they have access to the latest data and syntax. Always return value from ESQLKnowledgeBaseTool as is. Never return tags in the response, but make sure to include tags content in the response. Do not reflect on the quality of the returned search results in your response.`; export const GEMINI_USER_PROMPT = `Now, always using the tools at your disposal, step by step, come up with a response to this request:\n\n`; + +export const STRUCTURED_SYSTEM_PROMPT = `Respond to the human as helpfully and accurately as possible. You have access to the following tools: + +{tools} + +The tool action_input should ALWAYS follow the tool JSON schema args. + +Valid "action" values: "Final Answer" or {tool_names} + +Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input strictly adhering to the tool JSON schema args). + +Provide only ONE action per $JSON_BLOB, as shown: + +\`\`\` + +{{ + + "action": $TOOL_NAME, + + "action_input": $TOOL_INPUT + +}} + +\`\`\` + +Follow this format: + +Question: input question to answer + +Thought: consider previous and subsequent steps + +Action: + +\`\`\` + +$JSON_BLOB + +\`\`\` + +Observation: action result + +... (repeat Thought/Action/Observation N times) + +Thought: I know what to respond + +Action: + +\`\`\` + +{{ + + "action": "Final Answer", + + "action_input": "Final response to human"}} + +Begin! Reminder to ALWAYS respond with a valid json blob of a single action with no additional output. When using tools, ALWAYS input the expected JSON schema args. Your answer will be parsed as JSON, so never use double quotes within the output and instead use backticks. Single quotes may be used, such as apostrophes. Response format is Action:\`\`\`$JSON_BLOB\`\`\`then Observation`; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts index 4a7b1fd46ccb8..883047ed7b9df 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts @@ -11,6 +11,7 @@ import { DEFAULT_SYSTEM_PROMPT, GEMINI_SYSTEM_PROMPT, GEMINI_USER_PROMPT, + STRUCTURED_SYSTEM_PROMPT, } from './nodes/translations'; export const formatPrompt = (prompt: string, additionalPrompt?: string) => @@ -26,61 +27,7 @@ export const systemPrompts = { bedrock: `${DEFAULT_SYSTEM_PROMPT} ${BEDROCK_SYSTEM_PROMPT}`, // The default prompt overwhelms gemini, do not prepend gemini: GEMINI_SYSTEM_PROMPT, - structuredChat: `Respond to the human as helpfully and accurately as possible. You have access to the following tools: - -{tools} - -The tool action_input should ALWAYS follow the tool JSON schema args. - -Valid "action" values: "Final Answer" or {tool_names} - -Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input strictly adhering to the tool JSON schema args). - -Provide only ONE action per $JSON_BLOB, as shown: - -\`\`\` - -{{ - - "action": $TOOL_NAME, - - "action_input": $TOOL_INPUT - -}} - -\`\`\` - -Follow this format: - -Question: input question to answer - -Thought: consider previous and subsequent steps - -Action: - -\`\`\` - -$JSON_BLOB - -\`\`\` - -Observation: action result - -... (repeat Thought/Action/Observation N times) - -Thought: I know what to respond - -Action: - -\`\`\` - -{{ - - "action": "Final Answer", - - "action_input": "Final response to human"}} - -Begin! Reminder to ALWAYS respond with a valid json blob of a single action with no additional output. When using tools, ALWAYS input the expected JSON schema args. Your answer will be parsed as JSON, so never use double quotes within the output and instead use backticks. Single quotes may be used, such as apostrophes. Response format is Action:\`\`\`$JSON_BLOB\`\`\`then Observation`, + structuredChat: STRUCTURED_SYSTEM_PROMPT, }; export const openAIFunctionAgentPrompt = formatPrompt(systemPrompts.openai); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts index 17d06b0f7042e..69632be2ffdcd 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts @@ -20,6 +20,7 @@ export interface GraphInputs { conversationId?: string; llmType?: string; isStream?: boolean; + isOssModel?: boolean; input: string; responseLanguage?: string; } @@ -31,6 +32,7 @@ export interface AgentState extends AgentStateBase { lastNode: string; hasRespondStep: boolean; isStream: boolean; + isOssModel: boolean; bedrockChatEnabled: boolean; llmType: string; responseLanguage: string; diff --git a/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts b/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts index dd90241809015..47f6f1a486957 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts @@ -30,6 +30,7 @@ import { } from '../helpers'; import { transformESSearchToAnonymizationFields } from '../../ai_assistant_data_clients/anonymization_fields/helpers'; import { EsAnonymizationFieldsSchema } from '../../ai_assistant_data_clients/anonymization_fields/types'; +import { isOpenSourceModel } from '../utils'; export const SYSTEM_PROMPT_CONTEXT_NON_I18N = (context: string) => { return `CONTEXT:\n"""\n${context}\n"""`; @@ -99,7 +100,9 @@ export const chatCompleteRoute = ( const actions = ctx.elasticAssistant.actions; const actionsClient = await actions.getActionsClientWithRequest(request); const connectors = await actionsClient.getBulk({ ids: [connectorId] }); - actionTypeId = connectors.length > 0 ? connectors[0].actionTypeId : '.gen-ai'; + const connector = connectors.length > 0 ? connectors[0] : undefined; + actionTypeId = connector?.actionTypeId ?? '.gen-ai'; + const isOssModel = isOpenSourceModel(connector); // replacements const anonymizationFieldsRes = @@ -192,6 +195,7 @@ export const chatCompleteRoute = ( actionsClient, actionTypeId, connectorId, + isOssModel, conversationId: conversationId ?? newConversation?.id, context: ctx, getElser, diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts index c0c7bf3f6bc4e..59436070a7125 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts @@ -46,7 +46,7 @@ import { openAIFunctionAgentPrompt, structuredChatAgentPrompt, } from '../../lib/langchain/graphs/default_assistant_graph/prompts'; -import { getLlmClass, getLlmType } from '../utils'; +import { getLlmClass, getLlmType, isOpenSourceModel } from '../utils'; const DEFAULT_SIZE = 20; const ROUTE_HANDLER_TIMEOUT = 10 * 60 * 1000; // 10 * 60 seconds = 10 minutes @@ -174,10 +174,12 @@ export const postEvaluateRoute = ( name: string; graph: DefaultAssistantGraph; llmType: string | undefined; + isOssModel: boolean | undefined; }> = await Promise.all( connectors.map(async (connector) => { const llmType = getLlmType(connector.actionTypeId); - const isOpenAI = llmType === 'openai'; + const isOssModel = isOpenSourceModel(connector); + const isOpenAI = llmType === 'openai' && !isOssModel; const llmClass = getLlmClass(llmType, true); const createLlmInstance = () => new llmClass({ @@ -232,6 +234,7 @@ export const postEvaluateRoute = ( isEnabledKnowledgeBase, kbDataClient: dataClients?.kbDataClient, llm, + isOssModel, logger, modelExists: isEnabledKnowledgeBase, request: skeletonRequest, @@ -274,6 +277,7 @@ export const postEvaluateRoute = ( return { name: `${runName} - ${connector.name}`, llmType, + isOssModel, graph: getDefaultAssistantGraph({ agentRunnable, dataClients, @@ -287,7 +291,7 @@ export const postEvaluateRoute = ( ); // Run an evaluation for each graph so they show up separately (resulting in each dataset run grouped by connector) - await asyncForEach(graphs, async ({ name, graph, llmType }) => { + await asyncForEach(graphs, async ({ name, graph, llmType, isOssModel }) => { // Wrapper function for invoking the graph (to parse different input/output formats) const predict = async (input: { input: string }) => { logger.debug(`input:\n ${JSON.stringify(input, null, 2)}`); @@ -300,6 +304,7 @@ export const postEvaluateRoute = ( llmType, bedrockChatEnabled: true, isStreaming: false, + isOssModel, }, // TODO: Update to use the correct input format per dataset type { runName, @@ -310,15 +315,20 @@ export const postEvaluateRoute = ( return output; }; - const evalOutput = await evaluate(predict, { + evaluate(predict, { data: datasetName ?? '', evaluators: [], // Evals to be managed in LangSmith for now experimentPrefix: name, client: new Client({ apiKey: langSmithApiKey }), // prevent rate limiting and unexpected multiple experiment runs maxConcurrency: 5, - }); - logger.debug(`runResp:\n ${JSON.stringify(evalOutput, null, 2)}`); + }) + .then((output) => { + logger.debug(`runResp:\n ${JSON.stringify(output, null, 2)}`); + }) + .catch((err) => { + logger.error(`evaluation error:\n ${JSON.stringify(err, null, 2)}`); + }); }); return response.ok({ diff --git a/x-pack/plugins/elastic_assistant/server/routes/helpers.ts b/x-pack/plugins/elastic_assistant/server/routes/helpers.ts index 2c0c56c73a2b3..ebd9fd996dfe1 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/helpers.ts @@ -322,6 +322,7 @@ export interface LangChainExecuteParams { actionTypeId: string; connectorId: string; inference: InferenceServerStart; + isOssModel?: boolean; conversationId?: string; context: AwaitedProperties< Pick @@ -348,6 +349,7 @@ export const langChainExecute = async ({ telemetry, actionTypeId, connectorId, + isOssModel, context, actionsClient, inference, @@ -412,6 +414,7 @@ export const langChainExecute = async ({ inference, isStream, llmType: getLlmType(actionTypeId), + isOssModel, langChainMessages, logger, onNewReplacements, diff --git a/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts b/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts index 736d60ff666b0..4b65b5bb3f1e5 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts @@ -29,6 +29,7 @@ import { getSystemPromptFromUserConversation, langChainExecute, } from './helpers'; +import { isOpenSourceModel } from './utils'; export const postActionsConnectorExecuteRoute = ( router: IRouter, @@ -94,6 +95,9 @@ export const postActionsConnectorExecuteRoute = ( const actions = ctx.elasticAssistant.actions; const inference = ctx.elasticAssistant.inference; const actionsClient = await actions.getActionsClientWithRequest(request); + const connectors = await actionsClient.getBulk({ ids: [connectorId] }); + const connector = connectors.length > 0 ? connectors[0] : undefined; + const isOssModel = isOpenSourceModel(connector); const conversationsDataClient = await assistantContext.getAIAssistantConversationsDataClient(); @@ -129,6 +133,7 @@ export const postActionsConnectorExecuteRoute = ( actionsClient, actionTypeId, connectorId, + isOssModel, conversationId, context: ctx, getElser, diff --git a/x-pack/plugins/elastic_assistant/server/routes/utils.test.ts b/x-pack/plugins/elastic_assistant/server/routes/utils.test.ts new file mode 100644 index 0000000000000..3ca1b8edb5036 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/utils.test.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import { isOpenSourceModel } from './utils'; +import { + OPENAI_CHAT_URL, + OpenAiProviderType, +} from '@kbn/stack-connectors-plugin/common/openai/constants'; + +describe('Utils', () => { + describe('isOpenSourceModel', () => { + it('should return `false` when connector is undefined', async () => { + const isOpenModel = isOpenSourceModel(); + expect(isOpenModel).toEqual(false); + }); + + it('should return `false` when connector is a Bedrock', async () => { + const connector = { actionTypeId: '.bedrock' } as Connector; + const isOpenModel = isOpenSourceModel(connector); + expect(isOpenModel).toEqual(false); + }); + + it('should return `false` when connector is a Gemini', async () => { + const connector = { actionTypeId: '.gemini' } as Connector; + const isOpenModel = isOpenSourceModel(connector); + expect(isOpenModel).toEqual(false); + }); + + it('should return `false` when connector is a OpenAI and API url is not specified', async () => { + const connector = { + actionTypeId: '.gen-ai', + } as unknown as Connector; + const isOpenModel = isOpenSourceModel(connector); + expect(isOpenModel).toEqual(false); + }); + + it('should return `false` when connector is a OpenAI and OpenAI API url is specified', async () => { + const connector = { + actionTypeId: '.gen-ai', + config: { apiUrl: OPENAI_CHAT_URL }, + } as unknown as Connector; + const isOpenModel = isOpenSourceModel(connector); + expect(isOpenModel).toEqual(false); + }); + + it('should return `false` when connector is a AzureOpenAI', async () => { + const connector = { + actionTypeId: '.gen-ai', + config: { apiProvider: OpenAiProviderType.AzureAi }, + } as unknown as Connector; + const isOpenModel = isOpenSourceModel(connector); + expect(isOpenModel).toEqual(false); + }); + + it('should return `true` when connector is a OpenAI and non-OpenAI API url is specified', async () => { + const connector = { + actionTypeId: '.gen-ai', + config: { apiUrl: 'https://elastic.llm.com/llama/chat/completions' }, + } as unknown as Connector; + const isOpenModel = isOpenSourceModel(connector); + expect(isOpenModel).toEqual(true); + }); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/routes/utils.ts b/x-pack/plugins/elastic_assistant/server/routes/utils.ts index 651a809e1a56e..5811109b94ede 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/utils.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/utils.ts @@ -19,6 +19,11 @@ import { ActionsClientSimpleChatModel, ActionsClientChatVertexAI, } from '@kbn/langchain/server'; +import { Connector } from '@kbn/actions-plugin/server/application/connector/types'; +import { + OPENAI_CHAT_URL, + OpenAiProviderType, +} from '@kbn/stack-connectors-plugin/common/openai/constants'; import { CustomHttpRequestError } from './custom_http_request_error'; export interface OutputError { @@ -189,3 +194,26 @@ export const getLlmClass = (llmType?: string, bedrockChatEnabled?: boolean) => : llmType === 'gemini' && bedrockChatEnabled ? ActionsClientChatVertexAI : ActionsClientSimpleChatModel; + +export const isOpenSourceModel = (connector?: Connector): boolean => { + if (connector == null) { + return false; + } + + const llmType = getLlmType(connector.actionTypeId); + const connectorApiUrl = connector.config?.apiUrl + ? (connector.config.apiUrl as string) + : undefined; + const connectorApiProvider = connector.config?.apiProvider + ? (connector.config?.apiProvider as OpenAiProviderType) + : undefined; + + const isOpenAiType = llmType === 'openai'; + const isOpenAI = + isOpenAiType && + (!connectorApiUrl || + connectorApiUrl === OPENAI_CHAT_URL || + connectorApiProvider === OpenAiProviderType.AzureAi); + + return isOpenAiType && !isOpenAI; +}; diff --git a/x-pack/plugins/elastic_assistant/server/types.ts b/x-pack/plugins/elastic_assistant/server/types.ts index af8d019539a66..9062bc5a434b1 100755 --- a/x-pack/plugins/elastic_assistant/server/types.ts +++ b/x-pack/plugins/elastic_assistant/server/types.ts @@ -244,6 +244,7 @@ export interface AssistantToolParams { kbDataClient?: AIAssistantKnowledgeBaseDataClient; langChainTimeout?: number; llm?: ActionsClientLlm | AssistantToolLlm; + isOssModel?: boolean; logger: Logger; modelExists: boolean; onNewReplacements?: (newReplacements: Replacements) => void; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/common.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/common.ts new file mode 100644 index 0000000000000..ee2bee8fab806 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/common.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getPromptSuffixForOssModel = (toolName: string) => ` + When using ${toolName} tool ALWAYS pass the user's questions directly as input into the tool. + + Always return value from ${toolName} tool as is. + + The ES|QL query should ALWAYS be wrapped in triple backticks ("\`\`\`esql"). Add a new line character right before the triple backticks. + + It is important that ES|QL query is preceeded by a new line.`; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.test.ts index 7eeb11e8df37a..589c95e8483bf 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.test.ts @@ -12,6 +12,7 @@ import type { KibanaRequest } from '@kbn/core-http-server'; import type { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen'; import { loggerMock } from '@kbn/logging-mocks'; import type { AIAssistantKnowledgeBaseDataClient } from '@kbn/elastic-assistant-plugin/server/ai_assistant_data_clients/knowledge_base'; +import { getPromptSuffixForOssModel } from './common'; describe('EsqlLanguageKnowledgeBaseTool', () => { const kbDataClient = jest.fn() as unknown as AIAssistantKnowledgeBaseDataClient; @@ -108,5 +109,27 @@ describe('EsqlLanguageKnowledgeBaseTool', () => { expect(tool.tags).toEqual(['esql', 'query-generation', 'knowledge-base']); }); + + it('should return tool with the expected description for OSS model', () => { + const tool = ESQL_KNOWLEDGE_BASE_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + isOssModel: true, + ...rest, + }) as DynamicTool; + + expect(tool.description).toContain(getPromptSuffixForOssModel('ESQLKnowledgeBaseTool')); + }); + + it('should return tool with the expected description for non-OSS model', () => { + const tool = ESQL_KNOWLEDGE_BASE_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + isOssModel: false, + ...rest, + }) as DynamicTool; + + expect(tool.description).not.toContain(getPromptSuffixForOssModel('ESQLKnowledgeBaseTool')); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.ts index 6bf116c28719a..37e037898cd20 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.ts @@ -14,12 +14,15 @@ import { z } from '@kbn/zod'; import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; import { ESQL_RESOURCE } from '@kbn/elastic-assistant-plugin/server/routes/knowledge_base/constants'; import { APP_UI_ID } from '../../../../common'; +import { getPromptSuffixForOssModel } from './common'; + +const TOOL_NAME = 'ESQLKnowledgeBaseTool'; const toolDetails = { + id: 'esql-knowledge-base-tool', + name: TOOL_NAME, description: 'Call this for knowledge on how to build an ESQL query, or answer questions about the ES|QL query language. Input must always be the user query on a single line, with no other text. Your answer will be parsed as JSON, so never use quotes within the output and instead use backticks. Do not add any additional text to describe your output.', - id: 'esql-knowledge-base-tool', - name: 'ESQLKnowledgeBaseTool', }; export const ESQL_KNOWLEDGE_BASE_TOOL: AssistantTool = { ...toolDetails, @@ -31,12 +34,13 @@ export const ESQL_KNOWLEDGE_BASE_TOOL: AssistantTool = { getTool(params: AssistantToolParams) { if (!this.isSupported(params)) return null; - const { kbDataClient } = params as AssistantToolParams; + const { kbDataClient, isOssModel } = params as AssistantToolParams; if (kbDataClient == null) return null; return new DynamicStructuredTool({ name: toolDetails.name, - description: toolDetails.description, + description: + toolDetails.description + (isOssModel ? getPromptSuffixForOssModel(TOOL_NAME) : ''), schema: z.object({ question: z.string().describe(`The user's exact question about ESQL`), }), diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.test.ts new file mode 100644 index 0000000000000..f078bccb24a36 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.test.ts @@ -0,0 +1,162 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { RetrievalQAChain } from 'langchain/chains'; +import type { DynamicTool } from '@langchain/core/tools'; +import { NL_TO_ESQL_TOOL } from './nl_to_esql_tool'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen'; +import { loggerMock } from '@kbn/logging-mocks'; +import { getPromptSuffixForOssModel } from './common'; +import type { InferenceServerStart } from '@kbn/inference-plugin/server'; + +describe('NaturalLanguageESQLTool', () => { + const chain = {} as RetrievalQAChain; + const esClient = { + search: jest.fn().mockResolvedValue({}), + } as unknown as ElasticsearchClient; + const request = { + body: { + isEnabledKnowledgeBase: false, + alertsIndexPattern: '.alerts-security.alerts-default', + allow: ['@timestamp', 'cloud.availability_zone', 'user.name'], + allowReplacement: ['user.name'], + replacements: { key: 'value' }, + size: 20, + }, + } as unknown as KibanaRequest; + const logger = loggerMock.create(); + const inference = {} as InferenceServerStart; + const connectorId = 'fake-connector'; + const rest = { + chain, + esClient, + logger, + request, + inference, + connectorId, + }; + + describe('isSupported', () => { + it('returns false if isEnabledKnowledgeBase is false', () => { + const params = { + isEnabledKnowledgeBase: false, + modelExists: true, + ...rest, + }; + + expect(NL_TO_ESQL_TOOL.isSupported(params)).toBe(false); + }); + + it('returns false if modelExists is false (the ELSER model is not installed)', () => { + const params = { + isEnabledKnowledgeBase: true, + modelExists: false, // <-- ELSER model is not installed + ...rest, + }; + + expect(NL_TO_ESQL_TOOL.isSupported(params)).toBe(false); + }); + + it('returns true if isEnabledKnowledgeBase and modelExists are true', () => { + const params = { + isEnabledKnowledgeBase: true, + modelExists: true, + ...rest, + }; + + expect(NL_TO_ESQL_TOOL.isSupported(params)).toBe(true); + }); + }); + + describe('getTool', () => { + it('returns null if isEnabledKnowledgeBase is false', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: false, + modelExists: true, + ...rest, + }); + + expect(tool).toBeNull(); + }); + + it('returns null if modelExists is false (the ELSER model is not installed)', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: false, // <-- ELSER model is not installed + ...rest, + }); + + expect(tool).toBeNull(); + }); + + it('returns null if inference plugin is not provided', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + ...rest, + inference: undefined, + }); + + expect(tool).toBeNull(); + }); + + it('returns null if connectorId is not provided', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + ...rest, + connectorId: undefined, + }); + + expect(tool).toBeNull(); + }); + + it('should return a Tool instance if isEnabledKnowledgeBase and modelExists are true', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + ...rest, + }); + + expect(tool?.name).toEqual('NaturalLanguageESQLTool'); + }); + + it('should return a tool with the expected tags', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + ...rest, + }) as DynamicTool; + + expect(tool.tags).toEqual(['esql', 'query-generation', 'knowledge-base']); + }); + + it('should return tool with the expected description for OSS model', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + isOssModel: true, + ...rest, + }) as DynamicTool; + + expect(tool.description).toContain(getPromptSuffixForOssModel('NaturalLanguageESQLTool')); + }); + + it('should return tool with the expected description for non-OSS model', () => { + const tool = NL_TO_ESQL_TOOL.getTool({ + isEnabledKnowledgeBase: true, + modelExists: true, + isOssModel: false, + ...rest, + }) as DynamicTool; + + expect(tool.description).not.toContain(getPromptSuffixForOssModel('NaturalLanguageESQLTool')); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.ts index a26d16607ac46..96b865efeaed4 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/nl_to_esql_tool.ts @@ -11,6 +11,7 @@ import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant- import { lastValueFrom } from 'rxjs'; import { naturalLanguageToEsql } from '@kbn/inference-plugin/server'; import { APP_UI_ID } from '../../../../common'; +import { getPromptSuffixForOssModel } from './common'; export type ESQLToolParams = AssistantToolParams; @@ -37,7 +38,7 @@ export const NL_TO_ESQL_TOOL: AssistantTool = { getTool(params: ESQLToolParams) { if (!this.isSupported(params)) return null; - const { connectorId, inference, logger, request } = params as ESQLToolParams; + const { connectorId, inference, logger, request, isOssModel } = params as ESQLToolParams; if (inference == null || connectorId == null) return null; const callNaturalLanguageToEsql = async (question: string) => { @@ -46,6 +47,7 @@ export const NL_TO_ESQL_TOOL: AssistantTool = { client: inference.getClient({ request }), connectorId, input: question, + ...(isOssModel ? { functionCalling: 'simulated' } : {}), logger: { debug: (source) => { logger.debug(typeof source === 'function' ? source() : source); @@ -57,7 +59,8 @@ export const NL_TO_ESQL_TOOL: AssistantTool = { return new DynamicStructuredTool({ name: toolDetails.name, - description: toolDetails.description, + description: + toolDetails.description + (isOssModel ? getPromptSuffixForOssModel(TOOL_NAME) : ''), schema: z.object({ question: z.string().describe(`The user's exact question about ESQL`), }), From 55c2fd7fc1ef292961d5d69d20d1711b1fbbd468 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Mon, 7 Oct 2024 17:20:27 -0500 Subject: [PATCH 14/25] [Vega] Fix element sizing issues in fullscreen mode (#194330) - Fixes #194011 where a Vega visualization does not respect the enclosing element dimensions. - Fixes #194861 where resizing Vega visualization panel fails to update to the latest panel dimensions causing scroll bars. --- ....snap => vega_visualization.test.tsx.snap} | 4 +- .../public/components/vega_vis_component.tsx | 30 +++-------- .../vega/public/vega_view/vega_base_view.js | 12 ++--- ...on.test.js => vega_visualization.test.tsx} | 53 ++++++++++--------- .../vega/public/vega_visualization.ts | 5 +- 5 files changed, 44 insertions(+), 60 deletions(-) rename src/plugins/vis_types/vega/public/__snapshots__/{vega_visualization.test.js.snap => vega_visualization.test.tsx.snap} (77%) rename src/plugins/vis_types/vega/public/{vega_visualization.test.js => vega_visualization.test.tsx} (73%) diff --git a/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap b/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.tsx.snap similarity index 77% rename from src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap rename to src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.tsx.snap index a81418c79bb0b..dad12b304efdf 100644 --- a/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap +++ b/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.tsx.snap @@ -2,6 +2,6 @@ exports[`VegaVisualizations VegaVisualization - basics should show vega graph (may fail in dev env) 1`] = `"
"`; -exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 1`] = `"
  • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
"`; +exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 1`] = `"
  • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
"`; -exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 2`] = `"
  • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
"`; +exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 2`] = `"
  • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
"`; diff --git a/src/plugins/vis_types/vega/public/components/vega_vis_component.tsx b/src/plugins/vis_types/vega/public/components/vega_vis_component.tsx index b7a68593ff5dc..7b92bf085179c 100644 --- a/src/plugins/vis_types/vega/public/components/vega_vis_component.tsx +++ b/src/plugins/vis_types/vega/public/components/vega_vis_component.tsx @@ -7,9 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { useEffect, useRef, useMemo, useCallback } from 'react'; +import React, { useEffect, useRef, useCallback } from 'react'; import { EuiResizeObserver, EuiResizeObserverProps, useEuiTheme } from '@elastic/eui'; -import { throttle } from 'lodash'; import type { IInterpreterRenderHandlers, RenderMode } from '@kbn/expressions-plugin/common'; import { createVegaVisualization } from '../vega_visualization'; @@ -28,8 +27,6 @@ interface VegaVisComponentProps { type VegaVisController = InstanceType>; -const THROTTLE_INTERVAL = 300; - export const VegaVisComponent = ({ visData, fireEvent, @@ -64,26 +61,11 @@ export const VegaVisComponent = ({ } }, [renderComplete, visData]); - const resizeChart = useMemo( - () => - throttle( - (dimensions) => { - visController.current?.resize(dimensions); - }, - THROTTLE_INTERVAL, - { leading: false, trailing: true } - ), - [] - ); - - const onContainerResize: EuiResizeObserverProps['onResize'] = useCallback( - (dimensions) => { - if (renderCompleted.current) { - resizeChart(dimensions); - } - }, - [resizeChart] - ); + const onContainerResize: EuiResizeObserverProps['onResize'] = useCallback((dimensions) => { + if (renderCompleted.current) { + visController.current?.resize(dimensions); + } + }, []); const euiTheme = useEuiTheme(); diff --git a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js index c2efcced1bb78..d517e593e2227 100644 --- a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js @@ -282,9 +282,9 @@ export class VegaBaseView { } } - async resize() { + async resize(dimensions) { if (this._parser.useResize && this._view) { - this.updateVegaSize(this._view); + this.updateVegaSize(this._view, dimensions); await this._view.runAsync(); // The derived class should create this method @@ -293,12 +293,8 @@ export class VegaBaseView { } updateVegaSize(view, dimensions) { - const width = Math.floor( - Math.max(0, dimensions?.width ?? this._container.getBoundingClientRect().width) - ); - const height = Math.floor( - Math.max(0, dimensions?.height ?? this._container.getBoundingClientRect().height) - ); + const width = Math.floor(Math.max(0, dimensions?.width ?? this._container.clientWidth - 1)); + const height = Math.floor(Math.max(0, dimensions?.height ?? this._container.clientHeight - 1)); if (view.width() !== width || view.height() !== height) { view.width(width).height(height); diff --git a/src/plugins/vis_types/vega/public/vega_visualization.test.js b/src/plugins/vis_types/vega/public/vega_visualization.test.tsx similarity index 73% rename from src/plugins/vis_types/vega/public/vega_visualization.test.js rename to src/plugins/vis_types/vega/public/vega_visualization.test.tsx index 3d86b3ccdca07..ab51db562f4f2 100644 --- a/src/plugins/vis_types/vega/public/vega_visualization.test.js +++ b/src/plugins/vis_types/vega/public/vega_visualization.test.tsx @@ -8,8 +8,9 @@ */ import 'jest-canvas-mock'; +import { render, screen } from '@testing-library/react'; -import { createVegaVisualization } from './vega_visualization'; +import { VegaVisType, createVegaVisualization } from './vega_visualization'; import vegaliteGraph from './test_utils/vegalite_graph.json'; import vegaGraph from './test_utils/vega_graph.json'; @@ -21,37 +22,41 @@ import { setInjectedVars, setData, setNotifications } from './services'; 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 { VegaVisualizationDependencies } from './plugin'; +import React from 'react'; +import { TimeCache } from './data_model/time_cache'; jest.mock('./default_spec', () => ({ getDefaultSpec: () => jest.requireActual('./test_utils/default.spec.json'), })); describe('VegaVisualizations', () => { - let domNode; - let VegaVisualization; - let vegaVisualizationDependencies; - - let mockGetBoundingClientRect; - let mockedWidthValue; - let mockedHeightValue; + let domNode: HTMLDivElement; + let VegaVisualization: VegaVisType; + let vegaVisualizationDependencies: VegaVisualizationDependencies; + let mockedHeightValue: number; + let mockedWidthValue: number; const coreStart = coreMock.createStart(); const dataPluginStart = dataPluginMock.createStartContract(); const dataViewsPluginStart = dataViewPluginMocks.createStartContract(); const setupDOM = (width = 512, height = 512) => { + render(
); + domNode = screen.getByTestId('vega-vis-text'); + domNode.style.height = `${height}px`; + domNode.style.width = `${width}px`; mockedWidthValue = width; mockedHeightValue = height; - domNode = document.createElement('div'); - mockGetBoundingClientRect = jest - .spyOn(Element.prototype, 'getBoundingClientRect') - .mockImplementation(() => ({ width: mockedWidthValue, height: mockedHeightValue })); + // rtl does not update client dimensions on element, see https://github.com/testing-library/react-testing-library/issues/353 + jest + .spyOn(Element.prototype, 'clientHeight', 'get') + .mockImplementation(() => mockedHeightValue); + jest.spyOn(Element.prototype, 'clientWidth', 'get').mockImplementation(() => mockedWidthValue); }; - const mockGetServiceSettings = () => { - return {}; - }; + const mockGetServiceSettings = jest.fn() as any; beforeEach(() => { setInjectedVars({ @@ -68,7 +73,7 @@ describe('VegaVisualizations', () => { getServiceSettings: mockGetServiceSettings, }; - VegaVisualization = createVegaVisualization(vegaVisualizationDependencies); + VegaVisualization = createVegaVisualization(vegaVisualizationDependencies, 'view'); }); describe('VegaVisualization - basics', () => { @@ -76,15 +81,11 @@ describe('VegaVisualizations', () => { setupDOM(); }); - afterEach(() => { - mockGetBoundingClientRect.mockRestore(); - }); - test('should show vegalite graph and update on resize (may fail in dev env)', async () => { const mockedConsoleLog = jest.spyOn(console, 'log'); // mocked console.log to avoid messages in the console when running tests mockedConsoleLog.mockImplementation(() => {}); // comment this line when console logging for debugging comment this line - let vegaVis; + let vegaVis: InstanceType; try { vegaVis = new VegaVisualization(domNode, jest.fn()); @@ -95,8 +96,8 @@ describe('VegaVisualizations', () => { indexPatterns: dataViewsPluginStart, uiSettings: coreStart.uiSettings, }), - 0, - 0, + new TimeCache(dataPluginStart.query.timefilter.timefilter, 0), + {}, mockGetServiceSettings ); await vegaParser.parseAsync(); @@ -106,12 +107,14 @@ describe('VegaVisualizations', () => { mockedWidthValue = 256; mockedHeightValue = 250; + // @ts-expect-error - accessing private member await vegaVis.vegaView.resize(); expect(domNode.innerHTML).toMatchSnapshot(); } finally { vegaVis.destroy(); } + // eslint-disable-next-line no-console expect(console.log).toBeCalledTimes(2); mockedConsoleLog.mockRestore(); }); @@ -127,8 +130,8 @@ describe('VegaVisualizations', () => { indexPatterns: dataViewsPluginStart, uiSettings: coreStart.uiSettings, }), - 0, - 0, + new TimeCache(dataPluginStart.query.timefilter.timefilter, 0), + {}, mockGetServiceSettings ); await vegaParser.parseAsync(); diff --git a/src/plugins/vis_types/vega/public/vega_visualization.ts b/src/plugins/vis_types/vega/public/vega_visualization.ts index 2a1eb11f5e9c0..0060a1c0b7061 100644 --- a/src/plugins/vis_types/vega/public/vega_visualization.ts +++ b/src/plugins/vis_types/vega/public/vega_visualization.ts @@ -15,7 +15,10 @@ import { getNotifications, getData } from './services'; import type { VegaView } from './vega_view/vega_view'; import { createVegaStateRestorer } from './lib/vega_state_restorer'; -type VegaVisType = new (el: HTMLDivElement, fireEvent: IInterpreterRenderHandlers['event']) => { +export type VegaVisType = new ( + el: HTMLDivElement, + fireEvent: IInterpreterRenderHandlers['event'] +) => { render(visData: VegaParser): Promise; resize(dimensions?: { height: number; width: number }): Promise; destroy(): void; From 84d9187ab527400420118f9029e72f8d8009d4c5 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 00:23:33 +0200 Subject: [PATCH 15/25] Update dependency @redocly/cli to ^1.25.4 (main) (#195239) --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fb4b6ebcc44c9..a35db36de9b61 100644 --- a/package.json +++ b/package.json @@ -1491,7 +1491,7 @@ "@octokit/rest": "^17.11.2", "@parcel/watcher": "^2.1.0", "@playwright/test": "=1.46.0", - "@redocly/cli": "^1.25.3", + "@redocly/cli": "^1.25.4", "@statoscope/webpack-plugin": "^5.28.2", "@storybook/addon-a11y": "^6.5.16", "@storybook/addon-actions": "^6.5.16", diff --git a/yarn.lock b/yarn.lock index a0aa74c80e252..d244902ca5622 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8451,7 +8451,7 @@ require-from-string "^2.0.2" uri-js-replace "^1.0.1" -"@redocly/cli@^1.25.3": +"@redocly/cli@^1.25.4": version "1.25.5" resolved "https://registry.yarnpkg.com/@redocly/cli/-/cli-1.25.5.tgz#258f6d23e8298814518ec4d24d023c1e21e3b081" integrity sha512-sFh4A8wqwuig7mF/nYNVIyxSfKKZikWC+uVH6OB1IepYQXNsHFaLAU1VaNI9gS5mMvWmYx5SEuSCVB9LaNFBhw== From 28d6a22263ea1f60d9b17aacde7fa3517efd244e Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Mon, 7 Oct 2024 23:31:52 +0100 Subject: [PATCH 16/25] [Observability Onboarding] Update onboarding landing page (#194565) Resolves https://github.com/elastic/observability-dev/issues/3775 Resolves https://github.com/elastic/kibana/issues/192949 ## Summary Updates the "add data" page according to new design Screenshot 2024-10-01 at 12 36 15 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../onboarding_flow_form.tsx | 255 ++++++++++-------- .../application/onboarding_flow_form/types.ts | 2 +- ...y.ts => use_custom_cards_for_category.tsx} | 210 +++++++-------- .../use_virtual_search_results.ts | 22 +- .../application/packages_list/index.tsx | 35 +-- .../use_integration_card_list.ts | 8 +- .../public/application/packages_list/utils.ts | 21 -- .../public/application/shared/logo_icon.tsx | 6 +- .../public/assets/apple.svg | 9 + .../public/assets/linux.svg | 9 + .../translations/translations/fr-FR.json | 7 - .../translations/translations/ja-JP.json | 7 - .../translations/translations/zh-CN.json | 7 - 13 files changed, 308 insertions(+), 290 deletions(-) rename x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/{use_custom_cards_for_category.ts => use_custom_cards_for_category.tsx} (66%) delete mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/application/packages_list/utils.ts create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/assets/apple.svg create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/assets/linux.svg diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/onboarding_flow_form.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/onboarding_flow_form.tsx index 42f3dbd6f09f7..01a1e066c4ddb 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/onboarding_flow_form.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/onboarding_flow_form.tsx @@ -10,7 +10,6 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import type { FunctionComponent } from 'react'; import { - EuiAvatar, EuiCheckableCard, EuiFlexGroup, EuiFlexItem, @@ -21,12 +20,13 @@ import { useGeneratedHtmlId, useEuiTheme, EuiBadge, + EuiFlexGrid, } from '@elastic/eui'; +import { css } from '@emotion/react'; import { useSearchParams } from 'react-router-dom-v5-compat'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { OnboardingFlowPackageList } from '../packages_list'; -import { useCustomMargin } from '../shared/use_custom_margin'; import { Category } from './types'; import { useCustomCardsForCategory } from './use_custom_cards_for_category'; import { useVirtualSearchResults } from './use_virtual_search_results'; @@ -44,51 +44,63 @@ interface UseCaseOption { export const OnboardingFlowForm: FunctionComponent = () => { const options: UseCaseOption[] = [ { - id: 'logs', + id: 'host', label: i18n.translate( - 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.collectAndAnalyzeMyLabel', - { defaultMessage: 'Collect and analyze logs' } + 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.hostLabel', + { defaultMessage: 'Host' } ), description: i18n.translate( - 'xpack.observability_onboarding.onboardingFlowForm.detectPatternsAndOutliersLabel', + 'xpack.observability_onboarding.onboardingFlowForm.hostDescription', { defaultMessage: - 'Detect patterns, gain insights from logs, get alerted when surpassing error thresholds', + 'Monitor your host and the services running on it, set-up SLO, get alerted, remediate performance issues', } ), - logos: ['azure', 'aws', 'nginx', 'gcp'], - showIntegrationsBadge: true, + logos: ['kubernetes', 'opentelemetry', 'apache', 'mysql'], }, { - id: 'apm', + id: 'kubernetes', label: i18n.translate( - 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.monitorMyApplicationPerformanceLabel', - { defaultMessage: 'Monitor my application performance' } + 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.kubernetesLabel', + { defaultMessage: 'Kubernetes' } ), description: i18n.translate( - 'xpack.observability_onboarding.onboardingFlowForm.captureAndAnalyzeDistributedLabel', + 'xpack.observability_onboarding.onboardingFlowForm.kubernetesDescription', { defaultMessage: - 'Catch application problems, get alerted on performance issues or SLO breaches, expedite root cause analysis and remediation', + 'Observe your Kubernetes cluster, and your container workloads using logs, metrics, traces and profiling data', } ), - logos: ['opentelemetry', 'java', 'javascript', 'dotnet'], + logos: ['kubernetes', 'opentelemetry'], }, { - id: 'infra', + id: 'application', label: i18n.translate( - 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.monitorMyInfrastructureLabel', - { defaultMessage: 'Monitor infrastructure' } + 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.applicationLabel', + { defaultMessage: 'Application' } ), description: i18n.translate( - 'xpack.observability_onboarding.onboardingFlowForm.builtOnPowerfulElasticsearchLabel', + 'xpack.observability_onboarding.onboardingFlowForm.applicationDescription', { defaultMessage: - 'Check my system’s health, get alerted on performance issues or SLO breaches, expedite root cause analysis and remediation', + 'Monitor the frontend and backend application that you have developed, set-up synthetic monitors', } ), - logos: ['kubernetes', 'prometheus', 'docker', 'opentelemetry'], - showIntegrationsBadge: true, + logos: ['opentelemetry', 'java', 'javascript', 'dotnet'], + }, + { + id: 'cloud', + label: i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.euiCheckableCard.cloudLabel', + { defaultMessage: 'Cloud' } + ), + description: i18n.translate( + 'xpack.observability_onboarding.onboardingFlowForm.cloudDescription', + { + defaultMessage: 'Ingest telemetry data from the Cloud for your applications and services', + } + ), + logos: ['azure', 'aws', 'gcp'], }, ]; @@ -97,7 +109,6 @@ export const OnboardingFlowForm: FunctionComponent = () => { context: { isCloud }, }, } = useKibana(); - const customMargin = useCustomMargin(); const radioGroupId = useGeneratedHtmlId({ prefix: 'onboardingCategory' }); const categorySelectorTitleId = useGeneratedHtmlId(); const packageListTitleId = useGeneratedHtmlId(); @@ -152,33 +163,72 @@ export const OnboardingFlowForm: FunctionComponent = () => { return ( - - - + + + {i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.strong.startCollectingYourDataLabel', + { + defaultMessage: 'What do you want to monitor?', + } + )} + + + + {options.map((option) => ( {option.label}} + label={ + <> + + {option.label} + + {/* The description and logo icons are passed into `label` prop instead of `children` to ensure they are clickable */} + + + {option.description} + + {(option.logos || option.showIntegrationsBadge) && ( + <> + + + {option.logos?.map((logo) => ( + + + + ))} + {option.showIntegrationsBadge && ( + + + + )} + + + )} + + } checked={option.id === searchParams.get('category')} /** * onKeyDown and onKeyUp handlers disable @@ -204,54 +254,62 @@ export const OnboardingFlowForm: FunctionComponent = () => { ); } }} - > - - {option.description} - - {(option.logos || option.showIntegrationsBadge) && ( - <> - - - {option.logos?.map((logo) => ( - - - - ))} - {option.showIntegrationsBadge && ( - - - - )} - - - )} - + css={css` + flex-grow: 1; + + & > .euiPanel { + display: flex; + + & > .euiCheckableCard__label { + display: flex; + flex-direction: column; + } + } + `} + /> ))} - + {/* Hiding element instead of not rending these elements in order to preload available packages on page load */}