Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
rayane-djouah committed Sep 9, 2024
2 parents 1790284 + 62f8d36 commit f14a4e4
Show file tree
Hide file tree
Showing 17 changed files with 88 additions and 282 deletions.
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ jobs:
IS_AT_LEAST_ONE_PLATFORM_DEPLOYED: ${{ steps.checkDeploymentSuccess.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED }}
IS_ALL_PLATFORMS_DEPLOYED: ${{ steps.checkDeploymentSuccess.outputs.IS_ALL_PLATFORMS_DEPLOYED }}
needs: [android, desktop, iOS, web]
if: ${{ always() }}
steps:
- name: Check deployment success on at least one platform
id: checkDeploymentSuccess
Expand Down
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
versionCode 1009003100
versionName "9.0.31-0"
versionCode 1009003101
versionName "9.0.31-1"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
Expand Down
2 changes: 1 addition & 1 deletion ios/NewExpensify/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>9.0.31.0</string>
<string>9.0.31.1</string>
<key>FullStory</key>
<dict>
<key>OrgId</key>
Expand Down
2 changes: 1 addition & 1 deletion ios/NewExpensifyTests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>9.0.31.0</string>
<string>9.0.31.1</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion ios/NotificationServiceExtension/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<key>CFBundleShortVersionString</key>
<string>9.0.31</string>
<key>CFBundleVersion</key>
<string>9.0.31.0</string>
<string>9.0.31.1</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
"version": "9.0.31-0",
"version": "9.0.31-1",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
Expand Down
1 change: 0 additions & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,6 @@ const CONST = {
DEFAULT_ROOMS: 'defaultRooms',
DUPE_DETECTION: 'dupeDetection',
P2P_DISTANCE_REQUESTS: 'p2pDistanceRequests',
WORKFLOWS_ADVANCED_APPROVAL: 'workflowsAdvancedApproval',
SPOTNANA_TRAVEL: 'spotnanaTravel',
REPORT_FIELDS_FEATURE: 'reportFieldsFeature',
WORKSPACE_FEEDS: 'workspaceFeeds',
Expand Down
4 changes: 2 additions & 2 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,8 +653,8 @@ const ROUTES = {
},
WORKSPACE_WORKFLOWS_APPROVALS_APPROVER: {
route: 'settings/workspaces/:policyID/workflows/approvals/approver',
getRoute: (policyID: string, approverIndex?: number, backTo?: string) =>
getUrlWithBackToParam(`settings/workspaces/${policyID}/workflows/approvals/approver${approverIndex !== undefined ? `?approverIndex=${approverIndex}` : ''}` as const, backTo),
getRoute: (policyID: string, approverIndex: number, backTo?: string) =>
getUrlWithBackToParam(`settings/workspaces/${policyID}/workflows/approvals/approver?approverIndex=${approverIndex}` as const, backTo),
},
WORKSPACE_WORKFLOWS_PAYER: {
route: 'settings/workspaces/:policyID/workflows/payer',
Expand Down
22 changes: 17 additions & 5 deletions src/components/ReportActionItem/TaskPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {Str} from 'expensify-common';
import React from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import {useOnyx, withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import Avatar from '@components/Avatar';
import Checkbox from '@components/Checkbox';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
Expand Down Expand Up @@ -73,8 +74,8 @@ function TaskPreview({taskReport, taskReportID, action, contextMenuAnchor, chatR
: action?.childStateNum === CONST.REPORT.STATE_NUM.APPROVED && action?.childStatusNum === CONST.REPORT.STATUS_NUM.APPROVED;
const taskTitle = Str.htmlEncode(TaskUtils.getTaskTitle(taskReportID, action?.childReportName ?? ''));
const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? -1;
const htmlForTaskPreview =
taskAssigneeAccountID > 0 ? `<comment><mention-user accountid="${taskAssigneeAccountID}"></mention-user> ${taskTitle}</comment>` : `<comment>${taskTitle}</comment>`;
const [avatar] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: (personalDetails) => personalDetails?.[taskAssigneeAccountID]?.avatar});
const htmlForTaskPreview = `<comment>${taskTitle}</comment>`;
const isDeletedParentAction = ReportUtils.isCanceledTaskReport(taskReport, action);

if (isDeletedParentAction) {
Expand All @@ -94,7 +95,7 @@ function TaskPreview({taskReport, taskReportID, action, contextMenuAnchor, chatR
accessibilityLabel={translate('task.task')}
>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsStart]}>
<View style={[styles.taskCheckboxWrapper]}>
<View style={[styles.taskCheckboxWrapper, styles.alignSelfCenter]}>
<Checkbox
style={[styles.mr2]}
containerStyle={[styles.taskCheckbox]}
Expand All @@ -110,7 +111,18 @@ function TaskPreview({taskReport, taskReportID, action, contextMenuAnchor, chatR
accessibilityLabel={translate('task.task')}
/>
</View>
<RenderHTML html={isTaskCompleted ? `<completed-task>${htmlForTaskPreview}</completed-task>` : htmlForTaskPreview} />
{taskAssigneeAccountID > 0 && (
<Avatar
containerStyles={[styles.mr2, styles.alignSelfCenter, isTaskCompleted ? styles.opacitySemiTransparent : undefined]}
source={avatar}
size={CONST.AVATAR_SIZE.SMALL}
avatarID={taskAssigneeAccountID}
type={CONST.ICON_TYPE_AVATAR}
/>
)}
<View style={[styles.alignSelfCenter]}>
<RenderHTML html={isTaskCompleted ? `<completed-task>${htmlForTaskPreview}</completed-task>` : htmlForTaskPreview} />
</View>
</View>
<Icon
src={Expensicons.ArrowRight}
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1176,7 +1176,7 @@ type FullScreenNavigatorParamList = {
};
[SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_APPROVER]: {
policyID: string;
approverIndex?: number;
approverIndex: number;
backTo?: Routes;
};
[SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: {
Expand Down
5 changes: 0 additions & 5 deletions src/libs/Permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ function canUseP2PDistanceRequests(betas: OnyxEntry<Beta[]>, iouType: IOUType |
return !!betas?.includes(CONST.BETAS.P2P_DISTANCE_REQUESTS) || canUseAllBetas(betas) || iouType === CONST.IOU.TYPE.TRACK;
}

function canUseWorkflowsAdvancedApproval(betas: OnyxEntry<Beta[]>): boolean {
return !!betas?.includes(CONST.BETAS.WORKFLOWS_ADVANCED_APPROVAL) || canUseAllBetas(betas);
}

function canUseSpotnanaTravel(betas: OnyxEntry<Beta[]>): boolean {
return !!betas?.includes(CONST.BETAS.SPOTNANA_TRAVEL) || canUseAllBetas(betas);
}
Expand Down Expand Up @@ -65,7 +61,6 @@ export default {
canUseLinkPreviews,
canUseDupeDetection,
canUseP2PDistanceRequests,
canUseWorkflowsAdvancedApproval,
canUseSpotnanaTravel,
canUseWorkspaceFeeds,
canUseCompanyCardFeeds,
Expand Down
13 changes: 12 additions & 1 deletion src/libs/WorkflowUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,10 @@ type ConvertApprovalWorkflowToPolicyEmployeesParams = {
type: ValueOf<typeof CONST.APPROVAL_WORKFLOW.TYPE>;
};

/** Convert an approval workflow to a list of policy employees */
/**
* This function converts an approval workflow into a list of policy employees.
* An optimized list is created that contains only the updated employees to maintain minimal data changes.
*/
function convertApprovalWorkflowToPolicyEmployees({
approvalWorkflow,
previousEmployeeList,
Expand All @@ -221,6 +224,8 @@ function convertApprovalWorkflowToPolicyEmployees({
const nextApprover = approvalWorkflow.approvers.at(index + 1);
const forwardsTo = type === CONST.APPROVAL_WORKFLOW.TYPE.REMOVE ? '' : nextApprover?.email ?? '';

// For every approver, we check if the forwardsTo field has changed.
// If it has, we update the employee list with the new forwardsTo value.
if (previousEmployeeList[approver.email]?.forwardsTo === forwardsTo) {
return;
}
Expand All @@ -235,6 +240,8 @@ function convertApprovalWorkflowToPolicyEmployees({
approvalWorkflow.members.forEach(({email}) => {
const submitsTo = type === CONST.APPROVAL_WORKFLOW.TYPE.REMOVE ? '' : firstApprover.email ?? '';

// For every member, we check if the submitsTo field has changed.
// If it has, we update the employee list with the new submitsTo value.
if (previousEmployeeList[email]?.submitsTo === submitsTo) {
return;
}
Expand All @@ -246,6 +253,8 @@ function convertApprovalWorkflowToPolicyEmployees({
};
});

// For each member to remove, we update the employee list with submitsTo set to ''
// which will set the submitsTo field to the default approver email on backend.
membersToRemove?.forEach(({email}) => {
updatedEmployeeList[email] = {
...(updatedEmployeeList[email] ? updatedEmployeeList[email] : {email}),
Expand All @@ -254,6 +263,8 @@ function convertApprovalWorkflowToPolicyEmployees({
};
});

// For each approver to remove, we update the employee list with forwardsTo set to ''
// which will reset the forwardsTo on the backend.
approversToRemove?.forEach(({email}) => {
updatedEmployeeList[email] = {
...(updatedEmployeeList[email] ? updatedEmployeeList[email] : {email}),
Expand Down
29 changes: 21 additions & 8 deletions src/libs/actions/Workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ Onyx.connect({
},
});

let personalDetails: PersonalDetailsList | undefined;
let personalDetailsByEmail: PersonalDetailsList = {};
Onyx.connect({
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (value) => {
personalDetails = value;
callback: (personalDetails) => {
personalDetailsByEmail = lodashMapKeys(personalDetails, (value, key) => value?.login ?? key);
},
});

Expand All @@ -56,6 +56,11 @@ function createApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
const previousApprovalMode = policy.approvalMode;
const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({previousEmployeeList, approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.CREATE});

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

const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
Expand Down Expand Up @@ -127,6 +132,7 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
approversToRemove,
});

// If there are no changes to the employees list, we can exit early
if (isEmptyObject(updatedEmployees) && !newDefaultApprover) {
return;
}
Expand Down Expand Up @@ -258,12 +264,13 @@ function removeApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork
API.write(WRITE_COMMANDS.REMOVE_WORKSPACE_APPROVAL, parameters, {optimisticData, failureData, successData});
}

/** Set the members of the approval workflow that is currently edited */
function setApprovalWorkflowMembers(members: Member[]) {
Onyx.merge(ONYXKEYS.APPROVAL_WORKFLOW, {members, errors: null});
}

/**
* Set the approver at the specified index in the current approval workflow
* Set the approver at the specified index in the approval workflow that is currently edited
* @param approver - The new approver to set
* @param approverIndex - The index of the approver to set
* @param policyID - The ID of the policy
Expand All @@ -280,13 +287,14 @@ function setApprovalWorkflowApprover(approver: Approver, approverIndex: number,

// Check if the approver forwards to other approvers and add them to the list
if (policy.employeeList[approver.email]?.forwardsTo) {
const personalDetailsByEmail = lodashMapKeys(personalDetails, (value, key) => value?.login ?? key);
const additionalApprovers = calculateApprovers({employees: policy.employeeList, firstEmail: approver.email, personalDetailsByEmail});
approvers.splice(approverIndex, approvers.length, ...additionalApprovers);
}

// Always clear the additional approver error when an approver is added
const errors: Record<string, TranslationPaths | null> = {additionalApprover: null};
// Check for circular references and reset errors

// Check for circular references (approver forwards to themselves) and reset other errors
const updatedApprovers = approvers.map((existingApprover, index) => {
if (!existingApprover) {
return;
Expand All @@ -308,6 +316,7 @@ function setApprovalWorkflowApprover(approver: Approver, approverIndex: number,
Onyx.merge(ONYXKEYS.APPROVAL_WORKFLOW, {approvers: updatedApprovers, errors});
}

/** Clear one approver at the specified index in the approval workflow that is currently edited */
function clearApprovalWorkflowApprover(approverIndex: number) {
if (!currentApprovalWorkflow) {
return;
Expand All @@ -319,6 +328,7 @@ function clearApprovalWorkflowApprover(approverIndex: number) {
Onyx.merge(ONYXKEYS.APPROVAL_WORKFLOW, {approvers: lodashDropRightWhile(approvers, (approver) => !approver), errors: null});
}

/** Clear all approvers of the approval workflow that is currently edited */
function clearApprovalWorkflowApprovers() {
Onyx.merge(ONYXKEYS.APPROVAL_WORKFLOW, {approvers: []});
}
Expand All @@ -333,6 +343,11 @@ function clearApprovalWorkflow() {

type ApprovalWorkflowOnyxValidated = Omit<ApprovalWorkflowOnyx, 'approvers'> & {approvers: Approver[]};

/**
* Validates the approval workflow and sets the errors on the approval workflow
* @param approvalWorkflow the approval workflow to validate
* @returns true if the approval workflow is valid, false otherwise
*/
function validateApprovalWorkflow(approvalWorkflow: ApprovalWorkflowOnyx): approvalWorkflow is ApprovalWorkflowOnyxValidated {
const errors: Record<string, TranslationPaths> = {};

Expand All @@ -355,8 +370,6 @@ function validateApprovalWorkflow(approvalWorkflow: ApprovalWorkflowOnyx): appro
}

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

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

Expand Down
2 changes: 1 addition & 1 deletion src/pages/workspace/tags/WorkspaceTagsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
required: policyTagList.required,
rightElement: (
<ListItemRightCaretWithLabel
labelText={policyTagList.required ? translate('common.required') : undefined}
labelText={policyTagList.required && !!Object.values(policyTagList?.tags ?? {}).some((tag) => tag.enabled) ? translate('common.required') : undefined}
shouldShowCaret={false}
/>
),
Expand Down
Loading

0 comments on commit f14a4e4

Please sign in to comment.