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

[Advanced Approval Workflows] Update pendingAction/pendingFields when making an action offline #48086

19 changes: 17 additions & 2 deletions src/libs/WorkflowUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,17 @@ function convertPolicyEmployeesToApprovalWorkflows({employees, defaultApprover,

// Add each employee to the appropriate workflow
Object.values(employees).forEach((employee) => {
const {email, submitsTo} = employee;
const {email, submitsTo, pendingAction} = employee;
if (!email || !submitsTo) {
return;
}

const member: Member = {email, avatar: personalDetailsByEmail[email]?.avatar, displayName: personalDetailsByEmail[email]?.displayName ?? email};
const member: Member = {
email,
avatar: personalDetailsByEmail[email]?.avatar,
displayName: personalDetailsByEmail[email]?.displayName ?? email,
};

if (!approvalWorkflows[submitsTo]) {
const approvers = calculateApprovers({employees, firstEmail: submitsTo, personalDetailsByEmail});
if (submitsTo !== firstApprover) {
Expand All @@ -132,9 +137,14 @@ function convertPolicyEmployeesToApprovalWorkflows({employees, defaultApprover,
members: [],
approvers,
isDefault: defaultApprover === submitsTo,
pendingAction,
};
}

approvalWorkflows[submitsTo].members.push(member);
if (pendingAction) {
approvalWorkflows[submitsTo].pendingAction = pendingAction;
}
});

// Sort the workflows by the first approver's name (default workflow has priority)
Expand Down Expand Up @@ -189,25 +199,30 @@ function convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, membersToRe
throw new Error('Approval workflow must have at least one approver');
}

const pendingAction = type === CONST.APPROVAL_WORKFLOW.TYPE.CREATE ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE;

approvalWorkflow.approvers.forEach((approver, index) => {
const nextApprover = approvalWorkflow.approvers.at(index + 1);
updatedEmployeeList[approver.email] = {
email: approver.email,
forwardsTo: type === CONST.APPROVAL_WORKFLOW.TYPE.REMOVE ? '' : nextApprover?.email ?? '',
pendingAction,
};
});

approvalWorkflow.members.forEach(({email}) => {
updatedEmployeeList[email] = {
...(updatedEmployeeList[email] ? updatedEmployeeList[email] : {email}),
submitsTo: type === CONST.APPROVAL_WORKFLOW.TYPE.REMOVE ? '' : firstApprover.email ?? '',
pendingAction,
};
});

membersToRemove?.forEach(({email}) => {
updatedEmployeeList[email] = {
...(updatedEmployeeList[email] ? updatedEmployeeList[email] : {email}),
submitsTo: '',
pendingAction,
};
});

Expand Down
23 changes: 6 additions & 17 deletions src/libs/actions/Workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function createApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
return;
}

const previousEmployeeList = {...policy.employeeList};
const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value, pendingAction: null}]));
const previousApprovalMode = policy.approvalMode;
const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.CREATE});

Expand All @@ -70,7 +70,6 @@ function createApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
value: {
employeeList: updatedEmployees,
approvalMode: CONST.POLICY.APPROVAL_MODE.ADVANCED,
pendingFields: {employeeList: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD},
},
},
];
Expand All @@ -87,7 +86,6 @@ function createApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
value: {
employeeList: previousEmployeeList,
approvalMode: previousApprovalMode,
pendingFields: {employeeList: null},
},
},
];
Expand All @@ -102,7 +100,7 @@ function createApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
pendingFields: {employeeList: null},
employeeList: Object.fromEntries(Object.keys(updatedEmployees).map((key) => [key, {pendingAction: null}])),
},
},
];
Expand All @@ -120,7 +118,7 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork

const previousDefaultApprover = policy.approver ?? policy.owner;
const newDefaultApprover = approvalWorkflow.isDefault ? approvalWorkflow.approvers[0].email : undefined;
const previousEmployeeList = {...policy.employeeList};
const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value, pendingAction: null}]));
const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.UPDATE, membersToRemove});

const optimisticData: OnyxUpdate[] = [
Expand All @@ -136,7 +134,6 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
employeeList: updatedEmployees,
pendingFields: {employeeList: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE},
...(newDefaultApprover ? {approver: newDefaultApprover} : {}),
},
},
Expand Down Expand Up @@ -169,7 +166,7 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
pendingFields: {employeeList: null},
employeeList: Object.fromEntries(Object.keys(updatedEmployees).map((key) => [key, {pendingAction: null}])),
},
},
];
Expand All @@ -190,8 +187,8 @@ function removeApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
return;
}

const previousEmployeeList = {...policy.employeeList};
const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.REMOVE});
const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value, pendingAction: null}]));
const updatedEmployeeList = {...previousEmployeeList, ...updatedEmployees};

const defaultApprover = policy.approver ?? policy.owner;
Expand All @@ -212,7 +209,6 @@ function removeApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
value: {
employeeList: updatedEmployees,
approvalMode: hasMoreThanOneWorkflow ? CONST.POLICY.APPROVAL_MODE.ADVANCED : CONST.POLICY.APPROVAL_MODE.BASIC,
pendingFields: {employeeList: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE},
},
},
];
Expand All @@ -231,13 +227,6 @@ function removeApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
approvalMode: CONST.POLICY.APPROVAL_MODE.ADVANCED,
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
pendingFields: {employeeList: null},
},
},
];

const successData: OnyxUpdate[] = [
Expand All @@ -250,7 +239,7 @@ function removeApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
pendingFields: {employeeList: null},
employeeList: Object.fromEntries(Object.keys(updatedEmployees).map((key) => [key, {pendingAction: null}])),
},
},
];
Expand Down
2 changes: 1 addition & 1 deletion src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
<OfflineWithFeedback
// eslint-disable-next-line react/no-array-index-key
key={`workflow-${index}`}
pendingAction={policy?.pendingFields?.employeeList}
pendingAction={workflow.pendingAction}
>
<ApprovalWorkflowSection
approvalWorkflow={workflow}
Expand Down
5 changes: 3 additions & 2 deletions src/types/onyx/ApprovalWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {ValueOf} from 'type-fest';
import type {AvatarSource} from '@libs/UserUtils';
import type CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import type {OnyxValueWithOfflineFeedback} from './OnyxCommon';

/**
* Approver in the approval workflow
Expand Down Expand Up @@ -59,7 +60,7 @@ type Member = {
/**
* Approval workflow for a group of employees
*/
type ApprovalWorkflow = {
type ApprovalWorkflow = OnyxValueWithOfflineFeedback<{
/**
* List of member emails in the workflow
*/
Expand All @@ -76,7 +77,7 @@ type ApprovalWorkflow = {
* Is this the default workflow for the policy (first approver of this workflow is the same as the policy's default approver)
*/
isDefault: boolean;
};
}>;

/**
* Approval workflow for a group of employees with additional properties for the Onyx store
Expand Down
2 changes: 1 addition & 1 deletion src/types/onyx/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1564,7 +1564,7 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback<
/** Workspace account ID configured for Expensify Card */
workspaceAccountID?: number;
} & Partial<PendingJoinRequestPolicy>,
'addWorkspaceRoom' | 'employeeList' | keyof ACHAccount | keyof Attributes
'addWorkspaceRoom' | keyof ACHAccount | keyof Attributes
>;

/** Stages of policy connection sync */
Expand Down
Loading