Skip to content

Commit

Permalink
[Security Solution][Endpoint][Admin][TA by Policy] Policy details tru…
Browse files Browse the repository at this point in the history
…sted app tab downgrade experience (#114871)
  • Loading branch information
parkiino authored Oct 15, 2021
1 parent a8b4379 commit c5f3be6
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ 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;
policyName: string;
}

export const PolicyTrustedAppsEmptyUnassigned = memo<CommonProps>(({ policyId, policyName }) => {
const { isPlatinumPlus } = useEndpointPrivileges();
const navigateCallback = usePolicyDetailsNavigateCallback();
const { onClickHandler, toRouteUrl } = useGetLinkTo(policyId, policyName);
const onClickPrimaryButtonHandler = useCallback(
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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."
/>
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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';

Expand All @@ -41,6 +43,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => {
<EuiButton
fill
iconType="plusInCircle"
data-test-subj="assignTrustedAppButton"
onClick={() =>
navigateCallback({
show: 'list',
Expand Down Expand Up @@ -88,7 +91,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => {
</h2>
</EuiTitle>
</EuiPageHeaderSection>
<EuiPageHeaderSection>{assignTrustedAppButton}</EuiPageHeaderSection>
<EuiPageHeaderSection>{isPlatinumPlus && assignTrustedAppButton}</EuiPageHeaderSection>
</EuiPageHeader>
) : null}
<EuiPageContent
Expand All @@ -114,7 +117,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => {
<PolicyTrustedAppsList />
)}
</EuiPageContent>
{showListFlyout ? <PolicyTrustedAppsFlyout /> : null}
{isPlatinumPlus && showListFlyout ? <PolicyTrustedAppsFlyout /> : null}
</div>
) : null;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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];

Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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);
Expand Down Expand Up @@ -132,52 +134,58 @@ 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,
};

newCardProps.set(trustedApp, thisTrustedAppCardProps);
}

return newCardProps;
}, [allPoliciesById, getAppUrl, getTestId, isCardExpanded, trustedAppItems]);
}, [allPoliciesById, getAppUrl, getTestId, isCardExpanded, trustedAppItems, isPlatinumPlus]);

const provideCardProps = useCallback<Required<ArtifactCardGridProps>['cardComponentProps']>(
(item) => {
Expand Down

0 comments on commit c5f3be6

Please sign in to comment.