Merge branch 'main' into v3.7.0
This commit is contained in:
commit
9dabcf4eb1
3 changed files with 192 additions and 1 deletions
117
.github/workflows/auto-mark-released.yml
vendored
Normal file
117
.github/workflows/auto-mark-released.yml
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
name: Mark released issues on close
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [closed]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mark-released:
|
||||||
|
# Run only if closed as completed AND has at least one "status:" label
|
||||||
|
if: ${{ github.event.issue.state_reason == 'completed' && contains(toJSON(github.event.issue.labels), 'status:') }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set "released" label when closed completed and was ready for release
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
// Helpers
|
||||||
|
const owner = context.repo.owner;
|
||||||
|
const repo = context.repo.repo;
|
||||||
|
const issue = context.payload.issue;
|
||||||
|
if (!issue) {
|
||||||
|
core.info('No issue payload found, skipping.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only proceed if closed as "completed" (not "not_planned")
|
||||||
|
const isCompleted = issue.state_reason === 'completed';
|
||||||
|
if (!isCompleted) {
|
||||||
|
core.info('Issue state_reason is not "completed"; skipping.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getName = (l) => typeof l === 'string' ? l : l.name;
|
||||||
|
const labels = (issue.labels || []).map(getName);
|
||||||
|
|
||||||
|
// Status detection (robust to case/spacing)
|
||||||
|
const isStatusLabel = (name) => /^status:\s*/i.test(String(name || ''));
|
||||||
|
const hasAnyStatus = labels.some(isStatusLabel);
|
||||||
|
if (!hasAnyStatus) {
|
||||||
|
core.info('Issue has no "status:*" label; skipping.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const READY_REGEX = /^status:\s*ready\s*for\s*release$/i;
|
||||||
|
const hasReady = labels.some((n) => READY_REGEX.test(String(n || '')));
|
||||||
|
if (!hasReady) {
|
||||||
|
core.info('Issue is not "status: ready for release"; nothing to do.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const releasedLabel = 'status: released';
|
||||||
|
|
||||||
|
// Ensure "released" label exists
|
||||||
|
try {
|
||||||
|
await github.rest.issues.getLabel({ owner, repo, name: releasedLabel });
|
||||||
|
} catch (e) {
|
||||||
|
if (e.status === 404) {
|
||||||
|
core.info(`Label "${releasedLabel}" not found. Creating it.`);
|
||||||
|
await github.rest.issues.createLabel({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
name: releasedLabel,
|
||||||
|
color: '184738', // deep green
|
||||||
|
description: 'Feature released.'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all status:* labels, add "status: released"
|
||||||
|
const filtered = labels.filter((name) => !isStatusLabel(name));
|
||||||
|
const newLabels = [...new Set([...filtered, releasedLabel])];
|
||||||
|
|
||||||
|
await github.rest.issues.update({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
labels: newLabels
|
||||||
|
});
|
||||||
|
|
||||||
|
core.info(`Issue #${issue.number} relabeled as "${releasedLabel}".`);
|
||||||
|
|
||||||
|
// === NEW: add release comment with milestone if present ===
|
||||||
|
const milestone = issue.milestone;
|
||||||
|
if (!milestone || !milestone.title) {
|
||||||
|
core.info('Issue has no milestone; no release comment added.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commentBody = `Released in version ${milestone.title}.`;
|
||||||
|
|
||||||
|
// Avoid duplicate comment if the action reruns
|
||||||
|
const existingComments = await github.paginate(
|
||||||
|
github.rest.issues.listComments,
|
||||||
|
{ owner, repo, issue_number: issue.number, per_page: 100 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const alreadyCommented = existingComments.some(c =>
|
||||||
|
typeof c.body === 'string' && c.body.trim() === commentBody
|
||||||
|
);
|
||||||
|
|
||||||
|
if (alreadyCommented) {
|
||||||
|
core.info('Release comment already present; skipping comment creation.');
|
||||||
|
} else {
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
body: commentBody
|
||||||
|
});
|
||||||
|
core.info('Release comment added.');
|
||||||
|
}
|
||||||
75
.github/workflows/issues-validate-integration-field.yml
vendored
Normal file
75
.github/workflows/issues-validate-integration-field.yml
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
name: Issues Validate Integration Field
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened, edited]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-integration:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Validate dropdown selection (only for bug report form)
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const issue = context.payload.issue;
|
||||||
|
const bodyText = issue.body || "";
|
||||||
|
|
||||||
|
// ---- Guard: only act if this looks like a Bug Report form ----
|
||||||
|
if (!bodyText.includes("### The problem")) {
|
||||||
|
core.info("Skipped: This issue does not contain '### The problem', so it is not a Bug Report.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
core.info("Detected a Bug Report issue. Proceeding with validation...");
|
||||||
|
|
||||||
|
// Extract selection under "Which integration are you using?"
|
||||||
|
const header = "### Which integration are you using?";
|
||||||
|
const nextHeaderRegex = /\n### /;
|
||||||
|
let selection = "";
|
||||||
|
|
||||||
|
const startIdx = bodyText.indexOf(header);
|
||||||
|
if (startIdx !== -1) {
|
||||||
|
const afterHeader = bodyText.slice(startIdx + header.length).replace(/^\r?\n+/, "");
|
||||||
|
const nextHeaderIdx = afterHeader.search(nextHeaderRegex);
|
||||||
|
selection = (nextHeaderIdx === -1 ? afterHeader : afterHeader.slice(0, nextHeaderIdx)).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info(`Integration field extracted: "${selection}"`);
|
||||||
|
|
||||||
|
const isInvalid = !selection || selection.startsWith("-- Select an option");
|
||||||
|
if (isInvalid) {
|
||||||
|
core.info("Integration field is invalid or not selected.");
|
||||||
|
} else {
|
||||||
|
core.info("Integration field is valid. Nothing to do.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const owner = context.repo.owner;
|
||||||
|
const repo = context.repo.repo;
|
||||||
|
const issue_number = issue.number;
|
||||||
|
|
||||||
|
// Avoid duplicate comments
|
||||||
|
const marker = "please specify **Which integration are you using?**";
|
||||||
|
const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number });
|
||||||
|
const alreadyCommented = comments.some(c => c.body && c.body.includes(marker));
|
||||||
|
|
||||||
|
if (isInvalid) {
|
||||||
|
if (!alreadyCommented) {
|
||||||
|
core.info("No existing reminder found. Posting a new comment...");
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner, repo, issue_number,
|
||||||
|
body: [
|
||||||
|
`👋 Hello @${issue.user.login}!`,
|
||||||
|
`To complete your bug report, please specify which integration you are using.`,
|
||||||
|
"",
|
||||||
|
"Right now the field `**Which integration are you using?**` is set to `-- Select an option --`.",
|
||||||
|
"",
|
||||||
|
"Click **Edit** at the top right of the issue, choose a valid option from the dropdown, and save.",
|
||||||
|
].join("\n")
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
core.info("Reminder comment already exists. Skipping new comment.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -49,7 +49,6 @@ jobs:
|
||||||
const milestoneTitle = parsed.milestoneTitle; // es. "3.7.0"
|
const milestoneTitle = parsed.milestoneTitle; // es. "3.7.0"
|
||||||
const prereleaseTagDisplay = tagName.startsWith("v") ? tagName : `v${tagName}`;
|
const prereleaseTagDisplay = tagName.startsWith("v") ? tagName : `v${tagName}`;
|
||||||
const commentBody = `This feature is available for testing in the pre-release version [${prereleaseTagDisplay}](https://github.com/micz/ThunderAI/releases).`;
|
const commentBody = `This feature is available for testing in the pre-release version [${prereleaseTagDisplay}](https://github.com/micz/ThunderAI/releases).`;
|
||||||
|
|
||||||
core.info(`(MAIN) Looking for milestone '${milestoneTitle}' in ${owner}/${repo}`);
|
core.info(`(MAIN) Looking for milestone '${milestoneTitle}' in ${owner}/${repo}`);
|
||||||
|
|
||||||
// Find milestone (open or closed)
|
// Find milestone (open or closed)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue