diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4f78ee6b69bf..c106a25c8ab1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -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 \ No newline at end of file diff --git a/.github/scripts/createHelpRedirects.sh b/.github/scripts/createHelpRedirects.sh new file mode 100755 index 000000000000..04f55e19b4fb --- /dev/null +++ b/.github/scripts/createHelpRedirects.sh @@ -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" diff --git a/.github/workflows/updateHelpDotRedirects.yml b/.github/workflows/updateHelpDotRedirects.yml new file mode 100644 index 000000000000..531b8a3812fd --- /dev/null +++ b/.github/workflows/updateHelpDotRedirects.yml @@ -0,0 +1,31 @@ +name: Update Redirects to ExpensifyHelp + +on: + # Run on any push to main that has changes to the redirects file + push: + branches: + - main + paths: + - 'docs/redirects.csv' + + # Run on any manual trigger + workflow_dispatch: + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "redirects" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + + - name: Create help dot redirect + env: + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + CLOUDFLARE_LIST_TOKEN: ${{ secrets.CLOUDFLARE_LIST_TOKEN }} + run: ./.github/scripts/createHelpRedirects.sh diff --git a/docs/Expensify-Card-revenue share for ExpensifyApproved!-partners.md b/docs/Expensify-Card-revenue share for ExpensifyApproved!-partners.md deleted file mode 100644 index f9d18da76ef6..000000000000 --- a/docs/Expensify-Card-revenue share for ExpensifyApproved!-partners.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Expensify Card revenue share for ExpensifyApproved! partners -description: Earn money when your clients adopt the Expensify Card ---- - - -# About -Start making more with us! We're thrilled to announce a new incentive for our US-based ExpensifyApproved! partner accountants. You can now earn additional income for your firm every time your client uses their Expensify Card. We're offering 0.5% of the total Expensify Card spend of your clients in cashback returned to your firm. The more your clients spend, the more cashback your firm receives!
-
This program is currently only available to US-based ExpensifyApproved! partner accountants. - -# How-to -To benefit from this program, complete the following steps -1. Ensure that you are listed as the Primary Contact under your client's domain in Expensify. If you're not currently the Primary Contact for your client, you or your client can follow the instructions outlined in [our help article](https://community.expensify.com/discussion/5749/how-to-add-and-remove-domain-admins#:~:text=Domain%20Admins%20have%20total%20control,a%20member%20of%20the%20domain.) to assign you this role. -2. Add a Business Bank Account to the Primary Contact account. Follow the instructions in [our help article](https://community.expensify.com/discussion/4641/how-to-add-a-deposit-only-bank-account-both-personal-and-business) to get a Business Bank Account added. - -# FAQ -- What if my firm is not permitted to accept revenue share from our clients?
-
We understand that different firms may have different policies. If your firm is unable to accept this revenue share, you can pass the revenue share back to your client to give them an additional 0.5% of cash back using your own internal payment tools.

-How will I know which clients to pay back?
-
Every month you will receive an automated message via new.expensify.com and email providing a breakdown of revenue shared generated per client.

-- What if my firm does not wish to participate in the program?
-
Please reach out to your assigned partner manager at new.expensify.com to inform them you would not like to accept the revenue share nor do you want to pass the revenue share to your clients. diff --git a/docs/redirects.csv b/docs/redirects.csv new file mode 100644 index 000000000000..d4fb7723bd0f --- /dev/null +++ b/docs/redirects.csv @@ -0,0 +1,2 @@ +sourceURL,targetURL +https://community.expensify.com/discussion/5634/deep-dive-how-long-will-it-take-for-me-to-receive-my-reimbursement,https://help.expensify.com/articles/expensify-classic/get-paid-back/reports/Reimbursements