From e51ced1e43bcdbbdb4c034c37cf0a6dab3ec70cc Mon Sep 17 00:00:00 2001 From: Michaela Robosova Date: Tue, 7 Oct 2025 18:31:19 +0200 Subject: [PATCH] Fixes for Slack notifications and automated replies Fixes https://github.com/learningequality/.github/issues/30 Fixes https://github.com/learningequality/.github/issues/31 Co-authored-by: iamshobhraj --- .../workflows/contributor-issue-comment.yml | 31 ++++++++-- .github/workflows/is-close-contributor.yml | 57 +++++++++++++++++++ scripts/constants.js | 5 +- scripts/contributor-issue-comment.js | 7 ++- scripts/is-close-contributor.js | 56 ++++++++++++++++++ 5 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/is-close-contributor.yml create mode 100644 scripts/is-close-contributor.js diff --git a/.github/workflows/contributor-issue-comment.yml b/.github/workflows/contributor-issue-comment.yml index c26bd48..cbd4c2a 100644 --- a/.github/workflows/contributor-issue-comment.yml +++ b/.github/workflows/contributor-issue-comment.yml @@ -18,16 +18,26 @@ on: jobs: + check-if-close-contributor: + name: Check if user is a close contributor + uses: learningequality/.github/.github/workflows/is-close-contributor.yml@main + secrets: + LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }} + LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }} + with: + username: ${{ github.event.comment.user.login }} + + process-issue-comment: name: Process issue comment - + needs: [check-if-close-contributor] if: >- ${{ !github.event.issue.pull_request && - github.event.comment.author_association != 'MEMBER' && github.event.comment.author_association != 'OWNER' && github.event.comment.user.login != 'sentry-io[bot]' && - github.event.comment.user.login != 'learning-equality-bot[bot]' + github.event.comment.user.login != 'learning-equality-bot[bot]' && + (github.event.comment.author_association != 'MEMBER' || needs.check-if-close-contributor.outputs.is_close_contributor == 'true') }} runs-on: ubuntu-latest @@ -52,11 +62,11 @@ jobs: node-version: '20' - name: Install dependencies - run: npm install + run: yarn install --frozen-lockfile - name: Run script id: script - uses: actions/github-script@v7 + uses: actions/github-script@v7 with: github-token: ${{ steps.generate-token.outputs.token }} script: | @@ -65,6 +75,7 @@ jobs: env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL: ${{ secrets.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL }} + IS_CLOSE_CONTRIBUTOR: ${{ needs.check-if-close-contributor.outputs.is_close_contributor }} - name: Send Slack notification about GitHub comment uses: slackapi/slack-github-action@v2.1.0 @@ -87,3 +98,13 @@ jobs: "text": "${{ steps.script.outputs.slack_notification_bot_comment }}" } + - name: Send Slack notification about skipped GitHub bot reply + if: ${{ steps.script.outputs.bot_reply_skipped }} + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook-type: incoming-webhook + webhook: ${{ steps.script.outputs.webhook_url }} + payload: > + { + "text": "${{ steps.script.outputs.slack_notification_bot_skipped }}" + } diff --git a/.github/workflows/is-close-contributor.yml b/.github/workflows/is-close-contributor.yml new file mode 100644 index 0000000..91f502a --- /dev/null +++ b/.github/workflows/is-close-contributor.yml @@ -0,0 +1,57 @@ +name: Check if user is a close contributor + +on: + workflow_call: + inputs: + username: + description: 'Github username' + required: true + type: string + secrets: + LE_BOT_APP_ID: + required: true + LE_BOT_PRIVATE_KEY: + required: true + outputs: + is_close_contributor: + description: "True if the user is a close contributor, otherwise false" + value: ${{ jobs.check-contributor.outputs.is_close_contributor }} + +jobs: + check-contributor: + name: Run check + runs-on: ubuntu-latest + outputs: + is_close_contributor: ${{ steps.check-script.outputs.is_close_contributor }} + steps: + - name: Generate App Token + id: generate-token + uses: tibdex/github-app-token@v2 + with: + app_id: ${{ secrets.LE_BOT_APP_ID }} + private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }} + + - name: Checkout .github repository + uses: actions/checkout@v4 + with: + repository: learningequality/.github + ref: main + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Run script + id: check-script + uses: actions/github-script@v7 + env: + USERNAME: ${{ inputs.username }} + with: + github-token: ${{ steps.generate-token.outputs.token }} + script: | + const script = require('./scripts/is-close-contributor.js'); + return await script({core, github, context }); diff --git a/scripts/constants.js b/scripts/constants.js index 0033d1e..6945880 100644 --- a/scripts/constants.js +++ b/scripts/constants.js @@ -3,7 +3,9 @@ const LE_BOT_USERNAME = 'learning-equality-bot[bot]'; // close contributors are treated a bit special in some workflows, // for example, we receive a high priority notification about their // comments on all issues rather than just on 'help wanted' issues -const CLOSE_CONTRIBUTORS = ['BabyElias', 'Dimi20cen', 'EshaanAgg', 'GarvitSinghal47', 'habibayman', 'iamshobhraj', 'indirectlylit', 'Jakoma02', 'KshitijThareja', 'muditchoudhary', 'nathanaelg16', 'nikkuAg', 'Sahil-Sinha-11', 'shivam-daksh', 'shruti862', 'thesujai', 'WinnyChang']; +const CLOSE_CONTRIBUTORS = ['AadarshM07', 'Abhishek-Punhani', 'BabyElias', 'Dimi20cen', 'EshaanAgg', 'GarvitSinghal47', 'habibayman', 'iamshobhraj', 'indirectlylit', 'Jakoma02', 'KshitijThareja', 'muditchoudhary', 'nathanaelg16', 'nikkuAg', 'Sahil-Sinha-11', 'shivam-daksh', 'shruti862', 'thesujai', 'WinnyChang', 'yeshwanth235']; + +const TEAMS_WITH_CLOSE_CONTRIBUTORS = ['gsoc-contributors', 'learning-equality-community-guide']; const KEYWORDS_DETECT_ASSIGNMENT_REQUEST = [ 'assign', 'assigned', @@ -39,4 +41,5 @@ module.exports = { KEYWORDS_DETECT_ASSIGNMENT_REQUEST, ISSUE_LABEL_HELP_WANTED, BOT_MESSAGE_ISSUE_NOT_OPEN, + TEAMS_WITH_CLOSE_CONTRIBUTORS, }; diff --git a/scripts/contributor-issue-comment.js b/scripts/contributor-issue-comment.js index f89b171..7b81c02 100644 --- a/scripts/contributor-issue-comment.js +++ b/scripts/contributor-issue-comment.js @@ -1,6 +1,5 @@ const { LE_BOT_USERNAME, - CLOSE_CONTRIBUTORS, KEYWORDS_DETECT_ASSIGNMENT_REQUEST, ISSUE_LABEL_HELP_WANTED, BOT_MESSAGE_ISSUE_NOT_OPEN @@ -21,6 +20,7 @@ module.exports = async ({ github, context, core }) => { const owner = context.repo.owner; const supportDevSlackWebhookUrl = process.env.SLACK_WEBHOOK_URL; const supportDevNotificationsSlackWebhookUrl = process.env.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL; + const isCloseContributor = process.env.IS_CLOSE_CONTRIBUTOR === 'true'; const keywordRegexes = KEYWORDS_DETECT_ASSIGNMENT_REQUEST .map(k => k.trim().toLowerCase()) .filter(Boolean) @@ -81,7 +81,7 @@ module.exports = async ({ github, context, core }) => { } - if (await hasLabel(ISSUE_LABEL_HELP_WANTED) || CLOSE_CONTRIBUTORS.includes(commentAuthor)) { + if ( isCloseContributor || await hasLabel(ISSUE_LABEL_HELP_WANTED) ) { core.setOutput('webhook_url', supportDevSlackWebhookUrl); } else { core.setOutput('webhook_url', supportDevNotificationsSlackWebhookUrl); @@ -93,6 +93,9 @@ module.exports = async ({ github, context, core }) => { if(PastBotComments.length > 0){ lastBotComment = PastBotComments.at(-1); core.setOutput('bot_replied', false); + core.setOutput('bot_reply_skipped', true); + const slackMessage = `*[${repo}] Bot response skipped on issue: <${issueUrl}|${escapedTitle}> (less than 1 hour since last bot reply)*`; + core.setOutput('slack_notification_bot_skipped', slackMessage); } else if(PastBotComments.length === 0){ console.log("bot is replying"); lastBotComment = await botReply(); diff --git a/scripts/is-close-contributor.js b/scripts/is-close-contributor.js new file mode 100644 index 0000000..18c0257 --- /dev/null +++ b/scripts/is-close-contributor.js @@ -0,0 +1,56 @@ +const { CLOSE_CONTRIBUTORS, TEAMS_WITH_CLOSE_CONTRIBUTORS } = require('./constants'); + +module.exports = async ({ core, github, context }) => { + const username = process.env.USERNAME; + if (!username) { + core.setFailed('Missing username input.'); + return; + } + + if (CLOSE_CONTRIBUTORS.map(c => c.toLowerCase().trim()).includes(username.toLowerCase().trim())) { + core.info(`User '${username}' found in the CLOSE CONTRIBUTORS list.`); + core.setOutput('is_close_contributor', true); + return; + } + + const org = context.repo.owner; + + // Even though we check on team members here, it's best + // to add everyone to CLOSE_CONTRIBUTORS constant anyway + // for reliable results (e.g. check below won't work + // for private members) + const promises = TEAMS_WITH_CLOSE_CONTRIBUTORS.map(team_slug => + github.rest.teams.getMembershipForUserInOrg({ + org, + team_slug, + username, + }) + ); + + try { + const results = await Promise.allSettled(promises); + let isTeamMember = false; + + for (const result of results) { + if (result.status === 'fulfilled' && result.value.data.state === 'active') { + isTeamMember = true; + break; + } + + if (result.status === 'rejected' && result.reason.status !== 404) { + throw new Error(`API Error: ${result.reason.message}`); + } + } + + if (isTeamMember) { + core.info(`User '${username}' was found to be a member of a monitored team.`); + } else { + core.info(`User '${username}' was not found to be a member of a monitored team.`); + } + + core.setOutput('is_close_contributor', isTeamMember); + + } catch (error) { + core.setFailed(error.message); + } +}; \ No newline at end of file