infra: add issue validator and stale action (#4061)
* infra: update stale action * infra: add issue validator * code clean * add missing labels * fix reproduction check * code clean * add version check * fix version check * add missing label * add note to version message * code clean * update stale message
This commit is contained in:
parent
1b691f8e81
commit
4611284247
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@ -74,7 +74,7 @@ body:
|
|||||||
- type: input
|
- type: input
|
||||||
id: reproduction-repo
|
id: reproduction-repo
|
||||||
attributes:
|
attributes:
|
||||||
label: Reproduction
|
label: Reproduction Link
|
||||||
description: Provide a link to a repository with a reproduction of the bug, this is optional but it will make us to fix the bug faster
|
description: Provide a link to a repository with a reproduction of the bug, this is optional but it will make us to fix the bug faster
|
||||||
placeholder: Reproduction Repository
|
placeholder: Reproduction Repository
|
||||||
value: "repository link"
|
value: "repository link"
|
||||||
|
273
.github/scripts/validate.js
vendored
Normal file
273
.github/scripts/validate.js
vendored
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
const FIELD_MAPPINGS = {
|
||||||
|
Platform: 'What platforms are you having the problem on?',
|
||||||
|
Version: 'Version',
|
||||||
|
SystemVersion: 'System Version',
|
||||||
|
DeviceType: 'On what device are you experiencing the issue?',
|
||||||
|
Architecture: 'Architecture',
|
||||||
|
Description: 'What happened?',
|
||||||
|
ReproductionLink: 'Reproduction Link',
|
||||||
|
Reproduction: 'Reproduction',
|
||||||
|
};
|
||||||
|
|
||||||
|
const PLATFORM_LABELS = {
|
||||||
|
iOS: 'Platform: iOS',
|
||||||
|
visionOS: 'Platform: iOS',
|
||||||
|
'Apple tvOS': 'Platform: iOS',
|
||||||
|
Android: 'Platform: Android',
|
||||||
|
'Android TV': 'Platform: Android',
|
||||||
|
Windows: 'Platform: Windows',
|
||||||
|
web: 'Platform: Web',
|
||||||
|
};
|
||||||
|
|
||||||
|
const BOT_LABELS = [
|
||||||
|
'Missing Info',
|
||||||
|
'Repro Provided',
|
||||||
|
'Missing Repro',
|
||||||
|
'Waiting for Review',
|
||||||
|
'Newer Version Available',
|
||||||
|
...Object.values(PLATFORM_LABELS),
|
||||||
|
];
|
||||||
|
|
||||||
|
const MESSAGE = {
|
||||||
|
FEATURE_REQUEST: `Thank you for your feature request. We will review it and get back to you if we need more information.`,
|
||||||
|
BUG_REPORT: `Thank you for your bug report. We will review it and get back to you if we need more information.`,
|
||||||
|
MISSING_INFO: (missingFields) => {
|
||||||
|
return `Thank you for your issue report. Please note that the following information is missing or incomplete:\n\n${missingFields
|
||||||
|
.map((field) => `- ${field.replace('missing-', '')}`)
|
||||||
|
.join(
|
||||||
|
'\n',
|
||||||
|
)}\n\nPlease update your issue with this information to help us address it more effectively.
|
||||||
|
\n > Note: issues without complete information have a lower priority`;
|
||||||
|
},
|
||||||
|
OUTDATED_VERSION: (issueVersion, latestVersion) => {
|
||||||
|
return (
|
||||||
|
`There is a newer version of the library available.` +
|
||||||
|
`You are using version ${issueVersion}, while the latest stable version is ${latestVersion}.` +
|
||||||
|
`Please update to the latest version and check if the issue still exists.` +
|
||||||
|
`\n > Note: If the issue still exists, please update the issue report with the latest information.`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkLatestVersion = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
'https://registry.npmjs.org/react-native-video/latest',
|
||||||
|
);
|
||||||
|
const data = await response.json();
|
||||||
|
return data.version;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking latest version:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFieldValue = (body, field) => {
|
||||||
|
if (!FIELD_MAPPINGS[field]) {
|
||||||
|
console.warn('Field not supported:', field);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldValue = FIELD_MAPPINGS[field];
|
||||||
|
|
||||||
|
const sections = body.split('###');
|
||||||
|
const section = sections.find((section) => {
|
||||||
|
// Find the section that contains the field
|
||||||
|
// For Reproduction, we need to make sure that we don't match Reproduction Link
|
||||||
|
if (field === 'Reproduction') {
|
||||||
|
return (
|
||||||
|
section.includes(fieldValue) && !section.includes('Reproduction Link')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return section.includes(fieldValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
return section ? section.replace(fieldValue, '').trim() : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateBugReport = async (body, labels) => {
|
||||||
|
const selectedPlatforms = getFieldValue(body, 'Platform')
|
||||||
|
.split(',')
|
||||||
|
.map((p) => p.trim());
|
||||||
|
|
||||||
|
if (selectedPlatforms.length === 0) {
|
||||||
|
labels.add('missing-platform');
|
||||||
|
} else {
|
||||||
|
selectedPlatforms.forEach((platform) => {
|
||||||
|
const label = PLATFORM_LABELS[platform];
|
||||||
|
if (label) {
|
||||||
|
labels.add(label);
|
||||||
|
} else {
|
||||||
|
console.warn('Platform not supported', platform);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const version = getFieldValue(body, 'Version');
|
||||||
|
if (version) {
|
||||||
|
const words = version.split(' ');
|
||||||
|
const versionPattern = /\d+\.\d+\.\d+/;
|
||||||
|
const isVersionValid = words.some((word) => versionPattern.test(word));
|
||||||
|
|
||||||
|
if (!isVersionValid) {
|
||||||
|
labels.add('missing-version');
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestVersion = await checkLatestVersion();
|
||||||
|
if (latestVersion && latestVersion !== version) {
|
||||||
|
labels.add(`outdated-version-${version}-${latestVersion}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
name: 'SystemVersion',
|
||||||
|
invalidValue:
|
||||||
|
'What version of the system is using device that you are experiencing the issue?',
|
||||||
|
},
|
||||||
|
{name: 'DeviceType'},
|
||||||
|
{name: 'Architecture'},
|
||||||
|
{name: 'Description', invalidValue: 'A bug happened!'},
|
||||||
|
{name: 'Reproduction', invalidValue: 'Step to reproduce this bug are:'},
|
||||||
|
{name: 'ReproductionLink', invalidValue: 'repository link'},
|
||||||
|
];
|
||||||
|
|
||||||
|
fields.forEach(({name, invalidValue}) => {
|
||||||
|
const value = getFieldValue(body, name);
|
||||||
|
if (!value || value === invalidValue) {
|
||||||
|
const fieldName = FIELD_MAPPINGS[name];
|
||||||
|
labels.add(`missing-${fieldName.toLowerCase()}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateFeatureRequest = (body, labels) => {
|
||||||
|
// Implement feature request validation logic here
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleIssue = async ({github, context}) => {
|
||||||
|
const {issue} = context.payload;
|
||||||
|
const {body} = issue;
|
||||||
|
const labels = new Set(issue.labels.map((label) => label.name));
|
||||||
|
|
||||||
|
// Clear out labels that are added by the bot
|
||||||
|
BOT_LABELS.forEach((label) => labels.delete(label));
|
||||||
|
|
||||||
|
const isBug = labels.has('bug');
|
||||||
|
const isFeature = labels.has('feature');
|
||||||
|
|
||||||
|
if (isFeature) {
|
||||||
|
await handleFeatureRequest({github, context, body, labels});
|
||||||
|
} else if (isBug) {
|
||||||
|
await handleBugReport({github, context, body, labels});
|
||||||
|
} else {
|
||||||
|
console.warn('Issue is not a bug or feature request');
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateIssueLabels({github, context, labels});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFeatureRequest = async ({github, context, body, labels}) => {
|
||||||
|
validateFeatureRequest(body, labels);
|
||||||
|
|
||||||
|
const comment = MESSAGE.FEATURE_REQUEST;
|
||||||
|
await createComment({github, context, body: comment});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBugReport = async ({github, context, body, labels}) => {
|
||||||
|
await validateBugReport(body, labels);
|
||||||
|
|
||||||
|
if (Array.from(labels).some((label) => label.startsWith('missing-'))) {
|
||||||
|
await handleMissingInformation({github, context, labels});
|
||||||
|
} else {
|
||||||
|
await handleValidReport({github, context, labels});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMissingInformation = async ({github, context, labels}) => {
|
||||||
|
const missingFields = Array.from(labels).filter((label) =>
|
||||||
|
label.startsWith('missing-'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const outdatedVersionLabel = labels.find((label) =>
|
||||||
|
label.startsWith('outdated-version'),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (missingFields.length > 0) {
|
||||||
|
let comment = MESSAGE.MISSING_INFO(missingFields);
|
||||||
|
|
||||||
|
if (outdatedVersionLabel) {
|
||||||
|
const [, , issueVersion, latestVersion] = outdatedVersionLabel.split('-');
|
||||||
|
comment += `\n\n ${MESSAGE.OUTDATED_VERSION(
|
||||||
|
issueVersion,
|
||||||
|
latestVersion,
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await createComment({github, context, body: comment});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLabelsForMissingInfo(labels);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateLabelsForMissingInfo = (labels) => {
|
||||||
|
if (labels.has('missing-reproduction')) {
|
||||||
|
labels.add('Missing Repro');
|
||||||
|
labels.delete('Repro Provided');
|
||||||
|
} else {
|
||||||
|
labels.delete('Missing Repro');
|
||||||
|
labels.add('Repro Provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (labels.find((label) => label.startsWith('outdated-version'))) {
|
||||||
|
labels.add('Newer Version Available');
|
||||||
|
} else {
|
||||||
|
labels.delete('Newer Version Available');
|
||||||
|
}
|
||||||
|
|
||||||
|
labels.add('Missing Info');
|
||||||
|
labels.delete('Waiting for Review');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleValidReport = async ({github, context, labels}) => {
|
||||||
|
let comment = MESSAGE.BUG_REPORT;
|
||||||
|
|
||||||
|
const outdatedVersionLabel = Array.from(labels).find((label) =>
|
||||||
|
label.startsWith('outdated-version'),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (outdatedVersionLabel) {
|
||||||
|
const [, , issueVersion, latestVersion] = outdatedVersionLabel.split('-');
|
||||||
|
comment += `\n\n ${MESSAGE.OUTDATED_VERSION(issueVersion, latestVersion)}`;
|
||||||
|
labels.add('Newer Version Available');
|
||||||
|
}
|
||||||
|
|
||||||
|
await createComment({github, context, body: comment});
|
||||||
|
labels.add('Repro Provided');
|
||||||
|
labels.add('Waiting for Review');
|
||||||
|
};
|
||||||
|
|
||||||
|
const createComment = async ({github, context, body}) => {
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.payload.issue.number,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateIssueLabels = async ({github, context, labels}) => {
|
||||||
|
const labelsToAdd = Array.from(labels).filter(
|
||||||
|
(label) => !label.startsWith('missing-') && !label.startsWith('outdated-'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await github.rest.issues.update({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.payload.issue.number,
|
||||||
|
labels: labelsToAdd,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = handleIssue;
|
60
.github/stale.yml
vendored
60
.github/stale.yml
vendored
@ -1,60 +0,0 @@
|
|||||||
# Configuration for probot-stale - https://github.com/probot/stale
|
|
||||||
|
|
||||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
|
||||||
daysUntilStale: 60
|
|
||||||
|
|
||||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
|
||||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
|
||||||
daysUntilClose: 3
|
|
||||||
|
|
||||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
|
||||||
onlyLabels: []
|
|
||||||
|
|
||||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
|
||||||
exemptLabels:
|
|
||||||
- pinned
|
|
||||||
- security
|
|
||||||
|
|
||||||
# Set to true to ignore issues in a project (defaults to false)
|
|
||||||
exemptProjects: true
|
|
||||||
|
|
||||||
# Set to true to ignore issues in a milestone (defaults to false)
|
|
||||||
exemptMilestones: true
|
|
||||||
|
|
||||||
# Set to true to ignore issues with an assignee (defaults to false)
|
|
||||||
exemptAssignees: true
|
|
||||||
|
|
||||||
# Label to use when marking as stale
|
|
||||||
staleLabel: stale
|
|
||||||
|
|
||||||
# Comment to post when marking as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
This issue has been automatically marked as stale because it has not had
|
|
||||||
recent activity. It will be closed if no further activity occurs. Thank you
|
|
||||||
for your contributions. If you are having a similar problem, please open a
|
|
||||||
new issue and reference this one instead of commenting on a stale or closed
|
|
||||||
issue.
|
|
||||||
|
|
||||||
# Comment to post when removing the stale label.
|
|
||||||
unmarkComment: false
|
|
||||||
|
|
||||||
# Comment to post when closing a stale Issue or Pull Request.
|
|
||||||
closeComment: false
|
|
||||||
|
|
||||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
|
||||||
limitPerRun: 50
|
|
||||||
|
|
||||||
# Limit to only `issues` or `pulls`
|
|
||||||
only: issues
|
|
||||||
|
|
||||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
|
||||||
# pulls:
|
|
||||||
# daysUntilStale: 30
|
|
||||||
# markComment: >
|
|
||||||
# This pull request has been automatically marked as stale because it has not had
|
|
||||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
|
||||||
# for your contributions.
|
|
||||||
|
|
||||||
# issues:
|
|
||||||
# exemptLabels:
|
|
||||||
# - confirmed
|
|
24
.github/workflows/stale.yml
vendored
Normal file
24
.github/workflows/stale.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
name: Close inactive issues
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 1 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
close-issues:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v5
|
||||||
|
with:
|
||||||
|
days-before-issue-stale: 30
|
||||||
|
days-before-issue-close: 14
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity. If there won't be any activity in the next 14 days, this issue will be closed automatically."
|
||||||
|
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||||
|
days-before-pr-stale: -1
|
||||||
|
days-before-pr-close: -1
|
||||||
|
exempt-issue-labels: "feature,Accepted,good first issue"
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
19
.github/workflows/validate-issue.yml
vendored
Normal file
19
.github/workflows/validate-issue.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
name: Issue Validator and Labeler
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened, edited]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-and-label:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Validate Issue Template and Add Labels
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
script: |
|
||||||
|
const script = require('./.github/scripts/validate.js')
|
||||||
|
await script({github, context})
|
Loading…
Reference in New Issue
Block a user