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

[CP Staging][CRITICAL] Implement <WorkspaceWorkflowsApprovalsEditPage /> for Editing Workflow #47249

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d51a8da
Start working on edit screen
blazejkustra Aug 12, 2024
2438780
Merge branch 'approval-workflows/create-edit-screen' into approval-wo…
blazejkustra Aug 12, 2024
3ed8226
Bring back old canUseAllBetas
blazejkustra Aug 12, 2024
25d6df7
Improve edit logic
blazejkustra Aug 12, 2024
546a644
Merge branch 'approval-workflows/create-edit-screen' into approval-wo…
blazejkustra Aug 12, 2024
4501ced
Merge branch 'approval-workflows/create-edit-screen' into approval-wo…
blazejkustra Aug 13, 2024
aca6ce4
Rename flow to action
blazejkustra Aug 13, 2024
92ddb88
Add missing translations
blazejkustra Aug 13, 2024
dc013da
Fix the logic behind updating employees
blazejkustra Aug 13, 2024
bd6e8c0
Use new names for variables
blazejkustra Aug 13, 2024
7439819
Start testing removal flow
blazejkustra Aug 13, 2024
8b71e1a
Merge branch 'approval-workflows/create-edit-screen' into approval-wo…
blazejkustra Aug 13, 2024
1ef7ad2
Change what validation function returns
blazejkustra Aug 13, 2024
368109e
Parse email param
blazejkustra Aug 13, 2024
90a83e2
Clean the logic for opening edit screen
blazejkustra Aug 13, 2024
db71a31
Hide header on edit screens
blazejkustra Aug 13, 2024
2560c8e
Attach the update and remove commands to edit screen
blazejkustra Aug 13, 2024
2588c7c
Adjust the logic on create screen for better clarity
blazejkustra Aug 13, 2024
6a85197
Adjust tests to newer convertApprovalWorkflowToPolicyEmployees logic
blazejkustra Aug 13, 2024
b3f6d4c
Fix navigation bug when going back from after removal
blazejkustra Aug 13, 2024
669e3c0
Merge branch 'approval-workflows/create-edit-screen' into approval-wo…
blazejkustra Aug 13, 2024
7cc82a4
Fix opening edit page with an url
blazejkustra Aug 13, 2024
f5e26de
Fix crash on ios
blazejkustra Aug 13, 2024
04a1f3d
Fix removing members
blazejkustra Aug 13, 2024
d0e1875
Merge branch 'main' into approval-workflows/edit-screen
blazejkustra Aug 13, 2024
48f2ac8
Improve the comment
blazejkustra Aug 13, 2024
bdbe28a
Add new parameter to UpdateWorkspaceApproval command - defaultApprover
blazejkustra Aug 14, 2024
5328303
Fix a bug that approvalWorkflow wasn't updated in some cases
blazejkustra Aug 14, 2024
00868ec
Show not found page instead of going back
blazejkustra Aug 14, 2024
99938ed
Remove unused import
blazejkustra Aug 14, 2024
2c2b20e
Merge branch 'main' into approval-workflows/edit-screen
blazejkustra Aug 14, 2024
f76850a
Fix Not found page when removing the first approver
blazejkustra Aug 14, 2024
d4d225d
Uncomment changing the default approver
blazejkustra Aug 14, 2024
30b99d2
Enable button when offline
blazejkustra Aug 14, 2024
d18d2f0
Update default approver in optimisticData
blazejkustra Aug 14, 2024
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
19 changes: 7 additions & 12 deletions src/components/ApprovalWorkflowSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import Navigation from '@libs/Navigation/Navigation';
import ROUTES from '@src/ROUTES';
import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
Expand All @@ -17,19 +15,16 @@ type ApprovalWorkflowSectionProps = {
/** Single workflow displayed in this component */
approvalWorkflow: ApprovalWorkflow;

/** ID of the policy */
policyID: string;
/** A function that is called when the section is pressed */
onPress: () => void;
};

function ApprovalWorkflowSection({approvalWorkflow, policyID}: ApprovalWorkflowSectionProps) {
function ApprovalWorkflowSection({approvalWorkflow, onPress}: ApprovalWorkflowSectionProps) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@blazejkustra Why do you make this change? I think onPress function could be handled in ApprovalWorkflowSection.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I prefer this section to be controlled by the parent, so no business logic is built into it

const styles = useThemeStyles();
const theme = useTheme();
const {translate, toLocaleOrdinal} = useLocalize();
const {isSmallScreenWidth} = useWindowDimensions();
const openApprovalsEdit = useCallback(
() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(policyID, approvalWorkflow.approvers[0].email)),
[approvalWorkflow.approvers, policyID],
);

const approverTitle = useCallback(
(index: number) =>
approvalWorkflow.approvers.length > 1 ? `${toLocaleOrdinal(index + 1, true)} ${translate('workflowsPage.approver').toLowerCase()}` : `${translate('workflowsPage.approver')}`,
Expand All @@ -40,7 +35,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyID}: ApprovalWorkflowS
<PressableWithoutFeedback
accessibilityRole="button"
style={[styles.border, isSmallScreenWidth ? styles.p3 : styles.p4, styles.flexRow, styles.justifyContentBetween, styles.mt6, styles.mbn3]}
onPress={openApprovalsEdit}
onPress={onPress}
accessibilityLabel={translate('workflowsPage.addApprovalsTitle')}
>
<View style={[styles.flex1]}>
Expand Down Expand Up @@ -70,7 +65,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyID}: ApprovalWorkflowS
iconHeight={20}
iconWidth={20}
iconFill={theme.icon}
onPress={openApprovalsEdit}
onPress={onPress}
shouldRemoveBackground
/>

Expand All @@ -88,7 +83,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyID}: ApprovalWorkflowS
iconHeight={20}
iconWidth={20}
iconFill={theme.icon}
onPress={openApprovalsEdit}
onPress={onPress}
shouldRemoveBackground
/>
</View>
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,8 @@ export default {
},
workflowsEditApprovalsPage: {
title: 'Edit approval workflow',
deleteTitle: 'Delete approval workflow',
deletePrompt: 'Are you sure you want to delete this approval workflow? All members will subsequently follow the default workflow.',
},
workflowsExpensesFromPage: {
title: 'Expenses from',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1339,6 +1339,8 @@ export default {
},
workflowsEditApprovalsPage: {
title: 'Edicion flujo de aprobación',
deleteTitle: 'Eliminar flujo de trabajo de aprobación',
deletePrompt: '¿Estás seguro de que quieres eliminar este flujo de trabajo de aprobación? Todos los miembros pasarán a usar el flujo de trabajo predeterminado.',
},
workflowsExpensesFromPage: {
title: 'Gastos de',
Expand Down
4 changes: 3 additions & 1 deletion src/libs/API/parameters/WorkspaceApprovalParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ type CreateWorkspaceApprovalParams = {
employees: string;
};

type UpdateWorkspaceApprovalParams = CreateWorkspaceApprovalParams;
type UpdateWorkspaceApprovalParams = CreateWorkspaceApprovalParams & {
defaultApprover?: string;
};

type RemoveWorkspaceApprovalParams = CreateWorkspaceApprovalParams;

Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
},
[SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_EDIT]: {
path: ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.route,
parse: {
firstApproverEmail: (firstApproverEmail: string) => decodeURIComponent(firstApproverEmail),
},
},
[SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_EXPENSES_FROM]: {
path: ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.route,
Expand Down
17 changes: 12 additions & 5 deletions src/libs/WorkflowUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@ type ConvertApprovalWorkflowToPolicyEmployeesParams = {
approvalWorkflow: ApprovalWorkflow;

/**
* Current list of employees in the policy
* Members to remove from the approval workflow
*/
employeeList: PolicyEmployeeList;
membersToRemove?: Member[];

/**
* Mode to use when converting the approval workflow
Expand All @@ -164,7 +164,7 @@ type ConvertApprovalWorkflowToPolicyEmployeesParams = {
};

/** Convert an approval workflow to a list of policy employees */
function convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeList, type}: ConvertApprovalWorkflowToPolicyEmployeesParams): PolicyEmployeeList {
function convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, membersToRemove, type}: ConvertApprovalWorkflowToPolicyEmployeesParams): PolicyEmployeeList {
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved
const updatedEmployeeList: PolicyEmployeeList = {};
const firstApprover = approvalWorkflow.approvers.at(0);

Expand All @@ -175,18 +175,25 @@ function convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeLis
approvalWorkflow.approvers.forEach((approver, index) => {
const nextApprover = approvalWorkflow.approvers.at(index + 1);
updatedEmployeeList[approver.email] = {
...employeeList[approver.email],
email: approver.email,
forwardsTo: type === CONST.APPROVAL_WORKFLOW.TYPE.REMOVE ? '' : nextApprover?.email ?? '',
};
});

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

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

return updatedEmployeeList;
}

Expand Down
33 changes: 24 additions & 9 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 {isEmptyObject} from '@src/types/utils/EmptyObject';

let currentApprovalWorkflow: ApprovalWorkflowOnyx | undefined;
Onyx.connect({
Expand Down Expand Up @@ -53,7 +54,7 @@ function createApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork

const previousEmployeeList = {...policy.employeeList};
const previousApprovalMode = policy.approvalMode;
const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeList: previousEmployeeList, type: CONST.APPROVAL_WORKFLOW.TYPE.CREATE});
const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.CREATE});

const optimisticData: OnyxUpdate[] = [
{
Expand Down Expand Up @@ -110,15 +111,17 @@ function createApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
API.write(WRITE_COMMANDS.CREATE_WORKSPACE_APPROVAL, parameters, {optimisticData, failureData, successData});
}

function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWorkflow) {
function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWorkflow, membersToRemove: Member[]) {
const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];

if (!authToken || !policy) {
return;
}

const previousDefaultApprover = policy.approver ?? policy.owner;
const newDefaultApprover = approvalWorkflow.isDefault ? approvalWorkflow.approvers[0].email : undefined;
const previousEmployeeList = {...policy.employeeList};
const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeList: previousEmployeeList, type: CONST.APPROVAL_WORKFLOW.TYPE.UPDATE});
const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.UPDATE, membersToRemove});

const optimisticData: OnyxUpdate[] = [
{
Expand All @@ -134,6 +137,7 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
value: {
employeeList: updatedEmployees,
pendingFields: {employeeList: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE},
...(newDefaultApprover ? {approver: newDefaultApprover} : {}),
},
},
];
Expand All @@ -150,6 +154,7 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
value: {
employeeList: previousEmployeeList,
pendingFields: {employeeList: null},
...(newDefaultApprover ? {approver: previousDefaultApprover} : {}),
},
},
];
Expand All @@ -169,7 +174,12 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
},
];

const parameters: UpdateWorkspaceApprovalParams = {policyID, authToken, employees: JSON.stringify(Object.values(updatedEmployees))};
const parameters: UpdateWorkspaceApprovalParams = {
policyID,
authToken,
employees: JSON.stringify(Object.values(updatedEmployees)),
defaultApprover: newDefaultApprover,
};
API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_APPROVAL, parameters, {optimisticData, failureData, successData});
}

Expand All @@ -181,11 +191,12 @@ function removeApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
}

const previousEmployeeList = {...policy.employeeList};
const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeList: previousEmployeeList, type: CONST.APPROVAL_WORKFLOW.TYPE.REMOVE});
const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.REMOVE});
const updatedEmployeeList = {...previousEmployeeList, ...updatedEmployees};

const defaultApprover = policy.approver ?? policy.owner;
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved
// If there is more than one workflow, we need to keep the advanced approval mode (first workflow is the default)
const hasMoreThanOneWorkflow = Object.values(updatedEmployeeList).some((employee) => !!employee.submitsTo && employee.submitsTo !== policy.approver);
const hasMoreThanOneWorkflow = Object.values(updatedEmployeeList).some((employee) => !!employee.submitsTo && employee.submitsTo !== defaultApprover);

const optimisticData: OnyxUpdate[] = [
{
Expand Down Expand Up @@ -324,7 +335,9 @@ function clearApprovalWorkflow() {
Onyx.set(ONYXKEYS.APPROVAL_WORKFLOW, null);
}

function validateApprovalWorkflow(approvalWorkflow: ApprovalWorkflowOnyx): Record<string, TranslationPaths> {
type ApprovalWorkflowOnyxValidated = Omit<ApprovalWorkflowOnyx, 'approvers'> & {approvers: Approver[]};
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved

function validateApprovalWorkflow(approvalWorkflow: ApprovalWorkflowOnyx): approvalWorkflow is ApprovalWorkflowOnyxValidated {
const errors: Record<string, TranslationPaths> = {};

approvalWorkflow.approvers.forEach((approver, approverIndex) => {
Expand All @@ -337,7 +350,7 @@ function validateApprovalWorkflow(approvalWorkflow: ApprovalWorkflowOnyx): Recor
}
});

if (!approvalWorkflow.members.length) {
if (!approvalWorkflow.members.length && !approvalWorkflow.isDefault) {
errors.members = 'common.error.fieldRequired';
}

Expand All @@ -346,7 +359,9 @@ function validateApprovalWorkflow(approvalWorkflow: ApprovalWorkflowOnyx): Recor
}

Onyx.merge(ONYXKEYS.APPROVAL_WORKFLOW, {errors});
return errors;

// Return false if there are errors
return isEmptyObject(errors);
}

export {
Expand Down
3 changes: 2 additions & 1 deletion src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
);
return;
}

Workflow.setApprovalWorkflow({
...INITIAL_APPROVAL_WORKFLOW,
availableMembers: approvalWorkflows.at(0)?.members ?? [],
Expand Down Expand Up @@ -178,7 +179,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
>
<ApprovalWorkflowSection
approvalWorkflow={workflow}
policyID={route.params.policyID}
onPress={() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(route.params.policyID, workflow.approvers[0].email))}
/>
</OfflineWithFeedback>
))}
Expand Down
Loading
Loading