diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 37f64f0dcbf..d923854d7b2 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -38,9 +38,9 @@ body:
- type: textarea
id: extra
attributes:
- label: Snack, code example, screenshot, or link to a repository
+ label: Snack, screenshot, or link to a repository
description: |
- Please provide a Snack (https://snack.expo.dev/), a link to a repository on GitHub, or provide a minimal code example that reproduces the problem.
+ Please provide a Snack (https://snack.expo.dev/), a link to a repository on GitHub that reproduces the problem.
You may provide a screenshot of the application if you think it is relevant to your bug report.
Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve
Please note that a reproducer is **mandatory**. Issues without reproducer are more likely to stall and will be closed.
diff --git a/.github/workflow-scripts/checkForReproducer.js b/.github/workflow-scripts/checkForReproducer.js
index 72f5f0cac69..1b8973b3c01 100644
--- a/.github/workflow-scripts/checkForReproducer.js
+++ b/.github/workflow-scripts/checkForReproducer.js
@@ -8,7 +8,11 @@
*/
const NEEDS_REPRO_LABEL = 'Needs: Repro';
-const NEEDS_REPRO_MESSAGE = '| Missing Reproducible Example |';
+const NEEDS_REPRO_HEADER = 'Missing Reproducible Example';
+const NEEDS_REPRO_MESSAGE =
+ `| :warning: | Missing Reproducible Example |\n` +
+ `| --- | --- |\n` +
+ `| :information_source: | We could not detect a reproducible example in your issue report. Please provide either:
- If your bug is UI related: a [Snack](https://snack.expo.dev)
- If your bug is build/update related: use our [Reproducer Template](https://github.com/react-native-community/reproducer-react-native/generate). A reproducer needs to be in a GitHub repository under your username.
|`;
module.exports = async (github, context) => {
const issueData = {
@@ -20,30 +24,35 @@ module.exports = async (github, context) => {
const issue = await github.rest.issues.get(issueData);
const comments = await github.rest.issues.listComments(issueData);
- const botComment = comments.data.find(comment =>
- comment.body.includes(NEEDS_REPRO_MESSAGE),
+ const author = issue.data.user.login;
+
+ const maintainerChangedLabel = await hasMaintainerChangedLabel(
+ github,
+ issueData,
+ author,
);
- let commentBodies = comments.data.map(comment => comment.body);
- if (botComment) {
- commentBodies = commentBodies.filter(body => body !== botComment.body);
+ if (maintainerChangedLabel) {
+ return;
}
- const issueAndComments = [issue.data.body, ...commentBodies];
- const issueAndCommentsUniq = [...new Set(issueAndComments)];
+ const botComment = comments.data.find(comment =>
+ comment.body.includes(NEEDS_REPRO_HEADER),
+ );
- const user = issue.data.user.login;
+ const entities = [issue.data, ...comments.data];
- const hasValidReproducer = issueAndCommentsUniq.some(body => {
+ // Look for Snack or a GH repo associated with the user that added an issue or comment
+ const hasValidReproducer = entities.some(entity => {
const hasExpoSnackLink = containsPattern(
- body,
+ entity.body,
`https?:\\/\\/snack\\.expo\\.dev\\/[^\\s)\\]]+`,
);
- const hasGithubRepoLink = containsPattern(
- body,
- `https?:\\/\\/github\\.com\\/(${user})\\/[^/]+\\/?\\s?`,
- );
+ const hasGithubRepoLink = containsPattern(
+ entity.body,
+ `https?:\\/\\/github\\.com\\/(${entity.user.login})\\/[^/]+\\/?\\s?`,
+ );
return hasExpoSnackLink || hasGithubRepoLink;
});
@@ -70,6 +79,13 @@ module.exports = async (github, context) => {
...issueData,
labels: [NEEDS_REPRO_LABEL],
});
+
+ if (botComment) return;
+
+ await github.rest.issues.createComment({
+ ...issueData,
+ body: NEEDS_REPRO_MESSAGE,
+ });
}
};
@@ -77,3 +93,18 @@ function containsPattern(body, pattern) {
const regexp = new RegExp(pattern, 'gm');
return body.search(regexp) !== -1;
}
+
+// Prevents the bot from responding when maintainer has changed Needs: Repro the label
+async function hasMaintainerChangedLabel(github, issueData, author) {
+ const timeline = await github.rest.issues.listEventsForTimeline(issueData);
+
+ const labeledEvents = timeline.data.filter(
+ event => event.event === 'labeled' || event.event === 'unlabeled',
+ );
+ const userEvents = labeledEvents.filter(event => event.actor.type !== 'Bot');
+
+ return userEvents.some(
+ event =>
+ event.actor.login !== author && event.label.name === NEEDS_REPRO_LABEL,
+ );
+}
diff --git a/.github/workflows/check-for-reproducer.yml b/.github/workflows/check-for-reproducer.yml
index d15628a2058..8ef450fc1ae 100644
--- a/.github/workflows/check-for-reproducer.yml
+++ b/.github/workflows/check-for-reproducer.yml
@@ -10,7 +10,7 @@ on:
jobs:
check-for-reproducer:
runs-on: ubuntu-latest
- if: github.repository == 'facebook/react-native' && github.event.issue.pull_request == null
+ if: github.repository == 'facebook/react-native' && github.event.issue.pull_request == null && github.event.issue.state == 'open'
steps:
- uses: actions/checkout@v3
- uses: actions/github-script@v6