mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
## Summary [Internal link](https://github.com/elastic/security-team/issues/10820) to the feature details With these changes we add pagination functionality for the rules migration table. This way we will improve the performance within the page. Also, added as part of these PR: * moved `install` and `install_translated` routes to the `rules/api` folder; before those were located in `rules/api/rules` and made confusion * a new `translation_stats` route to return stats for the specific migration about the translated rules, like `total` number of the rules, and number of `prebuilt`, `custom` and `installable` rules * add `Updated` table column * small UI fixes: * use correct icon for "SIEM rule migration" * do not remove "Install translated rules" button and rather disable it when there are no installable rules * do not allow user to update translation status via UI --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
6e5fc696a6
commit
a662233d8b
40 changed files with 838 additions and 234 deletions
|
@ -352,6 +352,7 @@ import type {
|
|||
CreateRuleMigrationRequestBodyInput,
|
||||
CreateRuleMigrationResponse,
|
||||
GetAllStatsRuleMigrationResponse,
|
||||
GetRuleMigrationRequestQueryInput,
|
||||
GetRuleMigrationRequestParamsInput,
|
||||
GetRuleMigrationResponse,
|
||||
GetRuleMigrationResourcesRequestQueryInput,
|
||||
|
@ -359,6 +360,8 @@ import type {
|
|||
GetRuleMigrationResourcesResponse,
|
||||
GetRuleMigrationStatsRequestParamsInput,
|
||||
GetRuleMigrationStatsResponse,
|
||||
GetRuleMigrationTranslationStatsRequestParamsInput,
|
||||
GetRuleMigrationTranslationStatsResponse,
|
||||
InstallMigrationRulesRequestParamsInput,
|
||||
InstallMigrationRulesRequestBodyInput,
|
||||
InstallMigrationRulesResponse,
|
||||
|
@ -1415,6 +1418,8 @@ finalize it.
|
|||
[ELASTIC_HTTP_VERSION_HEADER]: '1',
|
||||
},
|
||||
method: 'GET',
|
||||
|
||||
query: props.query,
|
||||
})
|
||||
.catch(catchAxiosErrorFormatAndThrow);
|
||||
}
|
||||
|
@ -1453,6 +1458,24 @@ finalize it.
|
|||
})
|
||||
.catch(catchAxiosErrorFormatAndThrow);
|
||||
}
|
||||
/**
|
||||
* Retrieves the translation stats of a SIEM rules migration using the migration id provided
|
||||
*/
|
||||
async getRuleMigrationTranslationStats(props: GetRuleMigrationTranslationStatsProps) {
|
||||
this.log.info(`${new Date().toISOString()} Calling API GetRuleMigrationTranslationStats`);
|
||||
return this.kbnClient
|
||||
.request<GetRuleMigrationTranslationStatsResponse>({
|
||||
path: replaceParams(
|
||||
'/internal/siem_migrations/rules/{migration_id}/translation_stats',
|
||||
props.params
|
||||
),
|
||||
headers: {
|
||||
[ELASTIC_HTTP_VERSION_HEADER]: '1',
|
||||
},
|
||||
method: 'GET',
|
||||
})
|
||||
.catch(catchAxiosErrorFormatAndThrow);
|
||||
}
|
||||
/**
|
||||
* Get the details of an existing saved Timeline or Timeline template.
|
||||
*/
|
||||
|
@ -2334,6 +2357,7 @@ export interface GetRuleExecutionResultsProps {
|
|||
params: GetRuleExecutionResultsRequestParamsInput;
|
||||
}
|
||||
export interface GetRuleMigrationProps {
|
||||
query: GetRuleMigrationRequestQueryInput;
|
||||
params: GetRuleMigrationRequestParamsInput;
|
||||
}
|
||||
export interface GetRuleMigrationResourcesProps {
|
||||
|
@ -2343,6 +2367,9 @@ export interface GetRuleMigrationResourcesProps {
|
|||
export interface GetRuleMigrationStatsProps {
|
||||
params: GetRuleMigrationStatsRequestParamsInput;
|
||||
}
|
||||
export interface GetRuleMigrationTranslationStatsProps {
|
||||
params: GetRuleMigrationTranslationStatsRequestParamsInput;
|
||||
}
|
||||
export interface GetTimelineProps {
|
||||
query: GetTimelineRequestQueryInput;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ export const SIEM_RULE_MIGRATION_PATH = `${SIEM_RULE_MIGRATIONS_PATH}/{migration
|
|||
export const SIEM_RULE_MIGRATION_START_PATH = `${SIEM_RULE_MIGRATION_PATH}/start` as const;
|
||||
export const SIEM_RULE_MIGRATION_RETRY_PATH = `${SIEM_RULE_MIGRATION_PATH}/retry` as const;
|
||||
export const SIEM_RULE_MIGRATION_STATS_PATH = `${SIEM_RULE_MIGRATION_PATH}/stats` as const;
|
||||
export const SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH =
|
||||
`${SIEM_RULE_MIGRATION_PATH}/translation_stats` as const;
|
||||
export const SIEM_RULE_MIGRATION_STOP_PATH = `${SIEM_RULE_MIGRATION_PATH}/stop` as const;
|
||||
export const SIEM_RULE_MIGRATION_INSTALL_PATH = `${SIEM_RULE_MIGRATION_PATH}/install` as const;
|
||||
export const SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH =
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
RuleMigrationComments,
|
||||
RuleMigrationTaskStats,
|
||||
RuleMigration,
|
||||
RuleMigrationTranslationStats,
|
||||
RuleMigrationResourceData,
|
||||
RuleMigrationResourceType,
|
||||
RuleMigrationResource,
|
||||
|
@ -44,6 +45,13 @@ export const CreateRuleMigrationResponse = z.object({
|
|||
|
||||
export type GetAllStatsRuleMigrationResponse = z.infer<typeof GetAllStatsRuleMigrationResponse>;
|
||||
export const GetAllStatsRuleMigrationResponse = z.array(RuleMigrationTaskStats);
|
||||
export type GetRuleMigrationRequestQuery = z.infer<typeof GetRuleMigrationRequestQuery>;
|
||||
export const GetRuleMigrationRequestQuery = z.object({
|
||||
page: z.coerce.number().optional(),
|
||||
per_page: z.coerce.number().optional(),
|
||||
search_term: z.string().optional(),
|
||||
});
|
||||
export type GetRuleMigrationRequestQueryInput = z.input<typeof GetRuleMigrationRequestQuery>;
|
||||
|
||||
export type GetRuleMigrationRequestParams = z.infer<typeof GetRuleMigrationRequestParams>;
|
||||
export const GetRuleMigrationRequestParams = z.object({
|
||||
|
@ -52,7 +60,13 @@ export const GetRuleMigrationRequestParams = z.object({
|
|||
export type GetRuleMigrationRequestParamsInput = z.input<typeof GetRuleMigrationRequestParams>;
|
||||
|
||||
export type GetRuleMigrationResponse = z.infer<typeof GetRuleMigrationResponse>;
|
||||
export const GetRuleMigrationResponse = z.array(RuleMigration);
|
||||
export const GetRuleMigrationResponse = z.object({
|
||||
/**
|
||||
* The total number of rules in migration.
|
||||
*/
|
||||
total: z.number(),
|
||||
data: z.array(RuleMigration),
|
||||
});
|
||||
export type GetRuleMigrationResourcesRequestQuery = z.infer<
|
||||
typeof GetRuleMigrationResourcesRequestQuery
|
||||
>;
|
||||
|
@ -88,6 +102,21 @@ export type GetRuleMigrationStatsRequestParamsInput = z.input<
|
|||
export type GetRuleMigrationStatsResponse = z.infer<typeof GetRuleMigrationStatsResponse>;
|
||||
export const GetRuleMigrationStatsResponse = RuleMigrationTaskStats;
|
||||
|
||||
export type GetRuleMigrationTranslationStatsRequestParams = z.infer<
|
||||
typeof GetRuleMigrationTranslationStatsRequestParams
|
||||
>;
|
||||
export const GetRuleMigrationTranslationStatsRequestParams = z.object({
|
||||
migration_id: NonEmptyString,
|
||||
});
|
||||
export type GetRuleMigrationTranslationStatsRequestParamsInput = z.input<
|
||||
typeof GetRuleMigrationTranslationStatsRequestParams
|
||||
>;
|
||||
|
||||
export type GetRuleMigrationTranslationStatsResponse = z.infer<
|
||||
typeof GetRuleMigrationTranslationStatsResponse
|
||||
>;
|
||||
export const GetRuleMigrationTranslationStatsResponse = RuleMigrationTranslationStats;
|
||||
|
||||
export type InstallMigrationRulesRequestParams = z.infer<typeof InstallMigrationRulesRequestParams>;
|
||||
export const InstallMigrationRulesRequestParams = z.object({
|
||||
migration_id: NonEmptyString,
|
||||
|
|
|
@ -185,15 +185,40 @@ paths:
|
|||
schema:
|
||||
description: The migration id to start
|
||||
$ref: '../../common.schema.yaml#/components/schemas/NonEmptyString'
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: number
|
||||
- name: per_page
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: number
|
||||
- name: search_term
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
|
||||
responses:
|
||||
200:
|
||||
description: Indicates rule migration have been retrieved correctly.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '../../rule_migration.schema.yaml#/components/schemas/RuleMigration'
|
||||
type: object
|
||||
required:
|
||||
- total
|
||||
- data
|
||||
properties:
|
||||
total:
|
||||
type: number
|
||||
description: The total number of rules in migration.
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '../../rule_migration.schema.yaml#/components/schemas/RuleMigration'
|
||||
204:
|
||||
description: Indicates the migration id was not found.
|
||||
|
||||
|
@ -256,7 +281,7 @@ paths:
|
|||
in: path
|
||||
required: true
|
||||
schema:
|
||||
description: The migration id to start
|
||||
description: The migration id to fetch stats for
|
||||
$ref: '../../common.schema.yaml#/components/schemas/NonEmptyString'
|
||||
responses:
|
||||
200:
|
||||
|
@ -268,6 +293,31 @@ paths:
|
|||
204:
|
||||
description: Indicates the migration id was not found.
|
||||
|
||||
/internal/siem_migrations/rules/{migration_id}/translation_stats:
|
||||
get:
|
||||
summary: Gets a rule migration translation stats
|
||||
operationId: GetRuleMigrationTranslationStats
|
||||
x-codegen-enabled: true
|
||||
description: Retrieves the translation stats of a SIEM rules migration using the migration id provided
|
||||
tags:
|
||||
- SIEM Rule Migrations
|
||||
parameters:
|
||||
- name: migration_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
description: The migration id to fetch translation stats for
|
||||
$ref: '../../common.schema.yaml#/components/schemas/NonEmptyString'
|
||||
responses:
|
||||
200:
|
||||
description: Indicates the migration stats has been retrieved correctly.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../rule_migration.schema.yaml#/components/schemas/RuleMigrationTranslationStats'
|
||||
204:
|
||||
description: Indicates the migration id was not found.
|
||||
|
||||
/internal/siem_migrations/rules/{migration_id}/stop:
|
||||
put:
|
||||
summary: Stops an existing rule migration
|
||||
|
|
|
@ -242,6 +242,38 @@ export const RuleMigrationTaskStats = z.object({
|
|||
last_updated_at: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* The rule migration translation stats object.
|
||||
*/
|
||||
export type RuleMigrationTranslationStats = z.infer<typeof RuleMigrationTranslationStats>;
|
||||
export const RuleMigrationTranslationStats = z.object({
|
||||
/**
|
||||
* The migration id
|
||||
*/
|
||||
id: NonEmptyString,
|
||||
/**
|
||||
* The rules migration translation stats.
|
||||
*/
|
||||
rules: z.object({
|
||||
/**
|
||||
* The total number of rules to migrate.
|
||||
*/
|
||||
total: z.number().int(),
|
||||
/**
|
||||
* The number of rules that matched Elastic prebuilt rules.
|
||||
*/
|
||||
prebuilt: z.number().int(),
|
||||
/**
|
||||
* The number of rules that did not match Elastic prebuilt rules and will be installed as custom rules.
|
||||
*/
|
||||
custom: z.number().int(),
|
||||
/**
|
||||
* The number of rules that can be installed.
|
||||
*/
|
||||
installable: z.number().int(),
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* The type of the rule migration resource.
|
||||
*/
|
||||
|
|
|
@ -197,7 +197,39 @@ components:
|
|||
- running
|
||||
- stopped
|
||||
- finished
|
||||
|
||||
|
||||
RuleMigrationTranslationStats:
|
||||
type: object
|
||||
description: The rule migration translation stats object.
|
||||
required:
|
||||
- id
|
||||
- rules
|
||||
properties:
|
||||
id:
|
||||
description: The migration id
|
||||
$ref: './common.schema.yaml#/components/schemas/NonEmptyString'
|
||||
rules:
|
||||
type: object
|
||||
description: The rules migration translation stats.
|
||||
required:
|
||||
- total
|
||||
- prebuilt
|
||||
- custom
|
||||
- installable
|
||||
properties:
|
||||
total:
|
||||
type: integer
|
||||
description: The total number of rules to migrate.
|
||||
prebuilt:
|
||||
type: integer
|
||||
description: The number of rules that matched Elastic prebuilt rules.
|
||||
custom:
|
||||
type: integer
|
||||
description: The number of rules that did not match Elastic prebuilt rules and will be installed as custom rules.
|
||||
installable:
|
||||
type: integer
|
||||
description: The number of rules that can be installed.
|
||||
|
||||
RuleMigrationTranslationResult:
|
||||
type: string
|
||||
description: The rule translation result.
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { SVGProps } from 'react';
|
||||
import React from 'react';
|
||||
export const SiemMigrationsIcon: React.FC<SVGProps<SVGSVGElement>> = ({ ...props }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={16}
|
||||
height={16}
|
||||
fill="none"
|
||||
viewBox="0 0 32 32"
|
||||
{...props}
|
||||
>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_2763_341531)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 12C15.4292 12 19.8479 16.3267 19.9962 21.7201L20 22V23H11V32H10C4.47715 32 0 27.5228 0 22C0 16.4772 4.47715 12 10 12ZM9 21L9.00005 14.0619C5.05371 14.554 2 17.9204 2 22C2 25.9928 4.92513 29.3024 8.74934 29.9028L9 29.9381V21ZM11 21L11.0009 14.062L11.258 14.0983C14.7544 14.6506 17.4976 17.4677 17.9381 21H11Z"
|
||||
fill="#343741"
|
||||
/>
|
||||
<path d="M26 22C26 13.1634 18.8366 6 10 6V8C17.732 8 24 14.268 24 22H26Z" fill="#007871" />
|
||||
<path
|
||||
d="M32 22C32 9.84974 22.1503 0 10 0V2C21.0457 2 30 10.9543 30 22H32Z"
|
||||
fill="#007871"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2763_341531">
|
||||
<rect width="32" height="32" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</svg>
|
||||
);
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from '../../common/constants';
|
||||
import { SIEM_MIGRATIONS_RULES } from '../app/translations';
|
||||
import type { LinkItem } from '../common/links/types';
|
||||
import { IconConsoleCloud } from '../common/icons/console_cloud';
|
||||
import { SiemMigrationsIcon } from '../common/icons/siem_migrations';
|
||||
|
||||
export const siemMigrationsLinks: LinkItem = {
|
||||
id: SecurityPageName.siemMigrationsRules,
|
||||
|
@ -21,7 +21,7 @@ export const siemMigrationsLinks: LinkItem = {
|
|||
description: i18n.translate('xpack.securitySolution.appLinks.siemMigrationsRulesDescription', {
|
||||
defaultMessage: 'SIEM Rules Migrations.',
|
||||
}),
|
||||
landingIcon: IconConsoleCloud,
|
||||
landingIcon: SiemMigrationsIcon,
|
||||
path: SIEM_MIGRATIONS_RULES_PATH,
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
skipUrlState: true,
|
||||
|
|
|
@ -15,10 +15,12 @@ import {
|
|||
SIEM_RULE_MIGRATION_INSTALL_PATH,
|
||||
SIEM_RULE_MIGRATION_PATH,
|
||||
SIEM_RULE_MIGRATION_START_PATH,
|
||||
SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH,
|
||||
} from '../../../../common/siem_migrations/constants';
|
||||
import type {
|
||||
GetAllStatsRuleMigrationResponse,
|
||||
GetRuleMigrationResponse,
|
||||
GetRuleMigrationTranslationStatsResponse,
|
||||
InstallTranslatedMigrationRulesResponse,
|
||||
InstallMigrationRulesResponse,
|
||||
StartRuleMigrationRequestBody,
|
||||
|
@ -67,6 +69,31 @@ export const startRuleMigration = async ({
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the translation stats for the migraion.
|
||||
*
|
||||
* @param migrationId `id` of the migration to retrieve translation stats for
|
||||
* @param signal AbortSignal for cancelling request
|
||||
*
|
||||
* @throws An error if response is not OK
|
||||
*/
|
||||
export const getRuleMigrationTranslationStats = async ({
|
||||
migrationId,
|
||||
signal,
|
||||
}: {
|
||||
migrationId: string;
|
||||
signal: AbortSignal | undefined;
|
||||
}): Promise<GetRuleMigrationTranslationStatsResponse> => {
|
||||
return KibanaServices.get().http.fetch<GetRuleMigrationTranslationStatsResponse>(
|
||||
replaceParams(SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH, { migration_id: migrationId }),
|
||||
{
|
||||
method: 'GET',
|
||||
version: '1',
|
||||
signal,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves all the migration rule documents of a specific migration.
|
||||
*
|
||||
|
@ -77,14 +104,29 @@ export const startRuleMigration = async ({
|
|||
*/
|
||||
export const getRuleMigrations = async ({
|
||||
migrationId,
|
||||
page,
|
||||
perPage,
|
||||
searchTerm,
|
||||
signal,
|
||||
}: {
|
||||
migrationId: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
searchTerm?: string;
|
||||
signal: AbortSignal | undefined;
|
||||
}): Promise<GetRuleMigrationResponse> => {
|
||||
return KibanaServices.get().http.fetch<GetRuleMigrationResponse>(
|
||||
replaceParams(SIEM_RULE_MIGRATION_PATH, { migration_id: migrationId }),
|
||||
{ method: 'GET', version: '1', signal }
|
||||
{
|
||||
method: 'GET',
|
||||
version: '1',
|
||||
query: {
|
||||
page,
|
||||
per_page: perPage,
|
||||
search_term: searchTerm,
|
||||
},
|
||||
signal,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -9,26 +9,46 @@ import type { UseQueryOptions } from '@tanstack/react-query';
|
|||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { replaceParams } from '@kbn/openapi-common/shared';
|
||||
import { useCallback } from 'react';
|
||||
import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import { DEFAULT_QUERY_OPTIONS } from './constants';
|
||||
import { getRuleMigrations } from '../api';
|
||||
import type { GetRuleMigrationResponse } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { SIEM_RULE_MIGRATION_PATH } from '../../../../../common/siem_migrations/constants';
|
||||
|
||||
interface UseGetMigrationRulesQueryProps {
|
||||
migrationId: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
searchTerm?: string;
|
||||
}
|
||||
|
||||
export interface MigrationRulesQueryResponse {
|
||||
ruleMigrations: RuleMigration[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export const useGetMigrationRulesQuery = (
|
||||
migrationId: string,
|
||||
options?: UseQueryOptions<GetRuleMigrationResponse>
|
||||
queryArgs: UseGetMigrationRulesQueryProps,
|
||||
queryOptions?: UseQueryOptions<
|
||||
MigrationRulesQueryResponse,
|
||||
Error,
|
||||
MigrationRulesQueryResponse,
|
||||
[...string[], UseGetMigrationRulesQueryProps]
|
||||
>
|
||||
) => {
|
||||
const { migrationId } = queryArgs;
|
||||
const SPECIFIC_MIGRATION_PATH = replaceParams(SIEM_RULE_MIGRATION_PATH, {
|
||||
migration_id: migrationId,
|
||||
});
|
||||
return useQuery<GetRuleMigrationResponse>(
|
||||
['GET', SPECIFIC_MIGRATION_PATH],
|
||||
return useQuery(
|
||||
['GET', SPECIFIC_MIGRATION_PATH, queryArgs],
|
||||
async ({ signal }) => {
|
||||
return getRuleMigrations({ migrationId, signal });
|
||||
const response = await getRuleMigrations({ signal, ...queryArgs });
|
||||
|
||||
return { ruleMigrations: response.data, total: response.total };
|
||||
},
|
||||
{
|
||||
...DEFAULT_QUERY_OPTIONS,
|
||||
...options,
|
||||
...queryOptions,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -47,6 +67,10 @@ export const useInvalidateGetMigrationRulesQuery = (migrationId: string) => {
|
|||
});
|
||||
|
||||
return useCallback(() => {
|
||||
/**
|
||||
* Invalidate all queries that start with SPECIFIC_MIGRATION_PATH. This
|
||||
* includes the in-memory query cache and paged query cache.
|
||||
*/
|
||||
queryClient.invalidateQueries(['GET', SPECIFIC_MIGRATION_PATH], {
|
||||
refetchType: 'active',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { replaceParams } from '@kbn/openapi-common/shared';
|
||||
import { useCallback } from 'react';
|
||||
import { DEFAULT_QUERY_OPTIONS } from './constants';
|
||||
import { getRuleMigrationTranslationStats } from '../api';
|
||||
import type { GetRuleMigrationTranslationStatsResponse } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH } from '../../../../../common/siem_migrations/constants';
|
||||
|
||||
export const useGetMigrationTranslationStatsQuery = (
|
||||
migrationId: string,
|
||||
options?: UseQueryOptions<GetRuleMigrationTranslationStatsResponse>
|
||||
) => {
|
||||
const SPECIFIC_MIGRATION_TRANSLATION_PATH = replaceParams(
|
||||
SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH,
|
||||
{
|
||||
migration_id: migrationId,
|
||||
}
|
||||
);
|
||||
return useQuery<GetRuleMigrationTranslationStatsResponse>(
|
||||
['GET', SPECIFIC_MIGRATION_TRANSLATION_PATH],
|
||||
async ({ signal }) => {
|
||||
return getRuleMigrationTranslationStats({ migrationId, signal });
|
||||
},
|
||||
{
|
||||
...DEFAULT_QUERY_OPTIONS,
|
||||
...options,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* We should use this hook to invalidate the translation stats cache. For
|
||||
* example, rule migrations mutations, like installing a rule, should lead to cache invalidation.
|
||||
*
|
||||
* @returns A translation stats cache invalidation callback
|
||||
*/
|
||||
export const useInvalidateGetMigrationTranslationStatsQuery = (migrationId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const SPECIFIC_MIGRATION_TRANSLATION_PATH = replaceParams(
|
||||
SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH,
|
||||
{
|
||||
migration_id: migrationId,
|
||||
}
|
||||
);
|
||||
|
||||
return useCallback(() => {
|
||||
queryClient.invalidateQueries(['GET', SPECIFIC_MIGRATION_TRANSLATION_PATH], {
|
||||
refetchType: 'active',
|
||||
});
|
||||
}, [SPECIFIC_MIGRATION_TRANSLATION_PATH, queryClient]);
|
||||
};
|
|
@ -10,6 +10,7 @@ import type { InstallMigrationRulesResponse } from '../../../../../common/siem_m
|
|||
import { SIEM_RULE_MIGRATION_INSTALL_PATH } from '../../../../../common/siem_migrations/constants';
|
||||
import { installMigrationRules } from '../api';
|
||||
import { useInvalidateGetMigrationRulesQuery } from './use_get_migration_rules_query';
|
||||
import { useInvalidateGetMigrationTranslationStatsQuery } from './use_get_migration_translation_stats_query';
|
||||
|
||||
export const INSTALL_MIGRATION_RULES_MUTATION_KEY = ['POST', SIEM_RULE_MIGRATION_INSTALL_PATH];
|
||||
|
||||
|
@ -18,6 +19,8 @@ export const useInstallMigrationRulesMutation = (
|
|||
options?: UseMutationOptions<InstallMigrationRulesResponse, Error, string[]>
|
||||
) => {
|
||||
const invalidateGetRuleMigrationsQuery = useInvalidateGetMigrationRulesQuery(migrationId);
|
||||
const invalidateGetMigrationTranslationStatsQuery =
|
||||
useInvalidateGetMigrationTranslationStatsQuery(migrationId);
|
||||
|
||||
return useMutation<InstallMigrationRulesResponse, Error, string[]>(
|
||||
(ids: string[]) => installMigrationRules({ migrationId, ids }),
|
||||
|
@ -26,6 +29,7 @@ export const useInstallMigrationRulesMutation = (
|
|||
mutationKey: INSTALL_MIGRATION_RULES_MUTATION_KEY,
|
||||
onSettled: (...args) => {
|
||||
invalidateGetRuleMigrationsQuery();
|
||||
invalidateGetMigrationTranslationStatsQuery();
|
||||
|
||||
if (options?.onSettled) {
|
||||
options.onSettled(...args);
|
||||
|
|
|
@ -10,17 +10,20 @@ import type { InstallTranslatedMigrationRulesResponse } from '../../../../../com
|
|||
import { SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH } from '../../../../../common/siem_migrations/constants';
|
||||
import { installTranslatedMigrationRules } from '../api';
|
||||
import { useInvalidateGetMigrationRulesQuery } from './use_get_migration_rules_query';
|
||||
import { useInvalidateGetMigrationTranslationStatsQuery } from './use_get_migration_translation_stats_query';
|
||||
|
||||
export const INSTALL_ALL_MIGRATION_RULES_MUTATION_KEY = [
|
||||
'POST',
|
||||
SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH,
|
||||
];
|
||||
|
||||
export const useInstallAllMigrationRulesMutation = (
|
||||
export const useInstallTranslatedMigrationRulesMutation = (
|
||||
migrationId: string,
|
||||
options?: UseMutationOptions<InstallTranslatedMigrationRulesResponse, Error>
|
||||
) => {
|
||||
const invalidateGetRuleMigrationsQuery = useInvalidateGetMigrationRulesQuery(migrationId);
|
||||
const invalidateGetMigrationTranslationStatsQuery =
|
||||
useInvalidateGetMigrationTranslationStatsQuery(migrationId);
|
||||
|
||||
return useMutation<InstallTranslatedMigrationRulesResponse, Error>(
|
||||
() => installTranslatedMigrationRules({ migrationId }),
|
||||
|
@ -29,6 +32,7 @@ export const useInstallAllMigrationRulesMutation = (
|
|||
mutationKey: INSTALL_ALL_MIGRATION_RULES_MUTATION_KEY,
|
||||
onSettled: (...args) => {
|
||||
invalidateGetRuleMigrationsQuery();
|
||||
invalidateGetMigrationTranslationStatsQuery();
|
||||
|
||||
if (options?.onSettled) {
|
||||
options.onSettled(...args);
|
|
@ -28,9 +28,9 @@ export const BulkActions: React.FC<BulkActionsProps> = React.memo(
|
|||
installTranslatedRule,
|
||||
installSelectedRule,
|
||||
}) => {
|
||||
const showInstallTranslatedRulesButton = numberOfTranslatedRules > 0;
|
||||
const disableInstallTranslatedRulesButton = isTableLoading || !numberOfTranslatedRules;
|
||||
const showInstallSelectedRulesButton =
|
||||
showInstallTranslatedRulesButton && numberOfSelectedRules > 0;
|
||||
disableInstallTranslatedRulesButton && numberOfSelectedRules > 0;
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}>
|
||||
{showInstallSelectedRulesButton ? (
|
||||
|
@ -46,21 +46,21 @@ export const BulkActions: React.FC<BulkActionsProps> = React.memo(
|
|||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
{showInstallTranslatedRulesButton ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
data-test-subj="installTranslatedRulesButton"
|
||||
onClick={installTranslatedRule}
|
||||
disabled={isTableLoading}
|
||||
aria-label={i18n.INSTALL_ALL_ARIA_LABEL}
|
||||
>
|
||||
{i18n.INSTALL_ALL_RULES(numberOfTranslatedRules)}
|
||||
{isTableLoading && <EuiLoadingSpinner size="s" />}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
data-test-subj="installTranslatedRulesButton"
|
||||
onClick={installTranslatedRule}
|
||||
disabled={disableInstallTranslatedRulesButton}
|
||||
aria-label={i18n.INSTALL_TRANSLATED_ARIA_LABEL}
|
||||
>
|
||||
{numberOfTranslatedRules > 0
|
||||
? i18n.INSTALL_TRANSLATED_RULES(numberOfTranslatedRules)
|
||||
: i18n.INSTALL_TRANSLATED_RULES_EMPTY_STATE}
|
||||
{isTableLoading && <EuiLoadingSpinner size="s" />}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,54 +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 { EuiFlexGroup } from '@elastic/eui';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import * as i18n from './translations';
|
||||
import { RuleSearchField } from '../../../../detection_engine/rule_management_ui/components/rules_table/rules_table_filters/rule_search_field';
|
||||
import type { TableFilterOptions } from '../../hooks/use_filter_rules_to_install';
|
||||
|
||||
export interface FiltersComponentProps {
|
||||
/**
|
||||
* Currently selected table filter
|
||||
*/
|
||||
filterOptions: TableFilterOptions;
|
||||
|
||||
/**
|
||||
* Handles filter options changes
|
||||
*/
|
||||
setFilterOptions: Dispatch<SetStateAction<TableFilterOptions>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection of filters for filtering data within the SIEM Rules Migrations table.
|
||||
* Contains search bar and tag selection
|
||||
*/
|
||||
const FiltersComponent: React.FC<FiltersComponentProps> = ({ filterOptions, setFilterOptions }) => {
|
||||
const handleOnSearch = useCallback(
|
||||
(filterString: string) => {
|
||||
setFilterOptions((filters) => ({
|
||||
...filters,
|
||||
filter: filterString.trim(),
|
||||
}));
|
||||
},
|
||||
[setFilterOptions]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="m" justifyContent="flexEnd" wrap>
|
||||
<RuleSearchField
|
||||
initialValue={filterOptions.filter}
|
||||
onSearch={handleOnSearch}
|
||||
placeholder={i18n.SEARCH_PLACEHOLDER}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const Filters = React.memo(FiltersComponent);
|
||||
Filters.displayName = 'Filters';
|
|
@ -5,33 +5,30 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { CriteriaWithPagination } from '@elastic/eui';
|
||||
import {
|
||||
EuiInMemoryTable,
|
||||
EuiSkeletonLoading,
|
||||
EuiProgress,
|
||||
EuiSkeletonTitle,
|
||||
EuiSkeletonText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiBasicTable,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import {
|
||||
RULES_TABLE_INITIAL_PAGE_SIZE,
|
||||
RULES_TABLE_PAGE_SIZE_OPTIONS,
|
||||
} from '../../../../detection_engine/rule_management_ui/components/rules_table/constants';
|
||||
import { NoItemsMessage } from './no_items_message';
|
||||
import { Filters } from './filters';
|
||||
import { useRulesTableColumns } from '../../hooks/use_rules_table_columns';
|
||||
import type { TableFilterOptions } from '../../hooks/use_filter_rules_to_install';
|
||||
import { useFilterRulesToInstall } from '../../hooks/use_filter_rules_to_install';
|
||||
import { useRulePreviewFlyout } from '../../hooks/use_rule_preview_flyout';
|
||||
import { useInstallMigrationRules } from '../../logic/use_install_migration_rules';
|
||||
import { useGetMigrationRules } from '../../logic/use_get_migration_rules';
|
||||
import { useInstallAllMigrationRules } from '../../logic/use_install_all_migration_rules';
|
||||
import { useInstallTranslatedMigrationRules } from '../../logic/use_install_translated_migration_rules';
|
||||
import { BulkActions } from './bulk_actions';
|
||||
import { useGetMigrationTranslationStats } from '../../logic/use_get_migration_translation_stats';
|
||||
import { SearchField } from './search_field';
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
export interface RulesTableComponentProps {
|
||||
/**
|
||||
|
@ -44,29 +41,47 @@ export interface RulesTableComponentProps {
|
|||
* Table Component for displaying SIEM rules migrations
|
||||
*/
|
||||
const RulesTableComponent: React.FC<RulesTableComponentProps> = ({ migrationId }) => {
|
||||
const { data: ruleMigrations, isLoading: isDataLoading } = useGetMigrationRules(migrationId);
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||
const [searchTerm, setSearchTerm] = useState<string | undefined>();
|
||||
|
||||
const { data: translationStats, isLoading: isStatsLoading } =
|
||||
useGetMigrationTranslationStats(migrationId);
|
||||
|
||||
const {
|
||||
data: { ruleMigrations, total } = { ruleMigrations: [], total: 0 },
|
||||
isLoading: isDataLoading,
|
||||
} = useGetMigrationRules({
|
||||
migrationId,
|
||||
page: pageIndex,
|
||||
perPage: pageSize,
|
||||
searchTerm,
|
||||
});
|
||||
|
||||
const [selectedRuleMigrations, setSelectedRuleMigrations] = useState<RuleMigration[]>([]);
|
||||
|
||||
const [filterOptions, setFilterOptions] = useState<TableFilterOptions>({
|
||||
filter: '',
|
||||
});
|
||||
const pagination = useMemo(() => {
|
||||
return {
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalItemCount: total,
|
||||
};
|
||||
}, [pageIndex, pageSize, total]);
|
||||
|
||||
const filteredRuleMigrations = useFilterRulesToInstall({
|
||||
filterOptions,
|
||||
ruleMigrations: ruleMigrations ?? [],
|
||||
});
|
||||
const onTableChange = useCallback(({ page, sort }: CriteriaWithPagination<RuleMigration>) => {
|
||||
if (page) {
|
||||
setPageIndex(page.index);
|
||||
setPageSize(page.size);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleOnSearch = useCallback((value: string) => {
|
||||
setSearchTerm(value.trim());
|
||||
}, []);
|
||||
|
||||
const { mutateAsync: installMigrationRules } = useInstallMigrationRules(migrationId);
|
||||
const { mutateAsync: installAllMigrationRules } = useInstallAllMigrationRules(migrationId);
|
||||
|
||||
const numberOfTranslatedRules = useMemo(() => {
|
||||
return filteredRuleMigrations.filter(
|
||||
(rule) =>
|
||||
!rule.elastic_rule?.id &&
|
||||
(rule.elastic_rule?.prebuilt_rule_id || rule.translation_result === 'full')
|
||||
).length;
|
||||
}, [filteredRuleMigrations]);
|
||||
const { mutateAsync: installTranslatedMigrationRules } =
|
||||
useInstallTranslatedMigrationRules(migrationId);
|
||||
|
||||
const [isTableLoading, setTableLoading] = useState(false);
|
||||
const installSingleRule = useCallback(
|
||||
|
@ -85,12 +100,12 @@ const RulesTableComponent: React.FC<RulesTableComponentProps> = ({ migrationId }
|
|||
async (enable?: boolean) => {
|
||||
setTableLoading(true);
|
||||
try {
|
||||
await installAllMigrationRules();
|
||||
await installTranslatedMigrationRules();
|
||||
} finally {
|
||||
setTableLoading(false);
|
||||
}
|
||||
},
|
||||
[installAllMigrationRules]
|
||||
[installTranslatedMigrationRules]
|
||||
);
|
||||
|
||||
const ruleActionsFactory = useCallback(
|
||||
|
@ -105,8 +120,6 @@ const RulesTableComponent: React.FC<RulesTableComponentProps> = ({ migrationId }
|
|||
ruleActionsFactory,
|
||||
});
|
||||
|
||||
const shouldShowProgress = isDataLoading;
|
||||
|
||||
const rulesColumns = useRulesTableColumns({
|
||||
disableActions: isTableLoading,
|
||||
openMigrationRulePreview: openRulePreview,
|
||||
|
@ -115,14 +128,6 @@ const RulesTableComponent: React.FC<RulesTableComponentProps> = ({ migrationId }
|
|||
|
||||
return (
|
||||
<>
|
||||
{shouldShowProgress && (
|
||||
<EuiProgress
|
||||
data-test-subj="loadingRulesInfoProgress"
|
||||
size="xs"
|
||||
position="absolute"
|
||||
color="accent"
|
||||
/>
|
||||
)}
|
||||
<EuiSkeletonLoading
|
||||
isLoading={isDataLoading}
|
||||
loadingContent={
|
||||
|
@ -132,39 +137,36 @@ const RulesTableComponent: React.FC<RulesTableComponentProps> = ({ migrationId }
|
|||
</>
|
||||
}
|
||||
loadedContent={
|
||||
!filteredRuleMigrations.length ? (
|
||||
!translationStats?.rules.total ? (
|
||||
<NoItemsMessage />
|
||||
) : (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="m" justifyContent="flexEnd" wrap>
|
||||
<EuiFlexItem>
|
||||
<Filters filterOptions={filterOptions} setFilterOptions={setFilterOptions} />
|
||||
<SearchField initialValue={searchTerm} onSearch={handleOnSearch} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<BulkActions
|
||||
isTableLoading={isDataLoading || isTableLoading}
|
||||
numberOfTranslatedRules={numberOfTranslatedRules}
|
||||
isTableLoading={isStatsLoading || isDataLoading || isTableLoading}
|
||||
numberOfTranslatedRules={translationStats?.rules.installable ?? 0}
|
||||
numberOfSelectedRules={0}
|
||||
installTranslatedRule={installTranslatedRules}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiInMemoryTable
|
||||
<EuiBasicTable<RuleMigration>
|
||||
loading={isTableLoading}
|
||||
items={filteredRuleMigrations}
|
||||
sorting
|
||||
pagination={{
|
||||
initialPageSize: RULES_TABLE_INITIAL_PAGE_SIZE,
|
||||
pageSizeOptions: RULES_TABLE_PAGE_SIZE_OPTIONS,
|
||||
}}
|
||||
items={ruleMigrations}
|
||||
pagination={pagination}
|
||||
onChange={onTableChange}
|
||||
selection={{
|
||||
selectable: () => true,
|
||||
onSelectionChange: setSelectedRuleMigrations,
|
||||
initialSelected: selectedRuleMigrations,
|
||||
}}
|
||||
itemId="rule_id"
|
||||
data-test-subj="rules-translation-table"
|
||||
itemId={'id'}
|
||||
data-test-subj={'rules-translation-table'}
|
||||
columns={rulesColumns}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ChangeEvent } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiFieldSearch, EuiFlexItem } from '@elastic/eui';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const SearchBarWrapper = styled(EuiFlexItem)`
|
||||
min-width: 200px;
|
||||
& .euiPopover {
|
||||
// This is needed to "cancel" styles passed down from EuiTourStep that
|
||||
// interfere with EuiFieldSearch and don't allow it to take the full width
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
interface SearchFieldProps {
|
||||
initialValue?: string;
|
||||
onSearch: (value: string) => void;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export const SearchField: React.FC<SearchFieldProps> = React.memo(
|
||||
({ initialValue, onSearch, placeholder }) => {
|
||||
const [searchText, setSearchText] = useState(initialValue);
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => setSearchText(e.target.value),
|
||||
[setSearchText]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchText(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
return (
|
||||
<SearchBarWrapper grow>
|
||||
<EuiFieldSearch
|
||||
aria-label={i18n.SEARCH_MIGRATION_RULES}
|
||||
fullWidth
|
||||
incremental={false}
|
||||
placeholder={placeholder ?? i18n.SEARCH_MIGRATION_RULES_PLACEHOLDER}
|
||||
value={searchText}
|
||||
onChange={handleChange}
|
||||
onSearch={onSearch}
|
||||
data-test-subj="ruleSearchField"
|
||||
/>
|
||||
</SearchBarWrapper>
|
||||
);
|
||||
}
|
||||
);
|
||||
SearchField.displayName = 'SearchField';
|
|
@ -7,10 +7,17 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const SEARCH_PLACEHOLDER = i18n.translate(
|
||||
export const SEARCH_MIGRATION_RULES = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.table.searchAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Search migration rules',
|
||||
}
|
||||
);
|
||||
|
||||
export const SEARCH_MIGRATION_RULES_PLACEHOLDER = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.table.searchBarPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Search by rule name',
|
||||
defaultMessage: 'Search by migration rule name',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -42,12 +49,22 @@ export const INSTALL_SELECTED_RULES = (numberOfSelectedRules: number) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const INSTALL_ALL_RULES = (numberOfAllRules: number) => {
|
||||
return i18n.translate('xpack.securitySolution.siemMigrations.rules.table.installAllRules', {
|
||||
defaultMessage:
|
||||
'Install translated {numberOfAllRules, plural, one {rule} other {rules}} ({numberOfAllRules})',
|
||||
values: { numberOfAllRules },
|
||||
});
|
||||
export const INSTALL_TRANSLATED_RULES_EMPTY_STATE = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.table.installTranslatedRulesEmptyState',
|
||||
{
|
||||
defaultMessage: 'Install translated rules',
|
||||
}
|
||||
);
|
||||
|
||||
export const INSTALL_TRANSLATED_RULES = (numberOfAllRules: number) => {
|
||||
return i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.table.installTranslatedRules',
|
||||
{
|
||||
defaultMessage:
|
||||
'Install translated {numberOfAllRules, plural, one {rule} other {rules}} ({numberOfAllRules})',
|
||||
values: { numberOfAllRules },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const INSTALL_SELECTED_ARIA_LABEL = i18n.translate(
|
||||
|
@ -57,8 +74,8 @@ export const INSTALL_SELECTED_ARIA_LABEL = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const INSTALL_ALL_ARIA_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.table.installAllButtonAriaLabel',
|
||||
export const INSTALL_TRANSLATED_ARIA_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.table.installTranslatedButtonAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Install all translated rules',
|
||||
}
|
||||
|
|
|
@ -12,3 +12,4 @@ export * from './name';
|
|||
export * from './risk_score';
|
||||
export * from './severity';
|
||||
export * from './status';
|
||||
export * from './updated';
|
||||
|
|
|
@ -69,3 +69,10 @@ export const COLUMN_SEVERITY = i18n.translate(
|
|||
defaultMessage: 'Severity',
|
||||
}
|
||||
);
|
||||
|
||||
export const COLUMN_UPDATED = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.tableColumn.updatedLabel',
|
||||
{
|
||||
defaultMessage: 'Updated',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date';
|
||||
import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import * as i18n from './translations';
|
||||
import type { TableColumn } from './constants';
|
||||
|
||||
export const createUpdatedColumn = (): TableColumn => {
|
||||
return {
|
||||
field: 'updated_at',
|
||||
name: i18n.COLUMN_UPDATED,
|
||||
render: (value: RuleMigration['updated_at']) => (
|
||||
<FormattedRelativePreferenceDate value={value} dateFormat="M/D/YY" />
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: false,
|
||||
align: 'center',
|
||||
width: '10%',
|
||||
};
|
||||
};
|
|
@ -30,7 +30,7 @@ const StatusBadgeComponent: React.FC<Props> = ({
|
|||
installedRuleId,
|
||||
'data-test-subj': dataTestSubj = 'translation-result',
|
||||
}) => {
|
||||
const translationResult = installedRuleId || !value ? 'full' : value;
|
||||
const translationResult = installedRuleId ? 'full' : value ?? 'untranslatable';
|
||||
const displayValue = convertTranslationResultIntoText(translationResult);
|
||||
const color = statusToColorMap[translationResult];
|
||||
|
||||
|
|
|
@ -24,7 +24,10 @@ import type { RuleMigration } from '../../../../../../common/siem_migrations/mod
|
|||
import { TranslationTabHeader } from './header';
|
||||
import { RuleQueryComponent } from './rule_query';
|
||||
import * as i18n from './translations';
|
||||
import { convertTranslationResultIntoText } from '../../../utils/helpers';
|
||||
import {
|
||||
convertTranslationResultIntoColor,
|
||||
convertTranslationResultIntoText,
|
||||
} from '../../../utils/helpers';
|
||||
|
||||
interface TranslationTabProps {
|
||||
ruleMigration: RuleMigration;
|
||||
|
@ -66,9 +69,7 @@ export const TranslationTab = ({ ruleMigration }: TranslationTabProps) => {
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
color="primary"
|
||||
color={convertTranslationResultIntoColor(ruleMigration.translation_result)}
|
||||
onClick={() => {}}
|
||||
onClickAriaLabel={'Click to update translation status'}
|
||||
>
|
||||
|
|
|
@ -1,33 +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 { useMemo } from 'react';
|
||||
import type { RuleMigration } from '../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import type { FilterOptions } from '../../../detection_engine/rule_management/logic/types';
|
||||
|
||||
export type TableFilterOptions = Pick<FilterOptions, 'filter'>;
|
||||
|
||||
export const useFilterRulesToInstall = ({
|
||||
ruleMigrations,
|
||||
filterOptions,
|
||||
}: {
|
||||
ruleMigrations: RuleMigration[];
|
||||
filterOptions: TableFilterOptions;
|
||||
}) => {
|
||||
const filteredRules = useMemo(() => {
|
||||
const { filter } = filterOptions;
|
||||
return ruleMigrations.filter((migration) => {
|
||||
const name = migration.elastic_rule?.title ?? migration.original_rule.title;
|
||||
if (filter && !name.toLowerCase().includes(filter.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, [filterOptions, ruleMigrations]);
|
||||
|
||||
return filteredRules;
|
||||
};
|
|
@ -14,6 +14,7 @@ import {
|
|||
createRiskScoreColumn,
|
||||
createSeverityColumn,
|
||||
createStatusColumn,
|
||||
createUpdatedColumn,
|
||||
} from '../components/rules_table_columns';
|
||||
|
||||
export const useRulesTableColumns = ({
|
||||
|
@ -27,6 +28,7 @@ export const useRulesTableColumns = ({
|
|||
}): TableColumn[] => {
|
||||
return useMemo(
|
||||
() => [
|
||||
createUpdatedColumn(),
|
||||
createNameColumn({ openMigrationRulePreview }),
|
||||
createStatusColumn(),
|
||||
createRiskScoreColumn(),
|
||||
|
|
|
@ -14,6 +14,13 @@ export const GET_MIGRATION_RULES_FAILURE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const GET_MIGRATION_TRANSLATION_STATS_FAILURE = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.getMigrationTranslationStatsFailDescription',
|
||||
{
|
||||
defaultMessage: 'Failed to fetch migration translation stats',
|
||||
}
|
||||
);
|
||||
|
||||
export const INSTALL_MIGRATION_RULES_FAILURE = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.installMigrationRulesFailDescription',
|
||||
{
|
||||
|
|
|
@ -9,10 +9,15 @@ import { useAppToasts } from '../../../common/hooks/use_app_toasts';
|
|||
import { useGetMigrationRulesQuery } from '../api/hooks/use_get_migration_rules_query';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export const useGetMigrationRules = (migrationId: string) => {
|
||||
export const useGetMigrationRules = (params: {
|
||||
migrationId: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
searchTerm?: string;
|
||||
}) => {
|
||||
const { addError } = useAppToasts();
|
||||
|
||||
return useGetMigrationRulesQuery(migrationId, {
|
||||
return useGetMigrationRulesQuery(params, {
|
||||
onError: (error) => {
|
||||
addError(error, { title: i18n.GET_MIGRATION_RULES_FAILURE });
|
||||
},
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { useAppToasts } from '../../../common/hooks/use_app_toasts';
|
||||
import { useGetMigrationTranslationStatsQuery } from '../api/hooks/use_get_migration_translation_stats_query';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export const useGetMigrationTranslationStats = (migrationId: string) => {
|
||||
const { addError } = useAppToasts();
|
||||
|
||||
return useGetMigrationTranslationStatsQuery(migrationId, {
|
||||
onError: (error) => {
|
||||
addError(error, { title: i18n.GET_MIGRATION_TRANSLATION_STATS_FAILURE });
|
||||
},
|
||||
});
|
||||
};
|
|
@ -6,13 +6,13 @@
|
|||
*/
|
||||
|
||||
import { useAppToasts } from '../../../common/hooks/use_app_toasts';
|
||||
import { useInstallAllMigrationRulesMutation } from '../api/hooks/use_install_all_migration_rules_mutation';
|
||||
import { useInstallTranslatedMigrationRulesMutation } from '../api/hooks/use_install_translated_migration_rules_mutation';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export const useInstallAllMigrationRules = (migrationId: string) => {
|
||||
export const useInstallTranslatedMigrationRules = (migrationId: string) => {
|
||||
const { addError } = useAppToasts();
|
||||
|
||||
return useInstallAllMigrationRulesMutation(migrationId, {
|
||||
return useInstallTranslatedMigrationRulesMutation(migrationId, {
|
||||
onError: (error) => {
|
||||
addError(error, { title: i18n.INSTALL_MIGRATION_RULES_FAILURE });
|
||||
},
|
|
@ -11,6 +11,22 @@ import {
|
|||
} from '../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export const convertTranslationResultIntoColor = (status?: RuleMigrationTranslationResult) => {
|
||||
switch (status) {
|
||||
case RuleMigrationTranslationResultEnum.full:
|
||||
return 'primary';
|
||||
|
||||
case RuleMigrationTranslationResultEnum.partial:
|
||||
return 'warning';
|
||||
|
||||
case RuleMigrationTranslationResultEnum.untranslatable:
|
||||
return 'danger';
|
||||
|
||||
default:
|
||||
throw new Error(i18n.SIEM_TRANSLATION_RESULT_UNKNOWN_ERROR(status));
|
||||
}
|
||||
};
|
||||
|
||||
export const convertTranslationResultIntoText = (status?: RuleMigrationTranslationResult) => {
|
||||
switch (status) {
|
||||
case RuleMigrationTranslationResultEnum.full:
|
||||
|
@ -23,6 +39,6 @@ export const convertTranslationResultIntoText = (status?: RuleMigrationTranslati
|
|||
return i18n.SIEM_TRANSLATION_RESULT_UNTRANSLATABLE_LABEL;
|
||||
|
||||
default:
|
||||
return i18n.SIEM_TRANSLATION_RESULT_UNKNOWN_LABEL;
|
||||
throw new Error(i18n.SIEM_TRANSLATION_RESULT_UNKNOWN_ERROR(status));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -34,3 +34,13 @@ export const SIEM_TRANSLATION_RESULT_UNKNOWN_LABEL = i18n.translate(
|
|||
defaultMessage: 'Unknown',
|
||||
}
|
||||
);
|
||||
|
||||
export const SIEM_TRANSLATION_RESULT_UNKNOWN_ERROR = (status?: string) => {
|
||||
return i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.translationResult.unknownError',
|
||||
{
|
||||
defaultMessage: 'Unknown translation result status: ({status})',
|
||||
values: { status },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import type { IKibanaResponse, Logger } from '@kbn/core/server';
|
|||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
import {
|
||||
GetRuleMigrationRequestParams,
|
||||
GetRuleMigrationRequestQuery,
|
||||
type GetRuleMigrationResponse,
|
||||
} from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { SIEM_RULE_MIGRATION_PATH } from '../../../../../common/siem_migrations/constants';
|
||||
|
@ -29,18 +30,35 @@ export const registerSiemRuleMigrationsGetRoute = (
|
|||
{
|
||||
version: '1',
|
||||
validate: {
|
||||
request: { params: buildRouteValidationWithZod(GetRuleMigrationRequestParams) },
|
||||
request: {
|
||||
params: buildRouteValidationWithZod(GetRuleMigrationRequestParams),
|
||||
query: buildRouteValidationWithZod(GetRuleMigrationRequestQuery),
|
||||
},
|
||||
},
|
||||
},
|
||||
withLicense(async (context, req, res): Promise<IKibanaResponse<GetRuleMigrationResponse>> => {
|
||||
const migrationId = req.params.migration_id;
|
||||
const { migration_id: migrationId } = req.params;
|
||||
const { page, per_page: perPage, search_term: searchTerm } = req.query;
|
||||
try {
|
||||
const ctx = await context.resolve(['securitySolution']);
|
||||
const ruleMigrationsClient = ctx.securitySolution.getSiemRuleMigrationsClient();
|
||||
|
||||
const migrationRules = await ruleMigrationsClient.data.rules.get({ migrationId });
|
||||
let from = 0;
|
||||
if (page && perPage) {
|
||||
from = page * perPage;
|
||||
}
|
||||
const size = perPage;
|
||||
|
||||
return res.ok({ body: migrationRules });
|
||||
const result = await ruleMigrationsClient.data.rules.get(
|
||||
{
|
||||
migrationId,
|
||||
searchTerm,
|
||||
},
|
||||
from,
|
||||
size
|
||||
);
|
||||
|
||||
return res.ok({ body: result });
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return res.badRequest({ body: err.message });
|
||||
|
|
|
@ -12,13 +12,14 @@ import { registerSiemRuleMigrationsUpdateRoute } from './update';
|
|||
import { registerSiemRuleMigrationsGetRoute } from './get';
|
||||
import { registerSiemRuleMigrationsStartRoute } from './start';
|
||||
import { registerSiemRuleMigrationsStatsRoute } from './stats';
|
||||
import { registerSiemRuleMigrationsTranslationStatsRoute } from './translation_stats';
|
||||
import { registerSiemRuleMigrationsStopRoute } from './stop';
|
||||
import { registerSiemRuleMigrationsStatsAllRoute } from './stats_all';
|
||||
import { registerSiemRuleMigrationsResourceUpsertRoute } from './resources/upsert';
|
||||
import { registerSiemRuleMigrationsResourceGetRoute } from './resources/get';
|
||||
import { registerSiemRuleMigrationsRetryRoute } from './retry';
|
||||
import { registerSiemRuleMigrationsInstallRoute } from './rules/install';
|
||||
import { registerSiemRuleMigrationsInstallTranslatedRoute } from './rules/install_translated';
|
||||
import { registerSiemRuleMigrationsInstallRoute } from './install';
|
||||
import { registerSiemRuleMigrationsInstallTranslatedRoute } from './install_translated';
|
||||
|
||||
export const registerSiemRuleMigrationsRoutes = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
|
@ -31,6 +32,7 @@ export const registerSiemRuleMigrationsRoutes = (
|
|||
registerSiemRuleMigrationsStartRoute(router, logger);
|
||||
registerSiemRuleMigrationsRetryRoute(router, logger);
|
||||
registerSiemRuleMigrationsStatsRoute(router, logger);
|
||||
registerSiemRuleMigrationsTranslationStatsRoute(router, logger);
|
||||
registerSiemRuleMigrationsStopRoute(router, logger);
|
||||
registerSiemRuleMigrationsInstallRoute(router, logger);
|
||||
registerSiemRuleMigrationsInstallTranslatedRoute(router, logger);
|
||||
|
|
|
@ -7,15 +7,15 @@
|
|||
|
||||
import type { IKibanaResponse, Logger } from '@kbn/core/server';
|
||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
import { SIEM_RULE_MIGRATION_INSTALL_PATH } from '../../../../../../common/siem_migrations/constants';
|
||||
import type { InstallMigrationRulesResponse } from '../../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { SIEM_RULE_MIGRATION_INSTALL_PATH } from '../../../../../common/siem_migrations/constants';
|
||||
import type { InstallMigrationRulesResponse } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import {
|
||||
InstallMigrationRulesRequestBody,
|
||||
InstallMigrationRulesRequestParams,
|
||||
} from '../../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../../types';
|
||||
import { withLicense } from '../util/with_license';
|
||||
import { installTranslated } from '../util/installation';
|
||||
} from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../types';
|
||||
import { withLicense } from './util/with_license';
|
||||
import { installTranslated } from './util/installation';
|
||||
|
||||
export const registerSiemRuleMigrationsInstallRoute = (
|
||||
router: SecuritySolutionPluginRouter,
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
import type { IKibanaResponse, Logger } from '@kbn/core/server';
|
||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
import { SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH } from '../../../../../../common/siem_migrations/constants';
|
||||
import type { InstallTranslatedMigrationRulesResponse } from '../../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { InstallTranslatedMigrationRulesRequestParams } from '../../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../../types';
|
||||
import { withLicense } from '../util/with_license';
|
||||
import { installTranslated } from '../util/installation';
|
||||
import { SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH } from '../../../../../common/siem_migrations/constants';
|
||||
import type { InstallTranslatedMigrationRulesResponse } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { InstallTranslatedMigrationRulesRequestParams } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../types';
|
||||
import { withLicense } from './util/with_license';
|
||||
import { installTranslated } from './util/installation';
|
||||
|
||||
export const registerSiemRuleMigrationsInstallTranslatedRoute = (
|
||||
router: SecuritySolutionPluginRouter,
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { IKibanaResponse, Logger } from '@kbn/core/server';
|
||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
import type { GetRuleMigrationTranslationStatsResponse } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { GetRuleMigrationTranslationStatsRequestParams } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH } from '../../../../../common/siem_migrations/constants';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../types';
|
||||
import { withLicense } from './util/with_license';
|
||||
|
||||
export const registerSiemRuleMigrationsTranslationStatsRoute = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
logger: Logger
|
||||
) => {
|
||||
router.versioned
|
||||
.get({
|
||||
path: SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH,
|
||||
access: 'internal',
|
||||
security: { authz: { requiredPrivileges: ['securitySolution'] } },
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: '1',
|
||||
validate: {
|
||||
request: {
|
||||
params: buildRouteValidationWithZod(GetRuleMigrationTranslationStatsRequestParams),
|
||||
},
|
||||
},
|
||||
},
|
||||
withLicense(
|
||||
async (
|
||||
context,
|
||||
req,
|
||||
res
|
||||
): Promise<IKibanaResponse<GetRuleMigrationTranslationStatsResponse>> => {
|
||||
const migrationId = req.params.migration_id;
|
||||
try {
|
||||
const ctx = await context.resolve(['securitySolution']);
|
||||
const ruleMigrationsClient = ctx.securitySolution.getSiemRuleMigrationsClient();
|
||||
|
||||
const stats = await ruleMigrationsClient.data.rules.getTranslationStats(migrationId);
|
||||
|
||||
return res.ok({ body: stats });
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return res.badRequest({ body: err.message });
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
|
@ -177,7 +177,7 @@ export const installTranslated = async ({
|
|||
const detectionRulesClient = securitySolutionContext.getDetectionRulesClient();
|
||||
const ruleMigrationsClient = securitySolutionContext.getSiemRuleMigrationsClient();
|
||||
|
||||
const rulesToInstall = await ruleMigrationsClient.data.rules.get({
|
||||
const { data: rulesToInstall } = await ruleMigrationsClient.data.rules.get({
|
||||
migrationId,
|
||||
ids,
|
||||
installable: true,
|
||||
|
|
|
@ -15,11 +15,15 @@ import type {
|
|||
QueryDslQueryContainer,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { StoredRuleMigration } from '../types';
|
||||
import { SiemMigrationStatus } from '../../../../../common/siem_migrations/constants';
|
||||
import {
|
||||
SiemMigrationRuleTranslationResult,
|
||||
SiemMigrationStatus,
|
||||
} from '../../../../../common/siem_migrations/constants';
|
||||
import type {
|
||||
ElasticRule,
|
||||
RuleMigration,
|
||||
RuleMigrationTaskStats,
|
||||
RuleMigrationTranslationStats,
|
||||
} from '../../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import { RuleMigrationsDataBaseClient } from './rule_migrations_data_base_client';
|
||||
|
||||
|
@ -39,6 +43,7 @@ export interface RuleMigrationFilterOptions {
|
|||
status?: SiemMigrationStatus | SiemMigrationStatus[];
|
||||
ids?: string[];
|
||||
installable?: boolean;
|
||||
searchTerm?: string;
|
||||
}
|
||||
|
||||
/* BULK_MAX_SIZE defines the number to break down the bulk operations by.
|
||||
|
@ -46,6 +51,20 @@ export interface RuleMigrationFilterOptions {
|
|||
*/
|
||||
const BULK_MAX_SIZE = 500 as const;
|
||||
|
||||
const getInstallableConditions = (): QueryDslQueryContainer[] => {
|
||||
return [
|
||||
{ term: { translation_result: SiemMigrationRuleTranslationResult.FULL } },
|
||||
{
|
||||
nested: {
|
||||
path: 'elastic_rule',
|
||||
query: {
|
||||
bool: { must_not: { exists: { field: 'elastic_rule.id' } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient {
|
||||
/** Indexes an array of rule migrations to be processed */
|
||||
async create(ruleMigrations: CreateRuleMigrationInput[]): Promise<void> {
|
||||
|
@ -108,18 +127,24 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient
|
|||
}
|
||||
|
||||
/** Retrieves an array of rule documents of a specific migrations */
|
||||
async get(filters: RuleMigrationFilterOptions): Promise<StoredRuleMigration[]> {
|
||||
async get(
|
||||
filters: RuleMigrationFilterOptions,
|
||||
from?: number,
|
||||
size?: number
|
||||
): Promise<{ total: number; data: StoredRuleMigration[] }> {
|
||||
const index = await this.getIndexName();
|
||||
const query = this.getFilterQuery(filters);
|
||||
|
||||
const storedRuleMigrations = await this.esClient
|
||||
.search<RuleMigration>({ index, query, sort: '_doc' })
|
||||
.then(this.processResponseHits.bind(this))
|
||||
const result = await this.esClient
|
||||
.search<RuleMigration>({ index, query, sort: '_doc', from, size })
|
||||
.catch((error) => {
|
||||
this.logger.error(`Error searching rule migrations: ${error.message}`);
|
||||
throw error;
|
||||
});
|
||||
return storedRuleMigrations;
|
||||
return {
|
||||
total: this.getTotalHits(result),
|
||||
data: this.processResponseHits(result),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -217,6 +242,49 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient
|
|||
});
|
||||
}
|
||||
|
||||
/** Retrieves the translation stats for the rule migrations with the provided id */
|
||||
async getTranslationStats(migrationId: string): Promise<RuleMigrationTranslationStats> {
|
||||
const index = await this.getIndexName();
|
||||
const query = this.getFilterQuery({ migrationId });
|
||||
|
||||
const aggregations = {
|
||||
prebuilt: {
|
||||
filter: {
|
||||
nested: {
|
||||
path: 'elastic_rule',
|
||||
query: { exists: { field: 'elastic_rule.prebuilt_rule_id' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
installable: {
|
||||
filter: {
|
||||
bool: {
|
||||
must: getInstallableConditions(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = await this.esClient
|
||||
.search({ index, query, aggregations, _source: false })
|
||||
.catch((error) => {
|
||||
this.logger.error(`Error getting rule migrations stats: ${error.message}`);
|
||||
throw error;
|
||||
});
|
||||
|
||||
const bucket = result.aggregations ?? {};
|
||||
const total = this.getTotalHits(result);
|
||||
const prebuilt = (bucket.prebuilt as AggregationsFilterAggregate)?.doc_count ?? 0;
|
||||
return {
|
||||
id: migrationId,
|
||||
rules: {
|
||||
total,
|
||||
prebuilt,
|
||||
custom: total - prebuilt,
|
||||
installable: (bucket.installable as AggregationsFilterAggregate)?.doc_count ?? 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Retrieves the stats for the rule migrations with the provided id */
|
||||
async getStats(migrationId: string): Promise<RuleMigrationDataStats> {
|
||||
const index = await this.getIndexName();
|
||||
|
@ -295,6 +363,7 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient
|
|||
status,
|
||||
ids,
|
||||
installable,
|
||||
searchTerm,
|
||||
}: RuleMigrationFilterOptions): QueryDslQueryContainer {
|
||||
const filter: QueryDslQueryContainer[] = [{ term: { migration_id: migrationId } }];
|
||||
if (status) {
|
||||
|
@ -308,15 +377,15 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient
|
|||
filter.push({ terms: { _id: ids } });
|
||||
}
|
||||
if (installable) {
|
||||
filter.push(
|
||||
{ term: { translation_result: 'full' } },
|
||||
{
|
||||
nested: {
|
||||
path: 'elastic_rule',
|
||||
query: { bool: { must_not: { exists: { field: 'elastic_rule.id' } } } },
|
||||
},
|
||||
}
|
||||
);
|
||||
filter.push(...getInstallableConditions());
|
||||
}
|
||||
if (searchTerm?.length) {
|
||||
filter.push({
|
||||
nested: {
|
||||
path: 'elastic_rule',
|
||||
query: { match: { 'elastic_rule.title': searchTerm } },
|
||||
},
|
||||
});
|
||||
}
|
||||
return { bool: { filter } };
|
||||
}
|
||||
|
|
|
@ -19,17 +19,17 @@ export const ruleMigrationsFieldMap: FieldMap<SchemaFieldMapKeys<Omit<RuleMigrat
|
|||
original_rule: { type: 'nested', required: true },
|
||||
'original_rule.vendor': { type: 'keyword', required: true },
|
||||
'original_rule.id': { type: 'keyword', required: true },
|
||||
'original_rule.title': { type: 'keyword', required: true },
|
||||
'original_rule.description': { type: 'keyword', required: false },
|
||||
'original_rule.title': { type: 'text', required: true },
|
||||
'original_rule.description': { type: 'text', required: false },
|
||||
'original_rule.query': { type: 'text', required: true },
|
||||
'original_rule.query_language': { type: 'keyword', required: true },
|
||||
'original_rule.mitre_attack_ids': { type: 'keyword', array: true, required: false },
|
||||
elastic_rule: { type: 'nested', required: false },
|
||||
'elastic_rule.title': { type: 'keyword', required: true },
|
||||
'elastic_rule.title': { type: 'text', required: true },
|
||||
'elastic_rule.integration_ids': { type: 'keyword', array: true, required: false },
|
||||
'elastic_rule.query': { type: 'text', required: true },
|
||||
'elastic_rule.query_language': { type: 'keyword', required: true },
|
||||
'elastic_rule.description': { type: 'keyword', required: false },
|
||||
'elastic_rule.description': { type: 'text', required: false },
|
||||
'elastic_rule.severity': { type: 'keyword', required: false },
|
||||
'elastic_rule.prebuilt_rule_id': { type: 'keyword', required: false },
|
||||
'elastic_rule.id': { type: 'keyword', required: false },
|
||||
|
|
|
@ -92,12 +92,16 @@ import {
|
|||
GetRuleExecutionResultsRequestQueryInput,
|
||||
GetRuleExecutionResultsRequestParamsInput,
|
||||
} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen';
|
||||
import { GetRuleMigrationRequestParamsInput } from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import {
|
||||
GetRuleMigrationRequestQueryInput,
|
||||
GetRuleMigrationRequestParamsInput,
|
||||
} from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import {
|
||||
GetRuleMigrationResourcesRequestQueryInput,
|
||||
GetRuleMigrationResourcesRequestParamsInput,
|
||||
} from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { GetRuleMigrationStatsRequestParamsInput } from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { GetRuleMigrationTranslationStatsRequestParamsInput } from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { GetTimelineRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_timeline/get_timeline_route.gen';
|
||||
import { GetTimelinesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_timelines/get_timelines_route.gen';
|
||||
import { ImportRulesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen';
|
||||
|
@ -937,7 +941,8 @@ finalize it.
|
|||
)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
|
||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
|
||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||
.query(props.query);
|
||||
},
|
||||
/**
|
||||
* Retrieves resources for an existing SIEM rules migration
|
||||
|
@ -973,6 +978,27 @@ finalize it.
|
|||
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
|
||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
|
||||
},
|
||||
/**
|
||||
* Retrieves the translation stats of a SIEM rules migration using the migration id provided
|
||||
*/
|
||||
getRuleMigrationTranslationStats(
|
||||
props: GetRuleMigrationTranslationStatsProps,
|
||||
kibanaSpace: string = 'default'
|
||||
) {
|
||||
return supertest
|
||||
.get(
|
||||
routeWithNamespace(
|
||||
replaceParams(
|
||||
'/internal/siem_migrations/rules/{migration_id}/translation_stats',
|
||||
props.params
|
||||
),
|
||||
kibanaSpace
|
||||
)
|
||||
)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
|
||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
|
||||
},
|
||||
/**
|
||||
* Get the details of an existing saved Timeline or Timeline template.
|
||||
*/
|
||||
|
@ -1667,6 +1693,7 @@ export interface GetRuleExecutionResultsProps {
|
|||
params: GetRuleExecutionResultsRequestParamsInput;
|
||||
}
|
||||
export interface GetRuleMigrationProps {
|
||||
query: GetRuleMigrationRequestQueryInput;
|
||||
params: GetRuleMigrationRequestParamsInput;
|
||||
}
|
||||
export interface GetRuleMigrationResourcesProps {
|
||||
|
@ -1676,6 +1703,9 @@ export interface GetRuleMigrationResourcesProps {
|
|||
export interface GetRuleMigrationStatsProps {
|
||||
params: GetRuleMigrationStatsRequestParamsInput;
|
||||
}
|
||||
export interface GetRuleMigrationTranslationStatsProps {
|
||||
params: GetRuleMigrationTranslationStatsRequestParamsInput;
|
||||
}
|
||||
export interface GetTimelineProps {
|
||||
query: GetTimelineRequestQueryInput;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue