diff --git a/.github/actions/create-workflow-failure-issue/action.yml b/.github/actions/create-workflow-failure-issue/action.yml index 8c98f7359706..5c4b09c9ae3a 100644 --- a/.github/actions/create-workflow-failure-issue/action.yml +++ b/.github/actions/create-workflow-failure-issue/action.yml @@ -10,6 +10,11 @@ inputs: default: github/docs-engineering required: false +outputs: + issue_url: + description: URL of the created or updated workflow-failure issue (empty if creation failed). + value: ${{ steps.create-new.outputs.issue_url || steps.comment-existing.outputs.issue_url }} + runs: using: composite steps: @@ -31,6 +36,7 @@ runs: echo "existing_issue=$existing" >> "$GITHUB_OUTPUT" - name: Comment on existing issue + id: comment-existing if: steps.check-existing.outputs.existing_issue != '' shell: bash env: @@ -57,8 +63,10 @@ runs: gh issue comment "$ISSUE_NUMBER" \ --repo "$ISSUE_REPO" \ --body "$body" + echo "issue_url=$GITHUB_SERVER_URL/$ISSUE_REPO/issues/$ISSUE_NUMBER" >> "$GITHUB_OUTPUT" - name: Create workflow failure issue + id: create-new if: steps.check-existing.outputs.existing_issue == '' shell: bash env: @@ -86,9 +94,10 @@ runs: This issue was automatically created by the create-workflow-failure-issue action to enable automated diagnosis. EOF ) - gh issue create \ + url=$(gh issue create \ --repo "$ISSUE_REPO" \ --label "workflow-failure" \ --label "workflow-generated" \ --title "[Workflow Failure] $WORKFLOW_NAME" \ - --body "$body" + --body "$body") + echo "issue_url=$url" >> "$GITHUB_OUTPUT" diff --git a/.github/actions/slack-alert/action.yml b/.github/actions/slack-alert/action.yml index 5c5018d16c93..57a54fe96a1c 100644 --- a/.github/actions/slack-alert/action.yml +++ b/.github/actions/slack-alert/action.yml @@ -10,13 +10,61 @@ inputs: default: CG5MJHMB2 # docs-alerts required: false message: - description: The message to send to Slack - default: The last '${{ github.workflow }}' run failed. See ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + description: >- + Optional message override. When set, it is sent verbatim. When empty (the + default), a standard multi-line failure message is built from the run + context, plus a link to the failure issue if issue_url is provided. + default: '' + required: false + issue_url: + description: >- + Optional link to the tracking failure issue (e.g. the output of the + create-workflow-failure-issue action). Appended to the default message. + Ignored when a custom message is provided. + default: '' required: false runs: using: composite steps: + # Build the Slack text here so the default message can be multi-line (real + # newlines) and conditionally include the issue link. A caller-supplied + # message is passed through verbatim for backward compatibility. + - name: Build Slack message + id: build + shell: bash + env: + MESSAGE: ${{ inputs.message }} + ISSUE_URL: ${{ inputs.issue_url }} + SOURCE_REPO: ${{ github.repository }} + WORKFLOW_NAME: ${{ github.workflow }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + EVENT_NAME: ${{ github.event_name }} + GIT_REF: ${{ github.ref }} + ACTOR: ${{ github.actor }} + run: | + # Escape Slack mrkdwn control chars in interpolated context fields so a + # crafted branch/ref (e.g. containing ) can't inject mentions. + esc() { printf '%s' "$1" | sed -e 's/&/\&/g' -e 's//\>/g'; } + # Unique heredoc delimiter so a custom message can't collide with it. + delim="SLACK_EOF_${RANDOM}${RANDOM}" + { + printf 'text<<%s\n' "$delim" + if [ -n "$MESSAGE" ]; then + printf '%s\n' "$MESSAGE" + else + printf ':actions: *Workflow failure* in %s: %s\n' "$(esc "$SOURCE_REPO")" "$(esc "$WORKFLOW_NAME")" + printf 'on %s · %s · by %s\n' "$(esc "$EVENT_NAME")" "$(esc "$GIT_REF")" "$(esc "$ACTOR")" + printf 'Run: %s\n' "$RUN_URL" + if [ -n "$ISSUE_URL" ]; then + printf 'Issue: %s\n' "$ISSUE_URL" + else + printf ':warning: No issue created\n' + fi + fi + printf '%s\n' "$delim" + } >> "$GITHUB_OUTPUT" + - name: Send Slack notification if workflow fails uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 with: @@ -25,4 +73,4 @@ runs: errors: true payload: | channel: ${{ toJSON(inputs.slack_channel_id) }} - text: ${{ toJSON(inputs.message) }} + text: ${{ toJSON(steps.build.outputs.text) }} diff --git a/.github/workflows/benchmark-pages.yml b/.github/workflows/benchmark-pages.yml index 1f0366544a77..1feb36673967 100644 --- a/.github/workflows/benchmark-pages.yml +++ b/.github/workflows/benchmark-pages.yml @@ -157,12 +157,14 @@ jobs: echo "Done creating issue" fi - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/changelog-agent.yml b/.github/workflows/changelog-agent.yml index c34095467eba..746bd40e7cb5 100644 --- a/.github/workflows/changelog-agent.yml +++ b/.github/workflows/changelog-agent.yml @@ -736,12 +736,14 @@ jobs: if: ${{ failure() && github.event_name != 'workflow_dispatch' }} uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c77f93e7af24..28a2c2fb23ff 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,12 +37,14 @@ jobs: - uses: github/codeql-action/analyze@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7 continue-on-error: true - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'pull_request' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'pull_request' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/content-pipelines.yml b/.github/workflows/content-pipelines.yml index bb69d006eb9e..ed742b7d8918 100644 --- a/.github/workflows/content-pipelines.yml +++ b/.github/workflows/content-pipelines.yml @@ -187,12 +187,14 @@ jobs: --label "workflow-generated,content-pipeline-update,ready-for-doc-review,skip FR board" fi - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/copy-api-issue-to-internal.yml b/.github/workflows/copy-api-issue-to-internal.yml index 14214abdacc2..175fa36017b2 100644 --- a/.github/workflows/copy-api-issue-to-internal.yml +++ b/.github/workflows/copy-api-issue-to-internal.yml @@ -74,9 +74,16 @@ jobs: OLD_ISSUE: ${{ github.event.issue.html_url }} - name: Check out repo - if: ${{ failure() && github.event_name != 'workflow_dispatch' && github.repository == 'github/docs-internal' }} + if: ${{ failure() && github.event_name != 'workflow_dispatch' }} uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue + if: ${{ failure() && github.event_name != 'workflow_dispatch' }} + with: + token: ${{ secrets.DOCS_BOT_PAT_BASE }} + - uses: ./.github/actions/slack-alert - if: ${{ failure() && github.event_name != 'workflow_dispatch' && github.repository == 'github/docs-internal' }} + if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/create-changelog-pr.yml b/.github/workflows/create-changelog-pr.yml index a335cb02d939..bbffdd387582 100644 --- a/.github/workflows/create-changelog-pr.yml +++ b/.github/workflows/create-changelog-pr.yml @@ -159,12 +159,14 @@ jobs: core.info(`Failed to assign PR to @${context.payload.comment.user.login}: ${err.message}`); } - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/delete-orphan-translation-files.yml b/.github/workflows/delete-orphan-translation-files.yml index a071f1e7e64b..c827f9848abc 100644 --- a/.github/workflows/delete-orphan-translation-files.yml +++ b/.github/workflows/delete-orphan-translation-files.yml @@ -155,12 +155,14 @@ jobs: fi fi - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/docs-review-collect.yml b/.github/workflows/docs-review-collect.yml index 8454a38eaa35..d4b0af64226f 100644 --- a/.github/workflows/docs-review-collect.yml +++ b/.github/workflows/docs-review-collect.yml @@ -42,12 +42,14 @@ jobs: REVIEWER: 'docs-reviewers' FEATURE: 'Audit log event descriptions' - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/enterprise-dates.yml b/.github/workflows/enterprise-dates.yml index a1f1ad26cf93..6d14c1436a2a 100644 --- a/.github/workflows/enterprise-dates.yml +++ b/.github/workflows/enterprise-dates.yml @@ -68,12 +68,14 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.create-pull-request.outputs.pull-request-number }} - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/enterprise-release-issue.yml b/.github/workflows/enterprise-release-issue.yml index 07f112a2dee6..ac929c5f06a6 100644 --- a/.github/workflows/enterprise-release-issue.yml +++ b/.github/workflows/enterprise-release-issue.yml @@ -33,12 +33,14 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/index-autocomplete-search.yml b/.github/workflows/index-autocomplete-search.yml index 1093c1bdfe84..d9c4d96418ad 100644 --- a/.github/workflows/index-autocomplete-search.yml +++ b/.github/workflows/index-autocomplete-search.yml @@ -45,12 +45,14 @@ jobs: ELASTICSEARCH_URL: ${{ github.event_name == 'pull_request' && 'http://localhost:9200' || secrets.ELASTICSEARCH_URL }} run: npm run index-ai-search-autocomplete -- docs-internal-data - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name == 'schedule' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name == 'schedule' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/index-general-search.yml b/.github/workflows/index-general-search.yml index c2199f217615..e3debd22bba9 100644 --- a/.github/workflows/index-general-search.yml +++ b/.github/workflows/index-general-search.yml @@ -95,14 +95,16 @@ jobs: if: ${{ failure() && github.event_name != 'workflow_dispatch' }} uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: ./.github/actions/slack-alert - if: ${{ failure() && github.event_name != 'workflow_dispatch' }} - with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: token: ${{ secrets.DOCS_BOT_PAT_BASE }} + - uses: ./.github/actions/slack-alert + if: ${{ failure() && github.event_name != 'workflow_dispatch' }} + with: + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} updateElasticsearchIndexes: needs: figureOutMatrix @@ -240,15 +242,17 @@ jobs: path: /tmp/records/failures-summary.json retention-days: 1 - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} notifyScrapingFailures: name: Notify scraping failures @@ -383,12 +387,14 @@ jobs: slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} message: ${{ fromJSON(steps.aggregate.outputs.result || '{"message":""}').message }} - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/keep-caches-warm.yml b/.github/workflows/keep-caches-warm.yml index 4d94f838ceb9..d405d2454e96 100644 --- a/.github/workflows/keep-caches-warm.yml +++ b/.github/workflows/keep-caches-warm.yml @@ -43,12 +43,14 @@ jobs: - uses: ./.github/actions/precompute-pageinfo if: github.repository == 'github/docs-internal' - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/link-check-external.yml b/.github/workflows/link-check-external.yml index 5fec74b0438c..9e218dbbb2f1 100644 --- a/.github/workflows/link-check-external.yml +++ b/.github/workflows/link-check-external.yml @@ -67,12 +67,14 @@ jobs: content-filepath: artifacts/external-link-report.md labels: broken link report - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/link-check-github-github.yml b/.github/workflows/link-check-github-github.yml index 8fb9c929b748..7f6e14c92c9d 100644 --- a/.github/workflows/link-check-github-github.yml +++ b/.github/workflows/link-check-github-github.yml @@ -71,12 +71,14 @@ jobs: repository: ${{ env.REPORT_REPOSITORY }} labels: ${{ env.REPORT_LABEL }} - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/link-check-internal.yml b/.github/workflows/link-check-internal.yml index 18a766c4941a..9b8994564d70 100644 --- a/.github/workflows/link-check-internal.yml +++ b/.github/workflows/link-check-internal.yml @@ -56,15 +56,17 @@ jobs: INPUT_VERSION: ${{ inputs.version }} INPUT_LANGUAGE: ${{ inputs.language }} - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} check-internal-links: if: github.repository == 'github/docs-internal' @@ -183,15 +185,17 @@ jobs: core.info(`Created Copilot redirect issue: ${issue.data.html_url}`) - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} # Create combined report after all matrix jobs complete create-report: @@ -245,12 +249,14 @@ jobs: content-filepath: combined-report.md labels: broken link report - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/lint-entire-content-data-markdown.yml b/.github/workflows/lint-entire-content-data-markdown.yml index ff2192f38e13..9817a99675fa 100644 --- a/.github/workflows/lint-entire-content-data-markdown.yml +++ b/.github/workflows/lint-entire-content-data-markdown.yml @@ -43,12 +43,14 @@ jobs: REPORT_REPOSITORY: github/docs-content run: npm run lint-report -- --path /tmp/lint-results.json - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/moda-allowed-ips.yml b/.github/workflows/moda-allowed-ips.yml index 6360a4559014..9709bb7c191f 100644 --- a/.github/workflows/moda-allowed-ips.yml +++ b/.github/workflows/moda-allowed-ips.yml @@ -54,12 +54,14 @@ jobs: --head=$branchname echo "Pull request created" - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/move-reopened-issues-to-triage.yaml b/.github/workflows/move-reopened-issues-to-triage.yaml index 233674b2b297..bc2bd0ae34fa 100644 --- a/.github/workflows/move-reopened-issues-to-triage.yaml +++ b/.github/workflows/move-reopened-issues-to-triage.yaml @@ -46,7 +46,14 @@ jobs: - name: Check out repo if: ${{ failure() }} uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue + if: ${{ failure() }} + with: + token: ${{ secrets.DOCS_BOT_PAT_BASE }} + - uses: ./.github/actions/slack-alert if: ${{ failure() }} with: slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/needs-sme-stale-check.yaml b/.github/workflows/needs-sme-stale-check.yaml index 146736467f0d..97288588f967 100644 --- a/.github/workflows/needs-sme-stale-check.yaml +++ b/.github/workflows/needs-sme-stale-check.yaml @@ -36,12 +36,14 @@ jobs: - name: Check out repo if: ${{ failure() }} uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/no-response.yaml b/.github/workflows/no-response.yaml index c05f251f4e34..7c515f978b2a 100644 --- a/.github/workflows/no-response.yaml +++ b/.github/workflows/no-response.yaml @@ -67,12 +67,14 @@ jobs: - name: Check out repo if: ${{ failure() }} uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/notify-about-deployment.yml b/.github/workflows/notify-about-deployment.yml index 4d22c92b56af..51edd5ae4f55 100644 --- a/.github/workflows/notify-about-deployment.yml +++ b/.github/workflows/notify-about-deployment.yml @@ -46,12 +46,14 @@ jobs: GITHUB_TOKEN: ${{ secrets.DOCS_BOT_PAT_BASE }} run: npm run find-past-built-pr - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/orphaned-features-check.yml b/.github/workflows/orphaned-features-check.yml index 72d7a67d47e0..61b74b392bbe 100644 --- a/.github/workflows/orphaned-features-check.yml +++ b/.github/workflows/orphaned-features-check.yml @@ -101,12 +101,14 @@ jobs: --repo github/docs-internal \ --label docs-content-fr,workflow-generated - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name == 'schedule' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name == 'schedule' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/orphaned-files-check.yml b/.github/workflows/orphaned-files-check.yml index e0d61a826b32..8c9b1f8d7a83 100644 --- a/.github/workflows/orphaned-files-check.yml +++ b/.github/workflows/orphaned-files-check.yml @@ -114,12 +114,14 @@ jobs: --repo github/docs-internal \ --label docs-content-fr,workflow-generated - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name == 'schedule' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name == 'schedule' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/purge-fastly.yml b/.github/workflows/purge-fastly.yml index abbc26584170..4949dd1775a0 100644 --- a/.github/workflows/purge-fastly.yml +++ b/.github/workflows/purge-fastly.yml @@ -59,17 +59,6 @@ jobs: - name: Check out repo uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - name: Generate GitHub App token - # Lets a scheduled-run failure open a tracking issue in docs-engineering - # (the default GITHUB_TOKEN can't write cross-repo). - id: app-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 - with: - app-id: ${{ secrets.DOCS_BOT_APP_ID }} - private-key: ${{ secrets.DOCS_BOT_APP_PRIVATE_KEY }} - owner: github - repositories: docs-engineering - - uses: ./.github/actions/node-npm-setup - name: Validate confirmation input @@ -121,12 +110,14 @@ jobs: HEAD_SHA: ${{ github.event.deployment.sha }} run: npm run purge-fastly-changed-content - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ steps.app-token.outputs.token }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml index 58b03e39ed05..4b4e76de0dd3 100644 --- a/.github/workflows/repo-sync.yml +++ b/.github/workflows/repo-sync.yml @@ -187,12 +187,14 @@ jobs: } } - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index c6954fe3c1e6..c9fd9ffefe48 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -39,12 +39,14 @@ jobs: - name: Check out repo if: ${{ failure() }} uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/sync-audit-logs.yml b/.github/workflows/sync-audit-logs.yml index 8d820a567b86..6b1501c4b2f4 100644 --- a/.github/workflows/sync-audit-logs.yml +++ b/.github/workflows/sync-audit-logs.yml @@ -108,12 +108,14 @@ jobs: gh pr merge --auto echo "Set pull request to auto merge" - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/sync-codeql-cli.yml b/.github/workflows/sync-codeql-cli.yml index 8ceecbd8bb19..ebdaf09576eb 100644 --- a/.github/workflows/sync-codeql-cli.yml +++ b/.github/workflows/sync-codeql-cli.yml @@ -116,7 +116,14 @@ jobs: --repo github/docs-internal \ --label "codeql-cli-pipeline,skip FR board,ready-for-doc-review,workflow-generated" + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue + if: ${{ failure() && github.event_name != 'workflow_dispatch' }} + with: + token: ${{ secrets.DOCS_BOT_PAT_BASE }} + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/sync-graphql.yml b/.github/workflows/sync-graphql.yml index 5ceb53d8ad09..c836bd6091b3 100644 --- a/.github/workflows/sync-graphql.yml +++ b/.github/workflows/sync-graphql.yml @@ -72,15 +72,17 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.create-pull-request.outputs.pull-request-number }} - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} notify_ignored_changes: if: github.repository == 'github/docs-internal' && needs.update_graphql_files.outputs.ignored-count > 0 && github.event_name != 'workflow_dispatch' diff --git a/.github/workflows/sync-llms-txt.yml b/.github/workflows/sync-llms-txt.yml index ff635b861ccf..0484d24e0c68 100644 --- a/.github/workflows/sync-llms-txt.yml +++ b/.github/workflows/sync-llms-txt.yml @@ -241,12 +241,14 @@ jobs: --base "$DEFAULT_BRANCH" \ --label "docs" - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/sync-openapi.yml b/.github/workflows/sync-openapi.yml index 61a46d6bf060..3223173d394f 100644 --- a/.github/workflows/sync-openapi.yml +++ b/.github/workflows/sync-openapi.yml @@ -120,12 +120,14 @@ jobs: --label github-openapi-bot,workflow-generated \ --head=$branchname \ - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/sync-sdk-docs.yml b/.github/workflows/sync-sdk-docs.yml index e9ed35fc0810..919a731db676 100644 --- a/.github/workflows/sync-sdk-docs.yml +++ b/.github/workflows/sync-sdk-docs.yml @@ -238,12 +238,14 @@ jobs: if: always() run: rm -rf "$SDK_TMP" - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/sync-secret-scanning.yml b/.github/workflows/sync-secret-scanning.yml index a3a4f61c0191..fc7128be1e4c 100644 --- a/.github/workflows/sync-secret-scanning.yml +++ b/.github/workflows/sync-secret-scanning.yml @@ -75,12 +75,14 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.create-pull-request.outputs.pull-request-number }} - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/triage-issue-comments.yml b/.github/workflows/triage-issue-comments.yml index fb83074ebb81..d56749eb48c3 100644 --- a/.github/workflows/triage-issue-comments.yml +++ b/.github/workflows/triage-issue-comments.yml @@ -67,7 +67,14 @@ jobs: addLabels: 'triage' ignoreIfLabeled: true + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue + if: ${{ failure() }} + with: + token: ${{ secrets.DOCS_BOT_PAT_BASE }} + - uses: ./.github/actions/slack-alert if: ${{ failure() }} with: slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/triage-stale-check.yml b/.github/workflows/triage-stale-check.yml index 066360de6fff..01ed5e79d491 100644 --- a/.github/workflows/triage-stale-check.yml +++ b/.github/workflows/triage-stale-check.yml @@ -46,15 +46,17 @@ jobs: - name: Check out repo if: ${{ failure() }} uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} stale_staff: name: Remind staff about PRs waiting for review @@ -81,12 +83,14 @@ jobs: - name: Check out repo if: ${{ failure() }} uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/.github/workflows/validate-github-github-docs-urls.yml b/.github/workflows/validate-github-github-docs-urls.yml index 44bccb99098a..b9577e49f7d4 100644 --- a/.github/workflows/validate-github-github-docs-urls.yml +++ b/.github/workflows/validate-github-github-docs-urls.yml @@ -121,12 +121,14 @@ jobs: run: | npm run validate-github-github-docs-urls -- post-pr-comment checks.json --changed-files $CHANGED_FILES - - uses: ./.github/actions/slack-alert + - uses: ./.github/actions/create-workflow-failure-issue + id: create-failure-issue if: ${{ failure() && github.event_name == 'schedule' }} with: - slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + token: ${{ secrets.DOCS_BOT_PAT_BASE }} - - uses: ./.github/actions/create-workflow-failure-issue + - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name == 'schedule' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} + issue_url: ${{ steps.create-failure-issue.outputs.issue_url }} diff --git a/content/rest/using-the-rest-api/best-practices-for-using-the-rest-api.md b/content/rest/using-the-rest-api/best-practices-for-using-the-rest-api.md index b1f7b419e1b6..dc18f2ed09ce 100644 --- a/content/rest/using-the-rest-api/best-practices-for-using-the-rest-api.md +++ b/content/rest/using-the-rest-api/best-practices-for-using-the-rest-api.md @@ -26,6 +26,12 @@ category: You should subscribe to webhook events instead of polling the API for data. This will help your integration stay within the API rate limit. For more information, see [AUTOTITLE](/webhooks). +If you cannot use webhooks and you must poll the API, poll as efficiently as possible to avoid exceeding the rate limit: + +* Poll only as often as you need to, on a fixed schedule. If a response includes an `x-poll-interval` header, wait at least that many seconds before you poll the same endpoint again. +* Make authenticated conditional requests, so that unchanged data does not count against your primary rate limit. For more information, see [Use conditional requests](#use-conditional-requests). +* Request only the data that you need, and keep responses stable, so that more of your polls return `304 Not Modified`. For more information, see [Make requests that can be cached](#make-requests-that-can-be-cached). + ## Make authenticated requests Authenticated requests have a higher primary rate limit than unauthenticated requests. To avoid exceeding the rate limit, you should make authenticated requests. For more information, see [AUTOTITLE](/rest/using-the-rest-api/rate-limits-for-the-rest-api). @@ -67,28 +73,67 @@ Many API endpoints return URL values for fields in the response body. You should Similarly, you should not try to manually construct pagination queries. Instead, you should use the link headers to determine what pages of results you can request. For more information, see [AUTOTITLE](/rest/using-the-rest-api/using-pagination-in-the-rest-api). -## Use conditional requests if appropriate +## Use conditional requests + +Most endpoints return an `etag` header, and many endpoints return a `last-modified` header. You can use the values of these headers to make conditional `GET` requests. If the response has not changed, you will receive a `304 Not Modified` response. Making a conditional request does not count against your primary rate limit if a `304` response is returned and the request was made while correctly authorized with an `Authorization` header. This makes conditional requests especially useful when you poll an endpoint, because each `304 Not Modified` response is fast and does not use your rate limit. + +In the following examples, replace `YOUR-TOKEN` with your access token.{% ifversion ghes %} Replace `REPO-OWNER` with the account that owns the repository, and replace `REPO-NAME` with the name of the repository.{% endif %} + +To make a conditional request with an `etag`: + +1. Make a request and save the value of the `etag` header from the response. + + ```shell + curl --include --header "Authorization: Bearer YOUR-TOKEN" {% data variables.product.rest_url %}/repos/{% ifversion ghes %}REPO-OWNER/REPO-NAME{% else %}octocat/Spoon-Knife{% endif %}/pulls + ``` + + The response includes an `etag` header: + + ```text + HTTP/2 200 + etag: "644b5b0155e6404a9cc4bd9d8b1ae730" + ``` + +1. On your next request to the same URL, send the saved value in the `if-none-match` header. -Most endpoints return an `etag` header, and many endpoints return a `last-modified` header. You can use the values of these headers to make conditional `GET` requests. If the response has not changed, you will receive a `304 Not Modified` response. Making a conditional request does not count against your primary rate limit if a `304` response is returned and the request was made while correctly authorized with an `Authorization` header. + ```shell + curl --include --header "Authorization: Bearer YOUR-TOKEN" --header 'if-none-match: "644b5b0155e6404a9cc4bd9d8b1ae730"' {% data variables.product.rest_url %}/repos/{% ifversion ghes %}REPO-OWNER/REPO-NAME{% else %}octocat/Spoon-Knife{% endif %}/pulls + ``` -For example, if a previous request returned an `etag` header value of `644b5b0155e6404a9cc4bd9d8b1ae730`, you can use the `if-none-match` header in a future request: + If the data has not changed, you will receive a `304 Not Modified` response, which does not count against your primary rate limit: + + ```text + HTTP/2 304 + ``` + +You can also use the `last-modified` header. For example, if a previous request returned a `last-modified` header value of `Wed, 25 Oct 2023 19:17:59 GMT`, you can use the `if-modified-since` header in a future request: ```shell -curl -H "Authorization: Bearer YOUR-TOKEN" {% data variables.product.rest_url %}/meta --include --header 'if-none-match: "644b5b0155e6404a9cc4bd9d8b1ae730"' +curl --include --header "Authorization: Bearer YOUR-TOKEN" --header 'if-modified-since: Wed, 25 Oct 2023 19:17:59 GMT' {% data variables.product.rest_url %}/repos/{% ifversion ghes %}REPO-OWNER/REPO-NAME{% else %}octocat/Spoon-Knife{% endif %} ``` -For example, if a previous request returned a `last-modified` header value of `Wed, 25 Oct 2023 19:17:59 GMT`, you can use the `if-modified-since` header in a future request: +Conditional requests for unsafe methods, such as `POST`, `PUT`, `PATCH`, and `DELETE` are not supported unless otherwise noted in the documentation for a specific endpoint. + +## Make requests that can be cached + +A conditional request only saves you time and rate limit if the endpoint returns `304 Not Modified`. The endpoint returns `304` when the representation that you requested has not changed since you saved its `etag` or `last-modified` value; unrelated response headers, such as the date, can still differ. To make `304` responses more likely when you poll, keep your requests stable and specific. + +Request only the data that you need. A smaller, more specific response changes less often, so it returns `304 Not Modified` more often. For example, to check the pull requests for one branch, filter the list by that branch instead of listing every pull request and searching the results yourself. Replace `HEAD-OWNER` with the account that owns the head branch; for a pull request from a fork, this is the account that owns the fork. Replace `BRANCH-NAME` with the name of the branch, and URL-encode it if it contains special characters such as `#` or `&`: ```shell -curl -H "Authorization: Bearer YOUR-TOKEN" {% data variables.product.rest_url %}/repos/github/docs --include --header 'if-modified-since: Wed, 25 Oct 2023 19:17:59 GMT' +curl --include --header "Authorization: Bearer YOUR-TOKEN" "{% data variables.product.rest_url %}/repos/{% ifversion ghes %}REPO-OWNER/REPO-NAME{% else %}octocat/Spoon-Knife{% endif %}/pulls?head=HEAD-OWNER:BRANCH-NAME" ``` -Conditional requests for unsafe methods, such as `POST`, `PUT`, `PATCH`, and `DELETE` are not supported unless otherwise noted in the documentation for a specific endpoint. +If you page through a list, use a stable sort order. Some parameters, such as `sort=updated`, reorder the list whenever an item changes. When an item moves to a new position, the items between its old and new positions shift onto different pages, so pages that you already fetched can return new data instead of `304 Not Modified`. A stable order, such as the default, stops updates to existing items from reordering the list, although adding or removing items can still shift entries onto other pages. + +Use the same parameters every time you poll the same data. A different page size, page number, or filter produces a different response with a different `etag`. ## Do not ignore errors You should not ignore repeated `4xx` and `5xx` error codes. Instead, you should ensure that you are correctly interacting with the API. For example, if an endpoint requests a string and you are passing it a numeric value, you will receive a validation error. Similarly, attempting to access an unauthorized or nonexistent endpoint will result in a `4xx` error. +If you are polling and a resource repeatedly returns a `404 Not Found` response, do not keep requesting it on every poll. First, make sure that the `404` is not caused by authentication or authorization. {% data variables.product.company_short %} returns a `404 Not Found` response instead of a `403 Forbidden` response for some private resources when your credentials do not grant access, so a `404` does not always mean that the resource is absent. For more information, see [AUTOTITLE](/rest/using-the-rest-api/troubleshooting-the-rest-api#404-not-found-for-an-existing-resource). Once you have confirmed that your credentials are correct, wait much longer before you check again, or check again only when you have a reason to believe that the resource now exists. Repeatedly requesting a missing resource wastes your rate limit and can trigger a secondary rate limit. + Intentionally ignoring repeated validation errors may result in the suspension of your app for abuse. ## Further reading diff --git a/src/frame/components/lib/prefetch.ts b/src/frame/components/lib/prefetch.ts new file mode 100644 index 000000000000..84f7b3833a0c --- /dev/null +++ b/src/frame/components/lib/prefetch.ts @@ -0,0 +1,46 @@ +import { useCallback } from 'react' +import type { useRouter } from 'next/router' + +type Router = ReturnType + +// Session-lived de-dupe set. Module scope (not a per-component useRef) so it +// survives the sidebar's per-navigation remount — otherwise the cache would +// reset every nav and "de-duped per href" would only hold within a single page. +// Bounded by the number of distinct internal hrefs the user hovers/focuses. +const prefetchedHrefs = new Set() + +// Brand NavList/Breadcrumbs items render plain s navigated via router.push, +// so Next.js doesn't prefetch their destinations the way next/link would. This +// hook warms a route on hover/focus. +// +// Scope of the benefit here is small on purpose: every article is a +// getServerSideProps route, and router.prefetch only fetches the JS bundle for +// those (not the getServerSideProps data), while page data is already served fast +// from the Fastly edge. All articles also share one [...restPage] bundle, so after +// the first navigation there's usually nothing left to fetch. It still helps the +// first cold visit, and would fetch data too if a route ever moves to +// getStaticProps. router.prefetch is production-only (no prefetch in dev); we +// de-dupe per href so re-entering a link doesn't re-issue. +export function usePrefetchOnInteraction() { + const prefetch = useCallback(async (router: Router, href: string) => { + if ( + process.env.NODE_ENV !== 'production' || + !href.startsWith('/') || + prefetchedHrefs.has(href) + ) { + return + } + prefetchedHrefs.add(href) + try { + // hrefs already include the locale prefix (e.g. /en/...), so disable Next.js + // locale handling to match the router.push calls at the click sites. + await router.prefetch(href, undefined, { locale: false }) + } catch { + // A transient chunk/network failure shouldn't permanently suppress retries; + // un-mark so a later hover/focus can try again (mirrors next/link). + prefetchedHrefs.delete(href) + } + }, []) + + return prefetch +} diff --git a/src/frame/components/page-header/Breadcrumbs.tsx b/src/frame/components/page-header/Breadcrumbs.tsx index b3e7b854159e..d9be4e382051 100644 --- a/src/frame/components/page-header/Breadcrumbs.tsx +++ b/src/frame/components/page-header/Breadcrumbs.tsx @@ -1,4 +1,4 @@ -import type { MouseEvent } from 'react' +import { type MouseEvent, useCallback } from 'react' import { useRouter } from 'next/router' import cx from 'classnames' import { Breadcrumbs as BrandBreadcrumbs } from '@primer/react-brand' @@ -6,6 +6,7 @@ import { Breadcrumbs as BrandBreadcrumbs } from '@primer/react-brand' import { useMainContext } from '../context/MainContext' import { DEFAULT_VERSION, useVersion } from '@/versions/components/useVersion' import { useTranslation } from '@/languages/components/useTranslation' +import { usePrefetchOnInteraction } from '@/frame/components/lib/prefetch' type Props = { inHeader?: boolean @@ -26,6 +27,11 @@ export const Breadcrumbs = ({ inHeader, variant }: Props) => { const router = useRouter() const { currentVersion } = useVersion() const { t } = useTranslation('header') + const prefetchHref = usePrefetchOnInteraction() + + // Warm a breadcrumb destination on hover/focus. BrandBreadcrumbs.Item renders a + // plain navigated via router.push, so Next.js won't prefetch it otherwise. + const prefetch = useCallback((href: string) => prefetchHref(router, href), [router, prefetchHref]) const placement = variant ?? (inHeader ? 'header' : 'in-article') // Only the in-article placement hides the current (last) crumb; the header and @@ -74,6 +80,8 @@ export const Breadcrumbs = ({ inHeader, variant }: Props) => { href={homeHref} title={t('go_home')} onClick={(event) => handleClick(event, homeHref)} + onMouseEnter={() => prefetch(homeHref)} + onFocus={() => prefetch(homeHref)} > {t('go_home')} @@ -96,6 +104,8 @@ export const Breadcrumbs = ({ inHeader, variant }: Props) => { href={breadcrumb.href} title={title} onClick={(event) => handleClick(event, breadcrumb.href!)} + onMouseEnter={() => prefetch(breadcrumb.href!)} + onFocus={() => prefetch(breadcrumb.href!)} className={cx( // Show the last breadcrumb if it's in the header/bar, but not if it's in the article. // If there's only 1 breadcrumb, show it. diff --git a/src/landings/components/SidebarProduct.tsx b/src/landings/components/SidebarProduct.tsx index 761e358f398a..aef9775db617 100644 --- a/src/landings/components/SidebarProduct.tsx +++ b/src/landings/components/SidebarProduct.tsx @@ -1,24 +1,33 @@ import { useRouter } from 'next/router' -import { type MouseEvent, type ReactNode, useEffect, useState } from 'react' +import { + createContext, + memo, + type MouseEvent, + type ReactNode, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react' import { NavList } from '@primer/react-brand' import { ProductTreeNode, useMainContext } from '@/frame/components/context/MainContext' import { useAutomatedPageContext } from '@/automated-pipelines/components/AutomatedPageContext' import { nonAutomatedRestPaths } from '@/rest/lib/config' +import { usePrefetchOnInteraction } from '@/frame/components/lib/prefetch' import { SidebarExpandStateProvider, useSidebarExpandState } from './useSidebarExpandState' import { flattenDescendants, MAX_NAVLIST_LEVEL } from './sidebar-navlist-depth' import styles from './SidebarProduct.module.scss' +type Router = ReturnType + // Brand NavList.Item renders a plain (its `as` prop only accepts 'a' | 'button', // not next/link), so intercept clicks to restore next/link-style client-side // navigation. Modifier/middle clicks fall through to the browser so open-in-new-tab // still works, and the keeps links crawlable for SSR. Mirrors Breadcrumbs.tsx. -function handleNavClick( - router: ReturnType, - event: MouseEvent, - href: string, -) { +function handleNavClick(router: Router, event: MouseEvent, href: string) { if ( event.defaultPrevented || event.button !== 0 || @@ -36,6 +45,50 @@ function handleNavClick( router.push(href, undefined, { locale: false }) } +// The sidebar renders the full product tree (hundreds of nodes) and fully remounts +// on every navigation (key={asPath} in SidebarNav). To keep per-item cost down we +// subscribe to the router ONCE here and hand items a stable routePath plus stable +// navigate/prefetch callbacks, instead of every item calling useRouter itself. +type SidebarNavValue = { + routePath: string + navigate: (event: MouseEvent, href: string) => void + prefetch: (href: string) => void +} +const SidebarNavContext = createContext(null) + +function useSidebarNav(): SidebarNavValue { + const value = useContext(SidebarNavContext) + if (!value) { + throw new Error('useSidebarNav must be used within SidebarProduct') + } + return value +} + +// Separate context for the REST-only scroll-spy state (full asPath with query+hash, +// and query). Kept out of SidebarNavValue so its per-navigation identity churn +// doesn't invalidate the memoized common items — only RestNavListItem consumes it. +type RestNavValue = { + asPath: string + query: ReturnType['query'] +} +const RestNavContext = createContext(null) + +function useRestNav(): RestNavValue { + const value = useContext(RestNavContext) + if (!value) { + throw new Error('useRestNav must be used within SidebarProduct REST section') + } + return value +} + +// Hover/focus handlers for a leaf link: warm the destination so the click is fast. +function prefetchHandlers(prefetch: (href: string) => void, href: string) { + return { + onMouseEnter: () => prefetch(href), + onFocus: () => prefetch(href), + } +} + export const SidebarProduct = () => { const router = useRouter() const { @@ -47,6 +100,22 @@ export const SidebarProduct = () => { } = useMainContext() const isRestPage = currentProduct && currentProduct.id === 'rest' + const { asPath, locale, query } = router + const routePath = `/${locale}${asPath.split('?')[0].split('#')[0]}` + + const prefetchHref = usePrefetchOnInteraction() + // Stable callbacks so memoized items don't re-render on unrelated changes. + const navigate = useCallback( + (event: MouseEvent, href: string) => handleNavClick(router, event, href), + [router], + ) + const prefetch = useCallback((href: string) => prefetchHref(router, href), [router, prefetchHref]) + const navValue = useMemo( + () => ({ routePath, navigate, prefetch }), + [routePath, navigate, prefetch], + ) + const restNavValue = useMemo(() => ({ asPath, query }), [asPath, query]) + useEffect(() => { // Brand NavList auto-expands the whole ancestor chain of the active item, so // scroll to the item marked aria-current="page" (the active article) rather @@ -84,31 +153,35 @@ export const SidebarProduct = () => { nonAutomatedRestPaths.every((item: string) => !page.href.includes(item)), ) return ( -
- - {navListLevelSentinel()} - {conceptualPages.map((childPage) => ( - - ))} - + +
+ + {navListLevelSentinel()} + {conceptualPages.map((childPage) => ( + + ))} + -
+
- - {navListLevelSentinel()} - {restPages.map((category) => ( - - ))} - -
+ + {navListLevelSentinel()} + {restPages.map((category) => ( + + ))} + +
+ ) } return (
- - {isRestPage ? restSection() : productSection()} - + + + {isRestPage ? restSection() : productSection()} + +
) } @@ -169,26 +242,30 @@ function navListLevelSentinel() { ) } -function LeafLink({ node }: { node: ProductTreeNode }) { - const router = useRouter() - const { asPath, locale } = router - const routePath = `/${locale}${asPath.split('?')[0].split('#')[0]}` +const LeafLink = memo(function LeafLink({ node }: { node: ProductTreeNode }) { + const { routePath, navigate, prefetch } = useSidebarNav() return ( ) => handleNavClick(router, event, node.href)} + onClick={(event: MouseEvent) => navigate(event, node.href)} + {...prefetchHandlers(prefetch, node.href)} > {node.title} ) -} +}) -function NavListItem({ childPage, level = 1 }: { childPage: ProductTreeNode; level?: number }) { - const router = useRouter() - const { asPath, locale } = router - const routePath = `/${locale}${asPath.split('?')[0].split('#')[0]}` +const NavListItem = memo(function NavListItem({ + childPage, + level = 1, +}: { + childPage: ProductTreeNode + level?: number +}) { + const { routePath, navigate, prefetch } = useSidebarNav() + const locale = routePath.split('/')[1] const isActive = routePath === childPage.href const hasChildren = childPage.childPages.length > 0 const specialCategory = childPage.layout === 'category-landing' @@ -231,9 +308,8 @@ function NavListItem({ childPage, level = 1 }: { childPage: ProductTreeNode; lev as="a" href={sidebarLinkHref} aria-current={routePath === sidebarLinkHref ? 'page' : false} - onClick={(event: MouseEvent) => - handleNavClick(router, event, sidebarLinkHref) - } + onClick={(event: MouseEvent) => navigate(event, sidebarLinkHref)} + {...prefetchHandlers(prefetch, sidebarLinkHref)} > {childPage.sidebarLink.text} @@ -243,9 +319,8 @@ function NavListItem({ childPage, level = 1 }: { childPage: ProductTreeNode; lev as="a" href={childPage.href} aria-current={isActive ? 'page' : false} - onClick={(event: MouseEvent) => - handleNavClick(router, event, childPage.href) - } + onClick={(event: MouseEvent) => navigate(event, childPage.href)} + {...prefetchHandlers(prefetch, childPage.href)} > {childPage.title} @@ -255,13 +330,12 @@ function NavListItem({ childPage, level = 1 }: { childPage: ProductTreeNode; lev ))} ) -} +}) function RestNavListItem({ category }: { category: ProductTreeNode }) { - const router = useRouter() - const { query, asPath, locale } = router + const { routePath, navigate, prefetch } = useSidebarNav() + const { asPath, query } = useRestNav() const [visibleAnchor, setVisibleAnchor] = useState('') - const routePath = `/${locale}${asPath.split('?')[0].split('#')[0]}` const miniTocItems = query.productId === 'rest' || // These pages need the Article Page mini tocs instead of the Rest Pages @@ -305,7 +379,8 @@ function RestNavListItem({ category }: { category: ProductTreeNode }) { as="a" href={category.href} aria-current={routePath === category.href ? 'page' : false} - onClick={(event: MouseEvent) => handleNavClick(router, event, category.href)} + onClick={(event: MouseEvent) => navigate(event, category.href)} + {...prefetchHandlers(prefetch, category.href)} > {category.title} @@ -355,9 +430,8 @@ function RestNavListItem({ category }: { category: ProductTreeNode }) { as="a" href={childPage.href} aria-current={routePath === childPage.href ? 'page' : false} - onClick={(event: MouseEvent) => - handleNavClick(router, event, childPage.href) - } + onClick={(event: MouseEvent) => navigate(event, childPage.href)} + {...prefetchHandlers(prefetch, childPage.href)} > {childPage.title}