mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[8.10] [Security Solution] Prebuilt rules installation / upgrade flyout improvements (#164179) (#164897)
# Backport This will backport the following commits from `main` to `8.10`: - [[Security Solution] Prebuilt rules installation / upgrade flyout improvements (#164179)](https://github.com/elastic/kibana/pull/164179) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Nikita Indik","email":"nikita.indik@elastic.co"},"sourceCommit":{"committedDate":"2023-08-25T19:47:13Z","message":"[Security Solution] Prebuilt rules installation / upgrade flyout improvements (#164179)\n\n**Addresses: https://github.com/elastic/kibana/issues/162334**\r\n**Base PR: https://github.com/elastic/kibana/pull/163304**\r\n\r\n<img width=\"1177\" alt=\"Screenshot 2023-08-24 at 04 09 07\"\r\nsrc=\"73ac6726
-69d4-4c46-bb16-da704a02aba5\">\r\n\r\n## Summary\r\n\r\nThis is a follow-up refactoring and bugfix PR to improve the prebuilt\r\nrules flyout. Base PR: #163304\r\n\r\n#### Changes\r\n- [x] Tweak UI so that it matches the design more closely.\r\n[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)\r\n(external).\r\n- [x] Rewrite preview installation and upgrade API endpoints to respond\r\nwith `RuleResponse` instead of `DiffableRule`\r\n- [x] Revert some changes introduced by this\r\n[PR](https://github.com/elastic/kibana/pull/163304)\r\n- [x] Revert exports in\r\n`x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.ts`\r\n- [x] Delete\r\n`x-pack/plugins/security_solution/common/detection_engine/diffable_rule_to_rule_response.ts`\r\n- [x] Make the data contexts unaware of any UI elements that are\r\nconsuming them\r\n- [x] Move rendering of specialized flyout components into to the\r\ncontext provider so that the table is unaware of the flyout.\r\n- [x] Make \"flyoutRule\" and \"closeFlyout\" internal to the context.\r\nComponents outside don't need to know anything about how a rule is\r\ndisplayed. We can encapsulate this knowledge inside the context and\r\nexpose only a generic method, like openRulePreview(ruleId)\r\n - [x] Remove unnecessary checks after using \"invariant\"\r\n- [x] Make sure query, timeline template and all the other fields are\r\nshown in the flyout. Compare each rule in a flyout with the Rule Details\r\nto ensure that all fields are in place.\r\n- [x] Remove the enable / disable switch machine learning job UI switch\r\nelement\r\n- [x] Add custom highlighted fields to the flyout\r\n([comment](https://github.com/elastic/kibana/pull/163235#discussion_r1293821203))\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials. [Docs\r\nticket](https://github.com/elastic/security-docs/issues/3798)\r\n- [x] Any UI touched in this PR does not create any new axe failures\r\n(run axe in browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n- [x] This renders correctly on smaller devices using a responsive\r\nlayout. (You can test this [in your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n- [x] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)","sha":"c115f5d3d6f580b195e823c9e948f7b1daf8fddc","branchLabelMapping":{"^v8.11.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Detections and Resp","Team: SecuritySolution","Team:Detection Rule Management","Feature:Prebuilt Detection Rules","v8.10.0","v8.11.0"],"number":164179,"url":"https://github.com/elastic/kibana/pull/164179","mergeCommit":{"message":"[Security Solution] Prebuilt rules installation / upgrade flyout improvements (#164179)\n\n**Addresses: https://github.com/elastic/kibana/issues/162334**\r\n**Base PR: https://github.com/elastic/kibana/pull/163304**\r\n\r\n<img width=\"1177\" alt=\"Screenshot 2023-08-24 at 04 09 07\"\r\nsrc=\"73ac6726
-69d4-4c46-bb16-da704a02aba5\">\r\n\r\n## Summary\r\n\r\nThis is a follow-up refactoring and bugfix PR to improve the prebuilt\r\nrules flyout. Base PR: #163304\r\n\r\n#### Changes\r\n- [x] Tweak UI so that it matches the design more closely.\r\n[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)\r\n(external).\r\n- [x] Rewrite preview installation and upgrade API endpoints to respond\r\nwith `RuleResponse` instead of `DiffableRule`\r\n- [x] Revert some changes introduced by this\r\n[PR](https://github.com/elastic/kibana/pull/163304)\r\n- [x] Revert exports in\r\n`x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.ts`\r\n- [x] Delete\r\n`x-pack/plugins/security_solution/common/detection_engine/diffable_rule_to_rule_response.ts`\r\n- [x] Make the data contexts unaware of any UI elements that are\r\nconsuming them\r\n- [x] Move rendering of specialized flyout components into to the\r\ncontext provider so that the table is unaware of the flyout.\r\n- [x] Make \"flyoutRule\" and \"closeFlyout\" internal to the context.\r\nComponents outside don't need to know anything about how a rule is\r\ndisplayed. We can encapsulate this knowledge inside the context and\r\nexpose only a generic method, like openRulePreview(ruleId)\r\n - [x] Remove unnecessary checks after using \"invariant\"\r\n- [x] Make sure query, timeline template and all the other fields are\r\nshown in the flyout. Compare each rule in a flyout with the Rule Details\r\nto ensure that all fields are in place.\r\n- [x] Remove the enable / disable switch machine learning job UI switch\r\nelement\r\n- [x] Add custom highlighted fields to the flyout\r\n([comment](https://github.com/elastic/kibana/pull/163235#discussion_r1293821203))\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials. [Docs\r\nticket](https://github.com/elastic/security-docs/issues/3798)\r\n- [x] Any UI touched in this PR does not create any new axe failures\r\n(run axe in browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n- [x] This renders correctly on smaller devices using a responsive\r\nlayout. (You can test this [in your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n- [x] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)","sha":"c115f5d3d6f580b195e823c9e948f7b1daf8fddc"}},"sourceBranch":"main","suggestedTargetBranches":["8.10"],"targetPullRequestStates":[{"branch":"8.10","label":"v8.10.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.11.0","labelRegex":"^v8.11.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/164179","number":164179,"mergeCommit":{"message":"[Security Solution] Prebuilt rules installation / upgrade flyout improvements (#164179)\n\n**Addresses: https://github.com/elastic/kibana/issues/162334**\r\n**Base PR: https://github.com/elastic/kibana/pull/163304**\r\n\r\n<img width=\"1177\" alt=\"Screenshot 2023-08-24 at 04 09 07\"\r\nsrc=\"73ac6726
-69d4-4c46-bb16-da704a02aba5\">\r\n\r\n## Summary\r\n\r\nThis is a follow-up refactoring and bugfix PR to improve the prebuilt\r\nrules flyout. Base PR: #163304\r\n\r\n#### Changes\r\n- [x] Tweak UI so that it matches the design more closely.\r\n[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)\r\n(external).\r\n- [x] Rewrite preview installation and upgrade API endpoints to respond\r\nwith `RuleResponse` instead of `DiffableRule`\r\n- [x] Revert some changes introduced by this\r\n[PR](https://github.com/elastic/kibana/pull/163304)\r\n- [x] Revert exports in\r\n`x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.ts`\r\n- [x] Delete\r\n`x-pack/plugins/security_solution/common/detection_engine/diffable_rule_to_rule_response.ts`\r\n- [x] Make the data contexts unaware of any UI elements that are\r\nconsuming them\r\n- [x] Move rendering of specialized flyout components into to the\r\ncontext provider so that the table is unaware of the flyout.\r\n- [x] Make \"flyoutRule\" and \"closeFlyout\" internal to the context.\r\nComponents outside don't need to know anything about how a rule is\r\ndisplayed. We can encapsulate this knowledge inside the context and\r\nexpose only a generic method, like openRulePreview(ruleId)\r\n - [x] Remove unnecessary checks after using \"invariant\"\r\n- [x] Make sure query, timeline template and all the other fields are\r\nshown in the flyout. Compare each rule in a flyout with the Rule Details\r\nto ensure that all fields are in place.\r\n- [x] Remove the enable / disable switch machine learning job UI switch\r\nelement\r\n- [x] Add custom highlighted fields to the flyout\r\n([comment](https://github.com/elastic/kibana/pull/163235#discussion_r1293821203))\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials. [Docs\r\nticket](https://github.com/elastic/security-docs/issues/3798)\r\n- [x] Any UI touched in this PR does not create any new axe failures\r\n(run axe in browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n- [x] This renders correctly on smaller devices using a responsive\r\nlayout. (You can test this [in your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n- [x] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)","sha":"c115f5d3d6f580b195e823c9e948f7b1daf8fddc"}}]}] BACKPORT--> Co-authored-by: Nikita Indik <nikita.indik@elastic.co> Co-authored-by: Patryk Kopyciński <contact@patrykkopycinski.com>
This commit is contained in:
parent
14535262b7
commit
41b32c7a18
29 changed files with 536 additions and 685 deletions
|
@ -221,7 +221,7 @@ export const KqlQueryLanguage = t.keyof({ kuery: null, lucene: null });
|
|||
export type EqlQueryLanguage = t.TypeOf<typeof EqlQueryLanguage>;
|
||||
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,
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<typeof querySchema.create>;
|
||||
type RuleResponseSavedQueryFields = t.TypeOf<typeof savedQuerySchema.create>;
|
||||
type RuleResponseEqlFields = t.TypeOf<typeof eqlSchema.create>;
|
||||
type RuleResponseThreatMatchFields = t.TypeOf<typeof threatMatchSchema.create>;
|
||||
type RuleResponseThresholdFields = t.TypeOf<typeof thresholdSchema.create>;
|
||||
type RuleResponseMachineLearningFields = t.TypeOf<typeof machineLearningSchema.create>;
|
||||
type RuleResponseNewTermsFields = t.TypeOf<typeof newTermsSchema.create>;
|
||||
|
||||
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<SharedResponseProps> => {
|
||||
const { from, to, interval } = extractRuleSchedule(diffableRule.rule_schedule);
|
||||
|
||||
const commonFields: Partial<SharedResponseProps> = {
|
||||
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<RuleResponse> => {
|
||||
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);
|
||||
};
|
|
@ -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<Props> = ({ children, disableLinks }) => {
|
||||
const MarkdownRendererComponent: React.FC<Props> = ({ children, disableLinks, textSize = 'm' }) => {
|
||||
const MarkdownLinkProcessingComponent: React.FC<EuiLinkAnchorProps> = useMemo(
|
||||
// eslint-disable-next-line react/display-name
|
||||
() => (props) => <MarkdownLink {...props} disableLinks={disableLinks} />,
|
||||
|
@ -97,6 +98,7 @@ const MarkdownRendererComponent: React.FC<Props> = ({ children, disableLinks })
|
|||
<EuiMarkdownFormat
|
||||
parsingPluginList={parsingPlugins}
|
||||
processingPluginList={processingPluginList}
|
||||
textSize={textSize}
|
||||
>
|
||||
{children}
|
||||
</EuiMarkdownFormat>
|
||||
|
|
|
@ -164,6 +164,14 @@ const FalsePositives = ({ falsePositives }: { falsePositives: string[] }) => (
|
|||
</EuiText>
|
||||
);
|
||||
|
||||
interface InvestigationFieldsProps {
|
||||
investigationFields: string[];
|
||||
}
|
||||
|
||||
const InvestigationFields = ({ investigationFields }: InvestigationFieldsProps) => (
|
||||
<BadgeList badges={investigationFields} />
|
||||
);
|
||||
|
||||
interface LicenseProps {
|
||||
license: string;
|
||||
}
|
||||
|
@ -208,13 +216,10 @@ interface TagsProps {
|
|||
|
||||
const Tags = ({ tags }: TagsProps) => <BadgeList badges={tags} />;
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
const prepareAboutSectionListItems = (
|
||||
rule: Partial<RuleResponse>
|
||||
): 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: <Author author={rule.author} />,
|
||||
|
@ -228,14 +233,12 @@ const prepareAboutSectionListItems = (
|
|||
});
|
||||
}
|
||||
|
||||
if (rule.severity) {
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.SEVERITY_FIELD_LABEL,
|
||||
description: <SeverityBadge value={rule.severity} />,
|
||||
});
|
||||
}
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.SEVERITY_FIELD_LABEL,
|
||||
description: <SeverityBadge value={rule.severity} />,
|
||||
});
|
||||
|
||||
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: <RiskScore riskScore={rule.risk_score} />,
|
||||
});
|
||||
}
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.RISK_SCORE_FIELD_LABEL,
|
||||
description: <RiskScore riskScore={rule.risk_score} />,
|
||||
});
|
||||
|
||||
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: <References references={rule.references} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.false_positives && rule.false_positives.length > 0) {
|
||||
if (rule.false_positives.length > 0) {
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.FALSE_POSITIVES_FIELD_LABEL,
|
||||
description: <FalsePositives falsePositives={rule.false_positives} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.investigation_fields && rule.investigation_fields.length > 0) {
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.INVESTIGATION_FIELDS_FIELD_LABEL,
|
||||
description: <InvestigationFields investigationFields={rule.investigation_fields} />,
|
||||
});
|
||||
}
|
||||
|
||||
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: <Threat threat={rule.threat} />,
|
||||
|
@ -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: <Tags tags={rule.tags} />,
|
||||
|
@ -328,7 +336,7 @@ const prepareAboutSectionListItems = (
|
|||
};
|
||||
|
||||
export interface RuleAboutSectionProps {
|
||||
rule: Partial<RuleResponse>;
|
||||
rule: RuleResponse;
|
||||
}
|
||||
|
||||
export const RuleAboutSection = ({ rule }: RuleAboutSectionProps) => {
|
||||
|
|
|
@ -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) => (
|
||||
<EuiText size="s">{savedQueryName}</EuiText>
|
||||
);
|
||||
|
||||
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 (
|
||||
<EuiFlexGroup wrap responsive={false} gutterSize="xs">
|
||||
{flattenedFilters.map((filter, idx) => (
|
||||
<EuiFlexItem grow={false} key={`filter-${idx}`} css={{ width: '100%' }}>
|
||||
<EuiBadgeWrap color="hollow">
|
||||
{indexPattern != null ? (
|
||||
<FilterBadgeGroup filters={[filter]} dataViews={[indexPattern]} />
|
||||
) : (
|
||||
<EuiLoadingSpinner size="m" />
|
||||
)}
|
||||
</EuiBadgeWrap>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const QueryContent = styled.div`
|
||||
white-space: pre-wrap;
|
||||
`;
|
||||
|
||||
interface QueryProps {
|
||||
query: string;
|
||||
}
|
||||
|
||||
const Query = ({ query }: QueryProps) => <QueryContent>{query}</QueryContent>;
|
||||
|
||||
interface IndexProps {
|
||||
index: string[];
|
||||
}
|
||||
|
@ -53,6 +127,36 @@ const Threshold = ({ threshold }: ThresholdProps) => (
|
|||
</>
|
||||
);
|
||||
|
||||
interface AnomalyThresholdProps {
|
||||
anomalyThreshold: number;
|
||||
}
|
||||
|
||||
const AnomalyThreshold = ({ anomalyThreshold }: AnomalyThresholdProps) => (
|
||||
<EuiText size="s">{anomalyThreshold}</EuiText>
|
||||
);
|
||||
|
||||
interface MachineLearningJobListProps {
|
||||
jobIds: string[];
|
||||
}
|
||||
|
||||
const MachineLearningJobList = ({ jobIds }: MachineLearningJobListProps) => {
|
||||
const { jobs } = useSecurityJobs();
|
||||
|
||||
const relevantJobs = jobs.filter((job) => jobIds.includes(job.id));
|
||||
|
||||
return (
|
||||
<>
|
||||
{relevantJobs.map((job) => (
|
||||
<MlJobLink
|
||||
key={job.id}
|
||||
jobId={job.id}
|
||||
jobName={job.customSettings?.security_app_display_name}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getRuleTypeDescription = (ruleType: Type) => {
|
||||
switch (ruleType) {
|
||||
case 'machine_learning':
|
||||
|
@ -94,7 +198,7 @@ interface RequiredFieldsProps {
|
|||
const RequiredFields = ({ requiredFields }: RequiredFieldsProps) => (
|
||||
<EuiFlexGrid gutterSize={'s'}>
|
||||
{requiredFields.map((rF, index) => (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem grow={false} key={rF.name}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize={'xs'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<FieldIcon
|
||||
|
@ -162,8 +266,28 @@ const ThreatMapping = ({ threatMapping }: ThreatMappingProps) => {
|
|||
return <EuiText size="s">{description}</EuiText>;
|
||||
};
|
||||
|
||||
interface NewTermsFieldsProps {
|
||||
newTermsFields: string[];
|
||||
}
|
||||
|
||||
const NewTermsFields = ({ newTermsFields }: NewTermsFieldsProps) => (
|
||||
<BadgeList badges={newTermsFields} />
|
||||
);
|
||||
|
||||
interface HistoryWindowSizeProps {
|
||||
historyWindowStart?: string;
|
||||
}
|
||||
|
||||
const HistoryWindowSize = ({ historyWindowStart }: HistoryWindowSizeProps) => {
|
||||
const size = historyWindowStart ? convertHistoryStartToSize(historyWindowStart) : '7d';
|
||||
|
||||
return <EuiText size="s">{size}</EuiText>;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
const prepareDefinitionSectionListItems = (
|
||||
rule: Partial<RuleResponse>
|
||||
rule: RuleResponse,
|
||||
savedQuery?: SavedQuery
|
||||
): EuiDescriptionListProps['listItems'] => {
|
||||
const definitionSectionListItems: EuiDescriptionListProps['listItems'] = [];
|
||||
|
||||
|
@ -181,21 +305,62 @@ const prepareDefinitionSectionListItems = (
|
|||
});
|
||||
}
|
||||
|
||||
if (rule.type) {
|
||||
if (savedQuery) {
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.RULE_TYPE_FIELD_LABEL,
|
||||
description: <RuleType type={rule.type} />,
|
||||
title: descriptionStepI18n.SAVED_QUERY_NAME_LABEL,
|
||||
description: <SavedQueryName savedQueryName={savedQuery.attributes.title} />,
|
||||
});
|
||||
|
||||
if (savedQuery.attributes.filters) {
|
||||
definitionSectionListItems.push({
|
||||
title: descriptionStepI18n.SAVED_QUERY_FILTERS_LABEL,
|
||||
description: <Filters filters={savedQuery.attributes.filters as Filter[]} />,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if ('filters' in rule && rule.filters && rule.filters.length > 0) {
|
||||
definitionSectionListItems.push({
|
||||
title: savedQuery
|
||||
? descriptionStepI18n.SAVED_QUERY_FILTERS_LABEL
|
||||
: descriptionStepI18n.FILTERS_LABEL,
|
||||
description: (
|
||||
<Filters
|
||||
filters={rule.filters as Filter[]}
|
||||
dataViewId={rule.data_view_id}
|
||||
index={rule.index}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if ('query' in rule && rule.query) {
|
||||
definitionSectionListItems.push({
|
||||
title: savedQuery ? descriptionStepI18n.SAVED_QUERY_LABEL : descriptionStepI18n.QUERY_LABEL,
|
||||
description: <Query query={rule.query} />,
|
||||
});
|
||||
}
|
||||
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.RULE_TYPE_FIELD_LABEL,
|
||||
description: <RuleType type={rule.type} />,
|
||||
});
|
||||
|
||||
if ('anomaly_threshold' in rule && rule.anomaly_threshold) {
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.ANOMALY_THRESHOLD_FIELD_LABEL,
|
||||
description: <AnomalyThreshold anomalyThreshold={rule.anomaly_threshold} />,
|
||||
});
|
||||
}
|
||||
|
||||
if ('machine_learning_job_id' in rule) {
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.MACHINE_LEARNING_JOB_ID_FIELD_LABEL,
|
||||
description: <MlJobsDescription jobIds={rule.machine_learning_job_id as string[]} />,
|
||||
description: <MachineLearningJobList jobIds={rule.machine_learning_job_id as string[]} />,
|
||||
});
|
||||
}
|
||||
|
||||
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: <RequiredFields requiredFields={rule.required_fields} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.timeline_title) {
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.TIMELINE_TITLE_FIELD_LABEL,
|
||||
description: <TimelineTitle timelineTitle={rule.timeline_title} />,
|
||||
});
|
||||
}
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.TIMELINE_TITLE_FIELD_LABEL,
|
||||
description: (
|
||||
<TimelineTitle timelineTitle={rule.timeline_title || timelinesI18n.DEFAULT_TIMELINE_TITLE} />
|
||||
),
|
||||
});
|
||||
|
||||
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: (
|
||||
<Filters
|
||||
filters={rule.threat_filters as Filter[]}
|
||||
dataViewId={rule.data_view_id}
|
||||
index={rule.index}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if ('threat_query' in rule && rule.threat_query) {
|
||||
definitionSectionListItems.push({
|
||||
title: savedQuery
|
||||
? descriptionStepI18n.SAVED_QUERY_LABEL
|
||||
: descriptionStepI18n.THREAT_QUERY_LABEL,
|
||||
description: <Query query={rule.threat_query} />,
|
||||
});
|
||||
}
|
||||
|
||||
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: <NewTermsFields newTermsFields={rule.new_terms_fields} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.type === 'new_terms' || 'history_window_start' in rule) {
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.HISTORY_WINDOW_SIZE_FIELD_LABEL,
|
||||
description: <HistoryWindowSize historyWindowStart={rule.history_window_start} />,
|
||||
});
|
||||
}
|
||||
|
||||
return definitionSectionListItems;
|
||||
};
|
||||
|
||||
export interface RuleDefinitionSectionProps {
|
||||
rule: Partial<RuleResponse>;
|
||||
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 (
|
||||
<div>
|
||||
|
|
|
@ -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) => (
|
||||
<StyledFlexGroup direction="column" gutterSize="none">
|
||||
<StyledEuiFlexItem grow={true}>
|
||||
<StyledEuiTabbedContent {...props} />
|
||||
</StyledEuiFlexItem>
|
||||
</StyledFlexGroup>
|
||||
);
|
||||
|
||||
const tabPaddingClassName = css`
|
||||
padding: 0 ${euiThemeVars.euiSizeM} ${euiThemeVars.euiSizeXL} ${euiThemeVars.euiSizeM};
|
||||
`;
|
||||
|
||||
const TabContentPadding: React.FC = ({ children }) => (
|
||||
<div className={tabPaddingClassName}>{children}</div>
|
||||
);
|
||||
|
||||
interface RuleDetailsFlyoutProps {
|
||||
rule: Partial<RuleResponse>;
|
||||
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: (
|
||||
<RuleOverviewTab
|
||||
rule={rule}
|
||||
expandedOverviewSections={expandedOverviewSections}
|
||||
toggleOverviewSection={toggleOverviewSection}
|
||||
/>
|
||||
<TabContentPadding>
|
||||
<RuleOverviewTab
|
||||
rule={rule}
|
||||
expandedOverviewSections={expandedOverviewSections}
|
||||
toggleOverviewSection={toggleOverviewSection}
|
||||
/>
|
||||
</TabContentPadding>
|
||||
),
|
||||
}),
|
||||
[rule, expandedOverviewSections, toggleOverviewSection]
|
||||
|
@ -115,7 +138,11 @@ export const RuleDetailsFlyout = ({
|
|||
() => ({
|
||||
id: 'investigationGuide',
|
||||
name: i18n.INVESTIGATION_GUIDE_TAB_LABEL,
|
||||
content: <RuleInvestigationGuideTab note={rule.note ?? ''} />,
|
||||
content: (
|
||||
<TabContentPadding>
|
||||
<RuleInvestigationGuideTab note={rule.note ?? ''} />
|
||||
</TabContentPadding>
|
||||
),
|
||||
}),
|
||||
[rule.note]
|
||||
);
|
||||
|
@ -151,17 +178,17 @@ export const RuleDetailsFlyout = ({
|
|||
paddingSize="l"
|
||||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle size="m" data-test-subj="rulesBulkEditFormTitle">
|
||||
<EuiTitle size="m">
|
||||
<h2>{rule.name}</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="l" />
|
||||
</EuiFlyoutHeader>
|
||||
<StyledEuiFlyoutBody>
|
||||
<StyledFlexGroup direction="column" gutterSize="none">
|
||||
<StyledEuiFlexItem grow={true}>
|
||||
<StyledEuiTabbedContent tabs={tabs} selectedTab={selectedTab} onTabClick={onTabClick} />
|
||||
</StyledEuiFlexItem>
|
||||
</StyledFlexGroup>
|
||||
<ScrollableFlyoutTabbedContent
|
||||
tabs={tabs}
|
||||
selectedTab={selectedTab}
|
||||
onTabClick={onTabClick}
|
||||
/>
|
||||
</StyledEuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
|
|
|
@ -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 (
|
||||
<div
|
||||
css={css`
|
||||
padding: 0 ${euiThemeVars.euiSizeM};
|
||||
`}
|
||||
>
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<MarkdownRenderer>{note}</MarkdownRenderer>
|
||||
</div>
|
||||
<MarkdownRenderer textSize="s">{note}</MarkdownRenderer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<RuleResponse>;
|
||||
rule: RuleResponse;
|
||||
expandedOverviewSections: Record<keyof typeof defaultOverviewOpenSections, boolean>;
|
||||
toggleOverviewSection: Record<keyof typeof defaultOverviewOpenSections, () => void>;
|
||||
}
|
||||
|
@ -98,11 +96,7 @@ export const RuleOverviewTab = ({
|
|||
expandedOverviewSections,
|
||||
toggleOverviewSection,
|
||||
}: RuleOverviewTabProps) => (
|
||||
<div
|
||||
css={css`
|
||||
padding: 0 ${euiThemeVars.euiSizeM};
|
||||
`}
|
||||
>
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<ExpandableSection
|
||||
title={i18n.ABOUT_SECTION_LABEL}
|
||||
|
@ -127,9 +121,9 @@ export const RuleOverviewTab = ({
|
|||
>
|
||||
<RuleScheduleSection rule={rule} />
|
||||
</ExpandableSection>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
{rule.setup && (
|
||||
<>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<ExpandableSection
|
||||
title={i18n.SETUP_GUIDE_SECTION_LABEL}
|
||||
isOpen={expandedOverviewSections.setup}
|
||||
|
@ -137,8 +131,7 @@ export const RuleOverviewTab = ({
|
|||
>
|
||||
<RuleSetupGuideSection setup={rule.setup} />
|
||||
</ExpandableSection>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -27,25 +27,22 @@ const From = ({ from, interval }: FromProps) => (
|
|||
);
|
||||
|
||||
export interface RuleScheduleSectionProps {
|
||||
rule: Partial<RuleResponse>;
|
||||
rule: RuleResponse;
|
||||
}
|
||||
|
||||
export const RuleScheduleSection = ({ rule }: RuleScheduleSectionProps) => {
|
||||
const ruleSectionListItems = [];
|
||||
|
||||
if (rule.interval) {
|
||||
ruleSectionListItems.push({
|
||||
ruleSectionListItems.push(
|
||||
{
|
||||
title: i18n.INTERVAL_FIELD_LABEL,
|
||||
description: <Interval interval={rule.interval} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.interval && rule.from) {
|
||||
ruleSectionListItems.push({
|
||||
},
|
||||
{
|
||||
title: i18n.FROM_FIELD_LABEL,
|
||||
description: <From from={rule.from} interval={rule.interval} />,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
|
@ -16,7 +16,7 @@ interface RuleSetupGuideSectionProps {
|
|||
export const RuleSetupGuideSection = ({ setup }: RuleSetupGuideSectionProps) => {
|
||||
return (
|
||||
<div>
|
||||
<MarkdownRenderer>{setup}</MarkdownRenderer>
|
||||
<MarkdownRenderer textSize="s">{setup}</MarkdownRenderer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
{
|
||||
|
|
|
@ -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<RuleInstallationInfoForReview | null>(null);
|
||||
const [previewedRule, setRuleForPreview] = React.useState<RuleResponse | null>(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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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<RuleResponse> = diffableRuleToRuleResponse(flyoutRule);
|
||||
|
||||
return (
|
||||
<RuleDetailsFlyout
|
||||
rule={ruleResponse}
|
||||
actionButtonLabel={i18n.INSTALL_BUTTON_LABEL}
|
||||
isActionButtonDisabled={isFlyoutInstallButtonDisabled}
|
||||
onActionButtonClick={installOneRule}
|
||||
closeFlyout={closeFlyout}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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}
|
||||
/>
|
||||
|
||||
<AddPrebuiltRulesFlyout />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<SetStateAction<AddPrebuiltRulesTableFilterOptions>>;
|
||||
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<RuleSignatureId[]>([]);
|
||||
const [selectedRules, setSelectedRules] = useState<RuleInstallationInfoForReview[]>([]);
|
||||
const [selectedRules, setSelectedRules] = useState<RuleResponse[]>([]);
|
||||
|
||||
const [filterOptions, setFilterOptions] = useState<AddPrebuiltRulesTableFilterOptions>({
|
||||
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<AddPrebuiltRulesContextType>(() => {
|
||||
|
@ -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 (
|
||||
<AddPrebuiltRulesTableContext.Provider value={providerValue}>
|
||||
{children}
|
||||
<>
|
||||
{children}
|
||||
{previewedRule && (
|
||||
<RuleDetailsFlyout
|
||||
rule={previewedRule}
|
||||
actionButtonLabel={i18n.INSTALL_BUTTON_LABEL}
|
||||
isActionButtonDisabled={canPreviewedRuleBeInstalled}
|
||||
onActionButtonClick={installOneRule}
|
||||
closeFlyout={closeRulePreview}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</AddPrebuiltRulesTableContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<RuleInstallationInfoForReview>;
|
||||
export type TableColumn = EuiBasicTableColumn<RuleResponse>;
|
||||
|
||||
interface RuleNameProps {
|
||||
name: string;
|
||||
|
@ -32,13 +34,13 @@ interface RuleNameProps {
|
|||
|
||||
const RuleName = ({ name, ruleId }: RuleNameProps) => {
|
||||
const {
|
||||
actions: { openFlyoutForRuleId },
|
||||
actions: { openRulePreview },
|
||||
} = useAddPrebuiltRulesTableContext();
|
||||
|
||||
return (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
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) => (
|
||||
<RuleName name={value} ruleId={rule.rule_id} />
|
||||
render: (value: RuleResponse['name'], rule: RuleResponse) => (
|
||||
<RuleName name={value} ruleId={rule.id} />
|
||||
),
|
||||
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']) => <SeverityBadge value={value} />,
|
||||
sortable: ({ severity }: RuleInstallationInfoForReview) => getNormalizedSeverity(severity),
|
||||
sortable: ({ severity }: RuleResponse) => getNormalizedSeverity(severity),
|
||||
truncateText: true,
|
||||
width: '12%',
|
||||
},
|
||||
|
|
|
@ -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<FilterOptions, 'filter' | 'tags'>;
|
||||
|
@ -15,7 +15,7 @@ export const useFilterPrebuiltRulesToInstall = ({
|
|||
rules,
|
||||
filterOptions,
|
||||
}: {
|
||||
rules: RuleInstallationInfoForReview[];
|
||||
rules: RuleResponse[];
|
||||
filterOptions: AddPrebuiltRulesTableFilterOptions;
|
||||
}) => {
|
||||
const filteredRules = useMemo(() => {
|
||||
|
|
|
@ -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<RuleResponse> = diffableRuleToRuleResponse(flyoutRule);
|
||||
|
||||
return (
|
||||
<RuleDetailsFlyout
|
||||
rule={ruleResponse}
|
||||
actionButtonLabel={i18n.UPDATE_BUTTON_LABEL}
|
||||
isActionButtonDisabled={isFlyoutInstallButtonDisabled}
|
||||
onActionButtonClick={upgradeOneRule}
|
||||
closeFlyout={closeFlyout}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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 = (
|
||||
<EuiEmptyPrompt
|
||||
|
@ -119,8 +118,6 @@ export const UpgradePrebuiltRulesTable = React.memo(() => {
|
|||
data-test-subj="rules-upgrades-table"
|
||||
columns={rulesColumns}
|
||||
/>
|
||||
|
||||
<UpgradePrebuiltRulesFlyout />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<SetStateAction<UpgradePrebuiltRulesTableFilterOptions>>;
|
||||
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<UpgradePrebuiltRulesContextType>(() => {
|
||||
|
@ -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 (
|
||||
<UpgradePrebuiltRulesTableContext.Provider value={providerValue}>
|
||||
{isUpgradeModalVisible && (
|
||||
<MlJobUpgradeModal
|
||||
jobs={legacyJobsInstalled}
|
||||
onCancel={handleUpgradeCancel}
|
||||
onConfirm={handleUpgradeConfirm}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
<>
|
||||
{isUpgradeModalVisible && (
|
||||
<MlJobUpgradeModal
|
||||
jobs={legacyJobsInstalled}
|
||||
onCancel={handleUpgradeCancel}
|
||||
onConfirm={handleUpgradeConfirm}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
{previewedRule && (
|
||||
<RuleDetailsFlyout
|
||||
rule={previewedRule}
|
||||
actionButtonLabel={i18n.UPDATE_BUTTON_LABEL}
|
||||
isActionButtonDisabled={canPreviewedRuleBeUpgraded}
|
||||
onActionButtonClick={upgradeOneRule}
|
||||
closeFlyout={closeRulePreview}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</UpgradePrebuiltRulesTableContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -32,13 +32,13 @@ interface RuleNameProps {
|
|||
|
||||
const RuleName = ({ name, ruleId }: RuleNameProps) => {
|
||||
const {
|
||||
actions: { openFlyoutForRuleId },
|
||||
actions: { openRulePreview },
|
||||
} = useUpgradePrebuiltRulesTableContext();
|
||||
|
||||
return (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
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) => (
|
||||
<RuleName name={value} ruleId={rule.rule.rule_id} />
|
||||
),
|
||||
render: (
|
||||
value: RuleUpgradeInfoForReview['current_rule']['name'],
|
||||
rule: RuleUpgradeInfoForReview
|
||||
) => <RuleName name={value} ruleId={rule.id} />,
|
||||
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']) => (
|
||||
<EuiText data-test-subj="riskScore" size="s">
|
||||
|
@ -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']) => <SeverityBadge value={value} />,
|
||||
sortable: ({ rule: { severity } }: RuleUpgradeInfoForReview) =>
|
||||
sortable: ({ current_rule: { severity } }: RuleUpgradeInfoForReview) =>
|
||||
getNormalizedSeverity(severity),
|
||||
truncateText: true,
|
||||
width: '12%',
|
||||
|
|
|
@ -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) =>
|
|||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
<EuiSpacer />
|
||||
</ThreatEuiFlexGroupStyles>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
|
|
|
@ -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<ThreeWayDiff<unknown>>(
|
||||
ruleDiff.fields,
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue