Repository: facebook/react
Pull request: Add GitHub action to check for bug repro

Resolves #21541

This action can be tested at https://github.com/bvaughn/test-github-issue-template by [creating a new DevTools bug report](https://github.com/bvaughn/test-github-issue-template/issues/new?assignees=&labels=Component%3A+Developer+Tools%2CType%3A+Bug%2CStatus%3A+Unconfirmed&template=devtools_bug_report.yml&title=%5BDevTools+Bug%5D%3A+).

Note that the `debug` logs in this Workflow won't be visible unless we set `ACTIONS_STEP_DEBUG=true` in the repository "secrets" settings.

Changed files:
filename: .github/workflows/devtools_check_repro.yml
patch: @@ -0,0 +1,209 @@
+name: DevTools Check for bug repro
+on:
+  issues:
+    types: [opened, edited]
+  issue_comment:
+    types: [created, edited]
+
+jobs:
+  check-repro:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/github-script@v3
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          script: |
+            const URL_REGEXP = /### Website or app[\r\n]+([^#]+)###/m;
+            const REPRO_STEPS_REGEXP = /### Repro steps[\r\n]+([^#]+)###/m;
+            const LABEL_NEEDS_MORE_INFORMATION = "Resolution: Needs More Information";
+            const LABEL_UNCONFIRMED = "Status: Unconfirmed";
+
+            function debug(...args) {
+              core.info(args.map(JSON.stringify).join(' '));
+            }
+
+            if (context.payload.comment) {
+              debug('Ignoring comment update.');
+              return;
+            }
+
+            const user = context.payload.sender.login;
+            const issue = context.payload.issue;
+            const body = issue.body;
+
+            const urlMatch = body.match(URL_REGEXP);
+            const reproStepsMatch = body.match(REPRO_STEPS_REGEXP);
+
+            const url = urlMatch !== null ? urlMatch[1].trim() : null;
+            const reproSteps = reproStepsMatch !== null ? reproStepsMatch[1].trim() : null;
+
+            if (!url || !reproSteps) {
+              debug('This issue is not a DevTools bug report.');
+              return;
+            }
+
+            debug(`found URL "${url}"`);
+            debug(`found repro steps "${reproSteps}"`);
+
+            function formatComment(comment) {
+              return comment
+                .split("\n")
+                .map((line) => line.trim())
+                .join("\n")
+                .trim();
+            }
+
+            async function getGitHubActionComments() {
+              debug(`Loading existing comments...`);
+
+              const comments = await github.issues.listComments({
+                issue_number: context.issue.number,
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+              });
+
+              return comments.data.filter(comment => {
+                debug(`comment by user: "${comment.user.login}"`);
+                return comment.user.login === 'github-actions[bot]';
+              });
+            }
+
+            async function getIssueLabels() {
+              const issues = await github.issues.listLabelsOnIssue({
+                issue_number: context.issue.number,
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+              });
+
+              return issues.data;
+            }
+
+            async function closeWithComment(comment) {
+              if (issue.state !== 'open') {
+                debug(`Issue is not open`);
+                return;
+              }
+
+              const labels = await getIssueLabels();
+              const label = labels.find(label => label.name === LABEL_UNCONFIRMED);
+              if (!label) {
+                debug(`Issue was not opened via DevTools bug report template`);
+                return;
+              }
+
+              const comments = await getGitHubActionComments();
+              if (comments.length > 0) {
+                debug(`Already commented on issue; won't comment again`);
+                return;
+              }
+
+              debug(`Missing required information`);
+
+              await github.issues.addLabels({
+                issue_number: context.issue.number,
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                labels: [LABEL_NEEDS_MORE_INFORMATION],
+              });
+
+              await github.issues.createComment({
+                issue_number: context.issue.number,
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                body: formatComment(comment),
+              });
+
+              await github.issues.update({
+                issue_number: context.issue.number,
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                state: 'closed',
+              });
+            }
+
+            async function openWithComment(comment) {
+              if (issue.state !== 'closed') {
+                debug(`Issue is already open`);
+                return;
+              }
+
+              const labels = await getIssueLabels();
+              const label = labels.find(label => label.name === LABEL_NEEDS_MORE_INFORMATION);
+              if (!label) {
+                debug(`Issue was not tagged as needs information`);
+                return;
+              }
+
+              const comments = await getGitHubActionComments();
+              if (comments.length === 0) {
+                debug(`Issue was closed by someone else; won't reopen`);
+                return;
+              }
+
+              debug(`Re-opening closed issue`);
+
+              await github.issues.removeLabel({
+                issue_number: context.issue.number,
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                name: LABEL_NEEDS_MORE_INFORMATION,
+              });
+
+              await github.issues.createComment({
+                issue_number: context.issue.number,
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                body: formatComment(comment),
+              });
+
+              await github.issues.update({
+                issue_number: context.issue.number,
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                state: 'open',
+              });
+            }
+
+            const PROBABLY_NOT_A_URL_REGEX = /(^Chrome$|^Firefox$| Website)/i;
+
+            const COMMENT_HEADER = `
+              @${user}: We're sorry you've seen this error. ❤️
+            `.trim();
+
+            const COMMENT_FOOTER = `
+              Please help us by providing a link to a CodeSandbox (https://codesandbox.io/s/new), a repository on GitHub, or a minimal code example that reproduces the problem. (Screenshots or videos can also be helpful if they help provide context on how to repro the bug.)
+
+              Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve
+
+              Issues without repros are automatically closed but we will re-open if you update with repro info.
+            `.trim();
+
+            if (url.includes("/localhost")) {
+              closeWithComment(`
+                ${COMMENT_HEADER}
+
+                Unfortunately the URL you provided ("localhost") is not publicly accessible. (This means that we will not be able to reproduce the problem you're reporting.)
+
+                ${COMMENT_FOOTER}
+              `);
+            } else if (url.length < 10 || url.match(PROBABLY_NOT_A_URL_REGEX)) {
+              closeWithComment(`
+                ${COMMENT_HEADER}
+
+                It looks like you forgot to specify a valid URL. (This means that we will not be able to reproduce the problem you're reporting.)
+
+                ${COMMENT_FOOTER}
+              `);
+            } else if (reproSteps.length < 25) {
+              closeWithComment(`
+                ${COMMENT_HEADER}
+
+                Unfortunately, it doesn't look like this issue has enough info for one of us to reproduce and fix it though.
+
+                ${COMMENT_FOOTER}
+              `);
+            } else {
+              openWithComment(`
+                Thank you for providing repro steps! Re-opening issue now for triage.
+              `);
+            }