diff --git a/.github/scripts/close-release-bug-report-issue.ts b/.github/scripts/close-release-bug-report-issue.ts new file mode 100644 index 000000000..8f1cf8ed9 --- /dev/null +++ b/.github/scripts/close-release-bug-report-issue.ts @@ -0,0 +1,120 @@ +import * as core from '@actions/core'; +import { context, getOctokit } from '@actions/github'; +import { GitHub } from '@actions/github/lib/utils'; + +main().catch((error: Error): void => { + console.error(error); + process.exit(1); +}); + +async function main(): Promise { + // "GITHUB_TOKEN" is an automatically generated, repository-specific access token provided by GitHub Actions. + // We can't use "GITHUB_TOKEN" here, as its permissions are scoped to the repository where the action is running. + // "GITHUB_TOKEN" does not have access to other repositories, even when they belong to the same organization. + // As we want to update bug report issues which are not located in the same repository, + // we need to create our own "BUG_REPORT_TOKEN" with "repo" permissions. + // Such a token allows to access other repositories of the MetaMask organisation. + const personalAccessToken = process.env.BUG_REPORT_TOKEN; + if (!personalAccessToken) { + core.setFailed('BUG_REPORT_TOKEN not found'); + process.exit(1); + } + + const repoOwner = context.repo.owner; // MetaMask + + const bugReportRepo = process.env.BUG_REPORT_REPO; + if (!bugReportRepo) { + core.setFailed('BUG_REPORT_REPO not found'); + process.exit(1); + } + + // Extract branch name from the context + const branchName: string = context.payload.pull_request?.head.ref || ""; + + // Extract semver version number from the branch name + const releaseVersionNumberMatch = branchName.match(/^Version-v(\d+\.\d+\.\d+)$/); + + if (!releaseVersionNumberMatch) { + core.setFailed(`Failed to extract version number from branch name: ${branchName}`); + process.exit(1); + } + + const releaseVersionNumber = releaseVersionNumberMatch[1]; + + // Initialise octokit, required to call Github GraphQL API + const octokit: InstanceType = getOctokit(personalAccessToken); + + const bugReportIssue = await retrieveOpenBugReportIssue(octokit, repoOwner, bugReportRepo, releaseVersionNumber); + + if (!bugReportIssue) { + throw new Error(`No open bug report issue was found for release ${releaseVersionNumber} on ${repoOwner}/${bugReportRepo} repo`); + } + + if (bugReportIssue.title?.toLocaleLowerCase() !== `v${releaseVersionNumber} Bug Report`.toLocaleLowerCase()) { + throw new Error(`Unexpected bug report title: "${bugReportIssue.title}" instead of "v${releaseVersionNumber} Bug Report"`); + } + + console.log(`Closing bug report issue with title "${bugReportIssue.title}" and id: ${bugReportIssue.id}`); + + await closeIssue(octokit, bugReportIssue.id); + + console.log(`Issue with id: ${bugReportIssue.id} successfully closed`); +} + +// This function retrieves the issue titled "vx.y.z Bug Report" on a specific repo +async function retrieveOpenBugReportIssue(octokit: InstanceType, repoOwner: string, repoName: string, releaseVersionNumber: string): Promise<{ + id: string; + title: string; +} | undefined> { + + const retrieveOpenBugReportIssueQuery = ` + query RetrieveOpenBugReportIssue { + search(query: "repo:${repoOwner}/${repoName} type:issue is:open in:title v${releaseVersionNumber} Bug Report", type: ISSUE, first: 1) { + nodes { + ... on Issue { + id + title + } + } + } + } +`; + + const retrieveOpenBugReportIssueQueryResult: { + search: { + nodes: { + id: string; + title: string; + }[]; + }; + } = await octokit.graphql(retrieveOpenBugReportIssueQuery); + + const bugReportIssues = retrieveOpenBugReportIssueQueryResult?.search?.nodes; + + return bugReportIssues?.length > 0 ? bugReportIssues[0] : undefined; +} + + +// This function closes a Github issue, based on its ID +async function closeIssue(octokit: InstanceType, issueId: string): Promise { + + const closeIssueMutation = ` + mutation CloseIssue($issueId: ID!) { + updateIssue(input: {id: $issueId, state: CLOSED}) { + clientMutationId + } + } + `; + + const closeIssueMutationResult: { + updateIssue: { + clientMutationId: string; + }; + } = await octokit.graphql(closeIssueMutation, { + issueId, + }); + + const clientMutationId = closeIssueMutationResult?.updateIssue?.clientMutationId; + + return clientMutationId; +} diff --git a/.github/workflows/add-release-label.yml b/.github/workflows/add-release-label.yml index 8a9bf087f..3acc4e22a 100644 --- a/.github/workflows/add-release-label.yml +++ b/.github/workflows/add-release-label.yml @@ -37,4 +37,4 @@ jobs: env: RELEASE_LABEL_TOKEN: ${{ secrets.RELEASE_LABEL_TOKEN }} NEXT_SEMVER_VERSION: ${{ env.NEXT_SEMVER_VERSION }} - run: npm run add-release-label-to-pr-and-linked-issues + run: yarn run add-release-label-to-pr-and-linked-issues diff --git a/.github/workflows/check-pr-labels.yml b/.github/workflows/check-pr-labels.yml index 18e3304a1..cc1cb37db 100644 --- a/.github/workflows/check-pr-labels.yml +++ b/.github/workflows/check-pr-labels.yml @@ -35,4 +35,4 @@ jobs: id: check-pr-has-required-labels env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npm run check-pr-has-required-labels + run: yarn run check-pr-has-required-labels diff --git a/.github/workflows/close-bug-report.yml b/.github/workflows/close-bug-report.yml new file mode 100644 index 000000000..7d520c30f --- /dev/null +++ b/.github/workflows/close-bug-report.yml @@ -0,0 +1,34 @@ +name: Close release bug report issue when release branch gets merged + +on: + pull_request: + branches: + - master + types: + - closed + +jobs: + close-bug-report: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'Version-v') + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 # This retrieves only the latest commit. + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: yarn + + - name: Install dependencies + run: yarn --immutable + + - name: Close release bug report issue + id: close-release-bug-report-issue + env: + BUG_REPORT_REPO: MetaMask-planning + BUG_REPORT_TOKEN: ${{ secrets.BUG_REPORT_TOKEN }} + run: yarn run close-release-bug-report-issue diff --git a/.github/workflows/create-bug-report.yml b/.github/workflows/create-bug-report.yml new file mode 100644 index 000000000..f46296bea --- /dev/null +++ b/.github/workflows/create-bug-report.yml @@ -0,0 +1,47 @@ +name: Create release bug report issue when release branch gets created + +on: create + +jobs: + create-bug-report: + runs-on: ubuntu-latest + steps: + - name: Extract version from branch name if release branch + id: extract_version + run: | + if [[ "$GITHUB_REF" == "refs/heads/Version-v"* ]]; then + version="${GITHUB_REF#refs/heads/Version-v}" + echo "New release branch($version), continue next steps" + echo "version=$version" >> "$GITHUB_ENV" + else + echo "Not a release branch, skip next steps" + fi + + - name: Create bug report issue on planning repo + if: env.version + uses: octokit/request-action@v2.x + with: + route: POST /repos/MetaMask/MetaMask-planning/issues + owner: MetaMask + title: v${{ env.version }} Bug Report + body: | + This bug report was automatically created by a GitHub action upon the creation of release branch `Version-v${{ env.version }}` (release cut). + + + **Expected actions for release engineers:** + + 1. Convert this issue into a Zenhub epic and link all bugs identified during the release regression testing phase to this epic. + + 2. After completing the first regression run, move this epic to "Regression Completed" on the [Extension Release Regression board](https://app.zenhub.com/workspaces/extension-release-regression-6478c62d937eaa15e95c33c5/board?filterLogic=any&labels=release-${{ env.version }},release-task). + + + Note that once the release is prepared for store submission, meaning the `Version-v${{ env.version }}` branch merges into `master`, another GitHub action will automatically close this epic. + + labels: | + [ + "type-bug", + "regression-RC", + "release-${{ env.version }}" + ] + env: + GITHUB_TOKEN: ${{ secrets.BUG_REPORT_TOKEN }} diff --git a/package.json b/package.json index 5e7113b42..b0988abf3 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "validate-branch-name": "validate-branch-name", "add-release-label-to-pr-and-linked-issues": "ts-node ./.github/scripts/add-release-label-to-pr-and-linked-issues.ts", "check-pr-has-required-labels": "ts-node ./.github/scripts/check-pr-has-required-labels.ts", + "close-release-bug-report-issue": "ts-node ./.github/scripts/close-release-bug-report-issue.ts", "audit": "yarn npm audit --recursive --environment production --severity moderate" }, "resolutions": {