From 70a5bb33c438912b64259ea4c7a3c77c41f93f45 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Mon, 9 Dec 2024 20:21:16 +0100 Subject: [PATCH 1/3] [Rules migration] Add sorting functionality to rules migration table (#11379) (#203396) ## Summary [Internal link](https://github.com/elastic/security-team/issues/10820) to the feature details These changes add sorting functionality to the migration rules table. It is possible to sort migration rules by next columns: `Updated`, `Name`, `Status`, `Risk Score`, `Severity` and `Author`. ### Other changes Next fixes and adjustments were also implemented as part of this PR: * `Installed` status in migration rules table to indicate whether the rule was installed * Rules selection and installation of selected rules * Disable selection for not fully translated rules * `Author` column to show whether the translated rule matched one of the existing Elastic prebuilt rules * `Install and enable` and `Install without enabling` buttons within the migration rule details flyout --- .../kbn-index-adapter/src/field_maps/types.ts | 1 + .../model/api/rules/rule_migration.gen.ts | 10 +- .../api/rules/rule_migration.schema.yaml | 29 +++- .../common/siem_migrations/rules/utils.ts | 6 +- .../public/siem_migrations/rules/api/index.ts | 23 +++- .../components/rule_details_flyout/index.tsx | 3 +- .../components/rules_table/bulk_actions.tsx | 17 ++- .../rules/components/rules_table/index.tsx | 119 ++++++++++++++-- .../components/rules_table/translations.ts | 28 ++++ .../components/rules_table_columns/author.tsx | 39 ++++++ .../components/rules_table_columns/index.tsx | 1 + .../components/rules_table_columns/name.tsx | 11 +- .../rules_table_columns/risk_score.tsx | 2 +- .../rules_table_columns/severity.tsx | 5 +- .../components/rules_table_columns/status.tsx | 4 +- .../rules_table_columns/translations.ts | 21 +++ .../components/status_badge/index.test.tsx | 19 --- .../rules/components/status_badge/index.tsx | 36 +++-- .../components/status_badge/translations.ts | 15 +++ .../use_migration_rules_table_columns.tsx | 2 + .../rules/logic/use_get_migration_rules.ts | 2 + .../logic/use_install_migration_rules.ts | 4 +- .../lib/siem_migrations/rules/api/get.ts | 9 +- .../lib/siem_migrations/rules/api/install.ts | 3 +- .../rules/api/install_translated.ts | 1 + .../rules/api/util/installation.ts | 19 ++- .../data/rule_migrations_data_rules_client.ts | 61 +++------ .../rules/data/rule_migrations_field_maps.ts | 4 +- .../lib/siem_migrations/rules/data/search.ts | 42 ++++++ .../lib/siem_migrations/rules/data/sort.ts | 127 ++++++++++++++++++ 30 files changed, 541 insertions(+), 122 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/author.tsx delete mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/translations.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/search.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/sort.ts diff --git a/packages/kbn-index-adapter/src/field_maps/types.ts b/packages/kbn-index-adapter/src/field_maps/types.ts index 1cdafc7c61809..90fb44873a342 100644 --- a/packages/kbn-index-adapter/src/field_maps/types.ts +++ b/packages/kbn-index-adapter/src/field_maps/types.ts @@ -46,6 +46,7 @@ export type FieldMap = Record< array?: boolean; doc_values?: boolean; enabled?: boolean; + fields?: Record; format?: string; ignore_above?: number; multi_fields?: MultiField[]; diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts index 58944ff7f2f95..95a81d4436d8a 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts @@ -59,6 +59,8 @@ export type GetRuleMigrationRequestQuery = z.infer; @@ -154,7 +156,13 @@ export type InstallMigrationRulesRequestParamsInput = z.input< >; export type InstallMigrationRulesRequestBody = z.infer; -export const InstallMigrationRulesRequestBody = z.array(NonEmptyString); +export const InstallMigrationRulesRequestBody = z.object({ + ids: z.array(NonEmptyString), + /** + * Indicates whether installed rules should be enabled + */ + enabled: z.boolean().optional(), +}); export type InstallMigrationRulesRequestBodyInput = z.input< typeof InstallMigrationRulesRequestBody >; diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml index dff6089b2b48f..b7e495e2ea898 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml @@ -133,6 +133,19 @@ paths: required: false schema: type: number + - name: sort_field + in: query + required: false + schema: + $ref: '../../../../../common/api/model/primitives.schema.yaml#/components/schemas/NonEmptyString' + - name: sort_direction + in: query + required: false + schema: + type: string + enum: + - asc + - desc - name: search_term in: query required: false @@ -180,10 +193,18 @@ paths: content: application/json: schema: - type: array - items: - description: The rule migration id - $ref: '../../../../../common/api/model/primitives.schema.yaml#/components/schemas/NonEmptyString' + type: object + required: + - ids + properties: + ids: + type: array + items: + description: The rule migration id + $ref: '../../../../../common/api/model/primitives.schema.yaml#/components/schemas/NonEmptyString' + enabled: + type: boolean + description: Indicates whether installed rules should be enabled responses: 200: description: Indicates rules migrations have been installed correctly. diff --git a/x-pack/plugins/security_solution/common/siem_migrations/rules/utils.ts b/x-pack/plugins/security_solution/common/siem_migrations/rules/utils.ts index a9b8666b19981..8763e057052b5 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/rules/utils.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/rules/utils.ts @@ -22,13 +22,17 @@ export const isMigrationCustomRule = (rule?: ElasticRule): rule is MigrationCust !isMigrationPrebuiltRule(rule) && !!(rule?.title && rule?.description && rule?.query && rule?.query_language); -export const convertMigrationCustomRuleToSecurityRulePayload = (rule: MigrationCustomRule) => { +export const convertMigrationCustomRuleToSecurityRulePayload = ( + rule: MigrationCustomRule, + enabled: boolean +) => { return { type: rule.query_language, language: rule.query_language, query: rule.query, name: rule.title, description: rule.description, + enabled, ...DEFAULT_TRANSLATION_FIELDS, severity: (rule.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY, diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts index ac9e49ff861fc..57fb5d0422093 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts @@ -120,6 +120,10 @@ export interface GetRuleMigrationParams { page?: number; /** Optional number of documents per page to retrieve */ perPage?: number; + /** Optional field of the rule migration object to sort results by */ + sortField?: string; + /** Optional direction to sort results by */ + sortDirection?: 'asc' | 'desc'; /** Optional search term to filter documents */ searchTerm?: string; /** Optional AbortSignal for cancelling request */ @@ -130,12 +134,24 @@ export const getRuleMigrations = async ({ migrationId, page, perPage, + sortField, + sortDirection, searchTerm, signal, }: GetRuleMigrationParams): Promise => { return KibanaServices.get().http.get( replaceParams(SIEM_RULE_MIGRATION_PATH, { migration_id: migrationId }), - { version: '1', query: { page, per_page: perPage, search_term: searchTerm }, signal } + { + version: '1', + query: { + page, + per_page: perPage, + sort_field: sortField, + sort_direction: sortDirection, + search_term: searchTerm, + }, + signal, + } ); }; @@ -163,6 +179,8 @@ export interface InstallRulesParams { migrationId: string; /** The rule ids to install */ ids: string[]; + /** Optional indicator to enable the installed rule */ + enabled?: boolean; /** Optional AbortSignal for cancelling request */ signal?: AbortSignal; } @@ -170,11 +188,12 @@ export interface InstallRulesParams { export const installMigrationRules = async ({ migrationId, ids, + enabled, signal, }: InstallRulesParams): Promise => { return KibanaServices.get().http.post( replaceParams(SIEM_RULE_MIGRATION_INSTALL_PATH, { migration_id: migrationId }), - { version: '1', body: JSON.stringify(ids), signal } + { version: '1', body: JSON.stringify({ ids, enabled }), signal } ); }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx index 8fea17b51cb0e..9762cc578e0cc 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx @@ -84,7 +84,8 @@ export const MigrationRuleDetailsFlyout: React.FC { if (isMigrationCustomRule(ruleMigration.elastic_rule)) { return convertMigrationCustomRuleToSecurityRulePayload( - ruleMigration.elastic_rule + ruleMigration.elastic_rule, + false ) as RuleResponse; // TODO: we need to adjust RuleOverviewTab to allow partial RuleResponse as a parameter; } return matchedPrebuiltRule; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx index a58681b6e771f..8f32308ed52c4 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx @@ -6,7 +6,13 @@ */ import React from 'react'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, +} from '@elastic/eui'; import * as i18n from './translations'; export interface BulkActionsProps { @@ -29,13 +35,14 @@ export const BulkActions: React.FC = React.memo( installSelectedRule, }) => { const disableInstallTranslatedRulesButton = isTableLoading || !numberOfTranslatedRules; - const showInstallSelectedRulesButton = - disableInstallTranslatedRulesButton && numberOfSelectedRules > 0; + const showInstallSelectedRulesButton = isTableLoading || numberOfSelectedRules > 0; return ( {showInstallSelectedRulesButton ? ( - = React.memo( > {i18n.INSTALL_SELECTED_RULES(numberOfSelectedRules)} {isTableLoading && } - + ) : null} diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx index 13b451c2918d8..106e7ba514d3f 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { CriteriaWithPagination } from '@elastic/eui'; +import type { CriteriaWithPagination, EuiTableSelectionType } from '@elastic/eui'; import { EuiSkeletonLoading, EuiSkeletonTitle, @@ -14,6 +14,7 @@ import { EuiFlexItem, EuiSpacer, EuiBasicTable, + EuiButton, } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; @@ -30,8 +31,12 @@ import { useGetMigrationPrebuiltRules } from '../../logic/use_get_migration_preb import * as logicI18n from '../../logic/translations'; import { BulkActions } from './bulk_actions'; import { SearchField } from './search_field'; +import { SiemMigrationRuleTranslationResult } from '../../../../../common/siem_migrations/constants'; +import * as i18n from './translations'; const DEFAULT_PAGE_SIZE = 10; +const DEFAULT_SORT_FIELD = 'translation_result'; +const DEFAULT_SORT_DIRECTION = 'desc'; export interface MigrationRulesTableProps { /** @@ -49,6 +54,8 @@ export const MigrationRulesTable: React.FC = React.mem const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(DEFAULT_SORT_DIRECTION); const [searchTerm, setSearchTerm] = useState(); const { data: translationStats, isLoading: isStatsLoading } = @@ -64,10 +71,33 @@ export const MigrationRulesTable: React.FC = React.mem migrationId, page: pageIndex, perPage: pageSize, + sortField, + sortDirection, searchTerm, }); const [selectedRuleMigrations, setSelectedRuleMigrations] = useState([]); + const tableSelection: EuiTableSelectionType = useMemo( + () => ({ + selectable: (item: RuleMigration) => { + return ( + !item.elastic_rule?.id && + item.translation_result === SiemMigrationRuleTranslationResult.FULL + ); + }, + selectableMessage: (selectable: boolean, item: RuleMigration) => { + if (selectable) { + return ''; + } + return item.elastic_rule?.id + ? i18n.ALREADY_TRANSLATED_RULE_TOOLTIP + : i18n.NOT_FULLY_TRANSLATED_RULE_TOOLTIP; + }, + onSelectionChange: setSelectedRuleMigrations, + selected: selectedRuleMigrations, + }), + [selectedRuleMigrations] + ); const pagination = useMemo(() => { return { @@ -77,11 +107,25 @@ export const MigrationRulesTable: React.FC = React.mem }; }, [pageIndex, pageSize, total]); + const sorting = useMemo(() => { + return { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + }, [sortDirection, sortField]); + const onTableChange = useCallback(({ page, sort }: CriteriaWithPagination) => { if (page) { setPageIndex(page.index); setPageSize(page.size); } + if (sort) { + const { field, direction } = sort; + setSortField(field); + setSortDirection(direction); + } }, []); const handleOnSearch = useCallback((value: string) => { @@ -94,10 +138,10 @@ export const MigrationRulesTable: React.FC = React.mem const [isTableLoading, setTableLoading] = useState(false); const installSingleRule = useCallback( - async (migrationRule: RuleMigration, enable?: boolean) => { + async (migrationRule: RuleMigration, enabled = false) => { setTableLoading(true); try { - await installMigrationRules([migrationRule.id]); + await installMigrationRules({ ids: [migrationRule.id], enabled }); } catch (error) { addError(error, { title: logicI18n.INSTALL_MIGRATION_RULES_FAILURE }); } finally { @@ -107,6 +151,24 @@ export const MigrationRulesTable: React.FC = React.mem [addError, installMigrationRules] ); + const installSelectedRule = useCallback( + async (enabled = false) => { + setTableLoading(true); + try { + await installMigrationRules({ + ids: selectedRuleMigrations.map((rule) => rule.id), + enabled, + }); + } catch (error) { + addError(error, { title: logicI18n.INSTALL_MIGRATION_RULES_FAILURE }); + } finally { + setTableLoading(false); + setSelectedRuleMigrations([]); + } + }, + [addError, installMigrationRules, selectedRuleMigrations] + ); + const installTranslatedRules = useCallback( async (enable?: boolean) => { setTableLoading(true); @@ -121,12 +183,45 @@ export const MigrationRulesTable: React.FC = React.mem [addError, installTranslatedMigrationRules] ); + const isLoading = isStatsLoading || isPrebuiltRulesLoading || isDataLoading || isTableLoading; + const ruleActionsFactory = useCallback( (ruleMigration: RuleMigration, closeRulePreview: () => void) => { - // TODO: Add flyout action buttons - return null; + const canMigrationRuleBeInstalled = + !isLoading && + !ruleMigration.elastic_rule?.id && + ruleMigration.translation_result === SiemMigrationRuleTranslationResult.FULL; + return ( + + + { + installSingleRule(ruleMigration); + closeRulePreview(); + }} + data-test-subj="installMigrationRuleFromFlyoutButton" + > + {i18n.INSTALL_WITHOUT_ENABLING_BUTTON_LABEL} + + + + { + installSingleRule(ruleMigration, true); + closeRulePreview(); + }} + fill + data-test-subj="installAndEnableMigrationRuleFromFlyoutButton" + > + {i18n.INSTALL_AND_ENABLE_BUTTON_LABEL} + + + + ); }, - [] + [installSingleRule, isLoading] ); const { @@ -143,8 +238,6 @@ export const MigrationRulesTable: React.FC = React.mem installMigrationRule: installSingleRule, }); - const isLoading = isStatsLoading || isPrebuiltRulesLoading || isDataLoading || isTableLoading; - return ( <> = React.mem @@ -178,12 +272,9 @@ export const MigrationRulesTable: React.FC = React.mem loading={isTableLoading} items={ruleMigrations} pagination={pagination} + sorting={sorting} onChange={onTableChange} - selection={{ - selectable: () => true, - onSelectionChange: setSelectedRuleMigrations, - initialSelected: selectedRuleMigrations, - }} + selection={tableSelection} itemId={'id'} data-test-subj={'rules-translation-table'} columns={rulesColumns} diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts index 6803deb895d9b..79b5a1fe00900 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts @@ -80,3 +80,31 @@ export const INSTALL_TRANSLATED_ARIA_LABEL = i18n.translate( defaultMessage: 'Install all translated rules', } ); + +export const ALREADY_TRANSLATED_RULE_TOOLTIP = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.alreadyTranslatedTooltip', + { + defaultMessage: 'Already translated migration rule', + } +); + +export const NOT_FULLY_TRANSLATED_RULE_TOOLTIP = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.notFullyTranslatedTooltip', + { + defaultMessage: 'Not fully translated migration rule', + } +); + +export const INSTALL_WITHOUT_ENABLING_BUTTON_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.installWithoutEnablingButtonLabel', + { + defaultMessage: 'Install without enabling', + } +); + +export const INSTALL_AND_ENABLE_BUTTON_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.installAndEnableButtonLabel', + { + defaultMessage: 'Install and enable', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/author.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/author.tsx new file mode 100644 index 0000000000000..23980f5612f89 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/author.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; +import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; +import * as i18n from './translations'; +import type { TableColumn } from './constants'; + +const Author = ({ isPrebuiltRule }: { isPrebuiltRule: boolean }) => { + return ( + + {isPrebuiltRule && ( + + + + )} + + {isPrebuiltRule ? i18n.ELASTIC_AUTHOR_TITLE : i18n.CUSTOM_AUTHOR_TITLE} + + + ); +}; + +export const createAuthorColumn = (): TableColumn => { + return { + field: 'elastic_rule.prebuilt_rule_id', + name: i18n.COLUMN_AUTHOR, + render: (_, rule: RuleMigration) => { + return ; + }, + sortable: true, + width: '10%', + }; +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx index 4220a35ed4622..61af73ca7b9f8 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx @@ -8,6 +8,7 @@ export * from './constants'; export * from './actions'; +export * from './author'; export * from './name'; export * from './risk_score'; export * from './severity'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx index 085a2f5c6a254..dd77636521eda 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx @@ -12,12 +12,11 @@ import * as i18n from './translations'; import type { TableColumn } from './constants'; interface NameProps { - name: string; rule: RuleMigration; openMigrationRuleDetails: (rule: RuleMigration) => void; } -const Name = ({ name, rule, openMigrationRuleDetails }: NameProps) => { +const Name = ({ rule, openMigrationRuleDetails }: NameProps) => { return ( { @@ -25,7 +24,7 @@ const Name = ({ name, rule, openMigrationRuleDetails }: NameProps) => { }} data-test-subj="ruleName" > - {name} + {rule.elastic_rule?.title} ); }; @@ -36,10 +35,10 @@ export const createNameColumn = ({ openMigrationRuleDetails: (rule: RuleMigration) => void; }): TableColumn => { return { - field: 'original_rule.title', + field: 'elastic_rule.title', name: i18n.COLUMN_NAME, - render: (value: RuleMigration['original_rule']['title'], rule: RuleMigration) => ( - + render: (_, rule: RuleMigration) => ( + ), sortable: true, truncateText: true, diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx index e9eca65736a51..0fb78ae8bf709 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx @@ -22,6 +22,6 @@ export const createRiskScoreColumn = (): TableColumn => { ), sortable: true, truncateText: true, - width: '75px', + width: '10%', }; }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx index 4ea737844ad53..9a6c0b98ff317 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx @@ -8,9 +8,7 @@ import React from 'react'; import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import { DEFAULT_TRANSLATION_SEVERITY } from '../../../../../common/siem_migrations/constants'; -import { getNormalizedSeverity } from '../../../../detection_engine/rule_management_ui/components/rules_table/helpers'; import { SeverityBadge } from '../../../../common/components/severity_badge'; -import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; import type { TableColumn } from './constants'; import * as i18n from './translations'; @@ -19,8 +17,7 @@ export const createSeverityColumn = (): TableColumn => { field: 'elastic_rule.severity', name: i18n.COLUMN_SEVERITY, render: (value?: Severity) => , - sortable: ({ elastic_rule: elasticRule }: RuleMigration) => - getNormalizedSeverity((elasticRule?.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY), + sortable: true, truncateText: true, width: '12%', }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx index 982f6ba7580b2..5daec1f1b4fa9 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx @@ -18,8 +18,8 @@ export const createStatusColumn = (): TableColumn => { render: (value: RuleMigration['translation_result'], rule: RuleMigration) => ( ), - sortable: false, + sortable: true, truncateText: true, - width: '12%', + width: '15%', }; }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts index 5b40abd3d7485..64e459a609143 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts @@ -14,6 +14,27 @@ export const COLUMN_ACTIONS = i18n.translate( } ); +export const COLUMN_AUTHOR = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.authorLabel', + { + defaultMessage: 'Author', + } +); + +export const ELASTIC_AUTHOR_TITLE = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.elasticAuthorTitle', + { + defaultMessage: 'Elastic', + } +); + +export const CUSTOM_AUTHOR_TITLE = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.customAuthorTitle', + { + defaultMessage: 'Custom', + } +); + export const ACTIONS_VIEW_LABEL = i18n.translate( 'xpack.securitySolution.siemMigrations.rules.tableColumn.actionsViewLabel', { diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx deleted file mode 100644 index aaf256cfb60b5..0000000000000 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { shallow } from 'enzyme'; - -import { StatusBadge } from '.'; - -describe('StatusBadge', () => { - it('renders correctly', () => { - const wrapper = shallow(); - - expect(wrapper.find('HealthTruncateText')).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx index 60f0ed94862ca..8f8bcff40f674 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx @@ -8,9 +8,16 @@ import React from 'react'; import { euiLightVars } from '@kbn/ui-theme'; +import { EuiFlexGroup, EuiFlexItem, EuiHealth, EuiIcon, EuiToolTip } from '@elastic/eui'; +import { css } from '@emotion/css'; import type { RuleMigrationTranslationResult } from '../../../../../common/siem_migrations/model/rule_migration.gen'; -import { HealthTruncateText } from '../../../../common/components/health_truncate_text'; import { convertTranslationResultIntoText } from '../../utils/helpers'; +import * as i18n from './translations'; + +const statusTextWrapperClassName = css` + width: 100%; + display: inline-grid; +`; const { euiColorVis0, euiColorVis7, euiColorVis9 } = euiLightVars; const statusToColorMap: Record = { @@ -28,17 +35,28 @@ interface StatusBadgeProps { export const StatusBadge: React.FC = React.memo( ({ value, installedRuleId, 'data-test-subj': dataTestSubj = 'translation-result' }) => { const translationResult = installedRuleId ? 'full' : value ?? 'untranslatable'; - const displayValue = convertTranslationResultIntoText(translationResult); + const displayValue = installedRuleId + ? i18n.RULE_STATUS_INSTALLED + : convertTranslationResultIntoText(translationResult); const color = statusToColorMap[translationResult]; return ( - - {displayValue} - + + {installedRuleId ? ( + + + + + {displayValue} + + ) : ( + +
+ {displayValue} +
+
+ )} +
); } ); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/translations.ts new file mode 100644 index 0000000000000..0a7b1c37f7acf --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/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 RULE_STATUS_INSTALLED = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.status.installedLabel', + { + defaultMessage: 'Installed', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx index b8b37bccaffd3..3c2d5a696ccc1 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx @@ -10,6 +10,7 @@ import type { RuleMigration } from '../../../../common/siem_migrations/model/rul import type { TableColumn } from '../components/rules_table_columns'; import { createActionsColumn, + createAuthorColumn, createNameColumn, createRiskScoreColumn, createSeverityColumn, @@ -33,6 +34,7 @@ export const useMigrationRulesTableColumns = ({ createStatusColumn(), createRiskScoreColumn(), createSeverityColumn(), + createAuthorColumn(), createActionsColumn({ disableActions, openMigrationRuleDetails, diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts index 5f59ceb9f76c2..4109575459233 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts @@ -18,6 +18,8 @@ export const useGetMigrationRules = (params: { migrationId: string; page?: number; perPage?: number; + sortField: string; + sortDirection: 'asc' | 'desc'; searchTerm?: string; }) => { const { addError } = useAppToasts(); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts index 755faa03bff14..2b28b3b944990 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts @@ -23,8 +23,8 @@ export const useInstallMigrationRules = (migrationId: string) => { const invalidateGetMigrationTranslationStats = useInvalidateGetMigrationTranslationStats(migrationId); - return useMutation( - (ids: string[]) => installMigrationRules({ migrationId, ids }), + return useMutation( + ({ ids, enabled = false }) => installMigrationRules({ migrationId, ids, enabled }), { mutationKey: INSTALL_MIGRATION_RULES_MUTATION_KEY, onError: (error) => { diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts index dd13a75cdf83a..30037aeea88ae 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts @@ -39,13 +39,20 @@ export const registerSiemRuleMigrationsGetRoute = ( }, withLicense(async (context, req, res): Promise> => { const { migration_id: migrationId } = req.params; - const { page, per_page: perPage, search_term: searchTerm } = req.query; + const { + page, + per_page: perPage, + sort_field: sortField, + sort_direction: sortDirection, + search_term: searchTerm, + } = req.query; try { const ctx = await context.resolve(['securitySolution']); const ruleMigrationsClient = ctx.securitySolution.getSiemRuleMigrationsClient(); const options: RuleMigrationGetOptions = { filters: { searchTerm }, + sort: { sortField, sortDirection }, size: perPage, from: page && perPage ? page * perPage : 0, }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install.ts index 7b41ea536aadf..9fae2922b486f 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install.ts @@ -40,7 +40,7 @@ export const registerSiemRuleMigrationsInstallRoute = ( withLicense( async (context, req, res): Promise> => { const { migration_id: migrationId } = req.params; - const ids = req.body; + const { ids, enabled = false } = req.body; try { const ctx = await context.resolve(['core', 'alerting', 'securitySolution']); @@ -52,6 +52,7 @@ export const registerSiemRuleMigrationsInstallRoute = ( await installTranslated({ migrationId, ids, + enabled, securitySolutionContext, savedObjectsClient, rulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install_translated.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install_translated.ts index ac6a598c4b92f..2cf2a2e2c8efd 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install_translated.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install_translated.ts @@ -50,6 +50,7 @@ export const registerSiemRuleMigrationsInstallTranslatedRoute = ( await installTranslated({ migrationId, + enabled: false, securitySolutionContext, savedObjectsClient, rulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts index d74619e4c1251..8716c83ce6ba3 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts @@ -27,6 +27,7 @@ import { const installPrebuiltRules = async ( rulesToInstall: StoredRuleMigration[], + enabled: boolean, securitySolutionContext: SecuritySolutionApiRequestHandlerContext, rulesClient: RulesClient, savedObjectsClient: SavedObjectsClientContract, @@ -41,7 +42,7 @@ const installPrebuiltRules = async ( if (item.current) { acc.installed.push(item.current); } else { - acc.installable.push(item.target); + acc.installable.push({ ...item.target, enabled }); } return acc; }, @@ -85,6 +86,7 @@ const installPrebuiltRules = async ( export const installCustomRules = async ( rulesToInstall: StoredRuleMigration[], + enabled: boolean, detectionRulesClient: IDetectionRulesClient, logger: Logger ): Promise => { @@ -96,8 +98,11 @@ export const installCustomRules = async ( if (!isMigrationCustomRule(rule.elastic_rule)) { return; } - const payloadRule = convertMigrationCustomRuleToSecurityRulePayload(rule.elastic_rule); - const createdRule = await detectionRulesClient.createPrebuiltRule({ + const payloadRule = convertMigrationCustomRuleToSecurityRulePayload( + rule.elastic_rule, + enabled + ); + const createdRule = await detectionRulesClient.createCustomRule({ params: payloadRule, }); rulesToUpdate.push({ @@ -131,6 +136,11 @@ interface InstallTranslatedProps { */ ids?: string[]; + /** + * Indicates whether the installed migration rules should be enabled + */ + enabled: boolean; + /** * The security solution context */ @@ -155,6 +165,7 @@ interface InstallTranslatedProps { export const installTranslated = async ({ migrationId, ids, + enabled, securitySolutionContext, rulesClient, savedObjectsClient, @@ -186,6 +197,7 @@ export const installTranslated = async ({ const updatedPrebuiltRules = await installPrebuiltRules( prebuiltRulesToInstall, + enabled, securitySolutionContext, rulesClient, savedObjectsClient, @@ -194,6 +206,7 @@ export const installTranslated = async ({ const updatedCustomRules = await installCustomRules( customRulesToInstall, + enabled, detectionRulesClient, logger ); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts index f11b24e50b95a..1eeb3ced0572a 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts @@ -15,10 +15,7 @@ import type { QueryDslQueryContainer, } from '@elastic/elasticsearch/lib/api/types'; import type { StoredRuleMigration } from '../types'; -import { - SiemMigrationRuleTranslationResult, - SiemMigrationStatus, -} from '../../../../../common/siem_migrations/constants'; +import { SiemMigrationStatus } from '../../../../../common/siem_migrations/constants'; import type { ElasticRule, RuleMigration, @@ -26,6 +23,8 @@ import type { RuleMigrationTranslationStats, } from '../../../../../common/siem_migrations/model/rule_migration.gen'; import { RuleMigrationsDataBaseClient } from './rule_migrations_data_base_client'; +import { getSortingOptions, type RuleMigrationSort } from './sort'; +import { conditions as searchConditions } from './search'; export type CreateRuleMigrationInput = Omit< RuleMigration, @@ -47,6 +46,7 @@ export interface RuleMigrationFilters { } export interface RuleMigrationGetOptions { filters?: RuleMigrationFilters; + sort?: RuleMigrationSort; from?: number; size?: number; } @@ -120,13 +120,19 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient /** Retrieves an array of rule documents of a specific migrations */ async get( migrationId: string, - { filters = {}, from, size }: RuleMigrationGetOptions = {} + { filters = {}, sort = {}, from, size }: RuleMigrationGetOptions = {} ): Promise<{ total: number; data: StoredRuleMigration[] }> { const index = await this.getIndexName(); const query = this.getFilterQuery(migrationId, { ...filters }); const result = await this.esClient - .search({ index, query, sort: '_doc', from, size }) + .search({ + index, + query, + sort: sort.sortField ? getSortingOptions(sort) : undefined, + from, + size, + }) .catch((error) => { this.logger.error(`Error searching rule migrations: ${error.message}`); throw error; @@ -238,8 +244,8 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient const query = this.getFilterQuery(migrationId); const aggregations = { - prebuilt: { filter: conditions.isPrebuilt() }, - installable: { filter: { bool: { must: conditions.isInstallable() } } }, + prebuilt: { filter: searchConditions.isPrebuilt() }, + installable: { filter: { bool: { must: searchConditions.isInstallable() } } }, }; const result = await this.esClient .search({ index, query, aggregations, _source: false }) @@ -351,47 +357,14 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient filter.push({ terms: { _id: ids } }); } if (installable) { - filter.push(...conditions.isInstallable()); + filter.push(...searchConditions.isInstallable()); } if (prebuilt) { - filter.push(conditions.isPrebuilt()); + filter.push(searchConditions.isPrebuilt()); } if (searchTerm?.length) { - filter.push(conditions.matchTitle(searchTerm)); + filter.push(searchConditions.matchTitle(searchTerm)); } return { bool: { filter } }; } } - -const conditions = { - isFullyTranslated(): QueryDslQueryContainer { - return { term: { translation_result: SiemMigrationRuleTranslationResult.FULL } }; - }, - isNotInstalled(): QueryDslQueryContainer { - return { - nested: { - path: 'elastic_rule', - query: { bool: { must_not: { exists: { field: 'elastic_rule.id' } } } }, - }, - }; - }, - isPrebuilt(): QueryDslQueryContainer { - return { - nested: { - path: 'elastic_rule', - query: { exists: { field: 'elastic_rule.prebuilt_rule_id' } }, - }, - }; - }, - matchTitle(title: string): QueryDslQueryContainer { - return { - nested: { - path: 'elastic_rule', - query: { match: { 'elastic_rule.title': title } }, - }, - }; - }, - isInstallable(): QueryDslQueryContainer[] { - return [this.isFullyTranslated(), this.isNotInstalled()]; - }, -}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts index 7aca804c12890..952663c36123c 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts @@ -19,14 +19,14 @@ export const ruleMigrationsFieldMap: FieldMap + direction === 'desc' ? '_last' : '_first'; + +const sortingOptions = { + matchedPrebuiltRule(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] { + return [ + { + 'elastic_rule.prebuilt_rule_id': { + order: direction, + nested: { path: 'elastic_rule' }, + missing: sortMissingValue(direction), + }, + }, + ]; + }, + severity(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] { + const field = 'elastic_rule.severity'; + return [ + { + _script: { + order: direction, + type: 'number', + script: { + source: ` + if (doc.containsKey('${field}') && !doc['${field}'].empty) { + def value = doc['${field}'].value.toLowerCase(); + if (value == 'critical') { return 3 } + if (value == 'high') { return 2 } + if (value == 'medium') { return 1 } + if (value == 'low') { return 0 } + } + return -1; + `, + lang: 'painless', + }, + nested: { path: 'elastic_rule' }, + }, + }, + ]; + }, + status(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] { + const field = 'translation_result'; + const installedRuleField = 'elastic_rule.id'; + return [ + { + _script: { + order: direction, + type: 'number', + script: { + source: ` + if (doc.containsKey('${field}') && !doc['${field}'].empty) { + def value = doc['${field}'].value.toLowerCase(); + if (value == 'full') { return 2 } + if (value == 'partial') { return 1 } + if (value == 'untranslatable') { return 0 } + } + return -1; + `, + lang: 'painless', + }, + }, + }, + { + _script: { + order: direction, + type: 'number', + script: { + source: ` + if (doc.containsKey('${installedRuleField}') && !doc['${installedRuleField}'].empty) { + return 0; + } + return -1; + `, + lang: 'painless', + }, + nested: { path: 'elastic_rule' }, + }, + }, + ]; + }, + updated(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] { + return [{ updated_at: direction }]; + }, + name(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] { + return [ + { 'elastic_rule.title.keyword': { order: direction, nested: { path: 'elastic_rule' } } }, + ]; + }, +}; + +const DEFAULT_SORTING: estypes.Sort = [ + ...sortingOptions.status('desc'), + ...sortingOptions.matchedPrebuiltRule('desc'), + ...sortingOptions.severity(), + ...sortingOptions.updated(), +]; + +const sortingOptionsMap: { + [key: string]: (direction?: estypes.SortOrder) => estypes.SortCombinations[]; +} = { + 'elastic_rule.title': sortingOptions.name, + 'elastic_rule.severity': sortingOptions.severity, + 'elastic_rule.prebuilt_rule_id': sortingOptions.matchedPrebuiltRule, + translation_result: sortingOptions.status, + updated_at: sortingOptions.updated, +}; + +export const getSortingOptions = (sort?: RuleMigrationSort): estypes.Sort => { + if (!sort?.sortField) { + return DEFAULT_SORTING; + } + return sortingOptionsMap[sort.sortField]?.(sort.sortDirection) ?? DEFAULT_SORTING; +}; From 598d3defd15442766ce1eec5b717f479ea62b5df Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 9 Dec 2024 13:03:23 -0700 Subject: [PATCH 2/3] Preparation for High Contrast Mode, Security domains (#202609) ## Summary **Reviewers: Please test the code paths affected by this PR. See the "Risks" section below.** Part of work for enabling "high contrast mode" in Kibana. See https://github.com/elastic/kibana/issues/176219. **Background:** Kibana will soon have a user profile setting to allow users to enable "high contrast mode." This setting will activate a flag with `` that causes EUI components to render with higher contrast visual elements. Consumer plugins and packages need to be updated selected places where `` is wrapped, to pass the `UserProfileService` service dependency from the CoreStart contract. **NOTE:** **EUI currently does not yet support the high-contrast mode flag**, but support for that is expected to come in around 2 weeks. These first PRs are simply preparing the code by wiring up the `UserProvideService`. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [X] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [X] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [medium/high] The implementor of this change did not manually test the affected code paths and relied on type-checking and functional tests to drive the changes. Code owners for this PR need to manually test the affected code paths. - [ ] [medium] The `UserProfileService` dependency comes from the CoreStart contract. If acquiring the service causes synchronous code to become asynchronous, check for race conditions or errors in rendering React components. Code owners for this PR need to manually test the affected code paths. --- packages/kbn-mock-idp-plugin/public/plugin.tsx | 4 ++-- .../kbn-mock-idp-plugin/public/reload_page_toast.tsx | 4 +++- packages/kbn-mock-idp-plugin/public/role_switcher.tsx | 1 + packages/kbn-mock-idp-plugin/tsconfig.json | 1 + packages/kbn-user-profile-components/src/services.tsx | 6 ++++-- packages/kbn-user-profile-components/tsconfig.json | 1 + x-pack/plugins/security/public/index.ts | 2 +- .../management/roles/edit_role/edit_role_page.test.tsx | 4 ++++ .../management/roles/roles_grid/roles_grid_page.test.tsx | 9 ++++++++- .../management/roles/roles_grid/roles_grid_page.tsx | 8 ++------ .../components/insecure_cluster_alert.tsx | 2 ++ .../public/security_checkup/security_checkup_service.tsx | 2 ++ x-pack/plugins/security/tsconfig.json | 1 + .../edit_space/edit_space_content_tab.test.tsx | 3 +++ .../edit_space/edit_space_general_tab.test.tsx | 3 +++ .../management/edit_space/edit_space_roles_tab.test.tsx | 3 +++ .../management/edit_space/edit_space_roles_tab.tsx | 4 +++- .../edit_space/provider/edit_space_provider.test.tsx | 3 +++ .../edit_space/provider/edit_space_provider.tsx | 5 ++++- .../component/space_assign_role_privilege_form.test.tsx | 3 +++ .../public/management/spaces_management_app.test.tsx | 2 +- .../spaces/public/management/spaces_management_app.tsx | 5 +++-- .../spaces/public/space_selector/space_selector.tsx | 2 +- x-pack/plugins/spaces/tsconfig.json | 3 ++- 24 files changed, 61 insertions(+), 20 deletions(-) diff --git a/packages/kbn-mock-idp-plugin/public/plugin.tsx b/packages/kbn-mock-idp-plugin/public/plugin.tsx index c1f733027f656..d27a9d0bc8ad5 100644 --- a/packages/kbn-mock-idp-plugin/public/plugin.tsx +++ b/packages/kbn-mock-idp-plugin/public/plugin.tsx @@ -49,7 +49,7 @@ export const plugin: PluginInitializer< ]); ReactDOM.render( - + @@ -69,7 +69,7 @@ export const plugin: PluginInitializer< order: 4000 + 1, // Make sure it comes after the user menu mount: (element: HTMLElement) => { ReactDOM.render( - + diff --git a/packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx b/packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx index c3f93f3269da1..e254e21d47cde 100644 --- a/packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx +++ b/packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx @@ -13,6 +13,7 @@ import React from 'react'; import type { I18nStart } from '@kbn/core-i18n-browser'; import type { ToastInput } from '@kbn/core-notifications-browser'; import type { ThemeServiceStart } from '@kbn/core-theme-browser'; +import type { UserProfileService } from '@kbn/core-user-profile-browser'; import { toMountPoint } from '@kbn/react-kibana-mount'; import type { AuthenticatedUser } from '@kbn/security-plugin-types-common'; @@ -26,6 +27,7 @@ export const DATA_TEST_SUBJ_PAGE_RELOAD_BUTTON = 'pageReloadButton'; */ export const createReloadPageToast = (options: { user: Pick; + userProfile: UserProfileService; theme: ThemeServiceStart; i18n: I18nStart; }): ToastInput => { @@ -43,7 +45,7 @@ export const createReloadPageToast = (options: { , - { i18n: options.i18n, theme: options.theme } + options ), color: 'success', toastLifeTimeMs: 0x7fffffff, // Do not auto-hide toast since page is in an unknown state diff --git a/packages/kbn-mock-idp-plugin/public/role_switcher.tsx b/packages/kbn-mock-idp-plugin/public/role_switcher.tsx index 347293abbc6c7..7a3845b0cc54a 100644 --- a/packages/kbn-mock-idp-plugin/public/role_switcher.tsx +++ b/packages/kbn-mock-idp-plugin/public/role_switcher.tsx @@ -69,6 +69,7 @@ export const RoleSwitcher = () => { services.notifications.toasts.add( createReloadPageToast({ user: authenticateUserState.value, + userProfile: services.userProfile, theme: services.theme, i18n: services.i18n, }) diff --git a/packages/kbn-mock-idp-plugin/tsconfig.json b/packages/kbn-mock-idp-plugin/tsconfig.json index 83c4023733404..d8b5fad09fb06 100644 --- a/packages/kbn-mock-idp-plugin/tsconfig.json +++ b/packages/kbn-mock-idp-plugin/tsconfig.json @@ -26,5 +26,6 @@ "@kbn/mock-idp-utils", "@kbn/cloud-plugin", "@kbn/es", + "@kbn/core-user-profile-browser", ] } diff --git a/packages/kbn-user-profile-components/src/services.tsx b/packages/kbn-user-profile-components/src/services.tsx index 7cf7a2d66c82f..f59caee26a034 100644 --- a/packages/kbn-user-profile-components/src/services.tsx +++ b/packages/kbn-user-profile-components/src/services.tsx @@ -13,6 +13,7 @@ import React, { useContext } from 'react'; import type { I18nStart } from '@kbn/core-i18n-browser'; import type { NotificationsStart, ToastOptions } from '@kbn/core-notifications-browser'; import type { ThemeServiceStart } from '@kbn/core-theme-browser'; +import type { UserProfileService } from '@kbn/core-user-profile-browser'; import type { toMountPoint } from '@kbn/react-kibana-mount'; import type { UserProfileAPIClient } from './types'; @@ -47,6 +48,7 @@ export interface UserProfilesKibanaDependencies { core: { notifications: NotificationsStart; theme: ThemeServiceStart; + userProfile: UserProfileService; i18n: I18nStart; }; security: { @@ -70,7 +72,7 @@ export const UserProfilesKibanaProvider: FC { const { - core: { notifications, i18n, theme }, + core: { notifications, ...startServices }, security: { userProfiles: userProfileApiClient }, toMountPoint: toMountPointUtility, } = services; @@ -86,7 +88,7 @@ export const UserProfilesKibanaProvider: FC = (initializerContext: PluginInitializerContext) => new SecurityPlugin(initializerContext); // services needed for rendering React using shared modules -export type StartServices = Pick; +export type StartServices = Pick; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx index 9a9abab064fa8..166b1de7cf057 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx @@ -15,6 +15,7 @@ import { coreMock, scopedHistoryMock } from '@kbn/core/public/mocks'; import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks'; import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { KibanaFeature } from '@kbn/features-plugin/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; @@ -196,6 +197,8 @@ function getProps({ const analyticsMock = analyticsServiceMock.createAnalyticsServiceStart(); const i18nMock = i18nServiceMock.createStartContract(); const themeMock = themeServiceMock.createStartContract(); + const userProfileMock = userProfileServiceMock.createStart(); + return { action, roleName: role?.name, @@ -214,6 +217,7 @@ function getProps({ history: scopedHistoryMock.create(), spacesApiUi, buildFlavor, + userProfile: userProfileMock, theme: themeMock, i18n: i18nMock, analytics: analyticsMock, diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx index 57281f5ec754c..1c965551a63dc 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx @@ -44,7 +44,7 @@ const waitForRender = async ( describe('', () => { let apiClientMock: jest.Mocked>; let history: ReturnType; - const { theme, i18n, analytics, notifications } = coreMock.createStart(); + const { userProfile, theme, i18n, analytics, notifications } = coreMock.createStart(); beforeEach(() => { history = scopedHistoryMock.create(); @@ -93,6 +93,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} /> ); const initialIconCount = wrapper.find(EuiIcon).length; @@ -115,6 +116,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} /> ); const initialIconCount = wrapper.find(EuiIcon).length; @@ -139,6 +141,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} /> ); await waitForRender(wrapper, (updatedWrapper) => { @@ -157,6 +160,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} /> ); const initialIconCount = wrapper.find(EuiIcon).length; @@ -201,6 +205,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} /> ); const initialIconCount = wrapper.find(EuiIcon).length; @@ -332,6 +337,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} /> ); const initialIconCount = wrapper.find(EuiIcon).length; @@ -441,6 +447,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} readOnly /> ); diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx index 457b9053a0ac8..cfea3a7f29804 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx @@ -75,9 +75,7 @@ export const RolesGridPage: FC = ({ readOnly, buildFlavor, cloudOrgUrl, - analytics, - theme, - i18n: i18nStart, + ...startServices }) => { const [roles, setRoles] = useState([]); const [visibleRoles, setVisibleRoles] = useState([]); @@ -409,9 +407,7 @@ export const RolesGridPage: FC = ({ notifications={notifications} rolesAPIClient={rolesAPIClient} buildFlavor={buildFlavor} - theme={theme} - analytics={analytics} - i18n={i18nStart} + {...startServices} /> ) : null} diff --git a/x-pack/plugins/security/public/security_checkup/components/insecure_cluster_alert.tsx b/x-pack/plugins/security/public/security_checkup/components/insecure_cluster_alert.tsx index 71e1a53e7d2fb..37b58eecc4d9e 100644 --- a/x-pack/plugins/security/public/security_checkup/components/insecure_cluster_alert.tsx +++ b/x-pack/plugins/security/public/security_checkup/components/insecure_cluster_alert.tsx @@ -22,6 +22,7 @@ import type { I18nStart, MountPoint, ThemeServiceStart, + UserProfileService, } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -37,6 +38,7 @@ interface Deps { analytics: Pick; i18n: I18nStart; theme: Pick; + userProfile: UserProfileService; } export const insecureClusterAlertText = (deps: Deps, onDismiss: (persist: boolean) => void) => diff --git a/x-pack/plugins/security/public/security_checkup/security_checkup_service.tsx b/x-pack/plugins/security/public/security_checkup/security_checkup_service.tsx index 4818e3721818a..295aef39f02e4 100644 --- a/x-pack/plugins/security/public/security_checkup/security_checkup_service.tsx +++ b/x-pack/plugins/security/public/security_checkup/security_checkup_service.tsx @@ -16,6 +16,7 @@ import type { NotificationsStart, ThemeServiceStart, Toast, + UserProfileService, } from '@kbn/core/public'; import { insecureClusterAlertText, insecureClusterAlertTitle } from './components'; @@ -33,6 +34,7 @@ interface StartDeps { analytics: Pick; i18n: I18nStart; theme: Pick; + userProfile: UserProfileService; } const DEFAULT_SECURITY_CHECKUP_STATE = Object.freeze({ diff --git a/x-pack/plugins/security/tsconfig.json b/x-pack/plugins/security/tsconfig.json index bfbf5df127597..518af73f96877 100644 --- a/x-pack/plugins/security/tsconfig.json +++ b/x-pack/plugins/security/tsconfig.json @@ -91,6 +91,7 @@ "@kbn/core-capabilities-server", "@kbn/core-elasticsearch-server", "@kbn/core-http-server-utils", + "@kbn/core-user-profile-browser-mocks", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/spaces/public/management/edit_space/edit_space_content_tab.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/edit_space_content_tab.test.tsx index 6f668b79756c8..5946706874c4a 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/edit_space_content_tab.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/edit_space_content_tab.test.tsx @@ -16,6 +16,7 @@ import { overlayServiceMock, themeServiceMock, } from '@kbn/core/public/mocks'; +import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { EditSpaceContentTab } from './edit_space_content_tab'; @@ -37,6 +38,7 @@ const http = httpServiceMock.createStartContract(); const notifications = notificationServiceMock.createStartContract(); const overlays = overlayServiceMock.createStartContract(); const theme = themeServiceMock.createStartContract(); +const userProfile = userProfileServiceMock.createStart(); const i18n = i18nServiceMock.createStartContract(); const logger = loggingSystemMock.createLogger(); @@ -62,6 +64,7 @@ const TestComponent: React.FC = ({ children }) => { getPrivilegesAPIClient={getPrivilegeAPIClient} getSecurityLicense={getSecurityLicenseMock} theme={theme} + userProfile={userProfile} i18n={i18n} logger={logger} > diff --git a/x-pack/plugins/spaces/public/management/edit_space/edit_space_general_tab.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/edit_space_general_tab.test.tsx index 2344b92832db6..353c64b835c0e 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/edit_space_general_tab.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/edit_space_general_tab.test.tsx @@ -19,6 +19,7 @@ import { themeServiceMock, } from '@kbn/core/public/mocks'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; +import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { KibanaFeature } from '@kbn/features-plugin/common'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; @@ -43,6 +44,7 @@ const reloadWindow = jest.fn(); const http = httpServiceMock.createStartContract(); const notifications = notificationServiceMock.createStartContract(); const overlays = overlayServiceMock.createStartContract(); +const userProfile = userProfileServiceMock.createStart(); const theme = themeServiceMock.createStartContract(); const i18n = i18nServiceMock.createStartContract(); const logger = loggingSystemMock.createLogger(); @@ -83,6 +85,7 @@ describe('EditSpaceSettings', () => { getIsRoleManagementEnabled={() => Promise.resolve(() => undefined)} getPrivilegesAPIClient={getPrivilegeAPIClient} getSecurityLicense={getSecurityLicenseMock} + userProfile={userProfile} theme={theme} i18n={i18n} logger={logger} diff --git a/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.test.tsx index 542af2222c3f1..02a754e9d93f0 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.test.tsx @@ -16,6 +16,7 @@ import { overlayServiceMock, themeServiceMock, } from '@kbn/core/public/mocks'; +import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { EditSpaceAssignedRolesTab } from './edit_space_roles_tab'; @@ -34,6 +35,7 @@ const getPrivilegeAPIClient = getPrivilegeAPIClientMock; const http = httpServiceMock.createStartContract(); const notifications = notificationServiceMock.createStartContract(); const overlays = overlayServiceMock.createStartContract(); +const userProfile = userProfileServiceMock.createStart(); const theme = themeServiceMock.createStartContract(); const i18n = i18nServiceMock.createStartContract(); const logger = loggingSystemMock.createLogger(); @@ -77,6 +79,7 @@ describe('EditSpaceAssignedRolesTab', () => { getIsRoleManagementEnabled={getIsRoleManagementEnabled} getPrivilegesAPIClient={getPrivilegeAPIClient} getSecurityLicense={getSecurityLicenseMock} + userProfile={userProfile} theme={theme} i18n={i18n} logger={logger} diff --git a/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.tsx b/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.tsx index 18e11110d7564..34ebbf1989bbc 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.tsx @@ -33,6 +33,7 @@ export const EditSpaceAssignedRolesTab: FC = ({ space, features, isReadOn const { getUrlForApp, overlays, + userProfile, theme, i18n: i18nStart, logger, @@ -99,7 +100,7 @@ export const EditSpaceAssignedRolesTab: FC = ({ space, features, isReadOn }} /> , - { theme, i18n: i18nStart } + { theme, i18n: i18nStart, userProfile } ), { size: 'm', @@ -117,6 +118,7 @@ export const EditSpaceAssignedRolesTab: FC = ({ space, features, isReadOn features, invokeClient, getUrlForApp, + userProfile, theme, i18nStart, notifications.toasts, diff --git a/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.test.tsx index 4da9806b0dee0..df2135e256970 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.test.tsx @@ -18,6 +18,7 @@ import { themeServiceMock, } from '@kbn/core/public/mocks'; import type { ApplicationStart } from '@kbn/core-application-browser'; +import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { @@ -33,6 +34,7 @@ import { getSecurityLicenseMock } from '../../security_license.mock'; const http = httpServiceMock.createStartContract(); const notifications = notificationServiceMock.createStartContract(); const overlays = overlayServiceMock.createStartContract(); +const userProfile = userProfileServiceMock.createStart(); const theme = themeServiceMock.createStartContract(); const i18n = i18nServiceMock.createStartContract(); const logger = loggingSystemMock.createLogger(); @@ -55,6 +57,7 @@ const SUTProvider = ({ logger, i18n, http, + userProfile, theme, overlays, notifications, diff --git a/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.tsx b/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.tsx index 7eafb4ae7e391..8de774797c974 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.tsx @@ -35,7 +35,10 @@ import { import type { SpacesManager } from '../../../spaces_manager'; export interface EditSpaceProviderRootProps - extends Pick { + extends Pick< + CoreStart, + 'userProfile' | 'theme' | 'i18n' | 'overlays' | 'http' | 'notifications' + > { logger: Logger; capabilities: ApplicationStart['capabilities']; getUrlForApp: ApplicationStart['getUrlForApp']; diff --git a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx index 5b1e263e20f16..ea24864bb092c 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx @@ -19,6 +19,7 @@ import { overlayServiceMock, themeServiceMock, } from '@kbn/core/public/mocks'; +import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import type { Role, SecurityLicense } from '@kbn/security-plugin-types-common'; import { @@ -43,6 +44,7 @@ const privilegeAPIClient = createPrivilegeAPIClientMock(); const http = httpServiceMock.createStartContract(); const notifications = notificationServiceMock.createStartContract(); const overlays = overlayServiceMock.createStartContract(); +const userProfile = userProfileServiceMock.createStart(); const theme = themeServiceMock.createStartContract(); const i18n = i18nServiceMock.createStartContract(); const logger = loggingSystemMock.createLogger(); @@ -89,6 +91,7 @@ const renderPrivilegeRolesForm = ({ logger, i18n, http, + userProfile, theme, overlays, notifications, diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx index 67973542aae54..d721ff79600c3 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -192,7 +192,7 @@ describe('spacesManagementApp', () => { css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)." data-test-subj="kbnRedirectAppLink" > - Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"serverBasePath":"","http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"overlays":{"banners":{}},"notifications":{"toasts":{}},"theme":{"theme$":{}},"i18n":{},"logger":{"context":[]},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true,"allowSolutionVisibility":true} + Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"serverBasePath":"","http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"overlays":{"banners":{}},"notifications":{"toasts":{}},"userProfile":{},"theme":{"theme$":{}},"i18n":{},"logger":{"context":[]},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true,"allowSolutionVisibility":true} `); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx index 13546ef3e77f0..3e72ce63caeb9 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -82,7 +82,7 @@ export const spacesManagementApp = Object.freeze({ text: title, href: `/`, }; - const { notifications, application, chrome, http, overlays, theme } = coreStart; + const { notifications, application, chrome, http, overlays } = coreStart; chrome.docTitle.change(title); @@ -160,7 +160,8 @@ export const spacesManagementApp = Object.freeze({ http={http} overlays={overlays} notifications={notifications} - theme={theme} + userProfile={coreStart.userProfile} + theme={coreStart.theme} i18n={coreStart.i18n} logger={logger} spacesManager={spacesManager} diff --git a/x-pack/plugins/spaces/public/space_selector/space_selector.tsx b/x-pack/plugins/spaces/public/space_selector/space_selector.tsx index d334ee9efab35..8808aabd490aa 100644 --- a/x-pack/plugins/spaces/public/space_selector/space_selector.tsx +++ b/x-pack/plugins/spaces/public/space_selector/space_selector.tsx @@ -249,7 +249,7 @@ export class SpaceSelector extends Component { } export const renderSpaceSelectorApp = ( - services: Pick, + services: Pick, { element }: Pick, props: Props ) => { diff --git a/x-pack/plugins/spaces/tsconfig.json b/x-pack/plugins/spaces/tsconfig.json index 771f1b2e6139b..83c93cd898f10 100644 --- a/x-pack/plugins/spaces/tsconfig.json +++ b/x-pack/plugins/spaces/tsconfig.json @@ -54,7 +54,8 @@ "@kbn/core-application-browser-mocks", "@kbn/ui-theme", "@kbn/core-chrome-browser", - "@kbn/core-lifecycle-server" + "@kbn/core-lifecycle-server", + "@kbn/core-user-profile-browser-mocks", ], "exclude": [ "target/**/*", From 371fd70769a21d107deae77c8fb506f0e04ac54e Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 10 Dec 2024 09:56:45 +1100 Subject: [PATCH 3/3] skip failing test suite (#203477) --- .../test_suites/search/connectors/connectors_overview.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test_serverless/functional/test_suites/search/connectors/connectors_overview.ts b/x-pack/test_serverless/functional/test_suites/search/connectors/connectors_overview.ts index 31b52c3592626..95c98254dbb1f 100644 --- a/x-pack/test_serverless/functional/test_suites/search/connectors/connectors_overview.ts +++ b/x-pack/test_serverless/functional/test_suites/search/connectors/connectors_overview.ts @@ -19,7 +19,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const browser = getService('browser'); - describe('connectors', function () { + // Failing: See https://github.com/elastic/kibana/issues/203477 + describe.skip('connectors', function () { before(async () => { await pageObjects.svlSearchConnectorsPage.helpers.deleteAllConnectors(); await pageObjects.svlCommonPage.loginWithRole('developer');