-
Notifications
You must be signed in to change notification settings - Fork 38
chore(ci): add issue triage, stale, and Slack PR automation #725
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
9a465bf
chore(ci): add issue triage, stale, and Slack PR automation
anfibiacreativa f556556
chore(ci): reword PR Experience Workspace checkbox
anfibiacreativa afa7d59
chore(ci): remove Experience Workspace question from issue and PR tem…
anfibiacreativa a898ba2
chore(ci): remove Helix from the Scope / Area options
anfibiacreativa 0f01a90
feat(ci): three-stage stale flow with re-engage and final notice
anfibiacreativa 45532e7
docs(contributing): document stale cadence and pause labels
anfibiacreativa b8e0b86
refactor(ci): simplify triage per review feedback
anfibiacreativa 0872a62
Merge branch 'main' into chore/gh-issue-pr-automation
anfibiacreativa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| name: Bug report | ||
| description: Report something that is broken or not behaving as expected. | ||
| labels: ["bug"] | ||
| body: | ||
| - type: markdown | ||
| attributes: | ||
| value: | | ||
| Thanks for filing a bug. Fields marked required drive our triage automation | ||
| (scope labeling and the completeness check). Please fill in as much as you | ||
| can — a bot will tag you if anything required is missing. | ||
|
|
||
| - type: dropdown | ||
| id: scope | ||
| attributes: | ||
| label: Scope / Area | ||
| description: Which part of the product does this touch? | ||
| options: | ||
| - Editor / Canvas | ||
| - Blocks / Rendering | ||
| - Config / Sheets | ||
| - Auth / Permissions | ||
| - AI Assistant / Agent | ||
| - MCP / Tooling | ||
| - Infra / CI | ||
| - Docs | ||
| - Other / Unsure | ||
| validations: | ||
| required: true | ||
|
|
||
| - type: dropdown | ||
| id: affected | ||
| attributes: | ||
| label: Who is affected? | ||
| description: Is this customer-facing or internal? | ||
| options: | ||
| - Customer-facing | ||
| - Internal | ||
| validations: | ||
| required: true | ||
|
|
||
| - type: textarea | ||
| id: what-happened | ||
| attributes: | ||
| label: What happened? | ||
| description: A clear description of the bug, including what you expected instead. | ||
| validations: | ||
| required: true | ||
|
|
||
| - type: textarea | ||
| id: repro-steps | ||
| attributes: | ||
| label: Steps to reproduce | ||
| description: Minimal, numbered steps someone else can follow. | ||
| placeholder: | | ||
| 1. Go to ... | ||
| 2. Click ... | ||
| 3. See error | ||
| validations: | ||
| required: true | ||
|
|
||
| - type: textarea | ||
| id: repro-links | ||
| attributes: | ||
| label: Reproduction link(s) | ||
| description: URL(s) where the bug can be observed (before/after, test site, deployed branch). | ||
| placeholder: | | ||
| - Before: https://main--{repo}--{owner}.aem.live/ | ||
| - After: https://<branch>--{repo}--{owner}.aem.live/ | ||
|
mhaack marked this conversation as resolved.
|
||
| validations: | ||
| required: true | ||
|
|
||
| - type: input | ||
| id: video | ||
| attributes: | ||
| label: Video (optional) | ||
| description: A short screen recording helps a lot, but it is not required. | ||
| validations: | ||
| required: false | ||
|
anfibiacreativa marked this conversation as resolved.
|
||
|
|
||
| - type: input | ||
| id: version | ||
| attributes: | ||
| label: Environment / version | ||
| description: Branch, commit, or deployed URL where you saw this. | ||
| validations: | ||
| required: false | ||
|
anfibiacreativa marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| blank_issues_enabled: false | ||
| contact_links: | ||
| - name: Question or discussion | ||
| url: https://github.com/adobe/da-nx/discussions | ||
| about: For open-ended questions and ideas, please start a discussion instead of an issue. |
|
anfibiacreativa marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| name: Feature request | ||
| description: Suggest a new capability or an improvement. | ||
| labels: ["enhancement"] | ||
| body: | ||
| - type: markdown | ||
| attributes: | ||
| value: | | ||
| Thanks for the idea. Fields marked required drive our triage automation | ||
| (scope labeling and the completeness check). A bot will tag you if | ||
| anything required is missing. | ||
|
|
||
| - type: dropdown | ||
| id: scope | ||
| attributes: | ||
| label: Scope / Area | ||
| description: Which part of the product does this touch? | ||
| options: | ||
| - Editor / Canvas | ||
| - Blocks / Rendering | ||
| - Config / Sheets | ||
| - Auth / Permissions | ||
| - AI Assistant / Agent | ||
| - MCP / Tooling | ||
| - Infra / CI | ||
| - Docs | ||
| - Other / Unsure | ||
| validations: | ||
| required: true | ||
|
|
||
| - type: dropdown | ||
| id: affected | ||
| attributes: | ||
| label: Who is affected? | ||
| description: Is this customer-facing or internal? | ||
| options: | ||
| - Customer-facing | ||
| - Internal | ||
| validations: | ||
| required: true | ||
|
|
||
| - type: textarea | ||
| id: problem | ||
| attributes: | ||
| label: Problem / motivation | ||
| description: What are you trying to do, and why is it hard today? | ||
| validations: | ||
| required: true | ||
|
|
||
| - type: textarea | ||
| id: proposal | ||
| attributes: | ||
| label: Proposed solution | ||
| description: What would you like to see happen? | ||
| validations: | ||
| required: false |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| name: Issue completeness check | ||
|
|
||
| # Verifies a new/edited issue has the information triage needs. If anything is | ||
| # missing it applies the `triage-incomplete` label and leaves a single comment | ||
| # (tagging the reporter) listing what's needed. The comment is updated in place | ||
| # as the issue is fixed, and both the comment and label clear once it's complete. | ||
| # | ||
| # Required: a description, customer-vs-developer, a priority (P0-P3), and — for | ||
| # bugs — reproduction steps and a reproduction link. A video is prompted but | ||
| # never blocks. | ||
|
|
||
| on: | ||
| issues: | ||
| types: [opened, edited] | ||
|
|
||
| permissions: | ||
| issues: write | ||
|
|
||
| jobs: | ||
| check: | ||
| # Don't react to issues opened by bots (avoids automation loops). | ||
| if: ${{ github.event.issue.user.type != 'Bot' }} | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const MARKER = '<!-- issue-completeness-bot -->'; | ||
| const INCOMPLETE = 'triage-incomplete'; | ||
|
|
||
| const body = context.payload.issue.body || ''; | ||
| const author = context.payload.issue.user.login; | ||
| const { owner, repo } = context.repo; | ||
| const issue_number = context.payload.issue.number; | ||
| const labels = (context.payload.issue.labels || []).map(l => l.name); | ||
| const isBug = labels.includes('bug'); | ||
|
|
||
| const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | ||
| // Grab the first non-empty line under a "### <label>" heading. | ||
| const val = (label) => { | ||
| const m = body.match(new RegExp('###\\s*' + esc(label) + '\\s*\\n+([^\\n]+)')); | ||
| return m ? m[1].trim() : ''; | ||
| }; | ||
| const isEmpty = (v) => !v || /^_no response_$/i.test(v.trim()); | ||
|
|
||
| const missing = []; | ||
| const description = val('What happened?') || val('Problem / motivation'); | ||
| if (isEmpty(description)) missing.push('A description of the issue'); | ||
| if (isEmpty(val('Who is affected?'))) | ||
| missing.push('Who is affected (**customer-facing** or **internal**)'); | ||
| if (isBug) { | ||
| if (isEmpty(val('Steps to reproduce'))) missing.push('**Steps to reproduce**'); | ||
| if (isEmpty(val('Reproduction link(s)'))) missing.push('A **reproduction link**'); | ||
| } | ||
| const videoMissing = isBug && isEmpty(val('Video (optional)')); | ||
|
|
||
| // Find our existing tracked comment, if any. | ||
| const comments = await github.paginate(github.rest.issues.listComments, { | ||
| owner, repo, issue_number, per_page: 100, | ||
| }); | ||
| const existing = comments.find(c => (c.body || '').includes(MARKER)); | ||
|
|
||
| const hasLabel = labels.includes(INCOMPLETE); | ||
| const upsert = async (bodyMd) => { | ||
| if (existing) { | ||
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body: bodyMd }); | ||
| } else { | ||
| await github.rest.issues.createComment({ owner, repo, issue_number, body: bodyMd }); | ||
| } | ||
| }; | ||
|
|
||
| if (missing.length) { | ||
| if (!hasLabel) { | ||
| await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [INCOMPLETE] }); | ||
| } | ||
| let md = `${MARKER}\nHi @${author} :wave: thanks for opening this! ` | ||
| + `Before we can triage it, please add the following:\n\n` | ||
| + missing.map(m => `- [ ] ${m}`).join('\n') + '\n'; | ||
| if (videoMissing) { | ||
| md += `\n_Optional:_ a short screen recording of the problem helps a lot, but it is not required.\n`; | ||
| } | ||
| md += `\nEdit the issue description to add these — this notice and the \`${INCOMPLETE}\` ` | ||
| + `label clear automatically once everything is filled in.`; | ||
| await upsert(md); | ||
| core.info('Marked incomplete. Missing: ' + missing.join('; ')); | ||
| } else { | ||
| if (hasLabel) { | ||
| await github.rest.issues.removeLabel({ owner, repo, issue_number, name: INCOMPLETE }).catch(() => {}); | ||
| } | ||
| if (existing) { | ||
| const done = `${MARKER}\nThanks @${author} — everything triage needs is here. :white_check_mark:` | ||
| + (videoMissing ? ` (A short screen recording would still help if you can add one.)` : ''); | ||
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body: done }); | ||
| } else if (videoMissing) { | ||
| await upsert(`${MARKER}\nThanks @${author} — this looks complete. If you can, a short screen ` | ||
| + `recording would help us reproduce faster. Not required, just helpful!`); | ||
| } | ||
| core.info('Issue is complete.'); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| name: Slack PR Ticker | ||
|
|
||
| # Posts a one-line notice to Slack when a PR is opened / marked ready / reopened. | ||
| # Core PRs go to one channel, external PRs to another. Core vs external is decided | ||
| # from an explicit allowlist of core author usernames (CORE_AUTHORS below). | ||
| # | ||
| # Uses pull_request_target so the org secrets (webhook URLs) are available even | ||
| # for PRs from forks. This job only reads event metadata and never checks out or | ||
| # runs code from the PR, so it is not exposed to the pull_request_target risk of | ||
| # executing untrusted code. | ||
| # | ||
| # Required secrets: | ||
| # SLACK_WEBHOOK_CORE_PRS Incoming Webhook URL for the core-team channel | ||
| # SLACK_WEBHOOK_COMMUNITY_PRS Incoming Webhook URL for the contributor channel | ||
|
|
||
| on: | ||
| pull_request_target: | ||
| types: [opened, ready_for_review, reopened] | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| notify: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Route and post to Slack | ||
| env: | ||
| CORE_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CORE_PRS }} | ||
| COMMUNITY_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_COMMUNITY_PRS }} | ||
| PR_TITLE: ${{ github.event.pull_request.title }} | ||
| PR_URL: ${{ github.event.pull_request.html_url }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| PR_AUTHOR: ${{ github.event.pull_request.user.login }} | ||
| PR_DRAFT: ${{ github.event.pull_request.draft }} | ||
| REPO: ${{ github.repository }} | ||
| # Core contributors (edit to keep in sync with the team). | ||
| CORE_AUTHORS: 'svynod hannesolo mhaack anfibiacreativa' | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| # Don't ticker drafts; the ready_for_review event covers them later. | ||
| if [ "$PR_DRAFT" = "true" ]; then | ||
| echo "Draft PR; skipping." | ||
| exit 0 | ||
| fi | ||
|
|
||
| WEBHOOK="$COMMUNITY_WEBHOOK"; TIER="External Contribution" | ||
| for u in $CORE_AUTHORS; do | ||
| if [ "$(echo "$u" | tr '[:upper:]' '[:lower:]')" = "$(echo "$PR_AUTHOR" | tr '[:upper:]' '[:lower:]')" ]; then | ||
| WEBHOOK="$CORE_WEBHOOK"; TIER="Core Contribution"; break | ||
| fi | ||
| done | ||
|
|
||
| if [ -z "${WEBHOOK:-}" ]; then | ||
| echo "No webhook configured for tier=$TIER; skipping." | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Send discrete fields so a Slack Workflow Builder webhook can map them | ||
| # to variables (repo, number, title, author, url, tier) and compose the | ||
| # message itself. Raw URLs auto-linkify in Slack. | ||
| PAYLOAD="$(jq -n \ | ||
| --arg repo "$REPO" \ | ||
| --arg number "$PR_NUMBER" \ | ||
| --arg title "$PR_TITLE" \ | ||
| --arg author "$PR_AUTHOR" \ | ||
| --arg url "$PR_URL" \ | ||
| --arg tier "$TIER" \ | ||
| '{repo: $repo, number: $number, title: $title, author: $author, url: $url, tier: $tier}')" | ||
| curl -sS -X POST -H 'Content-type: application/json' --data "$PAYLOAD" "$WEBHOOK" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.