Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.9] [Security Solution] Fix Add Rules Page not refetching rules after package installed in background (#160389) #160892

Merged
merged 2 commits into from
Jun 30, 2023
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 @@ -20,6 +20,9 @@ export interface PrebuiltRulesStatusStats {
/** Number of installed prebuilt rules available for upgrade (stock + customized) */
num_prebuilt_rules_to_upgrade: number;

/** Total number of prebuilt rules available in package (including already installed) */
num_prebuilt_rules_total_in_package: number;

// In the future we could add more stats such as:
// - number of installed prebuilt rules which were deprecated
// - number of installed prebuilt rules which are not compatible with the current version of Kibana
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import { useMutation } from '@tanstack/react-query';
import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../common/detection_engine/constants';
import type { BulkInstallFleetPackagesProps } from '../api';
import { bulkInstallFleetPackages } from '../api';
import { useInvalidateFetchPrebuiltRulesInstallReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_install_review_query';
import { useInvalidateFetchPrebuiltRulesStatusQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_status_query';
import { useInvalidateFetchPrebuiltRulesUpgradeReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query';

export const BULK_INSTALL_FLEET_PACKAGES_MUTATION_KEY = [
'POST',
Expand All @@ -22,6 +24,8 @@ export const useBulkInstallFleetPackagesMutation = (
options?: UseMutationOptions<BulkInstallPackagesResponse, Error, BulkInstallFleetPackagesProps>
) => {
const invalidatePrePackagedRulesStatus = useInvalidateFetchPrebuiltRulesStatusQuery();
const invalidatePrebuiltRulesInstallReview = useInvalidateFetchPrebuiltRulesInstallReviewQuery();
const invalidatePrebuiltRulesUpdateReview = useInvalidateFetchPrebuiltRulesUpgradeReviewQuery();

return useMutation((props: BulkInstallFleetPackagesProps) => bulkInstallFleetPackages(props), {
...options,
Expand All @@ -34,6 +38,8 @@ export const useBulkInstallFleetPackagesMutation = (
if (rulesPackage && 'result' in rulesPackage && rulesPackage.result.status === 'installed') {
// The rules package was installed/updated, so invalidate the pre-packaged rules status query
invalidatePrePackagedRulesStatus();
invalidatePrebuiltRulesInstallReview();
invalidatePrebuiltRulesUpdateReview();
}

if (options?.onSettled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import { useMutation } from '@tanstack/react-query';
import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../common/detection_engine/constants';
import type { InstallFleetPackageProps } from '../api';
import { installFleetPackage } from '../api';
import { useInvalidateFetchPrebuiltRulesInstallReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_install_review_query';
import { useInvalidateFetchPrebuiltRulesStatusQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_status_query';
import { useInvalidateFetchPrebuiltRulesUpgradeReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query';

export const INSTALL_FLEET_PACKAGE_MUTATION_KEY = [
'POST',
Expand All @@ -22,6 +24,8 @@ export const useInstallFleetPackageMutation = (
options?: UseMutationOptions<InstallPackageResponse, Error, InstallFleetPackageProps>
) => {
const invalidatePrePackagedRulesStatus = useInvalidateFetchPrebuiltRulesStatusQuery();
const invalidatePrebuiltRulesInstallReview = useInvalidateFetchPrebuiltRulesInstallReviewQuery();
const invalidatePrebuiltRulesUpdateReview = useInvalidateFetchPrebuiltRulesUpgradeReviewQuery();

return useMutation((props: InstallFleetPackageProps) => installFleetPackage(props), {
...options,
Expand All @@ -31,6 +35,8 @@ export const useInstallFleetPackageMutation = (
if (packageName === PREBUILT_RULES_PACKAGE_NAME) {
// Invalidate the pre-packaged rules status query as there might be new rules to install
invalidatePrePackagedRulesStatus();
invalidatePrebuiltRulesInstallReview();
invalidatePrebuiltRulesUpdateReview();
}

if (options?.onSettled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import * as i18n from './translations';

export const AddPrebuiltRulesHeaderButtons = () => {
const {
state: { rules, selectedRules, loadingRules },
state: { rules, selectedRules, loadingRules, isRefetching, isUpgradingSecurityPackages },
actions: { installAllRules, installSelectedRules },
} = useAddPrebuiltRulesTableContext();

Expand All @@ -21,12 +21,13 @@ export const AddPrebuiltRulesHeaderButtons = () => {
const shouldDisplayInstallSelectedRulesButton = numberOfSelectedRules > 0;

const isRuleInstalling = loadingRules.length > 0;
const isRequestInProgress = isRuleInstalling || isRefetching || isUpgradingSecurityPackages;

return (
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}>
{shouldDisplayInstallSelectedRulesButton ? (
<EuiFlexItem grow={false}>
<EuiButton onClick={installSelectedRules} disabled={isRuleInstalling}>
<EuiButton onClick={installSelectedRules} disabled={isRequestInProgress}>
{i18n.INSTALL_SELECTED_RULES(numberOfSelectedRules)}
{isRuleInstalling ? <EuiLoadingSpinner size="s" /> : undefined}
</EuiButton>
Expand All @@ -38,7 +39,7 @@ export const AddPrebuiltRulesHeaderButtons = () => {
iconType="plusInCircle"
data-test-subj="installAllRulesButton"
onClick={installAllRules}
disabled={!isRulesAvailableForInstall || isRuleInstalling}
disabled={!isRulesAvailableForInstall || isRequestInProgress}
>
{i18n.INSTALL_ALL}
{isRuleInstalling ? <EuiLoadingSpinner size="s" /> : undefined}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
} from '@elastic/eui';
import React from 'react';

import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages';
import { RULES_TABLE_INITIAL_PAGE_SIZE, RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants';
import { AddPrebuiltRulesTableNoItemsMessage } from './add_prebuilt_rules_no_items_message';
import { useAddPrebuiltRulesTableContext } from './add_prebuilt_rules_table_context';
Expand All @@ -25,24 +24,29 @@ 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 isUpgradingSecurityPackages = useIsUpgradingSecurityPackages();

const addRulesTableContext = useAddPrebuiltRulesTableContext();

const {
state: { rules, filteredRules, isFetched, isLoading, isRefetching, selectedRules },
state: {
rules,
filteredRules,
isFetched,
isLoading,
isRefetching,
selectedRules,
isUpgradingSecurityPackages,
},
actions: { selectRules },
} = addRulesTableContext;
const rulesColumns = useAddPrebuiltRulesTableColumns();

const isTableEmpty = isFetched && rules.length === 0;

const shouldShowLinearProgress = (isFetched && isRefetching) || isUpgradingSecurityPackages;
const shouldShowLoadingOverlay = !isFetched && isRefetching;
const shouldShowProgress = isUpgradingSecurityPackages || isRefetching;

return (
<>
{shouldShowLinearProgress && (
{shouldShowProgress && (
<EuiProgress
data-test-subj="loadingRulesInfoProgress"
size="xs"
Expand All @@ -51,7 +55,7 @@ export const AddPrebuiltRulesTable = React.memo(() => {
/>
)}
<EuiSkeletonLoading
isLoading={isLoading || shouldShowLoadingOverlay}
isLoading={isLoading}
loadingContent={
<>
<EuiSkeletonTitle />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import type { Dispatch, SetStateAction } from 'react';
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
import { useFetchPrebuiltRulesStatusQuery } from '../../../../rule_management/api/hooks/prebuilt_rules/use_fetch_prebuilt_rules_status_query';
import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages';
import type { RuleInstallationInfoForReview } from '../../../../../../common/detection_engine/prebuilt_rules/api/review_rule_installation/response_schema';
import type { RuleSignatureId } from '../../../../../../common/detection_engine/rule_schema';
import { invariant } from '../../../../../../common/utils/invariant';
Expand Down Expand Up @@ -47,6 +49,11 @@ export interface AddPrebuiltRulesTableState {
* Is true whenever a background refetch is in-flight, which does not include initial loading
*/
isRefetching: boolean;
/**
* Is true when installing security_detection_rules
* package in background
*/
isUpgradingSecurityPackages: boolean;
/**
* List of rule IDs that are currently being upgraded
*/
Expand Down Expand Up @@ -92,6 +99,10 @@ export const AddPrebuiltRulesTableContextProvider = ({
tags: [],
});

const { data: prebuiltRulesStatus } = useFetchPrebuiltRulesStatusQuery();

const isUpgradingSecurityPackages = useIsUpgradingSecurityPackages();

const {
data: { rules, stats: { tags } } = {
rules: [],
Expand All @@ -105,6 +116,12 @@ export const AddPrebuiltRulesTableContextProvider = ({
} = usePrebuiltRulesInstallReview({
refetchInterval: 60000, // Refetch available rules for installation every minute
keepPreviousData: true, // Use this option so that the state doesn't jump between "success" and "loading" on page change
// Fetch rules to install only after background installation of security_detection_rules package is complete
enabled: Boolean(
!isUpgradingSecurityPackages &&
prebuiltRulesStatus &&
prebuiltRulesStatus.num_prebuilt_rules_total_in_package > 0
),
});

const { mutateAsync: installAllRulesRequest } = usePerformInstallAllRules();
Expand Down Expand Up @@ -175,6 +192,7 @@ export const AddPrebuiltRulesTableContextProvider = ({
isLoading,
loadingRules,
isRefetching,
isUpgradingSecurityPackages,
selectedRules,
lastUpdated: dataUpdatedAt,
},
Expand All @@ -189,6 +207,7 @@ export const AddPrebuiltRulesTableContextProvider = ({
isLoading,
loadingRules,
isRefetching,
isUpgradingSecurityPackages,
selectedRules,
dataUpdatedAt,
actions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,20 @@ const INTEGRATIONS_COLUMN: TableColumn = {

const createInstallButtonColumn = (
installOneRule: AddPrebuiltRulesTableActions['installOneRule'],
loadingRules: RuleSignatureId[]
loadingRules: RuleSignatureId[],
isDisabled: boolean
): TableColumn => ({
field: 'rule_id',
name: '',
render: (ruleId: RuleSignatureId) => {
const isRuleInstalling = loadingRules.includes(ruleId);
const isInstallButtonDisabled = isRuleInstalling || isDisabled;
return (
<EuiButtonEmpty size="s" disabled={isRuleInstalling} onClick={() => installOneRule(ruleId)}>
<EuiButtonEmpty
size="s"
disabled={isInstallButtonDisabled}
onClick={() => installOneRule(ruleId)}
>
{isRuleInstalling ? <EuiLoadingSpinner size="s" /> : i18n.INSTALL_RULE_BUTTON}
</EuiButtonEmpty>
);
Expand All @@ -106,10 +112,12 @@ export const useAddPrebuiltRulesTableColumns = (): TableColumn[] => {
const hasCRUDPermissions = hasUserCRUDPermission(canUserCRUD);
const [showRelatedIntegrations] = useUiSetting$<boolean>(SHOW_RELATED_INTEGRATIONS_SETTING);
const {
state: { loadingRules },
state: { loadingRules, isRefetching, isUpgradingSecurityPackages },
actions: { installOneRule },
} = useAddPrebuiltRulesTableContext();

const isDisabled = isRefetching || isUpgradingSecurityPackages;

return useMemo(
() => [
RULE_NAME_COLUMN,
Expand All @@ -135,8 +143,10 @@ export const useAddPrebuiltRulesTableColumns = (): TableColumn[] => {
truncateText: true,
width: '12%',
},
...(hasCRUDPermissions ? [createInstallButtonColumn(installOneRule, loadingRules)] : []),
...(hasCRUDPermissions
? [createInstallButtonColumn(installOneRule, loadingRules, isDisabled)]
: []),
],
[hasCRUDPermissions, installOneRule, loadingRules, showRelatedIntegrations]
[hasCRUDPermissions, installOneRule, loadingRules, isDisabled, showRelatedIntegrations]
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
} from '@elastic/eui';
import React from 'react';
import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations';
import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages';
import { RULES_TABLE_INITIAL_PAGE_SIZE, RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants';
import { UpgradePrebuiltRulesTableButtons } from './upgrade_prebuilt_rules_table_buttons';
import { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_table_context';
Expand All @@ -36,24 +35,29 @@ const NO_ITEMS_MESSAGE = (
* Table Component for displaying rules that have available updates
*/
export const UpgradePrebuiltRulesTable = React.memo(() => {
const isUpgradingSecurityPackages = useIsUpgradingSecurityPackages();

const upgradeRulesTableContext = useUpgradePrebuiltRulesTableContext();

const {
state: { rules, filteredRules, isFetched, isLoading, isRefetching, selectedRules },
state: {
rules,
filteredRules,
isFetched,
isLoading,
selectedRules,
isRefetching,
isUpgradingSecurityPackages,
},
actions: { selectRules },
} = upgradeRulesTableContext;
const rulesColumns = useUpgradePrebuiltRulesTableColumns();

const isTableEmpty = isFetched && rules.length === 0;

const shouldShowLinearProgress = (isFetched && isRefetching) || isUpgradingSecurityPackages;
const shouldShowLoadingOverlay = !isFetched && isRefetching;
const shouldShowProgress = isUpgradingSecurityPackages || isRefetching;

return (
<>
{shouldShowLinearProgress && (
{shouldShowProgress && (
<EuiProgress
data-test-subj="loadingRulesInfoProgress"
size="xs"
Expand All @@ -62,7 +66,7 @@ export const UpgradePrebuiltRulesTable = React.memo(() => {
/>
)}
<EuiSkeletonLoading
isLoading={isLoading || shouldShowLoadingOverlay}
isLoading={isLoading}
loadingContent={
<>
<EuiSkeletonTitle />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_ta

export const UpgradePrebuiltRulesTableButtons = () => {
const {
state: { rules, selectedRules, loadingRules },
state: { rules, selectedRules, loadingRules, isRefetching, isUpgradingSecurityPackages },
actions: { upgradeAllRules, upgradeSelectedRules },
} = useUpgradePrebuiltRulesTableContext();

Expand All @@ -21,12 +21,13 @@ export const UpgradePrebuiltRulesTableButtons = () => {
const shouldDisplayUpgradeSelectedRulesButton = numberOfSelectedRules > 0;

const isRuleUpgrading = loadingRules.length > 0;
const isRequestInProgress = isRuleUpgrading || isRefetching || isUpgradingSecurityPackages;

return (
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}>
{shouldDisplayUpgradeSelectedRulesButton ? (
<EuiFlexItem grow={false}>
<EuiButton onClick={upgradeSelectedRules} disabled={isRuleUpgrading}>
<EuiButton onClick={upgradeSelectedRules} disabled={isRequestInProgress}>
<>
{i18n.UPDATE_SELECTED_RULES(numberOfSelectedRules)}
{isRuleUpgrading ? <EuiLoadingSpinner size="s" /> : undefined}
Expand All @@ -39,7 +40,7 @@ export const UpgradePrebuiltRulesTableButtons = () => {
fill
iconType="plusInCircle"
onClick={upgradeAllRules}
disabled={!isRulesAvailableForUpgrade || isRuleUpgrading}
disabled={!isRulesAvailableForUpgrade || isRequestInProgress}
>
{i18n.UPDATE_ALL}
{isRuleUpgrading ? <EuiLoadingSpinner size="s" /> : undefined}
Expand Down
Loading