Skip to content

Commit

Permalink
Fix risk score missing privileges callout displayed on pages that onl…
Browse files Browse the repository at this point in the history
…y require read privileges
  • Loading branch information
machadoum committed Feb 27, 2025
1 parent a7857ef commit 9fe663c
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ const EntityAnalyticsRiskScoresComponent = <T extends EntityType>({
setUpdatedAt(Date.now());
}, [isTableLoading, isKpiLoading]); // Update the time when data loads

const privileges = useMissingRiskEnginePrivileges(['read']);
const privileges = useMissingRiskEnginePrivileges({ readonly: true });

if (!isAuthorized) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export const EnablementPanel: React.FC<EnableEntityStorePanelProps> = ({ state }

if (
riskEngineStatus !== RiskEngineStatusEnum.NOT_INSTALLED &&
(entityStoreStatus === 'running' || entityStoreStatus === 'stopped')
entityStoreStatus !== 'not_installed'
) {
return null;
}
Expand Down Expand Up @@ -224,14 +224,8 @@ export const EnablementPanel: React.FC<EnableEntityStorePanelProps> = ({ state }
visible={modal.visible}
toggle={(visible) => setModalState({ visible })}
enableStore={enableEntityStore}
riskScore={{
canToggle: riskEngineStatus === RiskEngineStatusEnum.NOT_INSTALLED,
checked: riskEngineStatus === RiskEngineStatusEnum.NOT_INSTALLED,
}}
entityStore={{
canToggle: entityStoreStatus !== 'running',
checked: entityStoreStatus === 'not_installed',
}}
riskEngineStatus={riskEngineStatus}
entityStoreStatus={entityStoreStatus}
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,20 @@ import {
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import type { RiskEngineStatus, StoreStatus } from '../../../../../common/api/entity_analytics';
import { RiskEngineStatusEnum } from '../../../../../common/api/entity_analytics';
import { useContractComponents } from '../../../../common/hooks/use_contract_component';
import {
ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY,
ENABLEMENT_DESCRIPTION_ENTITY_STORE_ONLY,
ENABLEMENT_WARNING_SELECT_TO_PROCEED,
} from '../translations';
import { useEntityEnginePrivileges } from '../hooks/use_entity_engine_privileges';
import { MissingPrivilegesCallout } from './missing_privileges_callout';
import { useMissingRiskEnginePrivileges } from '../../../hooks/use_missing_risk_engine_privileges';
import { RiskEnginePrivilegesCallOut } from '../../risk_engine_privileges_callout';
import { useEntityEnginePrivileges } from '../hooks/use_entity_engine_privileges';

export interface Enablements {
riskScore: boolean;
Expand All @@ -44,51 +46,70 @@ interface EntityStoreEnablementModalProps {
visible: boolean;
toggle: (visible: boolean) => void;
enableStore: (enablements: Enablements) => () => void;
riskScore: {
canToggle?: boolean;
checked?: boolean;
};
entityStore: {
canToggle?: boolean;
checked?: boolean;
};
riskEngineStatus?: RiskEngineStatus;
entityStoreStatus?: StoreStatus;
}

const shouldAllowEnablement = (
riskScoreEnabled: boolean,
entityStoreEnabled: boolean,
const isInstallButtonEnabled = (
canInstallRiskScore: boolean,
canInstallEntityStore: boolean,
userHasEnabled: Enablements
) => {
if (riskScoreEnabled) {
return userHasEnabled.entityStore;
}
if (entityStoreEnabled) {
return userHasEnabled.riskScore;
if (canInstallRiskScore || canInstallEntityStore) {
return userHasEnabled.riskScore || userHasEnabled.entityStore;
}
return userHasEnabled.riskScore || userHasEnabled.entityStore;

return false;
};

export const EntityStoreEnablementModal: React.FC<EntityStoreEnablementModalProps> = ({
visible,
toggle,
enableStore,
riskScore,
entityStore,
riskEngineStatus,
entityStoreStatus,
}) => {
const { euiTheme } = useEuiTheme();
const [enablements, setEnablements] = useState({
riskScore: !!riskScore.checked,
entityStore: !!entityStore.checked,
});
const riskEnginePrivileges = useMissingRiskEnginePrivileges();
const { data: entityEnginePrivileges, isLoading: isLoadingEntityEnginePrivileges } =
useEntityEnginePrivileges();
const riskEnginePrivileges = useMissingRiskEnginePrivileges();

const enablementOptions = shouldAllowEnablement(
!riskScore.canToggle,
!entityStore.canToggle,
enablements
const hasRiskScorePrivileges = !(
riskEnginePrivileges.isLoading || !riskEnginePrivileges?.hasAllRequiredPrivileges
);

const canInstallRiskScore =
hasRiskScorePrivileges && riskEngineStatus === RiskEngineStatusEnum.NOT_INSTALLED;

const hasEntityStorePrivileges = !(
isLoadingEntityEnginePrivileges || !entityEnginePrivileges?.has_all_required
);

const canInstallEntityStore = hasEntityStorePrivileges && entityStoreStatus === 'not_installed';

const { euiTheme } = useEuiTheme();
const [toggleState, setToggleState] = useState({
riskScore: false,
entityStore: false,
});

/**
* Update the toggle state when the install status changes because privileges are async.
* We automatically toggle the switch when the user can enable the engine.
*
*/
useEffect(() => {
setToggleState({
riskScore: canInstallRiskScore,
entityStore: canInstallEntityStore,
});
}, [canInstallRiskScore, canInstallEntityStore]);

const isInstallButtonDisabled = !isInstallButtonEnabled(
canInstallRiskScore,
canInstallEntityStore,
toggleState
);

const { AdditionalChargesMessage } = useContractComponents();

if (!visible) {
Expand Down Expand Up @@ -127,12 +148,9 @@ export const EntityStoreEnablementModal: React.FC<EntityStoreEnablementModalProp
defaultMessage="Risk Score"
/>
}
checked={enablements.riskScore}
disabled={
!riskScore.canToggle ||
(!riskEnginePrivileges.isLoading && !riskEnginePrivileges?.hasAllRequiredPrivileges)
}
onChange={() => setEnablements((prev) => ({ ...prev, riskScore: !prev.riskScore }))}
checked={toggleState.riskScore}
disabled={!canInstallRiskScore}
onChange={() => setToggleState((prev) => ({ ...prev, riskScore: !prev.riskScore }))}
data-test-subj="enablementRiskScoreSwitch"
/>
</EuiFlexItem>
Expand All @@ -154,13 +172,10 @@ export const EntityStoreEnablementModal: React.FC<EntityStoreEnablementModalProp
defaultMessage="Entity Store"
/>
}
checked={enablements.entityStore}
disabled={
!entityStore.canToggle ||
(!isLoadingEntityEnginePrivileges && !entityEnginePrivileges?.has_all_required)
}
checked={toggleState.entityStore}
disabled={!canInstallEntityStore}
onChange={() =>
setEnablements((prev) => ({ ...prev, entityStore: !prev.entityStore }))
setToggleState((prev) => ({ ...prev, entityStore: !prev.entityStore }))
}
data-test-subj="enablementEntityStoreSwitch"
/>
Expand All @@ -179,15 +194,17 @@ export const EntityStoreEnablementModal: React.FC<EntityStoreEnablementModalProp

<EuiModalFooter>
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
{!enablementOptions ? <EuiFlexItem>{proceedWarning}</EuiFlexItem> : null}
{isInstallButtonDisabled && (canInstallRiskScore || canInstallEntityStore) ? (
<EuiFlexItem>{proceedWarning}</EuiFlexItem>
) : null}
<EuiFlexItem grow={false}>
<EuiFlexGroup direction="row" justifyContent="flexEnd">
<EuiButtonEmpty onClick={() => toggle(false)}>{'Cancel'}</EuiButtonEmpty>
<EuiButton
onClick={enableStore(enablements)}
onClick={enableStore(toggleState)}
fill
isDisabled={!enablementOptions}
aria-disabled={!enablementOptions}
isDisabled={isInstallButtonDisabled}
aria-disabled={isInstallButtonDisabled}
data-test-subj="entityStoreEnablementModalButton"
>
<FormattedMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const RiskDetailsTabBodyComponent: React.FC<
[setContributorsToggleStatus]
);

const privileges = useMissingRiskEnginePrivileges();
const privileges = useMissingRiskEnginePrivileges({ readonly: true });

const RiskScoreUpsell = useUpsellingComponent('entity_analytics_panel');
if (RiskScoreUpsell) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const UserRiskScoreQueryTabBody = ({

const timerange = useMemo(() => ({ from, to }), [from, to]);

const privileges = useMissingRiskEnginePrivileges();
const privileges = useMissingRiskEnginePrivileges({ readonly: true });

const { data, inspect, isInspected, hasEngineBeenInstalled, loading, refetch, totalCount } =
useRiskScore({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ export type RiskEngineMissingPrivilegesResponse =
hasAllRequiredPrivileges: false;
};

interface UseMissingRiskEnginePrivilegesParams {
/**
* If `true`, only read privileges are required.
*/
readonly: boolean;
}
export const useMissingRiskEnginePrivileges = (
required: NonEmptyArray<RiskEngineIndexPrivilege> = ['read', 'write']
{ readonly }: UseMissingRiskEnginePrivilegesParams = { readonly: false }
): RiskEngineMissingPrivilegesResponse => {
const { data: privilegesResponse, isLoading } = useRiskEnginePrivileges();

Expand All @@ -41,16 +47,20 @@ export const useMissingRiskEnginePrivileges = (
};
}

const requiredIndexPrivileges: NonEmptyArray<RiskEngineIndexPrivilege> = readonly
? ['read']
: ['read', 'write'];

const { indexPrivileges, clusterPrivileges } = getMissingRiskEnginePrivileges(
privilegesResponse.privileges,
required
requiredIndexPrivileges
);

// privilegesResponse.has_all_required` is slightly misleading, it checks if it has *all* default required privileges.
// Here we check if there are no missing privileges of the provided set of required privileges
if (
indexPrivileges.every(([_, missingPrivileges]) => missingPrivileges.length === 0) &&
clusterPrivileges.length === 0
(readonly || clusterPrivileges.length === 0) // cluster privileges check is required for write operations
) {
return {
isLoading: false,
Expand All @@ -66,5 +76,5 @@ export const useMissingRiskEnginePrivileges = (
clusterPrivileges,
},
};
}, [isLoading, privilegesResponse, required]);
}, [isLoading, privilegesResponse, readonly]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const HostRiskScoreQueryTabBody = ({
}, [toggleStatus]);
const timerange = useMemo(() => ({ from, to }), [from, to]);

const privileges = useMissingRiskEnginePrivileges();
const privileges = useMissingRiskEnginePrivileges({ readonly: true });
const { data, inspect, isInspected, hasEngineBeenInstalled, loading, refetch, totalCount } =
useRiskScore({
filterQuery,
Expand Down

0 comments on commit 9fe663c

Please sign in to comment.