Skip to content

Commit

Permalink
Reduce the _review rule upgrade endpoint response size
Browse files Browse the repository at this point in the history
  • Loading branch information
xcrzx committed Feb 17, 2025
1 parent d6b250b commit 80fc74e
Show file tree
Hide file tree
Showing 17 changed files with 477 additions and 236 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,89 @@
* 2.0.
*/

import type { RuleObjectId, RuleSignatureId, RuleTagArray } from '../../model';
import { z } from '@kbn/zod';
import { SortOrder, type RuleObjectId, type RuleSignatureId, type RuleTagArray } from '../../model';
import type { PartialRuleDiff } from '../model';
import type { RuleResponse } from '../../model/rule_schema';
import type { RuleResponse, RuleVersion } from '../../model/rule_schema';
import { FindRulesSortField } from '../../rule_management';

export enum RuleCustomizationStatus {
CUSTOMIZED = 'CUSTOMIZED',
NOT_CUSTOMIZED = 'NOT_CUSTOMIZED',
}

export type ReviewRuleUpgradeFilter = z.infer<typeof ReviewRuleUpgradeFilter>;
export const ReviewRuleUpgradeFilter = z.object({
/**
* Rule IDs to return upgrade info for
*/
rule_ids: z.array(z.string()).optional(),
/**
* Tags to filter by
*/
tags: z.array(z.string()).optional(),
/**
* Rule name to filter by
*/
name: z.string().optional(),
/**
* Rule customization status to filter by
*/
customization_status: z.nativeEnum(RuleCustomizationStatus).optional(),
});

export type ReviewRuleUpgradeSort = z.infer<typeof ReviewRuleUpgradeSort>;
export const ReviewRuleUpgradeSort = z.object({
/**
* Field to sort by
*/
field: FindRulesSortField.optional(),
/**
* Sort order
*/
order: SortOrder.optional(),
});

export type ReviewRuleUpgradeRequestBody = z.infer<typeof ReviewRuleUpgradeRequestBody>;
export const ReviewRuleUpgradeRequestBody = z
.object({
filter: ReviewRuleUpgradeFilter.optional(),
sort: ReviewRuleUpgradeSort.optional(),

page: z.coerce.number().int().min(1).optional().default(1),
/**
* Rules per page
*/
per_page: z.coerce.number().int().min(0).optional().default(20),
})
.nullable();

export interface ReviewRuleUpgradeResponseBody {
/** Aggregated info about all rules available for upgrade */
stats: RuleUpgradeStatsForReview;

/** Info about individual rules: one object per each rule available for upgrade */
rules: RuleUpgradeInfoForReview[];

/** The requested page number */
page: number;

/** The requested number of items per page */
per_page: number;
}

export interface RuleUpgradeStatsForReview {
/** Number of installed prebuilt rules available for upgrade (stock + customized) */
num_rules_to_upgrade_total: number;

/** Number of installed prebuilt rules with upgrade conflicts (SOLVABLE or NON_SOLVABLE) */
/**
* @deprecated Always 0
*/
num_rules_with_conflicts: number;

/** Number of installed prebuilt rules with NON_SOLVABLE upgrade conflicts */
/**
* @deprecated Always 0
*/
num_rules_with_non_solvable_conflicts: number;

/** A union of all tags of all rules available for upgrade */
Expand All @@ -34,6 +97,7 @@ export interface RuleUpgradeStatsForReview {
export interface RuleUpgradeInfoForReview {
id: RuleObjectId;
rule_id: RuleSignatureId;
version: RuleVersion;
current_rule: RuleResponse;
target_rule: RuleResponse;
diff: PartialRuleDiff;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export const TAGS_FIELD = 'alert.attributes.tags';
export const PARAMS_TYPE_FIELD = 'alert.attributes.params.type';
export const PARAMS_IMMUTABLE_FIELD = 'alert.attributes.params.immutable';
export const LAST_RUN_OUTCOME_FIELD = 'alert.attributes.lastRun.outcome';
export const IS_CUSTOMIZED_FIELD = 'alert.attributes.params.ruleSource.isCustomized';
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@

import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
import type { RuleExecutionStatus } from '../../api/detection_engine';
import { RuleExecutionStatusEnum } from '../../api/detection_engine';
import { RuleCustomizationStatus, RuleExecutionStatusEnum } from '../../api/detection_engine';
import { prepareKQLStringParam } from '../../utils/kql';
import {
ENABLED_FIELD,
IS_CUSTOMIZED_FIELD,
LAST_RUN_OUTCOME_FIELD,
PARAMS_IMMUTABLE_FIELD,
PARAMS_TYPE_FIELD,
Expand All @@ -23,6 +24,8 @@ export const KQL_FILTER_IMMUTABLE_RULES = `${PARAMS_IMMUTABLE_FIELD}: true`;
export const KQL_FILTER_MUTABLE_RULES = `${PARAMS_IMMUTABLE_FIELD}: false`;
export const KQL_FILTER_ENABLED_RULES = `${ENABLED_FIELD}: true`;
export const KQL_FILTER_DISABLED_RULES = `${ENABLED_FIELD}: false`;
export const KQL_FILTER_CUSTOMIZED_RULES = `${IS_CUSTOMIZED_FIELD}: true`;
export const KQL_FILTER_NOT_CUSTOMIZED_RULES = `${IS_CUSTOMIZED_FIELD}: false`;

interface RulesFilterOptions {
filter: string;
Expand All @@ -32,6 +35,7 @@ interface RulesFilterOptions {
tags: string[];
excludeRuleTypes: Type[];
ruleExecutionStatus: RuleExecutionStatus;
customizationStatus: RuleCustomizationStatus;
ruleIds: string[];
}

Expand All @@ -50,6 +54,7 @@ export function convertRulesFilterToKQL({
tags,
excludeRuleTypes = [],
ruleExecutionStatus,
customizationStatus,
}: Partial<RulesFilterOptions>): string {
const kql: string[] = [];

Expand Down Expand Up @@ -85,6 +90,12 @@ export function convertRulesFilterToKQL({
kql.push(`${LAST_RUN_OUTCOME_FIELD}: "failed"`);
}

if (customizationStatus === RuleCustomizationStatus.CUSTOMIZED) {
kql.push(KQL_FILTER_CUSTOMIZED_RULES);
} else if (customizationStatus === RuleCustomizationStatus.NOT_CUSTOMIZED) {
kql.push(KQL_FILTER_NOT_CUSTOMIZED_RULES);
}

return kql.join(' AND ');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type {
GetPrebuiltRulesStatusResponseBody,
ReviewRuleUpgradeResponseBody,
ReviewRuleInstallationResponseBody,
ReviewRuleUpgradeRequestBody,
} from '../../../../common/api/detection_engine/prebuilt_rules';
import type {
BulkDuplicateRules,
Expand Down Expand Up @@ -637,13 +638,16 @@ export const getPrebuiltRulesStatus = async ({
*/
export const reviewRuleUpgrade = async ({
signal,
request,
}: {
signal: AbortSignal | undefined;
request: ReviewRuleUpgradeRequestBody;
}): Promise<ReviewRuleUpgradeResponseBody> =>
KibanaServices.get().http.fetch(REVIEW_RULE_UPGRADE_URL, {
method: 'POST',
version: '1',
signal,
body: JSON.stringify(request),
});

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@ import type { UseQueryOptions } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { reviewRuleUpgrade } from '../../api';
import { REVIEW_RULE_UPGRADE_URL } from '../../../../../../common/api/detection_engine/prebuilt_rules/urls';
import type { ReviewRuleUpgradeResponseBody } from '../../../../../../common/api/detection_engine/prebuilt_rules';
import type {
ReviewRuleUpgradeRequestBody,
ReviewRuleUpgradeResponseBody,
} from '../../../../../../common/api/detection_engine/prebuilt_rules';
import { DEFAULT_QUERY_OPTIONS } from '../constants';
import { retryOnRateLimitedError } from './retry_on_rate_limited_error';
import { cappedExponentialBackoff } from './capped_exponential_backoff';

export const REVIEW_RULE_UPGRADE_QUERY_KEY = ['POST', REVIEW_RULE_UPGRADE_URL];

export const useFetchPrebuiltRulesUpgradeReviewQuery = (
request: ReviewRuleUpgradeRequestBody,
options?: UseQueryOptions<ReviewRuleUpgradeResponseBody>
) => {
return useQuery<ReviewRuleUpgradeResponseBody>(
REVIEW_RULE_UPGRADE_QUERY_KEY,
[...REVIEW_RULE_UPGRADE_QUERY_KEY, request],
async ({ signal }) => {
const response = await reviewRuleUpgrade({ signal });
const response = await reviewRuleUpgrade({ signal, request });
return response;
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
* 2.0.
*/
import type { UseQueryOptions } from '@tanstack/react-query';
import type { ReviewRuleUpgradeResponseBody } from '../../../../../common/api/detection_engine/prebuilt_rules';
import type {
ReviewRuleUpgradeRequestBody,
ReviewRuleUpgradeResponseBody,
} from '../../../../../common/api/detection_engine/prebuilt_rules';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';

import * as i18n from '../translations';
Expand All @@ -18,11 +21,12 @@ import { useFetchPrebuiltRulesUpgradeReviewQuery } from '../../api/hooks/prebuil
* @returns useQuery result
*/
export const usePrebuiltRulesUpgradeReview = (
request: ReviewRuleUpgradeRequestBody,
options?: UseQueryOptions<ReviewRuleUpgradeResponseBody>
) => {
const { addError } = useAppToasts();

return useFetchPrebuiltRulesUpgradeReviewQuery({
return useFetchPrebuiltRulesUpgradeReviewQuery(request, {
onError: (error) => addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }),
...options,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import type { RuleSnooze } from '@kbn/alerting-plugin/common';
import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types';
import type { RuleSnoozeSettings } from '@kbn/triggers-actions-ui-plugin/public/types';
import type { WarningSchema } from '../../../../common/api/detection_engine';
import type {
RuleCustomizationStatus,
WarningSchema,
} from '../../../../common/api/detection_engine';
import type { RuleExecutionStatus } from '../../../../common/api/detection_engine/rule_monitoring';

import { SortOrder } from '../../../../common/api/detection_engine';
Expand Down Expand Up @@ -103,7 +106,7 @@ export interface FilterOptions {
excludeRuleTypes?: Type[];
enabled?: boolean; // undefined is to display all the rules
ruleExecutionStatus?: RuleExecutionStatus; // undefined means "all"
ruleSource?: RuleCustomizationEnum[]; // undefined is to display all the rules
ruleSource?: RuleCustomizationStatus[]; // undefined is to display all the rules
showRulesWithGaps?: boolean;
gapSearchRange?: GapRangeValue;
}
Expand Down Expand Up @@ -209,8 +212,3 @@ export interface FindRulesReferencedByExceptionsProps {
lists: FindRulesReferencedByExceptionsListProp[];
signal?: AbortSignal;
}

export enum RuleCustomizationEnum {
customized = 'CUSTOMIZED',
not_customized = 'NOT_CUSTOMIZED',
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiInMemoryTable,
EuiBasicTable,
EuiProgress,
EuiSkeletonLoading,
EuiSkeletonText,
Expand All @@ -19,9 +19,10 @@ import {
import React, { useCallback, useState } from 'react';
import type { RuleUpgradeState } from '../../../../rule_management/model/prebuilt_rule_upgrade';
import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations';
import { RULES_TABLE_INITIAL_PAGE_SIZE, RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants';
import { RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants';
import { RulesChangelogLink } from '../rules_changelog_link';
import { UpgradePrebuiltRulesTableButtons } from './upgrade_prebuilt_rules_table_buttons';
import type { UpgradePrebuiltRulesSortingOptions } from './upgrade_prebuilt_rules_table_context';
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';
Expand All @@ -44,20 +45,32 @@ export const UpgradePrebuiltRulesTable = React.memo(() => {
ruleUpgradeStates,
hasRulesToUpgrade,
isLoading,
isFetching,
isRefetching,
isUpgradingSecurityPackages,
pagination,
sortingOptions,
},
actions: { setPagination, setSortingOptions },
} = useUpgradePrebuiltRulesTableContext();
const [selected, setSelected] = useState<RuleUpgradeState[]>([]);

const rulesColumns = useUpgradePrebuiltRulesTableColumns();
const shouldShowProgress = isUpgradingSecurityPackages || isRefetching;
const [pageIndex, setPageIndex] = useState(0);
const handleTableChange = useCallback(
({ page: { index } }: CriteriaWithPagination<RuleUpgradeState>) => {
setPageIndex(index);
({ page: { index, size }, sort }: CriteriaWithPagination<RuleUpgradeState>) => {
setPagination({
page: index + 1,
perPage: size,
});
if (sort) {
setSortingOptions({
field: sort.field as UpgradePrebuiltRulesSortingOptions['field'],
order: sort.direction,
});
}
},
[setPageIndex]
[setPagination, setSortingOptions]
);

return (
Expand Down Expand Up @@ -104,23 +117,31 @@ export const UpgradePrebuiltRulesTable = React.memo(() => {
</EuiFlexItem>
</EuiFlexGroup>

<EuiInMemoryTable
<EuiBasicTable
loading={isFetching}
items={ruleUpgradeStates}
sorting
pagination={{
initialPageSize: RULES_TABLE_INITIAL_PAGE_SIZE,
totalItemCount: pagination.total,
pageSizeOptions: RULES_TABLE_PAGE_SIZE_OPTIONS,
pageIndex,
pageIndex: pagination.page - 1,
pageSize: pagination.perPage,
}}
selection={{
selectable: () => true,
onSelectionChange: setSelected,
initialSelected: selected,
}}
sorting={{
sort: {
// EuiBasicTable has incorrect `sort.field` types which accept only `keyof Item` and reject fields in dot notation
field: sortingOptions.field as keyof RuleUpgradeState,
direction: sortingOptions.order,
},
}}
itemId="rule_id"
data-test-subj="rules-upgrades-table"
columns={rulesColumns}
onTableChange={handleTableChange}
onChange={handleTableChange}
/>
</>
)
Expand Down
Loading

0 comments on commit 80fc74e

Please sign in to comment.