[Security Solution][Endpoint][Admin][TA by Policy] Policy details trusted app tab downgrade experience (#114871)

This commit is contained in:
Candace Park 2021-10-15 15:28:19 -04:00 committed by GitHub
parent a8b4379523
commit c5f3be6979
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 140 additions and 46 deletions

View file

@ -10,6 +10,7 @@ import { EuiEmptyPrompt, EuiButton, EuiPageTemplate, EuiLink } from '@elastic/eu
import { FormattedMessage } from '@kbn/i18n/react';
import { usePolicyDetailsNavigateCallback } from '../../policy_hooks';
import { useGetLinkTo } from './use_policy_trusted_apps_empty_hooks';
import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/use_endpoint_privileges';
interface CommonProps {
policyId: string;
@ -17,6 +18,7 @@ interface CommonProps {
}
export const PolicyTrustedAppsEmptyUnassigned = memo<CommonProps>(({ policyId, policyName }) => {
const { isPlatinumPlus } = useEndpointPrivileges();
const navigateCallback = usePolicyDetailsNavigateCallback();
const { onClickHandler, toRouteUrl } = useGetLinkTo(policyId, policyName);
const onClickPrimaryButtonHandler = useCallback(
@ -47,12 +49,21 @@ export const PolicyTrustedAppsEmptyUnassigned = memo<CommonProps>(({ policyId, p
/>
}
actions={[
<EuiButton color="primary" fill onClick={onClickPrimaryButtonHandler}>
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.trustedApps.empty.unassigned.primaryAction"
defaultMessage="Assign trusted applications"
/>
</EuiButton>,
...(isPlatinumPlus
? [
<EuiButton
color="primary"
fill
onClick={onClickPrimaryButtonHandler}
data-test-subj="assign-ta-button"
>
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.trustedApps.empty.unassigned.primaryAction"
defaultMessage="Assign trusted applications"
/>
</EuiButton>,
]
: []),
// eslint-disable-next-line @elastic/eui/href-or-on-click
<EuiLink onClick={onClickHandler} href={toRouteUrl}>
<FormattedMessage

View file

@ -219,7 +219,7 @@ export const PolicyTrustedAppsFlyout = React.memo(() => {
title={
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.trustedApps.layout.flyout.noAssignable"
defaultMessage="There are no assignable Trused Apps to assign to this policy"
defaultMessage="There are no trusted applications that can be assigned to this policy."
/>
}
/>

View file

@ -19,8 +19,20 @@ import { createLoadedResourceState, isLoadedResourceState } from '../../../../..
import { getPolicyDetailsArtifactsListPath } from '../../../../../common/routing';
import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data';
import { policyListApiPathHandlers } from '../../../store/test_mock_utils';
import { licenseService } from '../../../../../../common/hooks/use_license';
jest.mock('../../../../trusted_apps/service');
jest.mock('../../../../../../common/hooks/use_license', () => {
const licenseServiceInstance = {
isPlatinumPlus: jest.fn(),
};
return {
licenseService: licenseServiceInstance,
useLicense: () => {
return licenseServiceInstance;
},
};
});
let mockedContext: AppContextTestRender;
let waitForAction: MiddlewareActionSpyHelper['waitForAction'];
@ -106,4 +118,31 @@ describe('Policy trusted apps layout', () => {
expect(component.getByTestId('policyDetailsTrustedAppsCount')).not.toBeNull();
});
it('should hide assign button on empty state with unassigned policies when downgraded to a gold or below license', async () => {
(licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false);
const component = render();
mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234'));
await waitForAction('assignedTrustedAppsListStateChanged');
mockedContext.store.dispatch({
type: 'policyArtifactsDeosAnyTrustedAppExists',
payload: createLoadedResourceState(true),
});
expect(component.queryByTestId('assign-ta-button')).toBeNull();
});
it('should hide the `Assign trusted applications` button when there is data and the license is downgraded to gold or below', async () => {
(licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false);
TrustedAppsHttpServiceMock.mockImplementation(() => {
return {
getTrustedAppsList: () => getMockListResponse(),
};
});
const component = render();
mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234'));
await waitForAction('assignedTrustedAppsListStateChanged');
expect(component.queryByTestId('assignTrustedAppButton')).toBeNull();
});
});

View file

@ -25,6 +25,7 @@ import {
import { usePolicyDetailsNavigateCallback, usePolicyDetailsSelector } from '../../policy_hooks';
import { PolicyTrustedAppsFlyout } from '../flyout';
import { PolicyTrustedAppsList } from '../list/policy_trusted_apps_list';
import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/use_endpoint_privileges';
export const PolicyTrustedAppsLayout = React.memo(() => {
const location = usePolicyDetailsSelector(getCurrentArtifactsLocation);
@ -33,6 +34,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => {
const policyItem = usePolicyDetailsSelector(policyDetails);
const navigateCallback = usePolicyDetailsNavigateCallback();
const hasAssignedTrustedApps = usePolicyDetailsSelector(doesPolicyHaveTrustedApps);
const { isPlatinumPlus } = useEndpointPrivileges();
const showListFlyout = location.show === 'list';
@ -41,6 +43,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => {
<EuiButton
fill
iconType="plusInCircle"
data-test-subj="assignTrustedAppButton"
onClick={() =>
navigateCallback({
show: 'list',
@ -88,7 +91,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => {
</h2>
</EuiTitle>
</EuiPageHeaderSection>
<EuiPageHeaderSection>{assignTrustedAppButton}</EuiPageHeaderSection>
<EuiPageHeaderSection>{isPlatinumPlus && assignTrustedAppButton}</EuiPageHeaderSection>
</EuiPageHeader>
) : null}
<EuiPageContent
@ -114,7 +117,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => {
<PolicyTrustedAppsList />
)}
</EuiPageContent>
{showListFlyout ? <PolicyTrustedAppsFlyout /> : null}
{isPlatinumPlus && showListFlyout ? <PolicyTrustedAppsFlyout /> : null}
</div>
) : null;
});

View file

@ -21,6 +21,13 @@ import {
} from '../../../../../state';
import { fireEvent, within, act, waitFor } from '@testing-library/react';
import { APP_ID } from '../../../../../../../common/constants';
import {
EndpointPrivileges,
useEndpointPrivileges,
} from '../../../../../../common/components/user_privileges/use_endpoint_privileges';
jest.mock('../../../../../../common/components/user_privileges/use_endpoint_privileges');
const mockUseEndpointPrivileges = useEndpointPrivileges as jest.Mock;
describe('when rendering the PolicyTrustedAppsList', () => {
// The index (zero based) of the card created by the generator that is policy specific
@ -32,6 +39,16 @@ describe('when rendering the PolicyTrustedAppsList', () => {
let mockedApis: ReturnType<typeof policyDetailsPageAllApiHttpMocks>;
let waitForAction: AppContextTestRender['middlewareSpy']['waitForAction'];
const loadedUserEndpointPrivilegesState = (
endpointOverrides: Partial<EndpointPrivileges> = {}
): EndpointPrivileges => ({
loading: false,
canAccessFleet: true,
canAccessEndpointManagement: true,
isPlatinumPlus: true,
...endpointOverrides,
});
const getCardByIndexPosition = (cardIndex: number = 0) => {
const card = renderResult.getAllByTestId('policyTrustedAppsGrid-card')[cardIndex];
@ -66,8 +83,12 @@ describe('when rendering the PolicyTrustedAppsList', () => {
);
};
afterAll(() => {
mockUseEndpointPrivileges.mockReset();
});
beforeEach(() => {
appTestContext = createAppRootMockRenderer();
mockUseEndpointPrivileges.mockReturnValue(loadedUserEndpointPrivilegesState());
mockedApis = policyDetailsPageAllApiHttpMocks(appTestContext.coreStart.http);
appTestContext.setExperimentalFlag({ trustedAppsByPolicyEnabled: true });
@ -297,4 +318,16 @@ describe('when rendering the PolicyTrustedAppsList', () => {
})
);
});
it('does not show remove option in actions menu if license is downgraded to gold or below', async () => {
await render();
mockUseEndpointPrivileges.mockReturnValue(
loadedUserEndpointPrivilegesState({
isPlatinumPlus: false,
})
);
await toggleCardActionMenu(POLICY_SPECIFIC_CARD_INDEX);
expect(renderResult.queryByTestId('policyTrustedAppsGrid-removeAction')).toBeNull();
});
});

View file

@ -38,6 +38,7 @@ import { ContextMenuItemNavByRouterProps } from '../../../../../components/conte
import { ArtifactEntryCollapsibleCardProps } from '../../../../../components/artifact_entry_card';
import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator';
import { RemoveTrustedAppFromPolicyModal } from './remove_trusted_app_from_policy_modal';
import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/use_endpoint_privileges';
const DATA_TEST_SUBJ = 'policyTrustedAppsGrid';
@ -46,6 +47,7 @@ export const PolicyTrustedAppsList = memo(() => {
const toasts = useToasts();
const history = useHistory();
const { getAppUrl } = useAppUrl();
const { isPlatinumPlus } = useEndpointPrivileges();
const policyId = usePolicyDetailsSelector(policyIdFromParams);
const hasTrustedApps = usePolicyDetailsSelector(doesPolicyHaveTrustedApps);
const isLoading = usePolicyDetailsSelector(isPolicyTrustedAppListLoading);
@ -132,44 +134,50 @@ export const PolicyTrustedAppsList = memo(() => {
return byIdPolicies;
}, {});
const fullDetailsAction: ArtifactCardGridCardComponentProps['actions'] = [
{
icon: 'controlsHorizontal',
children: i18n.translate(
'xpack.securitySolution.endpoint.policy.trustedApps.list.viewAction',
{ defaultMessage: 'View full details' }
),
href: getAppUrl({ appId: APP_ID, path: viewUrlPath }),
navigateAppId: APP_ID,
navigateOptions: { path: viewUrlPath },
'data-test-subj': getTestId('viewFullDetailsAction'),
},
];
const thisTrustedAppCardProps: ArtifactCardGridCardComponentProps = {
expanded: Boolean(isCardExpanded[trustedApp.id]),
actions: [
{
icon: 'controlsHorizontal',
children: i18n.translate(
'xpack.securitySolution.endpoint.policy.trustedApps.list.viewAction',
{ defaultMessage: 'View full details' }
),
href: getAppUrl({ appId: APP_ID, path: viewUrlPath }),
navigateAppId: APP_ID,
navigateOptions: { path: viewUrlPath },
'data-test-subj': getTestId('viewFullDetailsAction'),
},
{
icon: 'trash',
children: i18n.translate(
'xpack.securitySolution.endpoint.policy.trustedApps.list.removeAction',
{ defaultMessage: 'Remove from policy' }
),
onClick: () => {
setTrustedAppsForRemoval([trustedApp]);
setShowRemovalModal(true);
},
disabled: isGlobal,
toolTipContent: isGlobal
? i18n.translate(
'xpack.securitySolution.endpoint.policy.trustedApps.list.removeActionNotAllowed',
{
defaultMessage:
'Globally applied trusted applications cannot be removed from policy.',
}
)
: undefined,
toolTipPosition: 'top',
'data-test-subj': getTestId('removeAction'),
},
],
actions: isPlatinumPlus
? [
...fullDetailsAction,
{
icon: 'trash',
children: i18n.translate(
'xpack.securitySolution.endpoint.policy.trustedApps.list.removeAction',
{ defaultMessage: 'Remove from policy' }
),
onClick: () => {
setTrustedAppsForRemoval([trustedApp]);
setShowRemovalModal(true);
},
disabled: isGlobal,
toolTipContent: isGlobal
? i18n.translate(
'xpack.securitySolution.endpoint.policy.trustedApps.list.removeActionNotAllowed',
{
defaultMessage:
'Globally applied trusted applications cannot be removed from policy.',
}
)
: undefined,
toolTipPosition: 'top',
'data-test-subj': getTestId('removeAction'),
},
]
: fullDetailsAction,
policies: assignedPoliciesMenuItems,
};
@ -177,7 +185,7 @@ export const PolicyTrustedAppsList = memo(() => {
}
return newCardProps;
}, [allPoliciesById, getAppUrl, getTestId, isCardExpanded, trustedAppItems]);
}, [allPoliciesById, getAppUrl, getTestId, isCardExpanded, trustedAppItems, isPlatinumPlus]);
const provideCardProps = useCallback<Required<ArtifactCardGridProps>['cardComponentProps']>(
(item) => {