diff --git a/.github/scripts/dapr_bot.js b/.github/scripts/dapr_bot.js new file mode 100644 index 00000000..2ededc62 --- /dev/null +++ b/.github/scripts/dapr_bot.js @@ -0,0 +1,204 @@ +// list of owner who can control dapr-bot workflow. +// TODO: Read owners from OWNERS file. +const owners = [ + "yaron2", + "youngbupark", + "Haishi2016", + "lukekim", + "amanbha", + "msfussell", + "shalabhms", + "LMWF", + "artursouza", + "vinayada1", + "mukundansundar", + "wcs1only", + "orizohar", + "pruthvidhodda", + "mchmarny", + "tcnghia", + "berndverst", + "halspang", + "tanvigour", + "dmitsh", + "pkedy", + "CodeMonkeyLeet", + "XavierGeerinck", + "amulyavarote", + "shubham1172" +]; + +/** + * Execute fn if label exists on an issue. + * @param {*} github GitHub object reference + * @param {*} issue GitHub issue reference + * @param {*} label label name + * @param {*} fn async function + */ + async function executeIfIssueHasLabel(github, issue, label, fn) { + const response = await github.issues.listLabelsOnIssue({ + issue_number: issue.number, + owner: issue.owner, + repo: issue.repo, + }); + + const labelNames = response.data.map((i) => i.name); + if (labelNames.indexOf(label) > -1) { + await fn(); + } +} + +/** + * Assign an issue to assignee. + * @param {*} github GitHub object reference + * @param {*} context GitHub action context + * @param {boolean} isPullRequest is the workflow triggered by a pull request? + */ +async function executeAssign(github, context, isPullRequest) { + const issue = context.issue; + + if (isPullRequest) { + console.log("[executeAssign] pull requests unsupported, skipping command execution."); + return; + } else if (issue.assignees && issue.assignees.length !== 0) { + console.log("[executeAssign] issue already has assignees, skipping command execution."); + return; + } + + await github.issues.addAssignees({ + owner: issue.owner, + repo: issue.repo, + issue_number: issue.number, + assignees: [context.actor], + }); +} + +/** + * Add a label to the issue indicating that it needs the author's feedback. + * @param {*} github GitHub object reference + * @param {*} context GitHub action context + * @param {boolean} isPullRequest is the workflow triggered by a pull request? + * @returns + */ +async function executePingAuthor(github, context, isPullRequest) { + const issue = context.issue; + + if (isPullRequest) { + console.log("[executePingAuthor] pull requests unsupported, skipping command execution."); + return; + } else if (owners.indexOf(context.actor) < 0) { + console.log("[executePingAuthor] user does not have privilege, skipping command execution."); + return; + } + + // if there is a 'needs-team-attention' label, remove it. + await executeIfIssueHasLabel(github, issue, 'needs-team-attention', async () => { + await github.issues.removeLabel({ + issue_number: issue.number, + owner: issue.owner, + repo: issue.repo, + name: 'needs-team-attention' + }); + }); + + // Add new label + await github.issues.addLabels({ + issue_number: issue.number, + owner: issue.owner, + repo: issue.repo, + labels: ['needs-author-feedback'] + }); +} + +/** + * Trigger e2e tests for pull request + * @param {*} github GitHub object reference + * @param {*} context GitHub action context + * @param {boolean} isPullRequest is the workflow triggered by a pull request? + */ +async function executeEndToEndTests(github, context, isPullRequest) { + const issue = context.issue; + + if (!isPullRequest) { + console.log("[executeEndToEndTests] issues unsupported, skipping command execution."); + return; + } else if (owners.indexOf(context.actor) < 0) { + console.log("[executeEndToEndTests] user does not have privilege, skipping command execution."); + return; + } + + // Get pull request + const pull = await github.pulls.get({ + owner: issue.owner, + repo: issue.repo, + pull_number: issue.number + }); + + if (pull && pull.data) { + // Get commit id and repo from pull head + const clientPayload = { + pull_head_ref: pull.data.head.sha, + pull_head_repo: pull.data.head.repo.full_name, + command: "ok-to-e2e-test", + issue: issue, + }; + + // Fire repository_dispatch event to trigger e2e test + await github.repos.createDispatchEvent({ + owner: issue.owner, + repo: issue.repo, + event_type: "e2e-test", + client_payload: clientPayload, + }); + + console.log(`[executeEndToEndTests] triggered E2E tests for ${JSON.stringify(clientPayload)}.`); + } +} + + +export default async ({ github, context }) => { + const payload = context.payload; + const issue = context.issue; + const isFromPulls = !!payload.issue.pull_request; + const commentBody = payload.comment.body; + + if (!commentBody) { + console.log("[main] comment body not found, exiting."); + return; + } + + // the author of this issue is interacting with it + if (!isFromPulls && context.actor == issue.owner) { + // if there is a 'needs-author-feedback' label, + // replace it with 'needs-team-attention' label. + await executeIfIssueHasLabel(github, issue, 'needs-author-feedback', async () => { + await github.issues.removeLabel({ + issue_number: issue.number, + owner: issue.owner, + repo: issue.repo, + name: 'needs-author-feedback' + }); + await github.issues.addLabels({ + issue_number: issue.number, + owner: issue.owner, + repo: issue.repo, + labels: ['needs-team-attention'] + }); + }); + } + + switch (commentBody) { + case "/assign": + executeAssign(github, context, isFromPulls); + break; + case "/ping-author": + executePingAuthor(github, context, isFromPulls); + break; + case "/ok-to-e2e-test": + executeEndToEndTests(github, context, isFromPulls); + break; + default: + console.log(`[main] command ${commentBody} not found, exiting.`); + break; + } +}; \ No newline at end of file diff --git a/.github/workflows/dapr-bot-schedule.yml b/.github/workflows/dapr-bot-schedule.yml new file mode 100644 index 00000000..288fd23f --- /dev/null +++ b/.github/workflows/dapr-bot-schedule.yml @@ -0,0 +1,54 @@ +# +# Copyright 2022 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: dapr-bot-schedule + +on: + schedule: + - cron: '*/10 * * * *' + workflow_dispatch: +jobs: + prune_stale: + name: Prune Stale + runs-on: ubuntu-latest + steps: + - name: Prune Stale + uses: actions/stale@v5.0.0 + with: + repo-token: ${{ secrets.DAPR_BOT_TOKEN }} + days-before-issue-stale: 60 + days-before-pr-stale: 30 + days-before-close: 7 + stale-issue-message: > + This issue has been automatically marked as stale because it has not had activity in the + last 60 days. It will be closed in the next 7 days unless it is tagged (pinned, good first issue, help wanted or triaged/resolved) or other activity + occurs. Thank you for your contributions. + close-issue-message: > + This issue has been automatically closed because it has not had activity in the + last 67 days. If this issue is still valid, please ping a maintainer and ask them to label it as pinned, good first issue, help wanted or triaged/resolved. + Thank you for your contributions. + stale-pr-message: > + This pull request has been automatically marked as stale because it has not had + activity in the last 30 days. It will be closed in 7 days if no further activity occurs. Please + feel free to give a status update now, ping for review, or re-open when it's ready. + Thank you for your contributions! + close-pr-message: > + This pull request has been automatically closed because it has not had + activity in the last 37 days. Please feel free to give a status update now, ping for review, or re-open when it's ready. + Thank you for your contributions! + stale-issue-label: 'stale' + exempt-issue-labels: 'pinned,good first issue,help wanted,triaged/resolved,triaged/unresolved' + stale-pr-label: 'stale' + exempt-pr-labels: 'pinned' + operations-per-run: 500 + ascending: true \ No newline at end of file diff --git a/.github/workflows/dapr-bot.yml b/.github/workflows/dapr-bot.yml index 3980d743..2c8a11b2 100644 --- a/.github/workflows/dapr-bot.yml +++ b/.github/workflows/dapr-bot.yml @@ -22,23 +22,11 @@ jobs: name: bot-processor runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 # to make the JS script available for the next step - name: Comment analyzer - uses: actions/github-script@v1 + uses: actions/github-script@v4 with: github-token: ${{secrets.DAPR_BOT_TOKEN}} script: | - const payload = context.payload; - const issue = context.issue; - const isFromPulls = !!payload.issue.pull_request; - const commentBody = payload.comment.body; - if (!isFromPulls && commentBody && commentBody.indexOf("/assign") == 0) { - if (!issue.assignees || issue.assignees.length === 0) { - await github.issues.addAssignees({ - owner: issue.owner, - repo: issue.repo, - issue_number: issue.number, - assignees: [context.actor], - }) - } - return; - } + const script = require('./.github/scripts/dapr_bot.js') + await script({github, context}) \ No newline at end of file diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index f988be70..63d690bb 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -24,6 +24,11 @@ on: branches: - master - release-* + # Manual trigger + workflow_dispatch: + # Dispatch on external events + repository_dispatch: + types: [e2e-test] jobs: test-e2e: