Skip to content

Commit a86df44

Browse files
authored
Merge branch 'Expensify:main' into fix/sq-crash-window
2 parents 297dd5b + bd8cedf commit a86df44

187 files changed

Lines changed: 3849 additions & 941 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/javascript/reviewerChecklist/index.js

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11588,6 +11588,10 @@ const pathToReviewerChecklist = 'https://raw.githubusercontent.com/Expensify/App
1158811588
const reviewerChecklistContains = '# Reviewer Checklist';
1158911589
const issue = github.context.payload.issue?.number ?? github.context.payload.pull_request?.number ?? -1;
1159011590
const combinedComments = [];
11591+
// Org members and owners are internal Expensify engineers; external contributors (including C+) are not.
11592+
const INTERNAL_EXPENSIFY_ASSOCIATIONS = new Set(['MEMBER', 'OWNER']);
11593+
// A reviewer's standing is their latest review in one of these states; plain "commented" reviews don't change it.
11594+
const DECISIVE_REVIEW_STATES = new Set(['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED']);
1159111595
function getNumberOfItemsFromReviewerChecklist() {
1159211596
console.log('Getting the number of items in the reviewer checklist...');
1159311597
return new Promise((resolve, reject) => {
@@ -11657,8 +11661,44 @@ function checkIssueForCompletedChecklist(numberOfChecklistItems) {
1165711661
core.setFailed("PR Reviewer Checklist is not completely filled out. Please check every box to verify you've thought about the item.");
1165811662
});
1165911663
}
11660-
getNumberOfItemsFromReviewerChecklist()
11661-
.then(checkIssueForCompletedChecklist)
11664+
// An approval from an internal Expensify engineer means we've decided this PR doesn't need a C+ checklist, so let the check pass.
11665+
// This workflow re-runs on every pull_request_review event, so we scan the whole review history: once an internal approval
11666+
// stands, a later "commented" or "changes requested" review from anyone must not re-require the checklist.
11667+
async function hasStandingInternalApproval() {
11668+
const { owner, repo } = github.context.repo;
11669+
const reviews = await GithubUtils_1.default.paginate(GithubUtils_1.default.octokit.pulls.listReviews, {
11670+
owner,
11671+
repo,
11672+
// eslint-disable-next-line @typescript-eslint/naming-convention
11673+
pull_number: issue,
11674+
// eslint-disable-next-line @typescript-eslint/naming-convention
11675+
per_page: 100,
11676+
});
11677+
// GitHub treats a reviewer's latest decisive review as their standing, so keep only that per internal engineer.
11678+
const latestStateByInternalReviewer = new Map();
11679+
for (const review of reviews) {
11680+
const login = review.user?.login;
11681+
const state = review.state ?? '';
11682+
if (!login || !INTERNAL_EXPENSIFY_ASSOCIATIONS.has(review.author_association ?? '') || !DECISIVE_REVIEW_STATES.has(state)) {
11683+
continue;
11684+
}
11685+
latestStateByInternalReviewer.set(login, state);
11686+
}
11687+
for (const state of latestStateByInternalReviewer.values()) {
11688+
if (state === 'APPROVED') {
11689+
return true;
11690+
}
11691+
}
11692+
return false;
11693+
}
11694+
hasStandingInternalApproval()
11695+
.then((isApproved) => {
11696+
if (isApproved) {
11697+
console.log('PR has a standing approval from an internal Expensify engineer, so the reviewer checklist is not required 🎉');
11698+
return;
11699+
}
11700+
return getNumberOfItemsFromReviewerChecklist().then(checkIssueForCompletedChecklist);
11701+
})
1166211702
.catch((err) => {
1166311703
console.error(err);
1166411704
core.setFailed(err);

.github/actions/javascript/reviewerChecklist/reviewerChecklist.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ const reviewerChecklistContains = '# Reviewer Checklist';
88
const issue: number = github.context.payload.issue?.number ?? github.context.payload.pull_request?.number ?? -1;
99
const combinedComments: string[] = [];
1010

11+
// Org members and owners are internal Expensify engineers; external contributors (including C+) are not.
12+
const INTERNAL_EXPENSIFY_ASSOCIATIONS = new Set(['MEMBER', 'OWNER']);
13+
14+
// A reviewer's standing is their latest review in one of these states; plain "commented" reviews don't change it.
15+
const DECISIVE_REVIEW_STATES = new Set(['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED']);
16+
1117
function getNumberOfItemsFromReviewerChecklist() {
1218
console.log('Getting the number of items in the reviewer checklist...');
1319
return new Promise<number>((resolve, reject) => {
@@ -87,8 +93,47 @@ function checkIssueForCompletedChecklist(numberOfChecklistItems: number) {
8793
});
8894
}
8995

90-
getNumberOfItemsFromReviewerChecklist()
91-
.then(checkIssueForCompletedChecklist)
96+
// An approval from an internal Expensify engineer means we've decided this PR doesn't need a C+ checklist, so let the check pass.
97+
// This workflow re-runs on every pull_request_review event, so we scan the whole review history: once an internal approval
98+
// stands, a later "commented" or "changes requested" review from anyone must not re-require the checklist.
99+
async function hasStandingInternalApproval(): Promise<boolean> {
100+
const {owner, repo} = github.context.repo;
101+
const reviews = await GitHubUtils.paginate(GitHubUtils.octokit.pulls.listReviews, {
102+
owner,
103+
repo,
104+
// eslint-disable-next-line @typescript-eslint/naming-convention
105+
pull_number: issue,
106+
// eslint-disable-next-line @typescript-eslint/naming-convention
107+
per_page: 100,
108+
});
109+
110+
// GitHub treats a reviewer's latest decisive review as their standing, so keep only that per internal engineer.
111+
const latestStateByInternalReviewer = new Map<string, string>();
112+
for (const review of reviews) {
113+
const login = review.user?.login;
114+
const state = review.state ?? '';
115+
if (!login || !INTERNAL_EXPENSIFY_ASSOCIATIONS.has(review.author_association ?? '') || !DECISIVE_REVIEW_STATES.has(state)) {
116+
continue;
117+
}
118+
latestStateByInternalReviewer.set(login, state);
119+
}
120+
121+
for (const state of latestStateByInternalReviewer.values()) {
122+
if (state === 'APPROVED') {
123+
return true;
124+
}
125+
}
126+
return false;
127+
}
128+
129+
hasStandingInternalApproval()
130+
.then((isApproved) => {
131+
if (isApproved) {
132+
console.log('PR has a standing approval from an internal Expensify engineer, so the reviewer checklist is not required 🎉');
133+
return;
134+
}
135+
return getNumberOfItemsFromReviewerChecklist().then(checkIssueForCompletedChecklist);
136+
})
92137
.catch((err: string | Error) => {
93138
console.error(err);
94139
core.setFailed(err);

.github/workflows/authorChecklist.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ jobs:
3131
OS_BOTIFY_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }}
3232

3333
- name: authorChecklist.ts
34-
if: steps.gate.outputs.IS_AUTHORIZED == 'true'
34+
# Internal Expensify engineers (org members/owners) skip the author checklist; external contributors still fill it out.
35+
if: |
36+
steps.gate.outputs.IS_AUTHORIZED == 'true'
37+
&& github.event.pull_request.author_association != 'MEMBER'
38+
&& github.event.pull_request.author_association != 'OWNER'
3539
uses: ./.github/actions/javascript/authorChecklist
3640
with:
3741
GITHUB_TOKEN: ${{ github.token }}

Mobile-Expensify

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ android {
111111
minSdkVersion rootProject.ext.minSdkVersion
112112
targetSdkVersion rootProject.ext.targetSdkVersion
113113
multiDexEnabled rootProject.ext.multiDexEnabled
114-
versionCode 1009039908
115-
versionName "9.3.99-8"
114+
versionCode 1009040000
115+
versionName "9.4.0-0"
116116
// Supported language variants must be declared here to avoid from being removed during the compilation.
117117
// This also helps us to not include unnecessary language variants in the APK.
118118
resConfigs "en", "es"

assets/images/moon.svg

Lines changed: 1 addition & 0 deletions
Loading

assets/images/sidebar-left.svg

Lines changed: 1 addition & 0 deletions
Loading

assets/images/sidebar-right.svg

Lines changed: 1 addition & 0 deletions
Loading

config/eslint/eslint.seatbelt.tsv

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,6 @@
336336
"../../src/libs/actions/IOU/SendMoney.ts" "no-restricted-syntax" 1
337337
"../../src/libs/actions/IOU/Split.ts" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 4
338338
"../../src/libs/actions/IOU/Split.ts" "@typescript-eslint/no-deprecated/Localize.translateLocal" 2
339-
"../../src/libs/actions/IOU/Split.ts" "@typescript-eslint/no-deprecated/getMoneyRequestPolicyTags" 1
340339
"../../src/libs/actions/IOU/Split.ts" "no-restricted-syntax" 3
341340
"../../src/libs/actions/IOU/SplitTransactionUpdate.ts" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 1
342341
"../../src/libs/actions/IOU/SplitTransactionUpdate.ts" "@typescript-eslint/no-deprecated/getMoneyRequestPolicyTags" 1

contributingGuides/REGRESSION_TEST_BEST_PRACTICES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Below are some examples to illustrate the writing style that covers this:
5656
- Bug: A blank page is shown for an archived room with a message in it
5757
- Proposed Test Steps:
5858
- Create a workspace if you don't have any
59-
- Go to members page and remove the other admin (Expensify setup specialist)
59+
- Go to members page and remove the other admin (Expensify account executive)
6060
- Search the announce room and send a message
6161
- Pin the room and delete the workspace
6262
- Wait for a few seconds (Reload if the chat is still visible)

0 commit comments

Comments
 (0)