Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ import { SortOrder } from '../../model';
export type PrebuiltRuleAssetsSortField = z.infer<typeof PrebuiltRuleAssetsSortField>;
export const PrebuiltRuleAssetsSortField = z.enum(['name', 'risk_score', 'severity']);

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

export type PrebuiltRuleAssetsSort = z.infer<typeof PrebuiltRuleAssetsSort>;
export const PrebuiltRuleAssetsSort = z.array(
z.object({
/**
* Field to sort by
*/
field: PrebuiltRuleAssetsSortField,
/**
* Sort order
*/
order: SortOrder,
})
);
export const PrebuiltRuleAssetsSort = z.array(PrebuiltRuleAssetsSortItem);
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,26 @@ import { PrebuiltRuleAssetsFilter } from '../common/prebuilt_rule_assets_filter'
import { PrebuiltRuleAssetsSort } from '../common/prebuilt_rule_assets_sort';

export type ReviewRuleInstallationRequestBody = z.infer<typeof ReviewRuleInstallationRequestBody>;
export const ReviewRuleInstallationRequestBody = z
.object({
/**
* Page number starting from 1
*/
page: z.coerce.number().int().min(1).optional(),
/**
* Rules per page
*/
per_page: z.coerce.number().int().min(1).max(10_000).optional(),

/**
* Filtering criteria
*/
filter: PrebuiltRuleAssetsFilter.optional(),

/**
* Sorting criteria
*/
sort: PrebuiltRuleAssetsSort.optional(),
})
.partial();
export const ReviewRuleInstallationRequestBody = z.object({
/**
* Page number starting from 1
*/
page: z.number().int().min(1).default(1),
/**
* Rules per page
*/
per_page: z.number().int().min(1).max(10_000).default(20),

/**
* Filtering criteria
*/
filter: PrebuiltRuleAssetsFilter.optional(),

/**
* Sorting criteria
*/
sort: PrebuiltRuleAssetsSort.optional(),
});

export interface ReviewRuleInstallationResponseBody {
/** Current page number */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type {
PerformRuleUpgradeResponseBody,
RevertPrebuiltRulesRequest,
RevertPrebuiltRulesResponseBody,
ReviewRuleInstallationRequestBody,
ReviewRuleInstallationResponseBody,
ReviewRuleUpgradeRequestBody,
ReviewRuleUpgradeResponseBody,
Expand Down Expand Up @@ -678,13 +679,16 @@ export const reviewRuleUpgrade = async ({
*/
export const reviewRuleInstall = async ({
signal,
request,
}: {
signal: AbortSignal | undefined;
request: ReviewRuleInstallationRequestBody;
}): Promise<ReviewRuleInstallationResponseBody> =>
KibanaServices.get().http.fetch(REVIEW_RULE_INSTALLATION_URL, {
method: 'POST',
version: '1',
signal,
body: JSON.stringify(request),
});

export const performInstallAllRules = async (): Promise<PerformRuleInstallationResponseBody> =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@ import type { UseQueryOptions } from '@kbn/react-query';
import { useQuery, useQueryClient } from '@kbn/react-query';
import { reviewRuleInstall } from '../../api';
import { REVIEW_RULE_INSTALLATION_URL } from '../../../../../../common/api/detection_engine/prebuilt_rules/urls';
import type { ReviewRuleInstallationResponseBody } from '../../../../../../common/api/detection_engine/prebuilt_rules';
import type {
ReviewRuleInstallationRequestBody,
ReviewRuleInstallationResponseBody,
} 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_INSTALLATION_QUERY_KEY = ['POST', REVIEW_RULE_INSTALLATION_URL];

export const useFetchPrebuiltRulesInstallReviewQuery = (
request: ReviewRuleInstallationRequestBody,
options?: UseQueryOptions<ReviewRuleInstallationResponseBody>
) => {
return useQuery<ReviewRuleInstallationResponseBody>(
REVIEW_RULE_INSTALLATION_QUERY_KEY,
[...REVIEW_RULE_INSTALLATION_QUERY_KEY, request],
async ({ signal }) => {
const response = await reviewRuleInstall({ signal });
const response = await reviewRuleInstall({ signal, request });
return response;
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ import type { ReviewRuleInstallationResponseBody } from '../../../../../common/a
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import * as i18n from '../translations';
import { useFetchPrebuiltRulesInstallReviewQuery } from '../../api/hooks/prebuilt_rules/use_fetch_prebuilt_rules_install_review_query';
import type { AddPrebuiltRulesTableFilterOptions } from '../../../rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context';
import type { PrebuiltRuleAssetsSortItem } from '../../../../../common/api/detection_engine/prebuilt_rules/common/prebuilt_rule_assets_sort';
import type { PrebuiltRuleAssetsFilter } from '../../../../../common/api/detection_engine/prebuilt_rules/common/prebuilt_rule_assets_filter';

interface UsePrebuiltRulesInstallReviewParams {
page: number;
perPage: number;
filterOptions?: AddPrebuiltRulesTableFilterOptions;
sortingOptions?: PrebuiltRuleAssetsSortItem;
}

/**
* A wrapper around useQuery provides default values to the underlying query,
Expand All @@ -18,12 +28,52 @@ import { useFetchPrebuiltRulesInstallReviewQuery } from '../../api/hooks/prebuil
* @returns useQuery result
*/
export const usePrebuiltRulesInstallReview = (
requestParameters: UsePrebuiltRulesInstallReviewParams,
options?: UseQueryOptions<ReviewRuleInstallationResponseBody>
) => {
const { addError } = useAppToasts();

return useFetchPrebuiltRulesInstallReviewQuery({
onError: (error) => addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }),
...options,
});
return useFetchPrebuiltRulesInstallReviewQuery(
{
page: requestParameters.page,
per_page: requestParameters.perPage,
filter: prepareFilters(requestParameters.filterOptions),
sort: requestParameters.sortingOptions ? [requestParameters.sortingOptions] : undefined,
},
{
onError: (error) => addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }),
...options,
}
);
};

/**
* Converts filter options from a simplified UI format to a format expected by the API.
*/
function prepareFilters(
filterOptions: AddPrebuiltRulesTableFilterOptions | undefined
): PrebuiltRuleAssetsFilter | undefined {
if (!filterOptions) {
return undefined;
}

const filter: PrebuiltRuleAssetsFilter = {
fields: {},
};

if (filterOptions.name) {
filter.fields.name = {
include: { values: [filterOptions.name] },
};
}

if (filterOptions.tags.length) {
filter.fields.tags = {
include: { values: filterOptions.tags },
};
}

const isEmptyFilter = Object.keys(filter.fields).length === 0;

return isEmptyFilter ? undefined : filter;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@

import type { CriteriaWithPagination } from '@elastic/eui';
import {
EuiInMemoryTable,
EuiSkeletonLoading,
EuiProgress,
EuiSkeletonTitle,
EuiSkeletonText,
EuiFlexGroup,
EuiFlexItem,
EuiBasicTable,
} from '@elastic/eui';
import React, { useCallback, useState } from 'react';
import React, { useCallback, useMemo } from 'react';

import type { PrebuiltRuleAssetsSortField } from '../../../../../../common/api/detection_engine/prebuilt_rules/common/prebuilt_rule_assets_sort';
import * as i18n from '../../../pages/add_rules/translations';
import type { RuleResponse } from '../../../../../../common/api/detection_engine/model/rule_schema';
import { RULES_TABLE_INITIAL_PAGE_SIZE, RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants';
import { RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants';
import { AddPrebuiltRulesTableNoItemsMessage } from './add_prebuilt_rules_no_items_message';
import { useAddPrebuiltRulesTableContext } from './add_prebuilt_rules_table_context';
import { AddPrebuiltRulesTableFilters } from './add_prebuilt_rules_table_filters';
Expand All @@ -28,31 +30,53 @@ import { useAddPrebuiltRulesTableColumns } from './use_add_prebuilt_rules_table_
* Table Component for displaying new rules that are available to be installed
*/
export const AddPrebuiltRulesTable = React.memo(() => {
const addRulesTableContext = useAddPrebuiltRulesTableContext();

const {
state: {
rules,
hasRulesToInstall,
isLoading,
isFetching,
isRefetching,
selectedRules,
isUpgradingSecurityPackages,
pagination,
sortingOptions,
},
actions: { selectRules },
} = addRulesTableContext;
actions: { setPagination, setSortingOptions, selectRules },
} = useAddPrebuiltRulesTableContext();

const rulesColumns = useAddPrebuiltRulesTableColumns();

const shouldShowProgress = isUpgradingSecurityPackages || isRefetching;

const [pageIndex, setPageIndex] = useState(0);
const handleTableChange = useCallback(
({ page: { index } }: CriteriaWithPagination<RuleResponse>) => {
setPageIndex(index);
({ page: { index, size }, sort }: CriteriaWithPagination<RuleResponse>) => {
setPagination({
page: index + 1,
perPage: size,
});

if (sort) {
setSortingOptions({
field: sort.field as PrebuiltRuleAssetsSortField,
order: sort.direction,
});
}
},
[setPageIndex]
[setPagination, setSortingOptions]
);

const sortingTableProp = useMemo(() => {
return sortingOptions
? {
sort: {
field: sortingOptions.field,
direction: sortingOptions.order,
},
}
: {};
}, [sortingOptions]);

return (
<>
{shouldShowProgress && (
Expand Down Expand Up @@ -89,23 +113,26 @@ export const AddPrebuiltRulesTable = React.memo(() => {
</EuiFlexItem>
</EuiFlexGroup>

<EuiInMemoryTable
<EuiBasicTable
loading={isFetching}
items={rules}
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: selectRules,
initialSelected: selectedRules,
}}
sorting={sortingTableProp}
itemId="rule_id"
data-test-subj="add-prebuilt-rules-table"
columns={rulesColumns}
onTableChange={handleTableChange}
onChange={handleTableChange}
tableCaption={i18n.PAGE_TITLE}
/>
</>
)
Expand Down
Loading