Skip to content

Commit

Permalink
Merge branch 'main' into MMS-1868-quote-card-standalone
Browse files Browse the repository at this point in the history
  • Loading branch information
ghgoodreau authored Feb 19, 2025
2 parents ab76395 + 0832bdd commit b6bbe67
Show file tree
Hide file tree
Showing 129 changed files with 3,398 additions and 1,959 deletions.
22 changes: 8 additions & 14 deletions .circleci/scripts/git-diff-default-branch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,12 @@ async function gitDiff(): Promise<string> {
return diffResult;
}

function writePrBodyToFile(prBody: string) {
function writePrBodyAndInfoToFile(prInfo: PRInfo) {
const prBodyPath = path.resolve(CHANGED_FILES_DIR, 'pr-body.txt');
fs.writeFileSync(prBodyPath, prBody.trim());
console.log(`PR body saved to ${prBodyPath}`);
const labels = prInfo.labels.map(label => label.name).join(', ');
const updatedPrBody = `PR labels: {${labels}}\nPR base: {${prInfo.base.ref}}\n${prInfo.body.trim()}`;
fs.writeFileSync(prBodyPath, updatedPrBody);
console.log(`PR body and info saved to ${prBodyPath}`);
}

/**
Expand All @@ -135,17 +137,9 @@ async function storeGitDiffOutputAndPrBody() {
if (!baseRef) {
console.log('Not a PR, skipping git diff');
return;
} else if (baseRef !== GITHUB_DEFAULT_BRANCH) {
console.log(`This is for a PR targeting '${baseRef}', skipping git diff`);
writePrBodyToFile(prInfo.body);
return;
} else if (
prInfo.labels.some((label) => label.name === 'skip-e2e-quality-gate')
) {
console.log('PR has the skip-e2e-quality-gate label, skipping git diff');
return;
}

// We perform git diff even if the PR base is not main or skip-e2e-quality-gate label is applied
// because we rely on the git diff results for other jobs
console.log('Attempting to get git diff...');
const diffOutput = await gitDiff();
console.log(diffOutput);
Expand All @@ -155,7 +149,7 @@ async function storeGitDiffOutputAndPrBody() {
fs.writeFileSync(outputPath, diffOutput.trim());
console.log(`Git diff results saved to ${outputPath}`);

writePrBodyToFile(prInfo.body);
writePrBodyAndInfoToFile(prInfo);

process.exit(0);
} catch (error: any) {
Expand Down
10 changes: 2 additions & 8 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,14 +299,8 @@ module.exports = {
rules: {
'react/no-unused-prop-types': 'warn',
'react/no-unused-state': 'warn',
'react/jsx-boolean-value': 'warn',
'react/jsx-curly-brace-presence': [
'warn',
{
props: 'never',
children: 'never',
},
],
'react/jsx-boolean-value': 'off',
'react/jsx-curly-brace-presence': 'off',
'react/no-deprecated': 'warn',
'react/default-props-match-prop-types': 'warn',
'react/jsx-no-duplicate-props': 'warn',
Expand Down
7 changes: 1 addition & 6 deletions .github/scripts/add-release-label-to-pr-and-linked-issues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { retrieveLinkedIssues } from './shared/issue';
import { Label } from './shared/label';
import { Labelable, addLabelToLabelable } from './shared/labelable';
import { retrievePullRequest } from './shared/pull-request';
import { isValidVersionFormat } from './shared/utils';

main().catch((error: Error): void => {
console.error(error);
Expand Down Expand Up @@ -90,9 +91,3 @@ async function main(): Promise<void> {
await addLabelToLabelable(octokit, linkedIssue, releaseLabel);
}
}

// This helper function checks if version has the correct format: "x.y.z" where "x", "y" and "z" are numbers.
function isValidVersionFormat(str: string): boolean {
const regex = /^\d+\.\d+\.\d+$/;
return regex.test(str);
}
66 changes: 2 additions & 64 deletions .github/scripts/check-template-and-add-labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
} from './shared/labelable';
import {
Label,
RegressionStage,
craftRegressionLabel,
externalContributorLabel,
flakyTestsLabel,
invalidIssueTemplateLabel,
Expand All @@ -21,14 +23,6 @@ import {
import { TemplateType, templates } from './shared/template';
import { retrievePullRequest } from './shared/pull-request';

enum RegressionStage {
DevelopmentFeature,
DevelopmentMain,
Testing,
Beta,
Production,
}

const knownBots = [
'metamaskbot',
'dependabot',
Expand Down Expand Up @@ -345,59 +339,3 @@ async function userBelongsToMetaMaskOrg(

return Boolean(userBelongsToMetaMaskOrgResult?.user?.organization?.id);
}

// This function crafts appropriate label, corresponding to regression stage and release version.
function craftRegressionLabel(
regressionStage: RegressionStage | undefined,
releaseVersion: string | undefined,
): Label {
switch (regressionStage) {
case RegressionStage.DevelopmentFeature:
return {
name: `feature-branch-bug`,
color: '5319E7', // violet
description: `bug that was found on a feature branch, but not yet merged in main branch`,
};

case RegressionStage.DevelopmentMain:
return {
name: `regression-main`,
color: '5319E7', // violet
description: `Regression bug that was found on main branch, but not yet present in production`,
};

case RegressionStage.Testing:
return {
name: `regression-RC-${releaseVersion || '*'}`,
color: '744C11', // orange
description: releaseVersion
? `Regression bug that was found in release candidate (RC) for release ${releaseVersion}`
: `TODO: Unknown release version. Please replace with correct 'regression-RC-x.y.z' label, where 'x.y.z' is the number of the release where bug was found.`,
};

case RegressionStage.Beta:
return {
name: `regression-beta-${releaseVersion || '*'}`,
color: 'D94A83', // pink
description: releaseVersion
? `Regression bug that was found in beta in release ${releaseVersion}`
: `TODO: Unknown release version. Please replace with correct 'regression-beta-x.y.z' label, where 'x.y.z' is the number of the release where bug was found.`,
};

case RegressionStage.Production:
return {
name: `regression-prod-${releaseVersion || '*'}`,
color: '5319E7', // violet
description: releaseVersion
? `Regression bug that was found in production in release ${releaseVersion}`
: `TODO: Unknown release version. Please replace with correct 'regression-prod-x.y.z' label, where 'x.y.z' is the number of the release where bug was found.`,
};

default:
return {
name: `regression-*`,
color: 'EDEDED', // grey
description: `TODO: Unknown regression stage. Please replace with correct regression label: 'regression-main', 'regression-RC-x.y.z', or 'regression-prod-x.y.z' label, where 'x.y.z' is the number of the release where bug was found.`,
};
}
}
129 changes: 129 additions & 0 deletions .github/scripts/create-bug-report-issue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import * as core from '@actions/core';
import { context, getOctokit } from '@actions/github';
import { GitHub } from '@actions/github/lib/utils';

import { createIssue, retrieveIssueByTitle } from './shared/issue';
import {
Label,
RegressionStage,
craftRegressionLabel,
craftTeamLabel,
createOrRetrieveLabel,
typeBugLabel,
} from './shared/label';
import { codeRepoToPlanningRepo, codeRepoToPlatform, getCurrentDateFormatted, isValidVersionFormat } from './shared/utils';
import { addIssueToGithubProject, GithubProject, GithubProjectField, retrieveGithubProject, updateGithubProjectDateFieldValue } from './shared/project';

main().catch((error: Error): void => {
console.error(error);
process.exit(1);
});

async function main(): Promise<void> {
// "GITHUB_TOKEN" is an automatically generated, repository-specific access token provided by GitHub Actions.
// We can't use "GITHUB_TOKEN" here, as its permissions don't allow neither to create new labels
// nor to retrieve the content of organisations Github Projects.
// In our case, we may want to create "regression-RC-x.y.z" label when it doesn't already exist.
// We may also want to retrieve the content of organisation's Github Projects.
// As a consequence, we need to create our own "BUG_REPORT_TOKEN" with "repo" and "read:org" permissions.
// Such a token allows both to create new labels and fetch the content of organisation's Github Projects.
const personalAccessToken = process.env.BUG_REPORT_TOKEN;
if (!personalAccessToken) {
core.setFailed('BUG_REPORT_TOKEN not found');
process.exit(1);
}

const projectNumber = Number(process.env.RELEASES_GITHUB_PROJECT_BOARD_NUMBER);
if (!projectNumber) {
core.setFailed('RELEASES_GITHUB_PROJECT_BOARD_NUMBER not found');
process.exit(1);
}

const projectViewNumber = Number(process.env.RELEASES_GITHUB_PROJECT_BOARD_VIEW_NUMBER);
if (!projectViewNumber) {
core.setFailed('RELEASES_GITHUB_PROJECT_BOARD_VIEW_NUMBER not found');
process.exit(1);
}

const releaseVersion = process.env.RELEASE_VERSION;
if (!releaseVersion) {
core.setFailed('RELEASE_VERSION not found');
process.exit(1);
}
if (!isValidVersionFormat(releaseVersion)) {
core.setFailed(`Invalid format for RELEASE_VERSION: ${releaseVersion}. Expected format: x.y.z`);
process.exit(1);
}

const repoOwner = context.repo.owner;
if (!repoOwner) {
core.setFailed('repo owner not found');
process.exit(1);
}
const codeRepoName = context.repo.repo;
if (!codeRepoName) {
core.setFailed('code repo name not found');
process.exit(1);
}
const planningRepoName = codeRepoToPlanningRepo[codeRepoName];
if (!planningRepoName) {
core.setFailed('planning repo name not found');
process.exit(1);
}

// Retrieve platform name
const platformName = codeRepoToPlatform[codeRepoName];
if (!platformName) {
core.setFailed('platform name not found');
process.exit(1);
}

// Initialise octokit, required to call Github GraphQL API
const octokit: InstanceType<typeof GitHub> = getOctokit(personalAccessToken, {
previews: ['bane'], // The "bane" preview is required for adding, updating, creating and deleting labels.
});

// Craft regression labels to add
const regressionLabelTesting: Label = craftRegressionLabel(RegressionStage.Testing, releaseVersion);
const regressionLabelProduction: Label = craftRegressionLabel(RegressionStage.Production, releaseVersion);
const teamLabel: Label = craftTeamLabel(`${platformName}-platform`);

// Create or retrieve the different labels
await createOrRetrieveLabel(octokit, repoOwner, codeRepoName, regressionLabelProduction);
await createOrRetrieveLabel(octokit, repoOwner, codeRepoName, regressionLabelTesting);
await createOrRetrieveLabel(octokit, repoOwner, planningRepoName, regressionLabelProduction);
const regressionLabelTestingId = await createOrRetrieveLabel(octokit, repoOwner, planningRepoName, regressionLabelTesting);
const typeBugLabelId = await createOrRetrieveLabel(octokit, repoOwner, planningRepoName, typeBugLabel);
const teamLabelId = await createOrRetrieveLabel(octokit, repoOwner, planningRepoName, teamLabel);

const issueTitle = `v${releaseVersion} Bug Report`;
const issueWithSameTitle = await retrieveIssueByTitle(octokit, repoOwner, planningRepoName, issueTitle);
if (issueWithSameTitle) {
core.setFailed(`Bug report already exists: https://github.com/${repoOwner}/${planningRepoName}/issues/${issueWithSameTitle.number}. This is not desired, but can happen in cases where a release gets re-cut.`);
process.exit(1);
}

const issueBody = `**What is this bug report issue for?**\n\n1. This issue is used to track release dates on this [Github Project board](https://github.com/orgs/MetaMask/projects/${projectNumber}/views/${projectViewNumber}), which content then gets pulled into our metrics system.\n\n2. This issue is also used by our Zapier automations, to determine if automated notifications shall be sent on Slack for release \`${releaseVersion}\`. Notifications will only be sent as long as this issue is open.\n\n**Who created and/or closed this issue?**\n\n- This issue was automatically created by a GitHub action upon the creation of the release branch \`Version-v${releaseVersion}\`, indicating the release was cut.\n\n- This issue gets automatically closed by another GitHub action, once the \`Version-v${releaseVersion}\` branch merges into \`main\`, indicating the release is prepared for store submission.`;
const issueId = await createIssue(octokit, repoOwner, planningRepoName, issueTitle, issueBody, [regressionLabelTestingId, typeBugLabelId, teamLabelId]);

// Retrieve project, in order to obtain its ID
const project: GithubProject = await retrieveGithubProject(octokit, projectNumber);

const projectFieldName: string = "RC Cut";

const projectField: GithubProjectField | undefined = project.fields.find(field => field.name === projectFieldName);

if (!projectField) {
throw new Error(`Project field with name ${projectFieldName} was not found on Github Project with ID ${project.id}.`);
}

if (!projectField.id) {
throw new Error(`Project field with name ${projectFieldName} was found on Github Project with ID ${project.id}, but it has no 'id' property.`);
}

// Add bug report issue to 'Releases' Github Project Board
await addIssueToGithubProject(octokit, project.id, issueId);

// Update bug report issue's date property on 'Releases' Github Project Board
await updateGithubProjectDateFieldValue(octokit, project.id, projectField.id, issueId, getCurrentDateFormatted());
}
Loading

0 comments on commit b6bbe67

Please sign in to comment.