Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/libs/WorkflowUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,11 @@ function convertApprovalWorkflowToPolicyEmployees({
continue;
}

const previousPendingAction = previousEmployeeList[approver.email]?.pendingAction;
updatedEmployeeList[approver.email] = {
email: approver.email,
forwardsTo,
pendingAction,
pendingAction: previousPendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? previousPendingAction : pendingAction,
pendingFields: {
forwardsTo: pendingAction,
},
Expand All @@ -281,10 +282,11 @@ function convertApprovalWorkflowToPolicyEmployees({
continue;
}

const previousPendingAction = previousEmployeeList[email]?.pendingAction;
updatedEmployeeList[email] = {
...(updatedEmployeeList[email] ? updatedEmployeeList[email] : {email}),
submitsTo,
pendingAction,
pendingAction: previousPendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? previousPendingAction : pendingAction,
pendingFields: {
submitsTo: pendingAction,
},
Expand All @@ -295,10 +297,11 @@ function convertApprovalWorkflowToPolicyEmployees({
// which will set the submitsTo field to the default approver email on backend.
if (membersToRemove) {
for (const {email} of membersToRemove) {
const previousPendingAction = previousEmployeeList[email]?.pendingAction;
updatedEmployeeList[email] = {
...(updatedEmployeeList[email] ? updatedEmployeeList[email] : {email}),
submitsTo: defaultApprover,
pendingAction,
pendingAction: previousPendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? previousPendingAction : pendingAction,
};
}
}
Expand All @@ -307,10 +310,11 @@ function convertApprovalWorkflowToPolicyEmployees({
// which will reset the forwardsTo on the backend.
if (approversToRemove) {
for (const {email} of approversToRemove) {
const previousPendingAction = previousEmployeeList[email]?.pendingAction;
updatedEmployeeList[email] = {
...(updatedEmployeeList[email] ? updatedEmployeeList[email] : {email}),
forwardsTo: '',
pendingAction,
pendingAction: previousPendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? previousPendingAction : pendingAction,
};
}
}
Expand Down
120 changes: 91 additions & 29 deletions src/libs/actions/Policy/Member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,31 @@
import {getDefaultApprover, isUserPolicyAdmin} from '@libs/PolicyUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import {updateWorkflowDataOnApproverRemoval} from '@libs/WorkflowUtils';
import * as FormActions from '@userActions/FormActions';
import {getRemoveApprovalWorkflowOnyxData, getUpdateApprovalWorkflowOnyxData} from '@userActions/Workflow';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {ImportedSpreadsheetMemberData, InvitedEmailsToAccountIDs, Policy, PolicyEmployee, PolicyOwnershipChangeChecks, Report, ReportAction, ReportActions} from '@src/types/onyx';
import type {
ImportedSpreadsheetMemberData,
InvitedEmailsToAccountIDs,
PersonalDetails,
PersonalDetailsList,
Policy,
PolicyEmployee,
PolicyOwnershipChangeChecks,
Report,
ReportAction,
ReportActions,
} from '@src/types/onyx';
import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow';
import type {PendingAction} from '@src/types/onyx/OnyxCommon';
import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage';
import type {ApprovalRule} from '@src/types/onyx/Policy';
import type {NotificationPreference, Participant} from '@src/types/onyx/Report';
import type {OnyxData} from '@src/types/onyx/Request';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {createPolicyExpenseChats} from './Policy';
import {createPolicyExpenseChats, getSetPolicyPreventSelfApprovalOnyxData} from './Policy';

type OnyxDataReturnType = {
optimisticData: OnyxUpdate[];
Expand All @@ -48,7 +62,7 @@
};

const allPolicies: OnyxCollection<Policy> = {};
Onyx.connect({

Check warning on line 65 in src/libs/actions/Policy/Member.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
callback: (val, key) => {
if (!key) {
Expand All @@ -64,7 +78,7 @@
});

let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 81 in src/libs/actions/Policy/Member.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => (allReportActions = actions),
Expand All @@ -72,7 +86,7 @@

let sessionEmail = '';
let sessionAccountID = 0;
Onyx.connect({

Check warning on line 89 in src/libs/actions/Policy/Member.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (val) => {
sessionEmail = val?.email ?? '';
Expand All @@ -81,7 +95,7 @@
});

let policyOwnershipChecks: Record<string, PolicyOwnershipChangeChecks>;
Onyx.connect({

Check warning on line 98 in src/libs/actions/Policy/Member.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.POLICY_OWNERSHIP_CHANGE_CHECKS,
callback: (value) => {
policyOwnershipChecks = value ?? {};
Expand Down Expand Up @@ -395,7 +409,13 @@
* Remove the passed members from the policy employeeList
* Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details
*/
function removeMembers(policyID: string, selectedMemberEmails: string[], policyMemberEmailsToAccountIDs: Record<string, number>) {
function removeMembers(
policyID: string,
selectedMemberEmails: string[],
policyMemberEmailsToAccountIDs: Record<string, number>,
approvalWorkflows: ApprovalWorkflow[],
allPersonalDetails: OnyxEntry<PersonalDetailsList>,
) {
if (selectedMemberEmails.length === 0) {
return;
}
Expand All @@ -407,6 +427,44 @@
// eslint-disable-next-line @typescript-eslint/no-deprecated
const policy = getPolicy(policyID);

const optimisticData: OnyxUpdate[] = [];
const successData: OnyxUpdate[] = [];
const failureData: OnyxUpdate[] = [];

// Update approval workflows after member removal
// Check if any of the account IDs are approvers
const hasApprovers = selectedMemberEmails.some((selectedMemberEmail) => isApprover(policy, selectedMemberEmail));
const ownerDetails = allPersonalDetails?.[policy?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID] ?? ({} as PersonalDetails);

if (hasApprovers) {
const ownerEmail = ownerDetails.login;
accountIDs.forEach((accountID) => {
const removedApprover = allPersonalDetails?.[accountID];
if (!removedApprover?.login || !ownerEmail) {
return;
}
const updatedWorkflows = updateWorkflowDataOnApproverRemoval({
approvalWorkflows,
removedApprover,
ownerDetails,
});
updatedWorkflows.forEach((workflow) => {
if (workflow?.removeApprovalWorkflow) {
const {removeApprovalWorkflow, ...updatedWorkflow} = workflow;
const onyxDataForRemoveApprovalWorkflow = getRemoveApprovalWorkflowOnyxData(updatedWorkflow, policy);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it let and move the onyx update assignment out of if... else... statement

optimisticData.push(...(onyxDataForRemoveApprovalWorkflow.optimisticData ?? []));
successData.push(...(onyxDataForRemoveApprovalWorkflow.successData ?? []));
failureData.push(...(onyxDataForRemoveApprovalWorkflow.failureData ?? []));
} else {
const onyxDataForUpdateApprovalWorkflow = getUpdateApprovalWorkflowOnyxData(workflow, [], [], policy);
optimisticData.push(...(onyxDataForUpdateApprovalWorkflow.optimisticData ?? []));
successData.push(...(onyxDataForUpdateApprovalWorkflow.successData ?? []));
failureData.push(...(onyxDataForUpdateApprovalWorkflow.failureData ?? []));
}
});
});
}

const workspaceChats = ReportUtils.getWorkspaceChats(policyID, accountIDs);
const optimisticClosedReportActions = workspaceChats.map(() =>
ReportUtils.buildOptimisticClosedReportAction(sessionEmail, policy?.name ?? '', CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY),
Expand Down Expand Up @@ -477,38 +535,32 @@
const approvalRules: ApprovalRule[] = policy?.rules?.approvalRules ?? [];
const optimisticApprovalRules = approvalRules.filter((rule) => !selectedMemberEmails.includes(rule?.approver ?? ''));

const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: policyKey,
value: {
employeeList: optimisticMembersState,
approver: selectedMemberEmails.includes(policy?.approver ?? '') ? policy?.owner : policy?.approver,
rules: {
...(policy?.rules ?? {}),
approvalRules: optimisticApprovalRules,
},
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: policyKey,
value: {
employeeList: optimisticMembersState,
approver: selectedMemberEmails.includes(policy?.approver ?? '') ? policy?.owner : policy?.approver,
rules: {
...(policy?.rules ?? {}),
approvalRules: optimisticApprovalRules,
},
},
];
});
optimisticData.push(...announceRoomMembers.optimisticData, ...adminRoomMembers.optimisticData, ...preferredExporterOnyxData.optimisticData);

const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: policyKey,
value: {employeeList: successMembersState},
},
];
successData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: policyKey,
value: {employeeList: successMembersState},
});
successData.push(...announceRoomMembers.successData, ...adminRoomMembers.successData, ...preferredExporterOnyxData.successData);

const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: policyKey,
value: {employeeList: failureMembersState, approver: policy?.approver, rules: policy?.rules},
},
];
failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: policyKey,
value: {employeeList: failureMembersState, approver: policy?.approver, rules: policy?.rules},
});
failureData.push(...announceRoomMembers.failureData, ...adminRoomMembers.failureData, ...preferredExporterOnyxData.failureData);

const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
Expand Down Expand Up @@ -647,6 +699,16 @@
policyID,
};

// Update "Prevent Self Approvals" after member removal

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Update "Prevent Self Approvals" after member removal

const previousEmployeesCount = Object.values(policy?.employeeList ?? {}).filter((employee) => employee.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length;
const remainingEmployeeCount = previousEmployeesCount - accountIDs.length;
if (remainingEmployeeCount === 1 && policy?.preventSelfApproval) {
const onyxDataForSetPolicyPreventSelfApproval = getSetPolicyPreventSelfApprovalOnyxData(policyID, false);
optimisticData.push(...(onyxDataForSetPolicyPreventSelfApproval.optimisticData ?? []));
successData.push(...(onyxDataForSetPolicyPreventSelfApproval.successData ?? []));
failureData.push(...(onyxDataForSetPolicyPreventSelfApproval.failureData ?? []));
}

Comment on lines +703 to +711

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this above const params

API.write(WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData});
}

Expand Down
29 changes: 22 additions & 7 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@
};

const allPolicies: OnyxCollection<Policy> = {};
Onyx.connect({

Check warning on line 206 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
callback: (val, key) => {
if (!key) {
Expand All @@ -219,7 +219,7 @@
});

let allReports: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 222 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -228,7 +228,7 @@
});

let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 231 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand All @@ -238,7 +238,7 @@

let sessionEmail = '';
let sessionAccountID = 0;
Onyx.connect({

Check warning on line 241 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (val) => {
sessionEmail = val?.email ?? '';
Expand All @@ -247,13 +247,13 @@
});

let allPersonalDetails: OnyxEntry<PersonalDetailsList>;
Onyx.connect({

Check warning on line 250 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (val) => (allPersonalDetails = val),
});

let allRecentlyUsedCurrencies: string[];
Onyx.connect({

Check warning on line 256 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.RECENTLY_USED_CURRENCIES,
callback: (val) => (allRecentlyUsedCurrencies = val ?? []),
});
Expand Down Expand Up @@ -5677,18 +5677,13 @@
});
}

/**
* Call the API to enable or disable self approvals for the reports
* @param policyID - id of the policy to apply the naming pattern to
* @param preventSelfApproval - flag whether to prevent workspace members from approving their own expense reports
*/
function setPolicyPreventSelfApproval(policyID: string, preventSelfApproval: boolean) {
function getSetPolicyPreventSelfApprovalOnyxData(policyID: string, preventSelfApproval: boolean): OnyxData {
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
// eslint-disable-next-line @typescript-eslint/no-deprecated
const policy = getPolicy(policyID);

if (preventSelfApproval === policy?.preventSelfApproval) {
return;
return {};
}

const optimisticData: OnyxUpdate[] = [
Expand Down Expand Up @@ -5733,6 +5728,25 @@
},
];

return {optimisticData, failureData, successData};
}

/**
* Call the API to enable or disable self approvals for the reports
* @param policyID - id of the policy to apply the naming pattern to
* @param preventSelfApproval - flag whether to prevent workspace members from approving their own expense reports
*/
function setPolicyPreventSelfApproval(policyID: string, preventSelfApproval: boolean) {
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
// eslint-disable-next-line @typescript-eslint/no-deprecated
const policy = getPolicy(policyID);

if (preventSelfApproval === policy?.preventSelfApproval) {
return;
}

const {optimisticData, failureData, successData} = getSetPolicyPreventSelfApprovalOnyxData(policyID, preventSelfApproval);

const parameters: SetPolicyPreventSelfApprovalParams = {
preventSelfApproval,
policyID,
Expand Down Expand Up @@ -6532,4 +6546,5 @@
clearPolicyTitleFieldError,
inviteWorkspaceEmployeesToUber,
setWorkspaceConfirmationCurrency,
getSetPolicyPreventSelfApprovalOnyxData,
};
55 changes: 49 additions & 6 deletions src/libs/actions/Workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type {ApprovalWorkflowOnyx, PersonalDetailsList, Policy} from '@src/types/onyx';
import type {Approver, Member} from '@src/types/onyx/ApprovalWorkflow';
import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow';
import type {OnyxData} from '@src/types/onyx/Request';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

type SetApprovalWorkflowApproverParams = {
Expand Down Expand Up @@ -82,9 +83,9 @@ function createApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, policy: Onyx
API.write(WRITE_COMMANDS.CREATE_WORKSPACE_APPROVAL, parameters, {optimisticData, failureData, successData});
}

function updateApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, membersToRemove: Member[], approversToRemove: Approver[], policy: OnyxEntry<Policy>) {
function getUpdateApprovalWorkflowOnyxData(approvalWorkflow: ApprovalWorkflow, membersToRemove: Member[], approversToRemove: Approver[], policy: OnyxEntry<Policy>): OnyxData {
if (!policy) {
return;
return {};
}

const previousDefaultApprover = getDefaultApprover(policy);
Expand All @@ -101,7 +102,7 @@ function updateApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, membersToRem

// If there are no changes to the employees list, we can exit early
if (isEmptyObject(updatedEmployees) && !newDefaultApprover) {
return;
return {};
}

const optimisticData: OnyxUpdate[] = [
Expand Down Expand Up @@ -142,6 +143,33 @@ function updateApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, membersToRem
},
];

return {optimisticData, failureData, successData};
}

function updateApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, membersToRemove: Member[], approversToRemove: Approver[], policy: OnyxEntry<Policy>) {
if (!policy) {
return;
}

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

// If there are no changes to the employees list, we can exit early
if (isEmptyObject(updatedEmployees) && !newDefaultApprover) {
return;
}
Comment on lines +154 to +169

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we return this from getUpdateApprovalWorkflowOnyxData?


const {optimisticData, failureData, successData} = getUpdateApprovalWorkflowOnyxData(approvalWorkflow, membersToRemove, approversToRemove, policy);

const parameters: UpdateWorkspaceApprovalParams = {
policyID: policy.id,
employees: JSON.stringify(Object.values(updatedEmployees)),
Expand All @@ -150,12 +178,12 @@ function updateApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, membersToRem
API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_APPROVAL, parameters, {optimisticData, failureData, successData});
}

function removeApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, policy: OnyxEntry<Policy>) {
function getRemoveApprovalWorkflowOnyxData(approvalWorkflow: ApprovalWorkflow, policy: OnyxEntry<Policy>): OnyxData {
if (!policy) {
return;
return {};
}

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

Expand Down Expand Up @@ -200,6 +228,19 @@ function removeApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, policy: Onyx
},
];

return {optimisticData, failureData, successData};
}

function removeApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, policy: OnyxEntry<Policy>) {
if (!policy) {
return;
}

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we return this from getRemoveApprovalWorkflowOnyxData?


const {optimisticData, failureData, successData} = getRemoveApprovalWorkflowOnyxData(approvalWorkflow, policy);

const parameters: RemoveWorkspaceApprovalParams = {policyID: policy.id, employees: JSON.stringify(Object.values(updatedEmployees))};
API.write(WRITE_COMMANDS.REMOVE_WORKSPACE_APPROVAL, parameters, {optimisticData, failureData, successData});
}
Expand Down Expand Up @@ -322,4 +363,6 @@ export {
clearApprovalWorkflowApprovers,
clearApprovalWorkflow,
validateApprovalWorkflow,
getRemoveApprovalWorkflowOnyxData,
getUpdateApprovalWorkflowOnyxData,
};
Loading
Loading