Skip to content

Commit

Permalink
Merge branch 'main' into Sebryu-DynamicChecklistPOC
Browse files Browse the repository at this point in the history
  • Loading branch information
sebryu committed Nov 8, 2023
2 parents c7751da + fac5b04 commit 9007e87
Show file tree
Hide file tree
Showing 440 changed files with 8,566 additions and 10,346 deletions.
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# Every PR gets a review from an internal Expensify engineer
* @Expensify/pullerbear

# Every PR that touches redirects gets reviewed by ring0
docs/redirects.csv @Expensify/infra
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,42 @@ const ActionUtils = require('../../../libs/ActionUtils');
const GitUtils = require('../../../libs/GitUtils');
const GithubUtils = require('../../../libs/GithubUtils');

const inputTag = core.getInput('TAG', {required: true});

const isProductionDeploy = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: false}, false);
const itemToFetch = isProductionDeploy ? 'release' : 'tag';
async function run() {
try {
const inputTag = core.getInput('TAG', {required: true});
const isProductionDeploy = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: false}, false);
const deployEnv = isProductionDeploy ? 'production' : 'staging';

console.log(`Looking for PRs deployed to ${deployEnv} in ${inputTag}...`);

const completedDeploys = (
await GithubUtils.octokit.actions.listWorkflowRuns({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
workflow_id: 'platformDeploy.yml',
status: 'completed',
event: isProductionDeploy ? 'release' : 'push',
})
).data.workflow_runs;

const inputTagIndex = _.findIndex(completedDeploys, (workflowRun) => workflowRun.head_branch === inputTag);
if (inputTagIndex < 0) {
throw new Error(`No completed deploy found for input tag ${inputTag}`);
}

/**
* Gets either releases or tags for a GitHub repo
*
* @param {boolean} fetchReleases
* @returns {*}
*/
function getTagsOrReleases(fetchReleases) {
if (fetchReleases) {
return GithubUtils.octokit.repos.listReleases({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
});
const priorTag = completedDeploys[inputTagIndex + 1].head_branch;
console.log(`Looking for PRs deployed to ${deployEnv} between ${priorTag} and ${inputTag}`);
const prList = await GitUtils.getPullRequestsMergedBetween(priorTag, inputTag);
console.log(`Found the pull request list: ${prList}`);
core.setOutput('PR_LIST', prList);
} catch (err) {
console.error(err.message);
core.setFailed(err);
}

return GithubUtils.octokit.repos.listTags({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
});
}

console.log(`Fetching ${itemToFetch} list from github...`);
getTagsOrReleases(isProductionDeploy)
.catch((githubError) => core.setFailed(githubError))
.then(({data}) => {
const keyToPluck = isProductionDeploy ? 'tag_name' : 'name';
const tags = _.pluck(data, keyToPluck);
const priorTagIndex = _.indexOf(tags, inputTag) + 1;

if (priorTagIndex === 0) {
console.log(`No ${itemToFetch} was found for input tag ${inputTag}. Comparing it to latest ${itemToFetch} ${tags[0]}`);
}

if (priorTagIndex === tags.length) {
const err = new Error("Somehow, the input tag was at the end of the paginated result, so we don't have the prior tag");
console.error(err.message);
core.setFailed(err);
return;
}

const priorTag = tags[priorTagIndex];
console.log(`Given ${itemToFetch}: ${inputTag}`);
console.log(`Prior ${itemToFetch}: ${priorTag}`);
if (require.main === module) {
run();
}

return GitUtils.getPullRequestsMergedBetween(priorTag, inputTag);
})
.then((pullRequestList) => {
console.log(`Found the pull request list: ${pullRequestList}`);
return core.setOutput('PR_LIST', pullRequestList);
})
.catch((error) => core.setFailed(error));
module.exports = run;
129 changes: 60 additions & 69 deletions .github/actions/javascript/getDeployPullRequestList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,59 @@
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({

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

const _ = __nccwpck_require__(5067);
const core = __nccwpck_require__(2186);
const github = __nccwpck_require__(5438);
const ActionUtils = __nccwpck_require__(970);
const GitUtils = __nccwpck_require__(669);
const GithubUtils = __nccwpck_require__(7999);

async function run() {
try {
const inputTag = core.getInput('TAG', {required: true});
const isProductionDeploy = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: false}, false);
const deployEnv = isProductionDeploy ? 'production' : 'staging';

console.log(`Looking for PRs deployed to ${deployEnv} in ${inputTag}...`);

const completedDeploys = (
await GithubUtils.octokit.actions.listWorkflowRuns({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
workflow_id: 'platformDeploy.yml',
status: 'completed',
event: isProductionDeploy ? 'release' : 'push',
})
).data.workflow_runs;

const inputTagIndex = _.findIndex(completedDeploys, (workflowRun) => workflowRun.head_branch === inputTag);
if (inputTagIndex < 0) {
throw new Error(`No completed deploy found for input tag ${inputTag}`);
}

const priorTag = completedDeploys[inputTagIndex + 1].head_branch;
console.log(`Looking for PRs deployed to ${deployEnv} between ${priorTag} and ${inputTag}`);
const prList = await GitUtils.getPullRequestsMergedBetween(priorTag, inputTag);
console.log(`Found the pull request list: ${prList}`);
core.setOutput('PR_LIST', prList);
} catch (err) {
console.error(err.message);
core.setFailed(err);
}
}

if (require.main === require.cache[eval('__filename')]) {
run();
}

module.exports = run;


/***/ }),

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

Expand Down Expand Up @@ -19556,74 +19609,12 @@ module.exports = JSON.parse('[[[0,44],"disallowed_STD3_valid"],[[45,46],"valid"]
/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/";
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
const _ = __nccwpck_require__(5067);
const core = __nccwpck_require__(2186);
const github = __nccwpck_require__(5438);
const ActionUtils = __nccwpck_require__(970);
const GitUtils = __nccwpck_require__(669);
const GithubUtils = __nccwpck_require__(7999);

const inputTag = core.getInput('TAG', {required: true});

const isProductionDeploy = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: false}, false);
const itemToFetch = isProductionDeploy ? 'release' : 'tag';

/**
* Gets either releases or tags for a GitHub repo
*
* @param {boolean} fetchReleases
* @returns {*}
*/
function getTagsOrReleases(fetchReleases) {
if (fetchReleases) {
return GithubUtils.octokit.repos.listReleases({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
});
}

return GithubUtils.octokit.repos.listTags({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
});
}

console.log(`Fetching ${itemToFetch} list from github...`);
getTagsOrReleases(isProductionDeploy)
.catch((githubError) => core.setFailed(githubError))
.then(({data}) => {
const keyToPluck = isProductionDeploy ? 'tag_name' : 'name';
const tags = _.pluck(data, keyToPluck);
const priorTagIndex = _.indexOf(tags, inputTag) + 1;

if (priorTagIndex === 0) {
console.log(`No ${itemToFetch} was found for input tag ${inputTag}. Comparing it to latest ${itemToFetch} ${tags[0]}`);
}

if (priorTagIndex === tags.length) {
const err = new Error("Somehow, the input tag was at the end of the paginated result, so we don't have the prior tag");
console.error(err.message);
core.setFailed(err);
return;
}

const priorTag = tags[priorTagIndex];
console.log(`Given ${itemToFetch}: ${inputTag}`);
console.log(`Prior ${itemToFetch}: ${priorTag}`);

return GitUtils.getPullRequestsMergedBetween(priorTag, inputTag);
})
.then((pullRequestList) => {
console.log(`Found the pull request list: ${pullRequestList}`);
return core.setOutput('PR_LIST', pullRequestList);
})
.catch((error) => core.setFailed(error));

})();

module.exports = __webpack_exports__;
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module is referenced by other modules so it can't be inlined
/******/ var __webpack_exports__ = __nccwpck_require__(5847);
/******/ module.exports = __webpack_exports__;
/******/
/******/ })()
;
125 changes: 125 additions & 0 deletions .github/scripts/createHelpRedirects.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/bin/bash
#
# Adds new routes to the Cloudflare Bulk Redirects list for communityDot to helpDot
# pages. Does some basic sanity checking.

set -e

source scripts/shellUtils.sh

info "Adding any new redirects from communityDot to helpDot"

declare -r LIST_ID="20eb13215038446a98fd69ccf6d1026d"
declare -r ZONE_ID="$CLOUDFLARE_ACCOUNT_ID"
declare -r REDIRECTS_FILE="docs/redirects.csv"

function checkCloudflareResult {
RESULTS=$1
RESULT_MESSAGE=$(echo "$RESULTS" | jq .success)

if ! [[ "$RESULT_MESSAGE" == "true" ]]; then
ERROR_MESSAGE=$(echo "$RESULTS" | jq .errors)
error "Error calling Cloudfalre API: $ERROR_MESSAGE"
exit 1
fi
}

declare -a ITEMS_TO_ADD

while read -r line; do
# Split each line of the file into a source and destination so we can sanity check
# and compare against the current list.
read -r -a LINE_PARTS < <(echo "$line" | tr ',' ' ')
SOURCE_URL=${LINE_PARTS[0]}
DEST_URL=${LINE_PARTS[1]}

# Make sure the format of the line is as execpted.
if [[ "${#LINE_PARTS[@]}" -gt 2 ]]; then
error "Found a line with more than one comma: $line"
exit 1
fi

# Basic sanity checking to make sure that the source and destination are in expected
# subdomains.
if ! [[ $SOURCE_URL =~ ^https://community\.expensify\.com ]]; then
error "Found source URL that is not a community URL: $SOURCE_URL"
exit 1
fi

if ! [[ $DEST_URL =~ ^https://help\.expensify\.com ]]; then
error "Found destination URL that is not a help URL: $DEST_URL"
exit 1
fi

info "Source: $SOURCE_URL and destination: $DEST_URL appear to be formatted correctly."

ITEMS_TO_ADD+=("$line")

# This line skips the first line in the csv because the first line is a header row.
done <<< "$(tail +2 $REDIRECTS_FILE)"

# Sanity check that we should actually be running this and we aren't about to delete
# every single redirect.
if [[ "${#ITEMS_TO_ADD[@]}" -lt 1 ]]; then
error "No items found to add, why are we running?"
exit 1
fi

# This block builds a single JSON object with all of our updates so we can
# reduce the number of API calls we make. You cannot add any logging or anything
# that prints to std out to this block or it will break. We capture all of the std out
# from this loop and pass it to jq to build the json object. Any non-json will break the
# jq call at the end.
PUT_JSON=$(for new in "${ITEMS_TO_ADD[@]}"; do
read -r -a LINE_PARTS < <(echo "$new" | tr ',' ' ')
SOURCE_URL=${LINE_PARTS[0]}
DEST_URL=${LINE_PARTS[1]}
# We strip the prefix here so that the rule will match both http and https. Since vanilla will eventially be removed,
# we need to catch both because we will not have the http > https redirect done by vanilla anymore.
NO_PREFIX_SOURCE_URL=${SOURCE_URL/https:\/\//}
jq -n --arg source "$NO_PREFIX_SOURCE_URL" --arg dest "$DEST_URL" '{"redirect": {source_url: $source, target_url: $dest}}'
done | jq -n '. |= [inputs]')

info "Adding redirects for $PUT_JSON"

# We use PUT here instead of POST so that we replace the entire list in place. This has many benefits:
# 1. We don't have to check if items are already in the list, allowing this script to run faster
# 2. We can support deleting redirects this way by simply removing them from the list
# 3. We can support updating redirects this way, in the case of typos or moved destinations.
#
# Additionally this API call is async, so after we finish it, we must poll to wait for it to finish to
# to know that it was actually completed.
PUT_RESULT=$(curl -s --request PUT --url "https://api.cloudflare.com/client/v4/accounts/$ZONE_ID/rules/lists/$LIST_ID/items" \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $CLOUDFLARE_LIST_TOKEN" \
--data "$PUT_JSON")

checkCloudflareResult "$PUT_RESULT"
OPERATION_ID=$(echo "$PUT_RESULT" | jq -r .result.operation_id)

DONE=false

# Poll for completition
while [[ $DONE == false ]]; do
CHECK_RESULT=$(curl -s --request GET --url "https://api.cloudflare.com/client/v4/accounts/$ZONE_ID/rules/lists/bulk_operations/$OPERATION_ID" \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $CLOUDFLARE_LIST_TOKEN")
checkCloudflareResult "$CHECK_RESULT"

STATUS=$(echo "$CHECK_RESULT" | jq -r .result.status)

# Exit on completed or failed, other options are pending or running, in both cases
# we want to keep polling.
if [[ $STATUS == "completed" ]]; then
DONE=true
fi

if [[ $STATUS == "failed" ]]; then
ERROR_MESSAGE=$(echo "$CHECK_RESULT" | jq .result.error)
error "List update failed with error: $ERROR_MESSAGE"
exit 1
fi
done

success "Updated lists successfully"
3 changes: 2 additions & 1 deletion .github/workflows/cherryPick.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
token: ${{ secrets.OS_BOTIFY_TOKEN }}

- name: Set up git for OSBotify
id: setupGitForOSBotify
uses: Expensify/App/.github/actions/composite/setupGitForOSBotifyApp@8c19d6da4a3d7ce3b15c9cd89a802187d208ecab
with:
GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }}
Expand Down Expand Up @@ -119,7 +120,7 @@ jobs:
**Important:** There may be conflicts that GitHub is not able to detect, so please _carefully_ review this pull request before approving."
gh pr edit --add-assignee "${{ github.actor }},${{ steps.getCPMergeCommit.outputs.MERGE_ACTOR }}"
env:
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }}
GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }}

- name: "Announces a CP failure in the #announce Slack room"
uses: 8398a7/action-slack@v3
Expand Down
Loading

0 comments on commit 9007e87

Please sign in to comment.