|
| 1 | +name: Welcome & Label First-Time Discussion Author |
| 2 | + |
| 3 | +on: |
| 4 | + discussion: |
| 5 | + types: [created] |
| 6 | + |
| 7 | +permissions: |
| 8 | + contents: read |
| 9 | + discussions: write |
| 10 | + issues: write |
| 11 | + |
| 12 | +jobs: |
| 13 | + label_welcome_discussion: |
| 14 | + runs-on: ubuntu-latest |
| 15 | + steps: |
| 16 | + - uses: actions/checkout@v4 |
| 17 | + |
| 18 | + - uses: actions/setup-python@v5 |
| 19 | + with: |
| 20 | + python-version: "3.11" |
| 21 | + |
| 22 | + - name: Check if GitHub employee (member of org "github") |
| 23 | + id: check_employee |
| 24 | + uses: actions/github-script@v7 |
| 25 | + with: |
| 26 | + github-token: ${{ secrets.READ_GITHUB_ORG_MEMBERS_TOKEN }} |
| 27 | + result-encoding: string |
| 28 | + script: | |
| 29 | + const username = (context.payload.discussion?.user?.login || "").trim(); |
| 30 | +
|
| 31 | + if (!username) { |
| 32 | + console.log("No username found in discussion payload; treating as not-employee."); |
| 33 | + return "false"; |
| 34 | + } |
| 35 | +
|
| 36 | + try { |
| 37 | + const response = await github.rest.orgs.checkMembershipForUser({ |
| 38 | + org: "github", |
| 39 | + username |
| 40 | + }); |
| 41 | +
|
| 42 | + if (response.status === 204) { |
| 43 | + console.log(`'${username}' IS a member of org 'github' (treat as employee).`); |
| 44 | + return "true"; |
| 45 | + } |
| 46 | +
|
| 47 | + console.log(`Unexpected status ${response.status}; treating as not-employee.`); |
| 48 | + return "false"; |
| 49 | + } catch (error) { |
| 50 | + if (error.status === 404) { |
| 51 | + console.log(`'${username}' is NOT a member of org 'github'.`); |
| 52 | + return "false"; |
| 53 | + } |
| 54 | +
|
| 55 | + console.log("Employee check failed; treating as not-employee."); |
| 56 | + return "false"; |
| 57 | + } |
| 58 | +
|
| 59 | + - name: Check first-time status (Python) |
| 60 | + id: first_time |
| 61 | + if: steps.check_employee.outputs.result != 'true' |
| 62 | + shell: bash |
| 63 | + env: |
| 64 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 65 | + USERNAME: ${{ github.event.discussion.user.login }} |
| 66 | + OWNER: ${{ github.repository_owner }} |
| 67 | + REPO: ${{ github.event.repository.name }} |
| 68 | + CURRENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} |
| 69 | + run: | |
| 70 | + set -euo pipefail |
| 71 | + python .github/workflows/scripts/first_time_discussion_author_live.py |
| 72 | +
|
| 73 | + - name: Resolve Discussion ID + Label ID ("Welcome 🎉") |
| 74 | + id: ids |
| 75 | + if: steps.check_employee.outputs.result != 'true' && steps.first_time.outputs.should_welcome == 'true' |
| 76 | + uses: actions/github-script@v7 |
| 77 | + env: |
| 78 | + LABEL_NAME: "Welcome 🎉" |
| 79 | + with: |
| 80 | + github-token: ${{ secrets.GITHUB_TOKEN }} |
| 81 | + result-encoding: string |
| 82 | + script: | |
| 83 | + const owner = context.repo.owner; |
| 84 | + const repo = context.repo.repo; |
| 85 | +
|
| 86 | + const discussionNumber = context.payload?.discussion?.number; |
| 87 | + if (!discussionNumber) { |
| 88 | + core.setFailed("Missing discussion number in event payload."); |
| 89 | + return ""; |
| 90 | + } |
| 91 | +
|
| 92 | + const labelName = process.env.LABEL_NAME; |
| 93 | +
|
| 94 | + // 1) Get Discussion node ID |
| 95 | + const discussionResp = await github.graphql( |
| 96 | + ` |
| 97 | + query($owner: String!, $repo: String!, $number: Int!) { |
| 98 | + repository(owner: $owner, name: $repo) { |
| 99 | + discussion(number: $number) { id number url title } |
| 100 | + } |
| 101 | + } |
| 102 | + `, |
| 103 | + { owner, repo, number: discussionNumber } |
| 104 | + ); |
| 105 | +
|
| 106 | + const discussionNode = discussionResp?.repository?.discussion; |
| 107 | + if (!discussionNode?.id) { |
| 108 | + throw new Error(`Unable to resolve discussion node id for #${discussionNumber}`); |
| 109 | + } |
| 110 | +
|
| 111 | + // 2) Paginate labels to find the correct label node |
| 112 | + let cursor = null; |
| 113 | + let foundLabel = null; |
| 114 | +
|
| 115 | + do { |
| 116 | + const labelsResp = await github.graphql( |
| 117 | + ` |
| 118 | + query($owner: String!, $repo: String!, $after: String) { |
| 119 | + repository(owner: $owner, name: $repo) { |
| 120 | + labels(first: 100, after: $after) { |
| 121 | + pageInfo { hasNextPage endCursor } |
| 122 | + nodes { name id } |
| 123 | + } |
| 124 | + } |
| 125 | + } |
| 126 | + `, |
| 127 | + { owner, repo, after: cursor } |
| 128 | + ); |
| 129 | +
|
| 130 | + const labelsConnection = labelsResp?.repository?.labels; |
| 131 | + const labels = labelsConnection?.nodes || []; |
| 132 | +
|
| 133 | + foundLabel = labels.find(l => l?.name === labelName); |
| 134 | +
|
| 135 | + const pageInfo = labelsConnection?.pageInfo; |
| 136 | + cursor = pageInfo?.hasNextPage ? pageInfo.endCursor : null; |
| 137 | + } while (!foundLabel && cursor); |
| 138 | +
|
| 139 | + if (!foundLabel) { |
| 140 | + throw new Error(`Label "${labelName}" not found in ${owner}/${repo} (checked all pages)`); |
| 141 | + } |
| 142 | +
|
| 143 | + core.setOutput("discussion_id", discussionNode.id); |
| 144 | + core.setOutput("label_id", foundLabel.id); |
| 145 | +
|
| 146 | + return "ok"; |
| 147 | +
|
| 148 | + - name: Apply label via GraphQL mutation |
| 149 | + if: steps.check_employee.outputs.result != 'true' && steps.first_time.outputs.should_welcome == 'true' |
| 150 | + env: |
| 151 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 152 | + shell: bash |
| 153 | + run: | |
| 154 | + set -euo pipefail |
| 155 | + echo "Applying label via GraphQL mutation..." |
| 156 | + gh api graphql -f query=' |
| 157 | + mutation($labelableId:ID!,$labelIds:[ID!]!) { |
| 158 | + addLabelsToLabelable(input:{labelableId:$labelableId,labelIds:$labelIds}) { |
| 159 | + labelable { |
| 160 | + ... on Discussion { |
| 161 | + number |
| 162 | + title |
| 163 | + labels(first:10) { nodes { name } } |
| 164 | + } |
| 165 | + } |
| 166 | + } |
| 167 | + } |
| 168 | + ' \ |
| 169 | + -F labelableId='${{ steps.ids.outputs.discussion_id }}' \ |
| 170 | + -F labelIds[]='${{ steps.ids.outputs.label_id }}' |
0 commit comments