From f5107159b239fb84da20ae831fd036147afe1355 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Fri, 6 Dec 2024 13:20:12 -0500 Subject: [PATCH] ci: use shared action for merge-by updates (#3341) * ci: use shared action for merge-by updates Signed-off-by: Trae Yelovich * fix(ci): change branch for shared action Signed-off-by: Trae Yelovich --------- Signed-off-by: Trae Yelovich Co-authored-by: Billie Simmons --- .github/workflows/merge-by-comments.yml | 7 +- .github/workflows/merge-by-table.yml | 22 +- .../merge-by/build-table-and-notify.js | 217 ------------------ .github/workflows/merge-by/post-date.js | 48 ---- 4 files changed, 5 insertions(+), 289 deletions(-) delete mode 100644 .github/workflows/merge-by/build-table-and-notify.js delete mode 100644 .github/workflows/merge-by/post-date.js diff --git a/.github/workflows/merge-by-comments.yml b/.github/workflows/merge-by-comments.yml index 5f0a412475..1f76a66c84 100644 --- a/.github/workflows/merge-by-comments.yml +++ b/.github/workflows/merge-by-comments.yml @@ -10,9 +10,6 @@ jobs: permissions: pull-requests: write steps: - - uses: actions/checkout@v3 - - uses: actions/github-script@v7 + - uses: zowe-actions/shared-actions/merge-by@main with: - script: | - const script = require("./.github/workflows/merge-by/post-date.js"); - await script({ github, context }); \ No newline at end of file + operation: "bump-dates" \ No newline at end of file diff --git a/.github/workflows/merge-by-table.yml b/.github/workflows/merge-by-table.yml index c5e0f09bbc..8129c44b34 100644 --- a/.github/workflows/merge-by-table.yml +++ b/.github/workflows/merge-by-table.yml @@ -2,7 +2,7 @@ name: Merge-by on: pull_request: - types: [opened, ready_for_review] + types: [opened, ready_for_review, converted_to_draft] pull_request_review: types: [submitted] push: @@ -20,22 +20,6 @@ jobs: discussions: write pull-requests: write steps: - - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v4 + - uses: zowe-actions/shared-actions/merge-by@main with: - version: 8 - run_install: false - - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install - - - uses: actions/github-script@v7 - with: - script: | - const script = require("./.github/workflows/merge-by/build-table-and-notify.js"); - await script({ github, context }); \ No newline at end of file + operation: "build-table" \ No newline at end of file diff --git a/.github/workflows/merge-by/build-table-and-notify.js b/.github/workflows/merge-by/build-table-and-notify.js deleted file mode 100644 index 1e23e9fd53..0000000000 --- a/.github/workflows/merge-by/build-table-and-notify.js +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Builds a row for the Markdown table given the GitHub repo owner, repo name and pull request. - * @param {string} owner The owner of the repository (user or organization) - * @param {string} repo The name of the repository on GitHub - * @param {Object} pr The pull request data to use for the table row - * @param {number} pr.number The number for the pull request - * @param {string} pr.author The author of the pull request - * @param {string} pr.title The title of the pull request - * @param {boolean} pr.hasReviews Whether the pull request has 2 or more approvals - * @param {boolean} pr.mergeable Whether the pull request is able to be merged - * @param {Object[]} pr.reviewers The list of requested reviewers for the pull request - * @param {string} pr.mergeBy (optional) The merge-by date for the pull request - * @returns - */ -const buildTableRow = (owner, repo, pr) => - `| [#${pr.number}](https://github.com/${owner}/${repo}/pull/${pr.number}) | [**${pr.title.trim()}**](https://github.com/${owner}/${repo}/pull/${pr.number}) | ${pr.author} | ${pr.mergeBy ?? "N/A"} | ${pr.hasReviews && pr.mergeable !== false ? ":white_check_mark:" : ":white_large_square:"} |`; - -const tableHeader = ` -| # | Title | Author | Merge by | Ready to merge? | -| - | ----- | ------ | ------------- | -------------- |`; - -/** - * Scans PRs and builds a table using Markdown. Updates an issue or creates a new one with the table. - * - * @param {Object} github The OctoKit/rest.js API for making requests to GitHub - * @param {string} owner The owner of the repository (user or organization) - * @param {Object[]} pullRequests The list of pull requests to include in the table - * @param {number} pullRequests[].number The number for the pull request - * @param {string} pullRequests[].author The author of the pull request - * @param {string} pullRequests[].title The title of the pull request - * @param {boolean} pullRequests[].hasReviews Whether the pull request has 2 or more approvals - * @param {boolean} pullRequests[].mergeable Whether the pull request is able to be merged - * @param {Object[]} pullRequests[].reviewers The list of requested reviewers for the pull request - * @param {string} pullRequests[].mergeBy (optional) The merge-by date for the pull request - * @param {string} repo The name of the repository on GitHub - */ -const scanPRsAndUpdateTable = async ({ github, owner, pullRequests, repo }) => { - // Build a table using Markdown to post within the issue - const body = `${tableHeader}\n${pullRequests.map((pr) => buildTableRow(owner, repo, pr)).join("\n")}`; - - const graphqlQuery = `query($owner:String!, $repo:String!) { - repository(owner:$owner, name:$repo) { - id - - discussionCategories(first: 100) { - nodes { - id - name - } - } - - discussions(first: 100) { - nodes { - id - body - title - } - } - } - }`; - - const discussionsQuery = await github.graphql(graphqlQuery, { - owner, - repo, - }); - const discussion = discussionsQuery?.repository?.discussions?.nodes?.find((d) => d.title === "PR Status List"); - - if (discussion != null) { - const mutation = `mutation($input:UpdateDiscussionInput!) { - updateDiscussion(input: $input) { - discussion { - id - } - } - }` - await github.graphql(mutation, { - input: { - discussionId: discussion.id, - body, - } - }); - } else { - const mutation = `mutation($input:CreateDiscussionInput!) { - createDiscussion(input: $input) { - discussion { - id - } - } - }`; - const generalCategory = discussionsQuery.repository?.discussionCategories?.nodes?.find((cat) => cat.name === "General"); - await github.graphql(mutation, { - input: { - categoryId: generalCategory.id, - repositoryId: discussionsQuery?.repository?.id, - body, - title: "PR Status List" - } - }); - } -} - -/** - * Notifies users for PRs that have a merge-by date <24 hours from now. - * - * @param {Object} dayJs Day.js exports for manipulating/querying time differences - * @param {Object} github The OctoKit/rest.js API for making requests to GitHub - * @param {string} owner The owner of the repo (user or organization) - * @param {Object[]} pullRequests The list of pull requests to include in the table - * @param {string} pullRequests[].number The number for the pull request - * @param {string} pullRequests[].author The author of the pull request - * @param {string} pullRequests[].title The title of the pull request - * @param {string} pullRequests[].mergeable Whether the pull request is able to be merged - * @param {string} pullRequests[].reviewers The list of requested reviewers for the pull request - * @param {string} pullRequests[].mergeBy (optional) The merge-by date for the pull request - * @param {string} repo The name of the GitHub repo - * @param {Object} today Today's date represented as a Day.js object - */ -const notifyUsers = async ({ dayJs, github, owner, pullRequests, repo, today }) => { - const prsCloseToMergeDate = pullRequests.filter((pr) => { - if (pr.mergeBy == null) { - return false; - } - - // Filter out any PRs that don't have merge-by dates within a day from now - const mergeByDate = dayJs(pr.mergeBy); - return mergeByDate.diff(today, "day") <= 1; - }); - - for (const pr of prsCloseToMergeDate) { - const comments = (await github.rest.issues.listComments({ owner, repo, issue_number: pr.number })).data; - // Try to find the reminder comment - const existingComment = comments?.find((comment) => - comment.user.login === "github-actions[bot]" && comment.body.includes("**Reminder:** This pull request has a merge-by date coming up within the next 24 hours. Please review this PR as soon as possible.")); - - if (existingComment != null) { - continue; - } - - // Make a comment on the PR and tag reviewers - const body = `**Reminder:** This pull request has a merge-by date coming up within the next 24 hours. Please review this PR as soon as possible.\n\n${pr.reviewers.map((r) => `@${r.login}`).join(" ")}` - await github.rest.issues.createComment({ - owner, - repo, - issue_number: pr.number, - body - }); - } -}; - -/** - * Fetches PRs with a merge-by date < 1 week from now. - * - * @param {Object} dayJs Day.js exports for manipulating/querying time differences - * @param {Object} github The OctoKit/rest.js API for making requests to GitHub - * @param {string} owner The owner of the repository (user or organization) - * @param {string} repo The name of the repository on GitHub - * @param {Object} today Today's date, represented as a day.js object - */ -const fetchPullRequests = async ({ dayJs, github, owner, repo, today }) => { - const nextWeek = today.add(7, "day"); - return (await Promise.all((await github.rest.pulls.list({ - owner, - repo, - state: "open" - }))?.data.filter((pr) => !pr.draft) - .map(async (pr) => { - const comments = (await github.rest.issues.listComments({ owner, repo, issue_number: pr.number })).data; - // Attempt to parse the merge-by date from the bot comment - const existingComment = comments?.find((comment) => - comment.user.login === "github-actions[bot]" && comment.body.includes("**📅 Suggested merge-by date:")); - - const reviews = (await github.rest.pulls.listReviews({ - owner, - repo, - pull_number: pr.number, - })).data; - - const hasTwoReviews = reviews.reduce((all, review) => review.state === "APPROVED" ? all + 1 : all, 0) >= 2; - - // Filter out reviewers if they have already reviewed and approved the pull request - const reviewersNotApproved = pr.requested_reviewers - .filter((reviewer) => - reviews.find((review) => review.state === "APPROVED" && reviewer.login === review.user.login) == null); - - return { - number: pr.number, - title: pr.title, - author: pr.user.login, - hasReviews: hasTwoReviews, - mergeable: pr.mergeable, - reviewers: reviewersNotApproved, - mergeBy: existingComment?.body.substring(existingComment.body.lastIndexOf("*") + 1).trim() - }; - }))).filter((pr) => { - if (pr.mergeBy == null) { - return true; - } - - // Filter out any PRs that have merge-by dates > 1 week from now - const mergeByDate = dayJs(pr.mergeBy); - return nextWeek.diff(mergeByDate, "day") <= 7; - }).reverse(); -} - -module.exports = async ({ github, context }) => { - const dayJs = require("dayjs"); - const today = dayJs(); - const owner = context.repo.owner; - const repo = context.repo.repo; - const pullRequests = await fetchPullRequests({ dayJs, github, owner, repo, today }); - // Look over existing PRs, grab all PRs with a merge-by date <= 1w from now, and update the issue with the new table - await scanPRsAndUpdateTable({ github, owner, pullRequests, repo }); - // Notify users for PRs with merge-by dates coming up within 24hrs from now - if (context.eventName === "schedule") { - await notifyUsers({ dayJs, github, owner, pullRequests, repo, today }); - } -} \ No newline at end of file diff --git a/.github/workflows/merge-by/post-date.js b/.github/workflows/merge-by/post-date.js deleted file mode 100644 index cb592fb5fd..0000000000 --- a/.github/workflows/merge-by/post-date.js +++ /dev/null @@ -1,48 +0,0 @@ -module.exports = async ({ github, context }) => { - // Ignore PR "opened" events if the PR was opened as draft - const wasJustOpened = context.action === "opened"; - if (wasJustOpened && context.payload.pull_request.draft) { - return; - } - - const wasJustPushed = context.action === "synchronize"; - - const owner = context.repo.owner; - const repo = context.repo.repo; - const comments = (await github.rest.issues.listComments({ owner, repo, issue_number: context.payload.pull_request.number }))?.data; - const existingComment = comments?.find((comment) => - comment.user.login === "github-actions[bot]" && comment.body.includes("**📅 Suggested merge-by date:")); - - // For existing PRs, only post the date if a bot comment doesn't already exist. - if (context.payload.pull_request.draft || (wasJustPushed && existingComment != null)) { - return; - } - - // Determine new merge-by date based on the last time the PR was marked as ready - const currentTime = new Date(); - const mergeBy = new Date(); - mergeBy.setDate(currentTime.getDate() + 14); - const mergeByDate = mergeBy.toLocaleDateString("en-US"); - - // Check if the bot already made a comment on this PR - const body = `**📅 Suggested merge-by date:** ${mergeByDate}`; - - // Update the existing comment if one exists, or post a new comment with the merge-by date - if (existingComment != null) { - console.log(`Updated existing comment (ID ${existingComment.id}) with new merge-by date: ${mergeByDate}`); - await github.rest.issues.updateComment({ - owner, - repo, - comment_id: existingComment.id, - body - }); - } else { - console.log(`Posted comment with new merge-by date: ${mergeByDate}`); - await github.rest.issues.createComment({ - owner, - repo, - issue_number: context.payload.pull_request.number, - body, - }); - } -} \ No newline at end of file