From bb89f90d6caa9c810ad6b73ef7a0d578c9297a04 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Fri, 25 Aug 2023 21:47:13 +0200 Subject: [PATCH] [Security Solution] Prebuilt rules installation / upgrade flyout improvements (#164179) **Addresses: https://github.com/elastic/kibana/issues/162334** **Base PR: https://github.com/elastic/kibana/pull/163304** Screenshot 2023-08-24 at 04 09 07 ## Summary This is a follow-up refactoring and bugfix PR to improve the prebuilt rules flyout. Base PR: #163304 #### Changes - [x] Tweak UI so that it matches the design more closely. [Design](https://www.figma.com/file/gLHm8LpTtSkAUQHrkG3RHU/%5B8.7%5D-%5BRules%5D-Rule-Immutability%2FCustomization?type=design&node-id=3563-612771&mode=design&t=yqZ6LI0vAjbir9xc-0) (external). - [x] Rewrite preview installation and upgrade API endpoints to respond with `RuleResponse` instead of `DiffableRule` - [x] Revert some changes introduced by this [PR](https://github.com/elastic/kibana/pull/163304) - [x] Revert exports in `x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.ts` - [x] Delete `x-pack/plugins/security_solution/common/detection_engine/diffable_rule_to_rule_response.ts` - [x] Make the data contexts unaware of any UI elements that are consuming them - [x] Move rendering of specialized flyout components into to the context provider so that the table is unaware of the flyout. - [x] Make "flyoutRule" and "closeFlyout" internal to the context. Components outside don't need to know anything about how a rule is displayed. We can encapsulate this knowledge inside the context and expose only a generic method, like openRulePreview(ruleId) - [x] Remove unnecessary checks after using "invariant" - [x] Make sure query, timeline template and all the other fields are shown in the flyout. Compare each rule in a flyout with the Rule Details to ensure that all fields are in place. - [x] Remove the enable / disable switch machine learning job UI switch element - [x] Add custom highlighted fields to the flyout ([comment](https://github.com/elastic/kibana/pull/163235#discussion_r1293821203)) ### 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. [Docs ticket](https://github.com/elastic/security-docs/issues/3798) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) (cherry picked from commit c115f5d3d6f580b195e823c9e948f7b1daf8fddc) # Conflicts: # x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts --- .../model/rule_schema/rule_schemas.ts | 14 +- .../review_rule_installation_route.ts | 11 +- .../review_rule_upgrade_route.ts | 7 +- .../diffable_rule_to_rule_response.ts | 351 ------------------ .../components/markdown_editor/renderer.tsx | 6 +- .../rule_details/rule_about_section.tsx | 56 +-- .../rule_details/rule_definition_section.tsx | 244 +++++++++++- .../rule_details/rule_details_flyout.tsx | 57 ++- .../rule_investigation_guide_tab.tsx | 12 +- .../rule_details/rule_overview_tab.tsx | 15 +- .../rule_details/rule_schedule_section.tsx | 17 +- .../rule_details/rule_setup_guide_section.tsx | 2 +- .../components/rule_details/translations.ts | 28 ++ .../rule_details/use_rule_details_flyout.tsx | 41 +- .../add_prebuilt_rules_flyout.tsx | 37 -- .../add_prebuilt_rules_table.tsx | 3 - .../add_prebuilt_rules_table_context.tsx | 66 ++-- .../use_add_prebuilt_rules_table_columns.tsx | 22 +- .../use_filter_prebuilt_rules_to_install.ts | 4 +- .../upgrade_prebuilt_rules_flyout.tsx | 37 -- .../upgrade_prebuilt_rules_table.tsx | 3 - .../upgrade_prebuilt_rules_table_context.tsx | 68 ++-- .../use_filter_prebuilt_rules_to_upgrade.ts | 6 +- ...e_upgrade_prebuilt_rules_table_columns.tsx | 23 +- .../description_step/threat_description.tsx | 3 +- .../pages/detection_engine/rules/helpers.tsx | 2 +- .../review_rule_installation_route.ts | 13 +- .../review_rule_upgrade_route.ts | 15 +- .../normalization/rule_converters.ts | 58 ++- 29 files changed, 536 insertions(+), 685 deletions(-) delete mode 100644 x-pack/plugins/security_solution/common/detection_engine/diffable_rule_to_rule_response.ts delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_flyout.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_flyout.tsx diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.ts index 24badba560b8..acc429eb2f34 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.ts @@ -221,7 +221,7 @@ export const KqlQueryLanguage = t.keyof({ kuery: null, lucene: null }); export type EqlQueryLanguage = t.TypeOf; export const EqlQueryLanguage = t.literal('eql'); -export const eqlSchema = buildRuleSchemas({ +const eqlSchema = buildRuleSchemas({ required: { type: t.literal('eql'), language: EqlQueryLanguage, @@ -256,7 +256,7 @@ export const EqlPatchParams = eqlSchema.patch; // ------------------------------------------------------------------------------------------------- // Indicator Match rule schema -export const threatMatchSchema = buildRuleSchemas({ +const threatMatchSchema = buildRuleSchemas({ required: { type: t.literal('threat_match'), query: RuleQuery, @@ -307,7 +307,7 @@ export const ThreatMatchPatchParams = threatMatchSchema.patch; // ------------------------------------------------------------------------------------------------- // Custom Query rule schema -export const querySchema = buildRuleSchemas({ +const querySchema = buildRuleSchemas({ required: { type: t.literal('query'), }, @@ -343,7 +343,7 @@ export const QueryPatchParams = querySchema.patch; // ------------------------------------------------------------------------------------------------- // Saved Query rule schema -export const savedQuerySchema = buildRuleSchemas({ +const savedQuerySchema = buildRuleSchemas({ required: { type: t.literal('saved_query'), saved_id, @@ -387,7 +387,7 @@ export const SavedQueryPatchParams = savedQuerySchema.patch; // ------------------------------------------------------------------------------------------------- // Threshold rule schema -export const thresholdSchema = buildRuleSchemas({ +const thresholdSchema = buildRuleSchemas({ required: { type: t.literal('threshold'), query: RuleQuery, @@ -422,7 +422,7 @@ export const ThresholdPatchParams = thresholdSchema.patch; // ------------------------------------------------------------------------------------------------- // Machine Learning rule schema -export const machineLearningSchema = buildRuleSchemas({ +const machineLearningSchema = buildRuleSchemas({ required: { type: t.literal('machine_learning'), anomaly_threshold, @@ -462,7 +462,7 @@ export const MachineLearningPatchParams = machineLearningSchema.patch; // ------------------------------------------------------------------------------------------------- // New Terms rule schema -export const newTermsSchema = buildRuleSchemas({ +const newTermsSchema = buildRuleSchemas({ required: { type: t.literal('new_terms'), query: RuleQuery, diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_installation/review_rule_installation_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_installation/review_rule_installation_route.ts index 7a7f4878defb..ddb452a73079 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_installation/review_rule_installation_route.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_installation/review_rule_installation_route.ts @@ -5,15 +5,15 @@ * 2.0. */ -import type { RuleSignatureId, RuleTagArray, RuleVersion } from '../../model'; -import type { DiffableRule } from '../model'; +import type { RuleTagArray } from '../../model'; +import type { RuleResponse } from '../../model/rule_schema/rule_schemas'; export interface ReviewRuleInstallationResponseBody { /** Aggregated info about all rules available for installation */ stats: RuleInstallationStatsForReview; /** Info about individual rules: one object per each rule available for installation */ - rules: RuleInstallationInfoForReview[]; + rules: RuleResponse[]; } export interface RuleInstallationStatsForReview { @@ -23,8 +23,3 @@ export interface RuleInstallationStatsForReview { /** A union of all tags of all rules available for installation */ tags: RuleTagArray; } - -export type RuleInstallationInfoForReview = DiffableRule & { - rule_id: RuleSignatureId; - version: RuleVersion; -}; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route.ts index a3347b5632b7..994e5908d393 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route.ts @@ -6,7 +6,8 @@ */ import type { RuleObjectId, RuleSignatureId, RuleTagArray } from '../../model'; -import type { DiffableRule, PartialRuleDiff } from '../model'; +import type { PartialRuleDiff } from '../model'; +import type { RuleResponse } from '../../model/rule_schema/rule_schemas'; export interface ReviewRuleUpgradeResponseBody { /** Aggregated info about all rules available for upgrade */ @@ -27,8 +28,8 @@ export interface RuleUpgradeStatsForReview { export interface RuleUpgradeInfoForReview { id: RuleObjectId; rule_id: RuleSignatureId; - rule: DiffableRule; - target_rule: DiffableRule; + current_rule: RuleResponse; + target_rule: RuleResponse; diff: PartialRuleDiff; revision: number; } diff --git a/x-pack/plugins/security_solution/common/detection_engine/diffable_rule_to_rule_response.ts b/x-pack/plugins/security_solution/common/detection_engine/diffable_rule_to_rule_response.ts deleted file mode 100644 index ace815148d45..000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/diffable_rule_to_rule_response.ts +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type * as t from 'io-ts'; -import { parseDuration } from '@kbn/alerting-plugin/common/parse_duration'; - -import type { - DiffableRule, - DiffableCustomQueryFields, - DiffableSavedQueryFields, - DiffableEqlFields, - DiffableThreatMatchFields, - DiffableThresholdFields, - DiffableMachineLearningFields, - DiffableNewTermsFields, -} from '../api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule'; -import type { - RuleSchedule, - SavedKqlQuery, - RuleDataSource as DiffableRuleDataSource, - RuleKqlQuery as DiffableRuleKqlQuery, -} from '../api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_field_types'; -import type { - RuleResponse, - querySchema, - savedQuerySchema, - eqlSchema, - threatMatchSchema, - thresholdSchema, - machineLearningSchema, - newTermsSchema, - SharedResponseProps, - KqlQueryLanguage, -} from '../api/detection_engine/model/rule_schema/rule_schemas'; -import type { RuleFilterArray } from '../api/detection_engine/model/rule_schema/common_attributes'; -import { assertUnreachable } from '../utility_types'; - -type RuleResponseCustomQueryFields = t.TypeOf; -type RuleResponseSavedQueryFields = t.TypeOf; -type RuleResponseEqlFields = t.TypeOf; -type RuleResponseThreatMatchFields = t.TypeOf; -type RuleResponseThresholdFields = t.TypeOf; -type RuleResponseMachineLearningFields = t.TypeOf; -type RuleResponseNewTermsFields = t.TypeOf; - -interface RuleResponseScheduleFields { - from: string; - to: string; - interval: string; -} - -const extractRuleSchedule = (ruleSchedule: RuleSchedule): RuleResponseScheduleFields => { - const { interval, lookback } = ruleSchedule; - const lookbackSeconds = Math.floor(parseDuration(lookback) / 1000); - const intervalSeconds = Math.floor(parseDuration(interval) / 1000); - const totalSeconds = lookbackSeconds + intervalSeconds; - - let totalDuration: string; - if (totalSeconds % 3600 === 0) { - totalDuration = `${totalSeconds / 3600}h`; - } else if (totalSeconds % 60 === 0) { - totalDuration = `${totalSeconds / 60}m`; - } else { - totalDuration = `${totalSeconds}s`; - } - - const from = `now-${totalDuration}`; - - return { - from, - to: 'now', - interval, - }; -}; - -type RuleResponseDataSource = { index: string[] } | { data_view_id: string }; - -const extractDataSource = ( - diffableRuleDataSource: DiffableRuleDataSource -): RuleResponseDataSource => { - if (diffableRuleDataSource.type === 'index_patterns') { - return { index: diffableRuleDataSource.index_patterns }; - } else if (diffableRuleDataSource.type === 'data_view') { - return { data_view_id: diffableRuleDataSource.data_view_id }; - } - - return assertUnreachable(diffableRuleDataSource); -}; - -type RuleResponseKqlQuery = - | { query: string; language: KqlQueryLanguage; filters: RuleFilterArray } - | { saved_id: string }; - -const extractKqlQuery = (diffableRuleKqlQuery: DiffableRuleKqlQuery): RuleResponseKqlQuery => { - if (diffableRuleKqlQuery.type === 'inline_query') { - return { - query: diffableRuleKqlQuery.query, - language: diffableRuleKqlQuery.language, - filters: diffableRuleKqlQuery.filters, - }; - } - - if (diffableRuleKqlQuery.type === 'saved_query') { - return { saved_id: diffableRuleKqlQuery.saved_query_id }; - } - - return assertUnreachable(diffableRuleKqlQuery); -}; - -const extractCommonFields = (diffableRule: DiffableRule): Partial => { - const { from, to, interval } = extractRuleSchedule(diffableRule.rule_schedule); - - const commonFields: Partial = { - rule_id: diffableRule.rule_id, - version: diffableRule.version, - meta: diffableRule.meta, - name: diffableRule.name, - tags: diffableRule.tags, - description: diffableRule.description, - severity: diffableRule.severity, - severity_mapping: diffableRule.severity_mapping, - risk_score: diffableRule.risk_score, - risk_score_mapping: diffableRule.risk_score_mapping, - references: diffableRule.references, - false_positives: diffableRule.false_positives, - threat: diffableRule.threat, - note: diffableRule.note, - related_integrations: diffableRule.related_integrations, - required_fields: diffableRule.required_fields, - author: diffableRule.author, - license: diffableRule.license, - from, - to, - interval, - actions: diffableRule.actions, - throttle: diffableRule.throttle, - exceptions_list: diffableRule.exceptions_list, - max_signals: diffableRule.max_signals, - setup: diffableRule.setup, - }; - - if (diffableRule.building_block?.type) { - commonFields.building_block_type = diffableRule.building_block.type; - } - - if (diffableRule.rule_name_override?.field_name) { - commonFields.rule_name_override = diffableRule.rule_name_override.field_name; - } - - if (diffableRule.timeline_template?.timeline_id) { - commonFields.timeline_id = diffableRule.timeline_template.timeline_id; - } - - if (diffableRule.timeline_template?.timeline_title) { - commonFields.timeline_title = diffableRule.timeline_template.timeline_title; - } - - if (diffableRule.timestamp_override?.field_name) { - commonFields.timestamp_override = diffableRule.timestamp_override.field_name; - } - - if (diffableRule.timestamp_override?.fallback_disabled) { - commonFields.timestamp_override_fallback_disabled = - diffableRule.timestamp_override.fallback_disabled; - } - - return commonFields; -}; - -const extractCustomQueryFields = ( - diffableRule: DiffableCustomQueryFields -): RuleResponseCustomQueryFields => { - const customQueryFields: RuleResponseCustomQueryFields = { - type: diffableRule.type, - ...(diffableRule.data_source ? extractDataSource(diffableRule.data_source) : {}), - ...(diffableRule.kql_query ? extractKqlQuery(diffableRule.kql_query) : {}), - }; - - if (diffableRule.alert_suppression) { - customQueryFields.alert_suppression = diffableRule.alert_suppression; - } - - return customQueryFields; -}; - -const extractSavedQueryFields = ( - diffableRule: DiffableSavedQueryFields -): RuleResponseSavedQueryFields => { - /* Typecasting to SavedKqlQuery because a "save_query" DiffableRule can only have "kql_query" of type SavedKqlQuery */ - const diffableRuleKqlQuery = diffableRule.kql_query as SavedKqlQuery; - - const savedQueryFields: RuleResponseSavedQueryFields = { - type: diffableRule.type, - saved_id: diffableRuleKqlQuery.saved_query_id, - ...(diffableRule.data_source ? extractDataSource(diffableRule.data_source) : {}), - }; - - if (diffableRule.alert_suppression) { - savedQueryFields.alert_suppression = diffableRule.alert_suppression; - } - - return savedQueryFields; -}; - -const extractEqlFields = (diffableRule: DiffableEqlFields): RuleResponseEqlFields => { - const eqlFields: RuleResponseEqlFields = { - type: diffableRule.type, - query: diffableRule.eql_query.query, - language: diffableRule.eql_query.language, - filters: diffableRule.eql_query.filters, - event_category_override: diffableRule.event_category_override, - timestamp_field: diffableRule.timestamp_field, - tiebreaker_field: diffableRule.tiebreaker_field, - ...(diffableRule.data_source ? extractDataSource(diffableRule.data_source) : {}), - }; - - return eqlFields; -}; - -const extractThreatMatchFields = ( - diffableRule: DiffableThreatMatchFields -): RuleResponseThreatMatchFields => { - const threatMatchFields: RuleResponseThreatMatchFields = { - type: diffableRule.type, - query: - '' /* Indicator match rules have a "query" equal to an empty string if saved query is used */, - threat_query: diffableRule.threat_query.query ?? '', - threat_language: diffableRule.threat_query.language ?? '', - threat_filters: diffableRule.threat_query.filters ?? [], - threat_index: diffableRule.threat_index, - threat_mapping: diffableRule.threat_mapping, - threat_indicator_path: diffableRule.threat_indicator_path, - ...(diffableRule.data_source ? extractDataSource(diffableRule.data_source) : {}), - ...(diffableRule.kql_query ? extractKqlQuery(diffableRule.kql_query) : {}), - }; - - if (diffableRule.concurrent_searches) { - threatMatchFields.concurrent_searches = diffableRule.concurrent_searches; - } - - if (diffableRule.items_per_search) { - threatMatchFields.items_per_search = diffableRule.items_per_search; - } - - return threatMatchFields; -}; - -const extractThresholdFields = ( - diffableRule: DiffableThresholdFields -): RuleResponseThresholdFields => { - const thresholdFields: RuleResponseThresholdFields = { - type: diffableRule.type, - query: '' /* Threshold rules have a "query" equal to an empty string if saved query is used */, - threshold: diffableRule.threshold, - ...(diffableRule.data_source ? extractDataSource(diffableRule.data_source) : {}), - ...(diffableRule.kql_query ? extractKqlQuery(diffableRule.kql_query) : {}), - }; - - return thresholdFields; -}; - -const extractMachineLearningFields = ( - diffableRule: DiffableMachineLearningFields -): RuleResponseMachineLearningFields => { - const machineLearningFields: RuleResponseMachineLearningFields = { - type: diffableRule.type, - machine_learning_job_id: diffableRule.machine_learning_job_id, - anomaly_threshold: diffableRule.anomaly_threshold, - }; - - return machineLearningFields; -}; - -const extractNewTermsFields = ( - diffableRule: DiffableNewTermsFields -): RuleResponseNewTermsFields => { - const newTermsFields: RuleResponseNewTermsFields = { - type: diffableRule.type, - query: diffableRule.kql_query.query, - language: diffableRule.kql_query.language, - filters: diffableRule.kql_query.filters, - new_terms_fields: diffableRule.new_terms_fields, - history_window_start: diffableRule.history_window_start, - ...(diffableRule.data_source ? extractDataSource(diffableRule.data_source) : {}), - }; - - return newTermsFields; -}; - -/** - * Converts a rule of type DiffableRule to a rule of type RuleResponse. - * Note that DiffableRule doesn't include all the fields that RuleResponse has, so they will be missing from the returned object. These are meta fields like "enabled", "created_at", "created_by", "updated_at", "updated_by", "id", "immutable", "output_index", "revision" - */ -export const diffableRuleToRuleResponse = (diffableRule: DiffableRule): Partial => { - const commonFields = extractCommonFields(diffableRule); - - if (diffableRule.type === 'query') { - return { - ...commonFields, - ...extractCustomQueryFields(diffableRule), - }; - } - - if (diffableRule.type === 'saved_query') { - return { - ...commonFields, - ...extractSavedQueryFields(diffableRule), - }; - } - - if (diffableRule.type === 'eql') { - return { - ...commonFields, - ...extractEqlFields(diffableRule), - }; - } - - if (diffableRule.type === 'threat_match') { - return { - ...commonFields, - ...extractThreatMatchFields(diffableRule), - }; - } - - if (diffableRule.type === 'threshold') { - return { - ...commonFields, - ...extractThresholdFields(diffableRule), - }; - } - - if (diffableRule.type === 'machine_learning') { - return { - ...commonFields, - ...extractMachineLearningFields(diffableRule), - }; - } - - if (diffableRule.type === 'new_terms') { - return { - ...commonFields, - ...extractNewTermsFields(diffableRule), - }; - } - - return assertUnreachable(diffableRule); -}; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/renderer.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/renderer.tsx index b51d1ad78680..431b9b0051f5 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/renderer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/renderer.tsx @@ -7,7 +7,7 @@ import React, { memo, useMemo } from 'react'; import { cloneDeep } from 'lodash/fp'; -import type { EuiLinkAnchorProps } from '@elastic/eui'; +import type { EuiLinkAnchorProps, EuiTextProps } from '@elastic/eui'; import { EuiMarkdownFormat, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; import unified from 'unified'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -21,9 +21,10 @@ import * as i18n from './translations'; interface Props { children: string; disableLinks?: boolean; + textSize?: EuiTextProps['size']; } -const MarkdownRendererComponent: React.FC = ({ children, disableLinks }) => { +const MarkdownRendererComponent: React.FC = ({ children, disableLinks, textSize = 'm' }) => { const MarkdownLinkProcessingComponent: React.FC = useMemo( // eslint-disable-next-line react/display-name () => (props) => , @@ -97,6 +98,7 @@ const MarkdownRendererComponent: React.FC = ({ children, disableLinks }) {children} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx index a02c299109cc..12ca5b5683f6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx @@ -164,6 +164,14 @@ const FalsePositives = ({ falsePositives }: { falsePositives: string[] }) => ( ); +interface InvestigationFieldsProps { + investigationFields: string[]; +} + +const InvestigationFields = ({ investigationFields }: InvestigationFieldsProps) => ( + +); + interface LicenseProps { license: string; } @@ -208,13 +216,10 @@ interface TagsProps { const Tags = ({ tags }: TagsProps) => ; -// eslint-disable-next-line complexity -const prepareAboutSectionListItems = ( - rule: Partial -): EuiDescriptionListProps['listItems'] => { +const prepareAboutSectionListItems = (rule: RuleResponse): EuiDescriptionListProps['listItems'] => { const aboutSectionListItems: EuiDescriptionListProps['listItems'] = []; - if (rule.author) { + if (rule.author.length > 0) { aboutSectionListItems.push({ title: i18n.AUTHOR_FIELD_LABEL, description: , @@ -228,14 +233,12 @@ const prepareAboutSectionListItems = ( }); } - if (rule.severity) { - aboutSectionListItems.push({ - title: i18n.SEVERITY_FIELD_LABEL, - description: , - }); - } + aboutSectionListItems.push({ + title: i18n.SEVERITY_FIELD_LABEL, + description: , + }); - if (rule.severity_mapping && rule.severity_mapping.length > 0) { + if (rule.severity_mapping.length > 0) { aboutSectionListItems.push( ...rule.severity_mapping .filter((severityMappingItem) => severityMappingItem.field !== '') @@ -248,14 +251,12 @@ const prepareAboutSectionListItems = ( ); } - if (rule.risk_score) { - aboutSectionListItems.push({ - title: i18n.RISK_SCORE_FIELD_LABEL, - description: , - }); - } + aboutSectionListItems.push({ + title: i18n.RISK_SCORE_FIELD_LABEL, + description: , + }); - if (rule.risk_score_mapping && rule.risk_score_mapping.length > 0) { + if (rule.risk_score_mapping.length > 0) { aboutSectionListItems.push( ...rule.risk_score_mapping .filter((riskScoreMappingItem) => riskScoreMappingItem.field !== '') @@ -268,20 +269,27 @@ const prepareAboutSectionListItems = ( ); } - if (rule.references && rule.references.length > 0) { + if (rule.references.length > 0) { aboutSectionListItems.push({ title: i18n.REFERENCES_FIELD_LABEL, description: , }); } - if (rule.false_positives && rule.false_positives.length > 0) { + if (rule.false_positives.length > 0) { aboutSectionListItems.push({ title: i18n.FALSE_POSITIVES_FIELD_LABEL, description: , }); } + if (rule.investigation_fields && rule.investigation_fields.length > 0) { + aboutSectionListItems.push({ + title: i18n.INVESTIGATION_FIELDS_FIELD_LABEL, + description: , + }); + } + if (rule.license) { aboutSectionListItems.push({ title: i18n.LICENSE_FIELD_LABEL, @@ -296,7 +304,7 @@ const prepareAboutSectionListItems = ( }); } - if (rule.threat && rule.threat.length > 0) { + if (rule.threat.length > 0) { aboutSectionListItems.push({ title: i18n.THREAT_FIELD_LABEL, description: , @@ -317,7 +325,7 @@ const prepareAboutSectionListItems = ( }); } - if (rule.tags && rule.tags.length > 0) { + if (rule.tags.length > 0) { aboutSectionListItems.push({ title: i18n.TAGS_FIELD_LABEL, description: , @@ -328,7 +336,7 @@ const prepareAboutSectionListItems = ( }; export interface RuleAboutSectionProps { - rule: Partial; + rule: RuleResponse; } export const RuleAboutSection = ({ rule }: RuleAboutSectionProps) => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx index 4839e7f2dc48..8957ebe1ff9d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx @@ -8,25 +8,99 @@ import React from 'react'; import { isEmpty } from 'lodash/fp'; import styled from 'styled-components'; -import { EuiDescriptionList, EuiText, EuiFlexGrid, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { + EuiDescriptionList, + EuiText, + EuiFlexGrid, + EuiFlexItem, + EuiFlexGroup, + EuiLoadingSpinner, + EuiBadge, +} from '@elastic/eui'; import type { EuiDescriptionListProps } from '@elastic/eui'; import type { Type, ThreatMapping as ThreatMappingType, } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { Filter } from '@kbn/es-query'; +import type { SavedQuery } from '@kbn/data-plugin/public'; +import { mapAndFlattenFilters } from '@kbn/data-plugin/public'; import { FieldIcon } from '@kbn/react-field'; import { castEsToKbnFieldTypeName } from '@kbn/field-types'; +import { FilterBadgeGroup } from '@kbn/unified-search-plugin/public'; import type { RuleResponse } from '../../../../../common/api/detection_engine/model/rule_schema/rule_schemas'; import type { Threshold as ThresholdType } from '../../../../../common/api/detection_engine/model/rule_schema/specific_attributes/threshold_attributes'; import type { RequiredFieldArray } from '../../../../../common/api/detection_engine/model/rule_schema/common_attributes'; import { assertUnreachable } from '../../../../../common/utility_types'; import * as descriptionStepI18n from '../../../../detections/components/rules/description_step/translations'; -import { MlJobsDescription } from '../../../../detections/components/rules/ml_jobs_description/ml_jobs_description'; import { RelatedIntegrationsDescription } from '../../../../detections/components/rules/related_integrations/integrations_description'; +import { useGetSavedQuery } from '../../../../detections/pages/detection_engine/rules/use_get_saved_query'; import * as threatMatchI18n from '../../../../common/components/threat_match/translations'; +import * as timelinesI18n from '../../../../timelines/components/timeline/translations'; +import { useRuleIndexPattern } from '../../../rule_creation_ui/pages/form'; +import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types'; +import { convertHistoryStartToSize } from '../../../../detections/pages/detection_engine/rules/helpers'; +import { MlJobLink } from '../../../../detections/components/rules/ml_job_link/ml_job_link'; +import { useSecurityJobs } from '../../../../common/components/ml_popover/hooks/use_security_jobs'; import { BadgeList } from './badge_list'; import * as i18n from './translations'; +interface SavedQueryNameProps { + savedQueryName: string; +} + +const SavedQueryName = ({ savedQueryName }: SavedQueryNameProps) => ( + {savedQueryName} +); + +const EuiBadgeWrap = styled(EuiBadge)` + .euiBadge__text { + white-space: pre-wrap !important; + } +`; + +interface FiltersProps { + filters: Filter[]; + dataViewId?: string; + index?: string[]; +} + +const Filters = ({ filters, dataViewId, index }: FiltersProps) => { + const { indexPattern } = useRuleIndexPattern({ + dataSourceType: dataViewId ? DataSourceType.DataView : DataSourceType.IndexPatterns, + index: index ?? [], + dataViewId, + }); + + const flattenedFilters = mapAndFlattenFilters(filters); + + return ( + + {flattenedFilters.map((filter, idx) => ( + + + {indexPattern != null ? ( + + ) : ( + + )} + + + ))} + + ); +}; + +const QueryContent = styled.div` + white-space: pre-wrap; +`; + +interface QueryProps { + query: string; +} + +const Query = ({ query }: QueryProps) => {query}; + interface IndexProps { index: string[]; } @@ -53,6 +127,36 @@ const Threshold = ({ threshold }: ThresholdProps) => ( ); +interface AnomalyThresholdProps { + anomalyThreshold: number; +} + +const AnomalyThreshold = ({ anomalyThreshold }: AnomalyThresholdProps) => ( + {anomalyThreshold} +); + +interface MachineLearningJobListProps { + jobIds: string[]; +} + +const MachineLearningJobList = ({ jobIds }: MachineLearningJobListProps) => { + const { jobs } = useSecurityJobs(); + + const relevantJobs = jobs.filter((job) => jobIds.includes(job.id)); + + return ( + <> + {relevantJobs.map((job) => ( + + ))} + + ); +}; + const getRuleTypeDescription = (ruleType: Type) => { switch (ruleType) { case 'machine_learning': @@ -94,7 +198,7 @@ interface RequiredFieldsProps { const RequiredFields = ({ requiredFields }: RequiredFieldsProps) => ( {requiredFields.map((rF, index) => ( - + { return {description}; }; +interface NewTermsFieldsProps { + newTermsFields: string[]; +} + +const NewTermsFields = ({ newTermsFields }: NewTermsFieldsProps) => ( + +); + +interface HistoryWindowSizeProps { + historyWindowStart?: string; +} + +const HistoryWindowSize = ({ historyWindowStart }: HistoryWindowSizeProps) => { + const size = historyWindowStart ? convertHistoryStartToSize(historyWindowStart) : '7d'; + + return {size}; +}; + +// eslint-disable-next-line complexity const prepareDefinitionSectionListItems = ( - rule: Partial + rule: RuleResponse, + savedQuery?: SavedQuery ): EuiDescriptionListProps['listItems'] => { const definitionSectionListItems: EuiDescriptionListProps['listItems'] = []; @@ -181,21 +305,62 @@ const prepareDefinitionSectionListItems = ( }); } - if (rule.type) { + if (savedQuery) { + definitionSectionListItems.push({ + title: descriptionStepI18n.SAVED_QUERY_NAME_LABEL, + description: , + }); + + if (savedQuery.attributes.filters) { + definitionSectionListItems.push({ + title: descriptionStepI18n.SAVED_QUERY_FILTERS_LABEL, + description: , + }); + } + } + + if ('filters' in rule && rule.filters && rule.filters.length > 0) { + definitionSectionListItems.push({ + title: savedQuery + ? descriptionStepI18n.SAVED_QUERY_FILTERS_LABEL + : descriptionStepI18n.FILTERS_LABEL, + description: ( + + ), + }); + } + + if ('query' in rule && rule.query) { + definitionSectionListItems.push({ + title: savedQuery ? descriptionStepI18n.SAVED_QUERY_LABEL : descriptionStepI18n.QUERY_LABEL, + description: , + }); + } + + definitionSectionListItems.push({ + title: i18n.RULE_TYPE_FIELD_LABEL, + description: , + }); + + if ('anomaly_threshold' in rule && rule.anomaly_threshold) { definitionSectionListItems.push({ - title: i18n.RULE_TYPE_FIELD_LABEL, - description: , + title: i18n.ANOMALY_THRESHOLD_FIELD_LABEL, + description: , }); } if ('machine_learning_job_id' in rule) { definitionSectionListItems.push({ title: i18n.MACHINE_LEARNING_JOB_ID_FIELD_LABEL, - description: , + description: , }); } - if (rule.related_integrations && rule.related_integrations.length > 0) { + if (rule.related_integrations.length > 0) { definitionSectionListItems.push({ title: i18n.RELATED_INTEGRATIONS_FIELD_LABEL, description: ( @@ -204,19 +369,19 @@ const prepareDefinitionSectionListItems = ( }); } - if (rule.required_fields && rule.required_fields.length > 0) { + if (rule.required_fields.length > 0) { definitionSectionListItems.push({ title: i18n.REQUIRED_FIELDS_FIELD_LABEL, description: , }); } - if (rule.timeline_title) { - definitionSectionListItems.push({ - title: i18n.TIMELINE_TITLE_FIELD_LABEL, - description: , - }); - } + definitionSectionListItems.push({ + title: i18n.TIMELINE_TITLE_FIELD_LABEL, + description: ( + + ), + }); if ('threshold' in rule && rule.threshold) { definitionSectionListItems.push({ @@ -239,15 +404,58 @@ const prepareDefinitionSectionListItems = ( }); } + if ('threat_filters' in rule && rule.threat_filters && rule.threat_filters.length > 0) { + definitionSectionListItems.push({ + title: savedQuery + ? descriptionStepI18n.SAVED_QUERY_FILTERS_LABEL + : descriptionStepI18n.FILTERS_LABEL, + description: ( + + ), + }); + } + + if ('threat_query' in rule && rule.threat_query) { + definitionSectionListItems.push({ + title: savedQuery + ? descriptionStepI18n.SAVED_QUERY_LABEL + : descriptionStepI18n.THREAT_QUERY_LABEL, + description: , + }); + } + + if ('new_terms_fields' in rule && rule.new_terms_fields && rule.new_terms_fields.length > 0) { + definitionSectionListItems.push({ + title: i18n.NEW_TERMS_FIELDS_FIELD_LABEL, + description: , + }); + } + + if (rule.type === 'new_terms' || 'history_window_start' in rule) { + definitionSectionListItems.push({ + title: i18n.HISTORY_WINDOW_SIZE_FIELD_LABEL, + description: , + }); + } + return definitionSectionListItems; }; export interface RuleDefinitionSectionProps { - rule: Partial; + rule: RuleResponse; } export const RuleDefinitionSection = ({ rule }: RuleDefinitionSectionProps) => { - const definitionSectionListItems = prepareDefinitionSectionListItems(rule); + const { savedQuery } = useGetSavedQuery({ + savedQueryId: rule.type === 'saved_query' ? rule.saved_id : '', + ruleType: rule.type, + }); + + const definitionSectionListItems = prepareDefinitionSectionListItems(rule, savedQuery); return (
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_details_flyout.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_details_flyout.tsx index 9cd5a751c5c5..e4a596f6264b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_details_flyout.tsx @@ -7,6 +7,8 @@ import React, { useMemo, useState, useEffect } from 'react'; import styled from 'styled-components'; +import { css } from '@emotion/css'; +import { euiThemeVars } from '@kbn/ui-theme'; import { EuiButton, EuiButtonEmpty, @@ -20,7 +22,7 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import type { EuiTabbedContentTab } from '@elastic/eui'; +import type { EuiTabbedContentTab, EuiTabbedContentProps } from '@elastic/eui'; import type { RuleResponse } from '../../../../../common/api/detection_engine/model/rule_schema/rule_schemas'; import { RuleOverviewTab, useOverviewTabSections } from './rule_overview_tab'; @@ -37,7 +39,7 @@ const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` .euiFlyoutBody__overflowContent { flex: 1; overflow: hidden; - padding: ${({ theme }) => `0 ${theme.eui.euiSizeL} ${theme.eui.euiSizeM}`}; + padding: ${({ theme }) => `0 ${theme.eui.euiSizeL} 0`}; } } `; @@ -79,8 +81,27 @@ const StyledEuiTabbedContent = styled(EuiTabbedContent)` } `; +/* + * Fixes tabs to the top and allows the content to scroll. + */ +const ScrollableFlyoutTabbedContent = (props: EuiTabbedContentProps) => ( + + + + + +); + +const tabPaddingClassName = css` + padding: 0 ${euiThemeVars.euiSizeM} ${euiThemeVars.euiSizeXL} ${euiThemeVars.euiSizeM}; +`; + +const TabContentPadding: React.FC = ({ children }) => ( +
{children}
+); + interface RuleDetailsFlyoutProps { - rule: Partial; + rule: RuleResponse; actionButtonLabel: string; isActionButtonDisabled: boolean; onActionButtonClick: (ruleId: string) => void; @@ -101,11 +122,13 @@ export const RuleDetailsFlyout = ({ id: 'overview', name: i18n.OVERVIEW_TAB_LABEL, content: ( - + + + ), }), [rule, expandedOverviewSections, toggleOverviewSection] @@ -115,7 +138,11 @@ export const RuleDetailsFlyout = ({ () => ({ id: 'investigationGuide', name: i18n.INVESTIGATION_GUIDE_TAB_LABEL, - content: , + content: ( + + + + ), }), [rule.note] ); @@ -151,17 +178,17 @@ export const RuleDetailsFlyout = ({ paddingSize="l" > - +

{rule.name}

- - - - - + diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_investigation_guide_tab.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_investigation_guide_tab.tsx index 6824714e070b..e6d01d1fca58 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_investigation_guide_tab.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_investigation_guide_tab.tsx @@ -6,9 +6,7 @@ */ import React from 'react'; -import { css } from '@emotion/react'; import { EuiSpacer } from '@elastic/eui'; -import { euiThemeVars } from '@kbn/ui-theme'; import { MarkdownRenderer } from '../../../../common/components/markdown_editor'; import type { InvestigationGuide } from '../../../../../common/api/detection_engine/model/rule_schema/common_attributes'; @@ -18,13 +16,9 @@ interface RuleInvestigationGuideTabProps { export const RuleInvestigationGuideTab = ({ note }: RuleInvestigationGuideTabProps) => { return ( -
+ <> - {note} -
+ {note} + ); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_overview_tab.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_overview_tab.tsx index ed54f5e8ce68..aa9b42abe6ba 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_overview_tab.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_overview_tab.tsx @@ -6,8 +6,6 @@ */ import React, { useState, useMemo, useCallback } from 'react'; -import { css } from '@emotion/react'; -import { euiThemeVars } from '@kbn/ui-theme'; import { EuiTitle, EuiAccordion, @@ -88,7 +86,7 @@ const ExpandableSection = ({ title, isOpen, toggle, children }: ExpandableSectio }; interface RuleOverviewTabProps { - rule: Partial; + rule: RuleResponse; expandedOverviewSections: Record; toggleOverviewSection: Record void>; } @@ -98,11 +96,7 @@ export const RuleOverviewTab = ({ expandedOverviewSections, toggleOverviewSection, }: RuleOverviewTabProps) => ( -
+ <> - {rule.setup && ( <> + - )} -
+ ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_schedule_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_schedule_section.tsx index 866fbdfdee93..e7d2bbaeb831 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_schedule_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_schedule_section.tsx @@ -27,25 +27,22 @@ const From = ({ from, interval }: FromProps) => ( ); export interface RuleScheduleSectionProps { - rule: Partial; + rule: RuleResponse; } export const RuleScheduleSection = ({ rule }: RuleScheduleSectionProps) => { const ruleSectionListItems = []; - if (rule.interval) { - ruleSectionListItems.push({ + ruleSectionListItems.push( + { title: i18n.INTERVAL_FIELD_LABEL, description: , - }); - } - - if (rule.interval && rule.from) { - ruleSectionListItems.push({ + }, + { title: i18n.FROM_FIELD_LABEL, description: , - }); - } + } + ); return (
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_setup_guide_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_setup_guide_section.tsx index 1fe5c5bfedd3..b6a358349a11 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_setup_guide_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_setup_guide_section.tsx @@ -16,7 +16,7 @@ interface RuleSetupGuideSectionProps { export const RuleSetupGuideSection = ({ setup }: RuleSetupGuideSectionProps) => { return (
- {setup} + {setup}
); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts index 8164a8395050..55b0edc254d7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts @@ -126,6 +126,13 @@ export const FALSE_POSITIVES_FIELD_LABEL = i18n.translate( } ); +export const INVESTIGATION_FIELDS_FIELD_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.investigationFieldsFieldLabel', + { + defaultMessage: 'Custom highlighted fields', + } +); + export const LICENSE_FIELD_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.licenseFieldLabel', { @@ -203,6 +210,13 @@ export const MACHINE_LEARNING_JOB_ID_FIELD_LABEL = i18n.translate( } ); +export const ANOMALY_THRESHOLD_FIELD_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.anomalyThresholdFieldLabel', + { + defaultMessage: 'Anomaly score threshold', + } +); + export const RELATED_INTEGRATIONS_FIELD_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.relatedIntegrationsFieldLabel', { @@ -245,6 +259,20 @@ export const THREAT_FILTERS_FIELD_LABEL = i18n.translate( } ); +export const NEW_TERMS_FIELDS_FIELD_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.newTermsFieldsFieldLabel', + { + defaultMessage: 'Fields', + } +); + +export const HISTORY_WINDOW_SIZE_FIELD_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.historyWindowSizeFieldLabel', + { + defaultMessage: 'History Window Size', + } +); + export const INTERVAL_FIELD_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.intervalFieldLabel', { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/use_rule_details_flyout.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/use_rule_details_flyout.tsx index 7aff5b7fb38f..ccce517db5b5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/use_rule_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/use_rule_details_flyout.tsx @@ -7,44 +7,41 @@ import React, { useCallback } from 'react'; import { invariant } from '../../../../../common/utils/invariant'; -import type { - RuleInstallationInfoForReview, - RuleSignatureId, -} from '../../../../../common/api/detection_engine'; -import type { DiffableRule } from '../../../../../common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule'; +import type { RuleObjectId } from '../../../../../common/api/detection_engine'; +import type { RuleResponse } from '../../../../../common/api/detection_engine/model/rule_schema'; export interface RuleDetailsFlyoutState { - flyoutRule: RuleInstallationInfoForReview | null; + previewedRule: RuleResponse | null; } export interface RuleDetailsFlyoutActions { - openFlyoutForRuleId: (ruleId: RuleSignatureId) => void; - closeFlyout: () => void; + openRulePreview: (ruleId: RuleObjectId) => void; + closeRulePreview: () => void; } export const useRuleDetailsFlyout = ( - rules: DiffableRule[] + rules: RuleResponse[] ): RuleDetailsFlyoutState & RuleDetailsFlyoutActions => { - const [flyoutRule, setFlyoutRule] = React.useState(null); + const [previewedRule, setRuleForPreview] = React.useState(null); - const openFlyoutForRuleId = useCallback( - (ruleId: RuleSignatureId) => { - const ruleToShowInFlyout = rules.find((rule) => rule.rule_id === ruleId); + const openRulePreview = useCallback( + (ruleId: RuleObjectId) => { + const ruleToShowInFlyout = rules.find((rule) => { + return rule.id === ruleId; + }); invariant(ruleToShowInFlyout, `Rule with id ${ruleId} not found`); - if (ruleToShowInFlyout) { - setFlyoutRule(ruleToShowInFlyout); - } + setRuleForPreview(ruleToShowInFlyout); }, - [rules, setFlyoutRule] + [rules, setRuleForPreview] ); - const closeFlyout = useCallback(() => { - setFlyoutRule(null); + const closeRulePreview = useCallback(() => { + setRuleForPreview(null); }, []); return { - openFlyoutForRuleId, - closeFlyout, - flyoutRule, + openRulePreview, + closeRulePreview, + previewedRule, }; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_flyout.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_flyout.tsx deleted file mode 100644 index e62246897a19..000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_flyout.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { useAddPrebuiltRulesTableContext } from './add_prebuilt_rules_table_context'; -import { RuleDetailsFlyout } from '../../../../rule_management/components/rule_details/rule_details_flyout'; -import type { RuleResponse } from '../../../../../../common/api/detection_engine/model/rule_schema/rule_schemas'; -import { diffableRuleToRuleResponse } from '../../../../../../common/detection_engine/diffable_rule_to_rule_response'; -import * as i18n from './translations'; - -export const AddPrebuiltRulesFlyout = () => { - const { - state: { flyoutRule, isFlyoutInstallButtonDisabled }, - actions: { installOneRule, closeFlyout }, - } = useAddPrebuiltRulesTableContext(); - - if (flyoutRule == null) { - return null; - } - - const ruleResponse: Partial = diffableRuleToRuleResponse(flyoutRule); - - return ( - - ); -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table.tsx index 029f31f19a68..e64645e967cc 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table.tsx @@ -22,7 +22,6 @@ import { AddPrebuiltRulesTableNoItemsMessage } from './add_prebuilt_rules_no_ite import { useAddPrebuiltRulesTableContext } from './add_prebuilt_rules_table_context'; import { AddPrebuiltRulesTableFilters } from './add_prebuilt_rules_table_filters'; import { useAddPrebuiltRulesTableColumns } from './use_add_prebuilt_rules_table_columns'; -import { AddPrebuiltRulesFlyout } from './add_prebuilt_rules_flyout'; /** * Table Component for displaying new rules that are available to be installed @@ -97,8 +96,6 @@ export const AddPrebuiltRulesTable = React.memo(() => { data-test-subj="add-prebuilt-rules-table" columns={rulesColumns} /> - - ) } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context.tsx index 9b7fd830a4f4..870025a9aa5f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context.tsx @@ -9,10 +9,7 @@ import type { Dispatch, SetStateAction } from 'react'; import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'; import { useFetchPrebuiltRulesStatusQuery } from '../../../../rule_management/api/hooks/prebuilt_rules/use_fetch_prebuilt_rules_status_query'; import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages'; -import type { - RuleInstallationInfoForReview, - RuleSignatureId, -} from '../../../../../../common/api/detection_engine'; +import type { RuleSignatureId } from '../../../../../../common/api/detection_engine'; import { invariant } from '../../../../../../common/utils/invariant'; import { usePerformInstallAllRules, @@ -22,16 +19,19 @@ import { usePrebuiltRulesInstallReview } from '../../../../rule_management/logic import type { AddPrebuiltRulesTableFilterOptions } from './use_filter_prebuilt_rules_to_install'; import { useFilterPrebuiltRulesToInstall } from './use_filter_prebuilt_rules_to_install'; import { useRuleDetailsFlyout } from '../../../../rule_management/components/rule_details/use_rule_details_flyout'; +import type { RuleResponse } from '../../../../../../common/api/detection_engine/model/rule_schema/rule_schemas'; +import { RuleDetailsFlyout } from '../../../../rule_management/components/rule_details/rule_details_flyout'; +import * as i18n from './translations'; export interface AddPrebuiltRulesTableState { /** * Rules available to be installed */ - rules: RuleInstallationInfoForReview[]; + rules: RuleResponse[]; /** * Rules to display in table after applying filters */ - filteredRules: RuleInstallationInfoForReview[]; + filteredRules: RuleResponse[]; /** * Currently selected table filter */ @@ -68,17 +68,7 @@ export interface AddPrebuiltRulesTableState { /** * Rule rows selected in EUI InMemory Table */ - selectedRules: RuleInstallationInfoForReview[]; - /** - * Rule that is currently displayed in the flyout or null if flyout is closed - */ - flyoutRule: RuleInstallationInfoForReview | null; - /** - * Is true when the install button in the flyout is disabled - * (e.g. when the rule is already being installed or when the table is being refetched) - * - **/ - isFlyoutInstallButtonDisabled: boolean; + selectedRules: RuleResponse[]; } export interface AddPrebuiltRulesTableActions { @@ -87,9 +77,8 @@ export interface AddPrebuiltRulesTableActions { installAllRules: () => void; installSelectedRules: () => void; setFilterOptions: Dispatch>; - selectRules: (rules: RuleInstallationInfoForReview[]) => void; - openFlyoutForRuleId: (ruleId: RuleSignatureId) => void; - closeFlyout: () => void; + selectRules: (rules: RuleResponse[]) => void; + openRulePreview: (ruleId: RuleSignatureId) => void; } export interface AddPrebuiltRulesContextType { @@ -107,7 +96,7 @@ export const AddPrebuiltRulesTableContextProvider = ({ children, }: AddPrebuiltRulesTableContextProviderProps) => { const [loadingRules, setLoadingRules] = useState([]); - const [selectedRules, setSelectedRules] = useState([]); + const [selectedRules, setSelectedRules] = useState([]); const [filterOptions, setFilterOptions] = useState({ filter: '', @@ -144,9 +133,9 @@ export const AddPrebuiltRulesTableContextProvider = ({ const filteredRules = useFilterPrebuiltRulesToInstall({ filterOptions, rules }); - const { openFlyoutForRuleId, closeFlyout, flyoutRule } = useRuleDetailsFlyout(filteredRules); - const isFlyoutInstallButtonDisabled = Boolean( - (flyoutRule?.rule_id && loadingRules.includes(flyoutRule.rule_id)) || + const { openRulePreview, closeRulePreview, previewedRule } = useRuleDetailsFlyout(filteredRules); + const canPreviewedRuleBeInstalled = Boolean( + (previewedRule?.rule_id && loadingRules.includes(previewedRule.rule_id)) || isRefetching || isUpgradingSecurityPackages ); @@ -199,17 +188,9 @@ export const AddPrebuiltRulesTableContextProvider = ({ installSelectedRules, reFetchRules: refetch, selectRules: setSelectedRules, - openFlyoutForRuleId, - closeFlyout, + openRulePreview, }), - [ - installAllRules, - installOneRule, - installSelectedRules, - refetch, - openFlyoutForRuleId, - closeFlyout, - ] + [installAllRules, installOneRule, installSelectedRules, refetch, openRulePreview] ); const providerValue = useMemo(() => { @@ -226,8 +207,6 @@ export const AddPrebuiltRulesTableContextProvider = ({ isUpgradingSecurityPackages, selectedRules, lastUpdated: dataUpdatedAt, - flyoutRule, - isFlyoutInstallButtonDisabled, }, actions, }; @@ -243,14 +222,23 @@ export const AddPrebuiltRulesTableContextProvider = ({ isUpgradingSecurityPackages, selectedRules, dataUpdatedAt, - flyoutRule, - isFlyoutInstallButtonDisabled, actions, ]); return ( - {children} + <> + {children} + {previewedRule && ( + + )} + ); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx index e384226213b2..275dc1276cb5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx @@ -15,15 +15,17 @@ import { IntegrationsPopover } from '../../../../../detections/components/rules/ import { SeverityBadge } from '../../../../../detections/components/rules/severity_badge'; import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; import type { Rule } from '../../../../rule_management/logic'; -import type { RuleInstallationInfoForReview } from '../../../../../../common/api/detection_engine/prebuilt_rules'; import { useUserData } from '../../../../../detections/components/user_info'; import { hasUserCRUDPermission } from '../../../../../common/utils/privileges'; import type { AddPrebuiltRulesTableActions } from './add_prebuilt_rules_table_context'; import { useAddPrebuiltRulesTableContext } from './add_prebuilt_rules_table_context'; -import type { RuleSignatureId } from '../../../../../../common/api/detection_engine/model/rule_schema'; +import type { + RuleSignatureId, + RuleResponse, +} from '../../../../../../common/api/detection_engine/model/rule_schema'; import { getNormalizedSeverity } from '../helpers'; -export type TableColumn = EuiBasicTableColumn; +export type TableColumn = EuiBasicTableColumn; interface RuleNameProps { name: string; @@ -32,13 +34,13 @@ interface RuleNameProps { const RuleName = ({ name, ruleId }: RuleNameProps) => { const { - actions: { openFlyoutForRuleId }, + actions: { openRulePreview }, } = useAddPrebuiltRulesTableContext(); return ( { - openFlyoutForRuleId(ruleId); + openRulePreview(ruleId); }} > {name} @@ -49,8 +51,8 @@ const RuleName = ({ name, ruleId }: RuleNameProps) => { export const RULE_NAME_COLUMN: TableColumn = { field: 'name', name: i18n.COLUMN_RULE, - render: (value: RuleInstallationInfoForReview['name'], rule: RuleInstallationInfoForReview) => ( - + render: (value: RuleResponse['name'], rule: RuleResponse) => ( + ), sortable: true, truncateText: true, @@ -62,7 +64,7 @@ const TAGS_COLUMN: TableColumn = { field: 'tags', name: null, align: 'center', - render: (tags: RuleInstallationInfoForReview['tags']) => { + render: (tags: RuleResponse['tags']) => { if (tags == null || tags.length === 0) { return null; } @@ -91,7 +93,7 @@ const INTEGRATIONS_COLUMN: TableColumn = { field: 'related_integrations', name: null, align: 'center', - render: (integrations: RuleInstallationInfoForReview['related_integrations']) => { + render: (integrations: RuleResponse['related_integrations']) => { if (integrations == null || integrations.length === 0) { return null; } @@ -159,7 +161,7 @@ export const useAddPrebuiltRulesTableColumns = (): TableColumn[] => { field: 'severity', name: i18n.COLUMN_SEVERITY, render: (value: Rule['severity']) => , - sortable: ({ severity }: RuleInstallationInfoForReview) => getNormalizedSeverity(severity), + sortable: ({ severity }: RuleResponse) => getNormalizedSeverity(severity), truncateText: true, width: '12%', }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_filter_prebuilt_rules_to_install.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_filter_prebuilt_rules_to_install.ts index a870b56e3073..245d879fa1c5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_filter_prebuilt_rules_to_install.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_filter_prebuilt_rules_to_install.ts @@ -6,7 +6,7 @@ */ import { useMemo } from 'react'; -import type { RuleInstallationInfoForReview } from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import type { RuleResponse } from '../../../../../../common/api/detection_engine/model/rule_schema'; import type { FilterOptions } from '../../../../rule_management/logic/types'; export type AddPrebuiltRulesTableFilterOptions = Pick; @@ -15,7 +15,7 @@ export const useFilterPrebuiltRulesToInstall = ({ rules, filterOptions, }: { - rules: RuleInstallationInfoForReview[]; + rules: RuleResponse[]; filterOptions: AddPrebuiltRulesTableFilterOptions; }) => { const filteredRules = useMemo(() => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_flyout.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_flyout.tsx deleted file mode 100644 index 4749af8e1d78..000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_flyout.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_table_context'; -import { RuleDetailsFlyout } from '../../../../rule_management/components/rule_details/rule_details_flyout'; -import type { RuleResponse } from '../../../../../../common/api/detection_engine/model/rule_schema/rule_schemas'; -import { diffableRuleToRuleResponse } from '../../../../../../common/detection_engine/diffable_rule_to_rule_response'; -import * as i18n from './translations'; - -export const UpgradePrebuiltRulesFlyout = () => { - const { - state: { flyoutRule, isFlyoutInstallButtonDisabled }, - actions: { upgradeOneRule, closeFlyout }, - } = useUpgradePrebuiltRulesTableContext(); - - if (flyoutRule == null) { - return null; - } - - const ruleResponse: Partial = diffableRuleToRuleResponse(flyoutRule); - - return ( - - ); -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx index fc91cbd6ccf6..c1468b458e33 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx @@ -23,7 +23,6 @@ import { UpgradePrebuiltRulesTableButtons } from './upgrade_prebuilt_rules_table import { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_table_context'; import { UpgradePrebuiltRulesTableFilters } from './upgrade_prebuilt_rules_table_filters'; import { useUpgradePrebuiltRulesTableColumns } from './use_upgrade_prebuilt_rules_table_columns'; -import { UpgradePrebuiltRulesFlyout } from './upgrade_prebuilt_rules_flyout'; const NO_ITEMS_MESSAGE = ( { data-test-subj="rules-upgrades-table" columns={rulesColumns} /> - - ) } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx index 0577292a84fe..1f99b3f65fff 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx @@ -23,6 +23,8 @@ import type { UpgradePrebuiltRulesTableFilterOptions } from './use_filter_prebui import { useFilterPrebuiltRulesToUpgrade } from './use_filter_prebuilt_rules_to_upgrade'; import { useAsyncConfirmation } from '../rules_table/use_async_confirmation'; import { useRuleDetailsFlyout } from '../../../../rule_management/components/rule_details/use_rule_details_flyout'; +import { RuleDetailsFlyout } from '../../../../rule_management/components/rule_details/rule_details_flyout'; +import * as i18n from './translations'; import { MlJobUpgradeModal } from '../../../../../detections/components/modals/ml_job_upgrade_modal'; @@ -73,16 +75,6 @@ export interface UpgradePrebuiltRulesTableState { * Rule rows selected in EUI InMemory Table */ selectedRules: RuleUpgradeInfoForReview[]; - /** - * Rule that is currently displayed in the flyout or null if flyout is closed - */ - flyoutRule: RuleUpgradeInfoForReview['rule'] | null; - /** - * Is true when the upgrade button in the flyout is disabled - * (e.g. when the rule is already being upgrade or when the table is being refetched) - * - **/ - isFlyoutInstallButtonDisabled: boolean; } export interface UpgradePrebuiltRulesTableActions { @@ -92,8 +84,7 @@ export interface UpgradePrebuiltRulesTableActions { upgradeAllRules: () => void; setFilterOptions: Dispatch>; selectRules: (rules: RuleUpgradeInfoForReview[]) => void; - openFlyoutForRuleId: (ruleId: RuleSignatureId) => void; - closeFlyout: () => void; + openRulePreview: (ruleId: string) => void; } export interface UpgradePrebuiltRulesContextType { @@ -141,11 +132,11 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ const filteredRules = useFilterPrebuiltRulesToUpgrade({ filterOptions, rules }); - const { openFlyoutForRuleId, closeFlyout, flyoutRule } = useRuleDetailsFlyout( + const { openRulePreview, closeRulePreview, previewedRule } = useRuleDetailsFlyout( filteredRules.map((upgradeInfo) => upgradeInfo.target_rule) ); - const isFlyoutInstallButtonDisabled = Boolean( - (flyoutRule?.rule_id && loadingRules.includes(flyoutRule.rule_id)) || + const canPreviewedRuleBeUpgraded = Boolean( + (previewedRule?.rule_id && loadingRules.includes(previewedRule.rule_id)) || isRefetching || isUpgradingSecurityPackages ); @@ -176,7 +167,7 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ await upgradeSpecificRulesRequest([ { rule_id: ruleId, - version: rule.diff.fields.version?.target_version ?? rule.rule.version, + version: rule.diff.fields.version?.target_version ?? rule.current_rule.version, revision: rule.revision, }, ]); @@ -190,7 +181,7 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ const upgradeSelectedRules = useCallback(async () => { const rulesToUpgrade = selectedRules.map((rule) => ({ rule_id: rule.rule_id, - version: rule.diff.fields.version?.target_version ?? rule.rule.version, + version: rule.diff.fields.version?.target_version ?? rule.current_rule.version, revision: rule.revision, })); setLoadingRules((prev) => [...prev, ...rulesToUpgrade.map((r) => r.rule_id)]); @@ -227,17 +218,9 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ upgradeAllRules, setFilterOptions, selectRules: setSelectedRules, - openFlyoutForRuleId, - closeFlyout, + openRulePreview, }), - [ - refetch, - upgradeOneRule, - upgradeSelectedRules, - upgradeAllRules, - openFlyoutForRuleId, - closeFlyout, - ] + [refetch, upgradeOneRule, upgradeSelectedRules, upgradeAllRules, openRulePreview] ); const providerValue = useMemo(() => { @@ -254,8 +237,6 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ selectedRules, loadingRules, lastUpdated: dataUpdatedAt, - flyoutRule, - isFlyoutInstallButtonDisabled, }, actions, }; @@ -272,21 +253,30 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ selectedRules, loadingRules, dataUpdatedAt, - flyoutRule, - isFlyoutInstallButtonDisabled, actions, ]); return ( - {isUpgradeModalVisible && ( - - )} - {children} + <> + {isUpgradeModalVisible && ( + + )} + {children} + {previewedRule && ( + + )} + ); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts index 715810b7a4fd..342a1e6e8768 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts @@ -20,13 +20,13 @@ export const useFilterPrebuiltRulesToUpgrade = ({ }) => { const filteredRules = useMemo(() => { const { filter, tags } = filterOptions; - return rules.filter(({ rule }) => { - if (filter && !rule.name.toLowerCase().includes(filter.toLowerCase())) { + return rules.filter((ruleInfo) => { + if (filter && !ruleInfo.current_rule.name.toLowerCase().includes(filter.toLowerCase())) { return false; } if (tags && tags.length > 0) { - return tags.every((tag) => rule.tags.includes(tag)); + return tags.every((tag) => ruleInfo.current_rule.tags.includes(tag)); } return true; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx index ad110c223b63..054d77cb5e44 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx @@ -32,13 +32,13 @@ interface RuleNameProps { const RuleName = ({ name, ruleId }: RuleNameProps) => { const { - actions: { openFlyoutForRuleId }, + actions: { openRulePreview }, } = useUpgradePrebuiltRulesTableContext(); return ( { - openFlyoutForRuleId(ruleId); + openRulePreview(ruleId); }} > {name} @@ -47,11 +47,12 @@ const RuleName = ({ name, ruleId }: RuleNameProps) => { }; const RULE_NAME_COLUMN: TableColumn = { - field: 'rule.name', + field: 'current_rule.name', name: i18n.COLUMN_RULE, - render: (value: RuleUpgradeInfoForReview['rule']['name'], rule: RuleUpgradeInfoForReview) => ( - - ), + render: ( + value: RuleUpgradeInfoForReview['current_rule']['name'], + rule: RuleUpgradeInfoForReview + ) => , sortable: true, truncateText: true, width: '60%', @@ -59,7 +60,7 @@ const RULE_NAME_COLUMN: TableColumn = { }; const TAGS_COLUMN: TableColumn = { - field: 'rule.tags', + field: 'current_rule.tags', name: null, align: 'center', render: (tags: Rule['tags']) => { @@ -88,7 +89,7 @@ const TAGS_COLUMN: TableColumn = { }; const INTEGRATIONS_COLUMN: TableColumn = { - field: 'rule.related_integrations', + field: 'current_rule.related_integrations', name: null, align: 'center', render: (integrations: Rule['related_integrations']) => { @@ -144,7 +145,7 @@ export const useUpgradePrebuiltRulesTableColumns = (): TableColumn[] => { ...(showRelatedIntegrations ? [INTEGRATIONS_COLUMN] : []), TAGS_COLUMN, { - field: 'rule.risk_score', + field: 'current_rule.risk_score', name: i18n.COLUMN_RISK_SCORE, render: (value: Rule['risk_score']) => ( @@ -156,10 +157,10 @@ export const useUpgradePrebuiltRulesTableColumns = (): TableColumn[] => { width: '85px', }, { - field: 'rule.severity', + field: 'current_rule.severity', name: i18n.COLUMN_SEVERITY, render: (value: Rule['severity']) => , - sortable: ({ rule: { severity } }: RuleUpgradeInfoForReview) => + sortable: ({ current_rule: { severity } }: RuleUpgradeInfoForReview) => getNormalizedSeverity(severity), truncateText: true, width: '12%', diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/threat_description.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/threat_description.tsx index f36c57db1bc8..375113977c9f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/threat_description.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/threat_description.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiFlexItem, EuiLink, EuiFlexGroup, EuiSpacer, EuiButtonEmpty } from '@elastic/eui'; +import { EuiFlexItem, EuiLink, EuiFlexGroup, EuiButtonEmpty } from '@elastic/eui'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import type { BuildThreatDescription } from './types'; @@ -120,7 +120,6 @@ export const ThreatEuiFlexGroup = ({ label, threat }: BuildThreatDescription) => ); })} - ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index 7ef797d64eeb..78c81f5c9b1d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -144,7 +144,7 @@ export const getDefineStepsData = (rule: Rule): DefineStepRule => ({ rule.alert_suppression?.missing_fields_strategy ?? DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY, }); -const convertHistoryStartToSize = (relativeTime: string) => { +export const convertHistoryStartToSize = (relativeTime: string) => { if (relativeTime.startsWith('now-')) { return relativeTime.substring(4); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts index 701724389d20..c3d1a0ea7623 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts @@ -9,17 +9,16 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { REVIEW_RULE_INSTALLATION_URL } from '../../../../../../common/api/detection_engine/prebuilt_rules'; import type { ReviewRuleInstallationResponseBody, - RuleInstallationInfoForReview, RuleInstallationStatsForReview, } from '../../../../../../common/api/detection_engine/prebuilt_rules'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { buildSiemResponse } from '../../../routes/utils'; -import { convertRuleToDiffable } from '../../logic/diff/normalization/convert_rule_to_diffable'; import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; import { fetchRuleVersionsTriad } from '../../logic/rule_versions/fetch_rule_versions_triad'; import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; import { getVersionBuckets } from '../../model/rule_versions/get_version_buckets'; +import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/normalization/rule_converters'; export const reviewRuleInstallationRoute = (router: SecuritySolutionPluginRouter) => { router.post( @@ -48,7 +47,9 @@ export const reviewRuleInstallationRoute = (router: SecuritySolutionPluginRouter const body: ReviewRuleInstallationResponseBody = { stats: calculateRuleStats(installableRules), - rules: calculateRuleInfos(installableRules), + rules: installableRules.map((prebuiltRuleAsset) => + convertPrebuiltRuleAssetToRuleResponse(prebuiltRuleAsset) + ), }; return response.ok({ body }); @@ -77,9 +78,3 @@ const calculateRuleStats = ( tags: tagsOfRulesToInstall, }; }; - -const calculateRuleInfos = ( - rulesToInstall: PrebuiltRuleAsset[] -): RuleInstallationInfoForReview[] => { - return rulesToInstall.map((rule) => convertRuleToDiffable(rule)); -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts index 8a29b5120a73..a740f9912f26 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts @@ -15,6 +15,7 @@ import type { ThreeWayDiff, } from '../../../../../../common/api/detection_engine/prebuilt_rules'; import { invariant } from '../../../../../../common/utils/invariant'; +import type { RuleResponse } from '../../../../../../common/api/detection_engine/model/rule_schema/rule_schemas'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { buildSiemResponse } from '../../../routes/utils'; import type { CalculateRuleDiffResult } from '../../logic/diff/calculate_rule_diff'; @@ -23,6 +24,7 @@ import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; import { fetchRuleVersionsTriad } from '../../logic/rule_versions/fetch_rule_versions_triad'; import { getVersionBuckets } from '../../model/rule_versions/get_version_buckets'; +import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/normalization/rule_converters'; export const reviewRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => { router.post( @@ -86,16 +88,21 @@ const calculateRuleInfos = (results: CalculateRuleDiffResult[]): RuleUpgradeInfo return results.map((result) => { const { ruleDiff, ruleVersions } = result; const installedCurrentVersion = ruleVersions.input.current; - const diffableCurrentVersion = ruleVersions.output.current; - const diffableTargetVersion = ruleVersions.output.target; + const targetVersion = ruleVersions.input.target; invariant(installedCurrentVersion != null, 'installedCurrentVersion not found'); + invariant(targetVersion != null, 'targetVersion not found'); + + const targetRule: RuleResponse = { + ...convertPrebuiltRuleAssetToRuleResponse(targetVersion), + id: installedCurrentVersion.id, + }; return { id: installedCurrentVersion.id, rule_id: installedCurrentVersion.rule_id, revision: installedCurrentVersion.revision, - rule: diffableCurrentVersion, - target_rule: diffableTargetVersion, + current_rule: installedCurrentVersion, + target_rule: targetRule, diff: { fields: pickBy>( ruleDiff.fields, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts index ecb379a1dcc2..385fe889be40 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts @@ -8,7 +8,7 @@ import { v4 as uuidv4 } from 'uuid'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; -import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; +import { validate, validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { ruleTypeMappings } from '@kbn/securitysolution-rules'; import type { ResolvedSanitizedRule, SanitizedRule } from '@kbn/alerting-plugin/common'; @@ -24,7 +24,6 @@ import type { RequiredFieldArray, SetupGuide, RuleCreateProps, - RuleResponse, TypeSpecificCreateProps, TypeSpecificResponse, } from '../../../../../common/api/detection_engine/model/rule_schema'; @@ -36,6 +35,7 @@ import { SavedQueryPatchParams, ThreatMatchPatchParams, ThresholdPatchParams, + RuleResponse, } from '../../../../../common/api/detection_engine/model/rule_schema'; import { @@ -76,6 +76,11 @@ import type { import { transformFromAlertThrottle, transformToActionFrequency } from './rule_actions'; import { convertAlertSuppressionToCamel, convertAlertSuppressionToSnake } from '../utils/utils'; import { createRuleExecutionSummary } from '../../rule_monitoring'; +import type { PrebuiltRuleAsset } from '../../prebuilt_rules'; + +const DEFAULT_FROM = 'now-6m' as const; +const DEFAULT_TO = 'now' as const; +const DEFAULT_INTERVAL = '5m' as const; // These functions provide conversions from the request API schema to the internal rule schema and from the internal rule schema // to the response API schema. This provides static type-check assurances that the internal schema is in sync with the API schema for @@ -472,7 +477,7 @@ export const convertCreateAPIToInternalSchema = ( ruleId: newRuleId, falsePositives: input.false_positives ?? [], investigationFields: input.investigation_fields ?? [], - from: input.from ?? 'now-6m', + from: input.from ?? DEFAULT_FROM, immutable, license: input.license, outputIndex: input.output_index ?? '', @@ -488,7 +493,7 @@ export const convertCreateAPIToInternalSchema = ( threat: input.threat ?? [], timestampOverride: input.timestamp_override, timestampOverrideFallbackDisabled: input.timestamp_override_fallback_disabled, - to: input.to ?? 'now', + to: input.to ?? DEFAULT_TO, references: input.references ?? [], namespace: input.namespace, note: input.note, @@ -680,3 +685,48 @@ export const internalRuleToAPIResponse = ( execution_summary: executionSummary ?? undefined, }; }; + +export const convertPrebuiltRuleAssetToRuleResponse = ( + prebuiltRuleAsset: PrebuiltRuleAsset +): RuleResponse => { + const prebuiltRuleAssetDefaults = { + enabled: false, + risk_score_mapping: [], + severity_mapping: [], + interval: DEFAULT_INTERVAL, + to: DEFAULT_TO, + from: DEFAULT_FROM, + exceptions_list: [], + false_positives: [], + max_signals: DEFAULT_MAX_SIGNALS, + actions: [], + related_integrations: [], + required_fields: [], + setup: '', + references: [], + threat: [], + tags: [], + author: [], + }; + + const ruleResponseSpecificFields = { + id: uuidv4(), + updated_at: new Date(0).toISOString(), + updated_by: '', + created_at: new Date(0).toISOString(), + created_by: '', + immutable: true, + revision: 1, + }; + + const [rule, error] = validate( + { ...prebuiltRuleAssetDefaults, ...prebuiltRuleAsset, ...ruleResponseSpecificFields }, + RuleResponse + ); + + if (!rule) { + throw new Error(error); + } + + return rule; +};