Skip to content

Commit

Permalink
Merge branch 'main' of github.com:Expensify/App into andrew-fix-e2e-fork
Browse files Browse the repository at this point in the history
  • Loading branch information
hannojg committed Sep 19, 2023
2 parents b9df629 + 778f763 commit 153174c
Show file tree
Hide file tree
Showing 716 changed files with 23,180 additions and 8,174 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ module.exports = {
},
},
{
files: ['tests/**/*.{js,jsx,ts,tsx}', '.github/**/*.{js,jsx,ts,tsx}'],
files: ['workflow_tests/**/*.{js,jsx,ts,tsx}', 'tests/**/*.{js,jsx,ts,tsx}', '.github/**/*.{js,jsx,ts,tsx}'],
rules: {
'@lwc/lwc/no-async-await': 'off',
'no-await-in-loop': 'off',
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/DesignDoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ labels: Daily, NewFeature
- [ ] Confirm that the doc has the minimum necessary number of reviews before proceeding
- [ ] Email `[email protected]` one last time to let them know the Design Doc is moving into the implementation phase
- [ ] Implement the changes
- [ ] Add regression tests so that QA can test your feature with every deploy ([instructions](https://stackoverflowteams.com/c/expensify/questions/363))
- [ ] Send out a follow up email to `[email protected]` once everything has been implemented and do a **Project Wrap-Up** retrospective that provides:
- Summary of what we accomplished with this project
- What went well?
Expand Down
29 changes: 2 additions & 27 deletions .github/actions/composite/setupNode/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,13 @@ runs:
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-modules-${{ hashFiles('package-lock.json') }}
key: ${{ runner.os }}-node-modules-${{ hashFiles('package-lock.json', 'patches/**') }}

- id: cache-desktop-node-modules
uses: actions/cache@v3
with:
path: desktop/node_modules
key: ${{ runner.os }}-desktop-node-modules-${{ hashFiles('desktop/package-lock.json') }}

- name: Check if patch files changed
id: patchCheck
shell: bash
run: |
set -e
if [[ `git diff main --name-only | grep \.patch` != null ]]; then
echo 'CHANGES_IN_PATCH_FILES=true' >> "$GITHUB_OUTPUT"
else
echo 'CHANGES_IN_PATCH_FILES=false' >> "$GITHUB_OUTPUT"
fi
- name: Patch root project node packages
shell: bash
if: |
steps.patchCheck.outputs.CHANGES_IN_PATCH_FILES == 'true' &&
steps.cache-node-modules.outputs.cache-hit == 'true'
run: npx patch-package

- name: Patch node packages for desktop submodule
shell: bash
if: |
steps.patchCheck.outputs.CHANGES_IN_PATCH_FILES == 'true' &&
steps.cache-desktop-node-modules.outputs.cache-hit == 'true'
run: cd desktop && npx patch-package
key: ${{ runner.os }}-desktop-node-modules-${{ hashFiles('desktop/package-lock.json', 'desktop/patches/**') }}

- name: Install root project node packages
if: steps.cache-node-modules.outputs.cache-hit != 'true'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,7 @@ function fetchTag(tag) {
console.log(`Running command: ${command}`);
execSync(command);
} catch (e) {
// This can happen if the tag was only created locally but does not exist in the remote. In this case, we'll fetch history of the staging branch instead
const command = `git fetch origin staging --no-tags --shallow-exclude=${previousPatchVersion}`;
console.log(`Running command: ${command}`);
execSync(command);
console.error(e);
}
}

Expand Down Expand Up @@ -301,13 +298,14 @@ function getValidMergedPRs(commits) {
* @returns {Promise<Array<Number>>} – Pull request numbers
*/
function getPullRequestsMergedBetween(fromTag, toTag) {
console.log(`Looking for commits made between ${fromTag} and ${toTag}...`);
return getCommitHistoryAsJSON(fromTag, toTag).then((commitList) => {
console.log(`Commits made between ${fromTag} and ${toTag}:`, commitList);

// Find which commit messages correspond to merged PR's
const pullRequestNumbers = getValidMergedPRs(commitList);
console.log(`List of pull requests merged between ${fromTag} and ${toTag}`, pullRequestNumbers);
return pullRequestNumbers;
return _.map(pullRequestNumbers, (prNum) => Number.parseInt(prNum, 10));
});
}

Expand Down
8 changes: 3 additions & 5 deletions .github/actions/javascript/getDeployPullRequestList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,7 @@ function fetchTag(tag) {
console.log(`Running command: ${command}`);
execSync(command);
} catch (e) {
// This can happen if the tag was only created locally but does not exist in the remote. In this case, we'll fetch history of the staging branch instead
const command = `git fetch origin staging --no-tags --shallow-exclude=${previousPatchVersion}`;
console.log(`Running command: ${command}`);
execSync(command);
console.error(e);
}
}

Expand Down Expand Up @@ -264,13 +261,14 @@ function getValidMergedPRs(commits) {
* @returns {Promise<Array<Number>>} – Pull request numbers
*/
function getPullRequestsMergedBetween(fromTag, toTag) {
console.log(`Looking for commits made between ${fromTag} and ${toTag}...`);
return getCommitHistoryAsJSON(fromTag, toTag).then((commitList) => {
console.log(`Commits made between ${fromTag} and ${toTag}:`, commitList);

// Find which commit messages correspond to merged PR's
const pullRequestNumbers = getValidMergedPRs(commitList);
console.log(`List of pull requests merged between ${fromTag} and ${toTag}`, pullRequestNumbers);
return pullRequestNumbers;
return _.map(pullRequestNumbers, (prNum) => Number.parseInt(prNum, 10));
});
}

Expand Down
206 changes: 97 additions & 109 deletions .github/actions/javascript/markPullRequestsAsDeployed/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,12 @@ module.exports =
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {

const _ = __nccwpck_require__(2947);
const lodashGet = __nccwpck_require__(6908);
const core = __nccwpck_require__(2186);
const {context} = __nccwpck_require__(5438);
const CONST = __nccwpck_require__(4097);
const ActionUtils = __nccwpck_require__(970);
const GithubUtils = __nccwpck_require__(7999);

const prList = ActionUtils.getJSONInput('PR_LIST', {required: true});
const isProd = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: true});
const version = core.getInput('DEPLOY_VERSION', {required: true});

/**
* Return a nicely formatted message for the table based on the result of the GitHub action job
*
Expand All @@ -40,122 +35,115 @@ function getDeployTableMessage(platformResult) {
}
}

const androidResult = getDeployTableMessage(core.getInput('ANDROID', {required: true}));
const desktopResult = getDeployTableMessage(core.getInput('DESKTOP', {required: true}));
const iOSResult = getDeployTableMessage(core.getInput('IOS', {required: true}));
const webResult = getDeployTableMessage(core.getInput('WEB', {required: true}));

const workflowURL = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;

/**
* @param {String} deployer
* @param {String} deployVerb
* @param {String} prTitle
* @returns {String}
*/
function getDeployMessage(deployer, deployVerb, prTitle) {
let message = `🚀 [${deployVerb}](${workflowURL}) to ${isProd ? 'production' : 'staging'}`;
message += ` by https://github.com/${deployer} in version: ${version} 🚀`;
message += `\n\n platform | result \n ---|--- \n🤖 android 🤖|${androidResult} \n🖥 desktop 🖥|${desktopResult}`;
message += `\n🍎 iOS 🍎|${iOSResult} \n🕸 web 🕸|${webResult}`;

if (deployVerb === 'Cherry-picked' && !/no qa/gi.test(prTitle)) {
// eslint-disable-next-line max-len
message +=
'\n\n@Expensify/applauseleads please QA this PR and check it off on the [deploy checklist](https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3AStagingDeployCash) if it passes.';
}

return message;
}

/**
* Comment Single PR
*
* @param {Number} PR
* @param {String} message
* @returns {Promise<void>}
*/
function commentPR(PR, message) {
return GithubUtils.createComment(context.repo.repo, PR, message)
.then(() => console.log(`Comment created on #${PR} successfully 🎉`))
.catch((err) => {
console.log(`Unable to write comment on #${PR} 😞`);
core.setFailed(err.message);
});
async function commentPR(PR, message) {
try {
await GithubUtils.createComment(context.repo.repo, PR, message);
console.log(`Comment created on #${PR} successfully 🎉`);
} catch (err) {
console.log(`Unable to write comment on #${PR} 😞`);
core.setFailed(err.message);
}
}

const run = function () {
const workflowURL = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;

async function run() {
const prList = _.map(ActionUtils.getJSONInput('PR_LIST', {required: true}), (num) => Number.parseInt(num, 10));
const isProd = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: true});
const version = core.getInput('DEPLOY_VERSION', {required: true});

const androidResult = getDeployTableMessage(core.getInput('ANDROID', {required: true}));
const desktopResult = getDeployTableMessage(core.getInput('DESKTOP', {required: true}));
const iOSResult = getDeployTableMessage(core.getInput('IOS', {required: true}));
const webResult = getDeployTableMessage(core.getInput('WEB', {required: true}));

/**
* @param {String} deployer
* @param {String} deployVerb
* @param {String} prTitle
* @returns {String}
*/
function getDeployMessage(deployer, deployVerb, prTitle) {
let message = `🚀 [${deployVerb}](${workflowURL}) to ${isProd ? 'production' : 'staging'}`;
message += ` by https://github.com/${deployer} in version: ${version} 🚀`;
message += `\n\nplatform | result\n---|---\n🤖 android 🤖|${androidResult}\n🖥 desktop 🖥|${desktopResult}`;
message += `\n🍎 iOS 🍎|${iOSResult}\n🕸 web 🕸|${webResult}`;

if (deployVerb === 'Cherry-picked' && !/no ?qa/gi.test(prTitle)) {
// eslint-disable-next-line max-len
message +=
'\n\n@Expensify/applauseleads please QA this PR and check it off on the [deploy checklist](https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3AStagingDeployCash) if it passes.';
}

return message;
}

if (isProd) {
// First find the deployer (who closed the last deploy checklist)?
return GithubUtils.octokit.issues
.listForRepo({
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
labels: CONST.LABELS.STAGING_DEPLOY,
state: 'closed',
})
.then(({data}) => _.first(data).number)
.then((lastDeployChecklistNumber) => GithubUtils.getActorWhoClosedIssue(lastDeployChecklistNumber))
.then((actor) => {
// Create comment on each pull request (one after another to avoid throttling issues)
const deployMessage = getDeployMessage(actor, 'Deployed');
_.reduce(prList, (promise, pr) => promise.then(() => commentPR(pr, deployMessage)), Promise.resolve());
});
// Find the previous deploy checklist
const {data: deployChecklists} = await GithubUtils.octokit.issues.listForRepo({
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
labels: CONST.LABELS.STAGING_DEPLOY,
state: 'closed',
});
const previousChecklistID = _.first(deployChecklists).number;

// who closed the last deploy checklist?
const deployer = await GithubUtils.getActorWhoClosedIssue(previousChecklistID);

// Create comment on each pull request (one at a time to avoid throttling issues)
const deployMessage = getDeployMessage(deployer, 'Deployed');
for (const pr of prList) {
await commentPR(pr, deployMessage);
}
return;
}

// First find out if this is a normal staging deploy or a CP by looking at the commit message on the tag
return GithubUtils.octokit.repos
.listTags({
const {data: recentTags} = await GithubUtils.octokit.repos.listTags({
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
per_page: 100,
});
const currentTag = _.find(recentTags, (tag) => tag.name === version);
if (!currentTag) {
const err = `Could not find tag matching ${version}`;
console.error(err);
core.setFailed(err);
return;
}
const {data: commit} = await GithubUtils.octokit.git.getCommit({
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
commit_sha: currentTag.commit.sha,
});
const isCP = /[\S\s]*\(cherry picked from commit .*\)/.test(commit.message);

for (const prNumber of prList) {
/*
* Determine who the deployer for the PR is. The "deployer" for staging deploys is:
* 1. For regular staging deploys, the person who merged the PR.
* 2. For CPs, the person who committed the cherry-picked commit (not necessarily the author of the commit).
*/
const {data: pr} = await GithubUtils.octokit.pulls.get({
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
per_page: 100,
})
.then(({data}) => {
const tagSHA = _.find(data, (tag) => tag.name === version).commit.sha;
return GithubUtils.octokit.git.getCommit({
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
commit_sha: tagSHA,
});
})
.then(({data}) => {
const isCP = /Merge pull request #\d+ from Expensify\/.*-?cherry-pick-staging-\d+/.test(data.message);
_.reduce(
prList,
(promise, PR) =>
promise

// Then, for each PR, find out who merged it and determine the deployer
.then(() =>
GithubUtils.octokit.pulls.get({
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
pull_number: PR,
}),
)
.then((response) => {
/*
* The deployer for staging deploys is:
* 1. For regular staging deploys, the person who merged the PR.
* 2. For automatic CPs (using the label), the person who merged the PR.
* 3. For manual CPs (using the GH UI), the person who triggered the workflow
* (reflected in the branch name).
*/
let deployer = lodashGet(response, 'data.merged_by.login', '');
const issueTitle = lodashGet(response, 'data.title', '');
const CPActorMatches = data.message.match(/Merge pull request #\d+ from Expensify\/(.+)-cherry-pick-staging-\d+/);
if (_.isArray(CPActorMatches) && CPActorMatches.length === 2 && CPActorMatches[1] !== CONST.OS_BOTIFY) {
deployer = CPActorMatches[1];
}

// Finally, comment on the PR
const deployMessage = getDeployMessage(deployer, isCP ? 'Cherry-picked' : 'Deployed', issueTitle);
return commentPR(PR, deployMessage);
}),
Promise.resolve(),
);
pull_number: prNumber,
});
};
const deployer = isCP ? commit.committer.name : pr.merged_by.login;

const title = pr.title;
const deployMessage = getDeployMessage(deployer, isCP ? 'Cherry-picked' : 'Deployed', title);
await commentPR(prNumber, deployMessage);
}
}

if (require.main === require.cache[eval('__filename')]) {
run();
Expand Down Expand Up @@ -8171,7 +8159,7 @@ var modules = [
__nccwpck_require__(9557),
__nccwpck_require__(1155),
__nccwpck_require__(1644),
__nccwpck_require__(373),
__nccwpck_require__(6657),
__nccwpck_require__(1080),
__nccwpck_require__(1012),
__nccwpck_require__(9695),
Expand Down Expand Up @@ -8395,7 +8383,7 @@ InternalDecoderCesu8.prototype.end = function() {

/***/ }),

/***/ 373:
/***/ 6657:
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {

"use strict";
Expand Down Expand Up @@ -10455,7 +10443,7 @@ module.exports = Map;
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {

var mapCacheClear = __nccwpck_require__(1610),
mapCacheDelete = __nccwpck_require__(6657),
mapCacheDelete = __nccwpck_require__(5991),
mapCacheGet = __nccwpck_require__(1372),
mapCacheHas = __nccwpck_require__(609),
mapCacheSet = __nccwpck_require__(5582);
Expand Down Expand Up @@ -11297,7 +11285,7 @@ module.exports = mapCacheClear;

/***/ }),

/***/ 6657:
/***/ 5991:
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {

var getMapData = __nccwpck_require__(9980);
Expand Down
Loading

0 comments on commit 153174c

Please sign in to comment.