Skip to content

Commit

Permalink
Bulk unassign data policy for application
Browse files Browse the repository at this point in the history
Signed-off-by: Gowtham Shanmugasundaram <[email protected]>
  • Loading branch information
GowthamShanmugam committed Jun 21, 2023
1 parent 7561878 commit 9a4e5cf
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 24 deletions.
7 changes: 7 additions & 0 deletions locales/en/plugin__odf-console.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@
"Select the subscriptions groups you wish to replicate via": "Select the subscriptions groups you wish to replicate via",
"Manage Policy": "Manage Policy",
"Assign policy to protect the application and ensure quick recovery. Unassign policy from an application when they no longer require to be managed.": "Assign policy to protect the application and ensure quick recovery. Unassign policy from an application when they no longer require to be managed.",
"Manage list view alert": "Manage list view alert",
"Confirm unassign": "Confirm unassign",
"Policy type": "Policy type",
"Assigned on": "Assigned on",
"View configurations": "View configurations",
Expand All @@ -203,6 +205,11 @@
"Search input": "Search input",
"Secondary actions": "Secondary actions",
"Actions": "Actions",
"Selected policies ({{ count }}) will be removed for your application. This may have some affect on other applications sharing the placement._one": "Selected policies ({{ count }}) will be removed for your application. This may have some affect on other applications sharing the placement.",
"Selected policies ({{ count }}) will be removed for your application. This may have some affect on other applications sharing the placement._other": "Selected policies ({{ count }}) will be removed for your application. This may have some affect on other applications sharing the placement.",
"Selected policies ({{ count }}) unassigned for the application._one": "Selected policies ({{ count }}) unassigned for the application.",
"Selected policies ({{ count }}) unassigned for the application._other": "Selected policies ({{ count }}) unassigned for the application.",
"Unable to unassign all selected policies for the application.": "Unable to unassign all selected policies for the application.",
"My policies": "My policies",
"Assign policy": "Assign policy",
"List all the connected applications under a policy.": "List all the connected applications under a policy.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Modal, ModalVariant } from '@patternfly/react-core';
import { PolicyListView } from './policy-list-view';
import {
ManagePolicyStateType,
MessageType,
ModalActionContext,
ModalViewContext,
initialPolicyState,
Expand Down Expand Up @@ -39,12 +40,13 @@ export const AppManagePoliciesModal: React.FC<AppManagePoliciesModalProps> = ({
payload: modalViewContext,
});

const setError = (error: string) =>
const setMessage = (message: MessageType) => {
dispatch({
type: ManagePolicyStateType.SET_ERROR,
type: ManagePolicyStateType.SET_MESSAGE,
context: state.modalViewContext,
payload: error,
payload: message,
});
};

return (
<Modal
Expand All @@ -66,7 +68,7 @@ export const AppManagePoliciesModal: React.FC<AppManagePoliciesModalProps> = ({
dispatch={dispatch}
setModalContext={setModalContext}
setModalActionContext={setModalActionContext}
setError={setError}
setMessage={setMessage}
/>
)
) : (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as React from 'react';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import {
Alert,
AlertVariant,
Button,
ButtonVariant,
} from '@patternfly/react-core';
import { ModalActionContext, PolicyListViewState } from '../utils/reducer';
import '../../../../style.scss';

const AlertMessage: React.FC<AlertMessageType> = ({
title,
variant,
children,
}) => {
const { t } = useCustomTranslation();
return (
<Alert
area-label={t('Manage list view alert')}
title={title}
variant={variant}
isInline
className="odf-alert"
>
{children}
</Alert>
);
};

export const ListViewMessages: React.FC<ListViewMessagesProps> = ({
state,
OnCancel,
OnConfirm,
}) => {
const { t } = useCustomTranslation();
const { message, modalActionContext } = state;
return (
<>
{(modalActionContext === ModalActionContext.UN_ASSIGNING_POLICIES && (
<AlertMessage
title={message.title}
variant={message?.variant || AlertVariant.warning}
>
<Button variant={ButtonVariant.link} onClick={OnConfirm}>
{t('Confirm unassign')}
</Button>
<Button variant={ButtonVariant.link} onClick={OnCancel}>
{t('Cancel')}
</Button>
</AlertMessage>
)) ||
([
ModalActionContext.UN_ASSIGN_POLICIES_SUCCEEDED,
ModalActionContext.UN_ASSIGN_POLICIES_FAILED,
].includes(modalActionContext) && (
<AlertMessage
title={message.title}
variant={message?.variant || AlertVariant.info}
>
{message?.description}
</AlertMessage>
))}
</>
);
};

export type ListViewMessagesProps = {
state: PolicyListViewState;
OnCancel: () => void;
OnConfirm: () => void;
};

type AlertMessageType = {
title: string;
variant: AlertVariant;
children?: React.ReactNode;
};
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ const PolicyListViewTableRow: React.FC<RowComponentType<DataPolicyType>> = ({
const { t } = useCustomTranslation();
const columnNames = getColumnNames(t);
const { kind, isValidated, activity, assignedOn } = policy;
const { setPolicy, setModalContext }: RowExtraProps = extraProps;
const { isActionDisabled, setPolicy, setModalContext }: RowExtraProps =
extraProps;
const status = isValidated ? t('Validated') : t('Not Validated');
const assignedDateStr = utcDateTimeFormatter.format(new Date(assignedOn));

Expand Down Expand Up @@ -104,7 +105,7 @@ const PolicyListViewTableRow: React.FC<RowComponentType<DataPolicyType>> = ({
<Td translate={null} isActionCell>
<ActionsColumn
items={RowActions(t)}
isDisabled={!!policy?.metadata?.deletionTimestamp}
isDisabled={!!policy?.metadata?.deletionTimestamp || isActionDisabled}
/>
</Td>
</>
Expand All @@ -115,6 +116,7 @@ export const PolicyListViewTable: React.FC<PolicyListViewTableProps> = ({
policies,
selectedPolicies,
modalActionContext,
isActionDisabled,
setPolicy,
setPolicies,
setModalContext,
Expand Down Expand Up @@ -163,6 +165,7 @@ export const PolicyListViewTable: React.FC<PolicyListViewTableProps> = ({
selectedRows={selectedPolicies}
setSelectedRows={setSelectedRowList}
extraProps={{
isActionDisabled,
setPolicy,
setModalContext,
}}
Expand All @@ -172,6 +175,7 @@ export const PolicyListViewTable: React.FC<PolicyListViewTableProps> = ({
};

type RowExtraProps = {
isActionDisabled: boolean;
setModalContext: (modalViewContext: ModalViewContext) => void;
setPolicy: (
policies: DataPolicyType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { screen, render, fireEvent } from '@testing-library/react';
import { screen, render, fireEvent, waitFor } from '@testing-library/react';
import { PLACEMENT_REF_LABEL } from '../../../../constants';
import { ArgoApplicationSetResourceKind } from '../../../../hooks';
import { DisasterRecoveryResourceKind } from '../../../../hooks/disaster-recovery';
Expand Down Expand Up @@ -191,6 +191,9 @@ jest.mock('@odf/mco/hooks/argo-application-set', () => ({
__esModule: true,
useArgoApplicationSetResourceWatch: () => [appResources, true, ''],
}));
jest.mock('../utils/k8s-utils', () => ({
unAssignPromises: jest.fn(() => [Promise.resolve({ data: {} })]),
}));

describe('ApplicationSet manage data policy modal', () => {
beforeEach(async () => {
Expand Down Expand Up @@ -248,4 +251,32 @@ describe('ApplicationSet manage data policy modal', () => {
expect(screen.getByText('Not found')).toBeInTheDocument();
fireEvent.change(searchBox, { target: { value: '' } });
});

test('Bulk unassign policy action test', async () => {
// Select all policy
fireEvent.click(screen.getByLabelText('Select all rows'));
// Check primary action is disabled
expect(screen.getByText('Assign policy')).toBeDisabled();
// Check secondary action is enabled
expect(screen.getByLabelText('Select input')).toBeEnabled();
// Trigger bulk unassign
fireEvent.click(screen.getByLabelText('Select input'));
fireEvent.click(screen.getByText('Unassign policy'));
expect(
screen.getByText(
'Selected policies ({{ count }}) will be removed for your application. This may have some affect on other applications sharing the placement.'
)
).toBeInTheDocument();
expect(screen.getByText('Cancel')).toBeInTheDocument();
expect(screen.getByText('Confirm unassign')).toBeInTheDocument();
await waitFor(() => {
fireEvent.click(screen.getByText('Confirm unassign'));
});
// Confirm unassign is successful
expect(
screen.getByText(
'Selected policies ({{ count }}) unassigned for the application.'
)
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ActionDropdown } from '@odf/shared/dropdown/action-dropdown';
import { ModalBody } from '@odf/shared/modals/Modal';
import { getName } from '@odf/shared/selectors';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import { TFunction } from 'i18next';
import { getErrorMessage } from '@odf/shared/utils';
import {
Button,
Pagination,
Expand All @@ -13,16 +13,20 @@ import {
ToolbarContent,
ToolbarItem,
Text,
AlertVariant,
} from '@patternfly/react-core';
import { ListViewMessages } from './helper/messages';
import { PolicyListViewTable } from './helper/policy-list-view-table';
import { unAssignPromises } from './utils/k8s-utils';
import {
ManagePolicyStateType,
MessageType,
ModalActionContext,
ModalViewContext,
PolicyListViewState,
} from './utils/reducer';
import { ManagePolicyStateAction } from './utils/reducer';
import { DataPolicyType } from './utils/types';
import { DataPolicyType, DRPlacementControlType } from './utils/types';
import './style.scss';

const INITIAL_PAGE_NUMBER = 1;
Expand All @@ -40,12 +44,14 @@ const filterPolicies = (dataPolicyInfo: DataPolicyType[], searchText: string) =>
);

export const PolicyListViewToolBar: React.FC<PolicyListViewToolBarProps> = ({
selectedPolicyCount,
searchText,
isActionDisabled,
onSearchChange,
setModalActionContext,
t,
setMessage,
}) => {
const { t } = useCustomTranslation();
return (
<Toolbar>
<ToolbarContent>
Expand All @@ -64,7 +70,15 @@ export const PolicyListViewToolBar: React.FC<PolicyListViewToolBarProps> = ({
text={t('Actions')}
toggleVariant={'primary'}
isDisabled={isActionDisabled}
onSelect={(id: ModalActionContext) => setModalActionContext(id)}
onSelect={(id: ModalActionContext) => {
setModalActionContext(id);
setMessage({
title: t(
'Selected policies ({{ count }}) will be removed for your application. This may have some affect on other applications sharing the placement.',
{ count: selectedPolicyCount }
),
});
}}
dropdownItems={[
{
id: ModalActionContext.UN_ASSIGNING_POLICIES,
Expand All @@ -84,6 +98,7 @@ export const PolicyListView: React.FC<PolicyListViewProps> = ({
dispatch,
setModalContext,
setModalActionContext,
setMessage,
}) => {
const { t } = useCustomTranslation();
const [page, setPage] = React.useState(INITIAL_PAGE_NUMBER);
Expand All @@ -110,6 +125,41 @@ export const PolicyListView: React.FC<PolicyListViewProps> = ({
payload: policy,
});

const unAssignPolicies = () => {
// unassign DRPolicy
const drpcs: DRPlacementControlType[] = state.policies?.reduce(
(acc, policy) => [...acc, ...policy?.placementControInfo],
[]
);
const promises = unAssignPromises(drpcs);
Promise.all(promises)
.then(() => {
setMessage({
title: t(
'Selected policies ({{ count }}) unassigned for the application.',
{ count: state.policies.length }
),
variant: AlertVariant.success,
});
dispatch({
type: ManagePolicyStateType.SET_SELECTED_POLICIES,
context: ModalViewContext.POLICY_LIST_VIEW,
payload: [],
});
setModalActionContext(ModalActionContext.UN_ASSIGN_POLICIES_SUCCEEDED);
})
.catch((error) => {
setMessage({
title: t(
'Unable to unassign all selected policies for the application.'
),
description: getErrorMessage(error),
variant: AlertVariant.danger,
});
setModalActionContext(ModalActionContext.UN_ASSIGN_POLICIES_FAILED);
});
};

return (
<ModalBody>
<div className="mco-manage-policies__header">
Expand All @@ -124,17 +174,24 @@ export const PolicyListView: React.FC<PolicyListViewProps> = ({
</Button>
</div>
<PolicyListViewToolBar
selectedPolicyCount={state.policies.length}
searchText={searchText}
isActionDisabled={!state.policies.length}
onSearchChange={onSearchChange}
setModalActionContext={setModalActionContext}
t={t}
setMessage={setMessage}
/>
<div className="mco-manage-policies__col-padding">
<ListViewMessages
state={state}
OnCancel={() => setModalActionContext(null)}
OnConfirm={unAssignPolicies}
/>
<PolicyListViewTable
policies={paginatedPolicies}
selectedPolicies={state.policies}
modalActionContext={state.modalActionContext}
isActionDisabled={!!state.policies.length}
setModalActionContext={setModalActionContext}
setModalContext={setModalContext}
setPolicies={setPolicies}
Expand Down Expand Up @@ -167,13 +224,14 @@ type PolicyListViewProps = {
dispatch: React.Dispatch<ManagePolicyStateAction>;
setModalContext: (modalViewContext: ModalViewContext) => void;
setModalActionContext: (modalActionContext: ModalActionContext) => void;
setError: (error: string) => void;
setMessage: (error: MessageType) => void;
};

type PolicyListViewToolBarProps = {
selectedPolicyCount: number;
searchText: string;
isActionDisabled: boolean;
onSearchChange: React.Dispatch<React.SetStateAction<string>>;
setModalActionContext: (modalActionContext: ModalActionContext) => void;
t: TFunction;
setMessage: (error: MessageType) => void;
};
Loading

0 comments on commit 9a4e5cf

Please sign in to comment.