mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
Preserve community decomposition decisions in issue label sync
An empty form field or unrelated edit cannot prove comment-raised topics were dispositioned. Only label newly declared topics, leaving removal to whole-thread triage and respecting a completed manual disposition. Change-source: pulse-maintainer
This commit is contained in:
@@ -197,12 +197,6 @@ function buildTriageState(issue, core, latestVersion) {
|
||||
}
|
||||
|
||||
const hasAdditionalActionableTopics = classifyAdditionalActionableTopics(issue.body);
|
||||
if (hasAdditionalActionableTopics === true) {
|
||||
core.info("Issue declares additional actionable topics; decomposition is required.");
|
||||
nextLabels.add(NEEDS_DECOMPOSITION_LABEL);
|
||||
} else if (hasAdditionalActionableTopics === false) {
|
||||
nextLabels.delete(NEEDS_DECOMPOSITION_LABEL);
|
||||
}
|
||||
|
||||
const reportedVersion = extractPulseVersion(issue.title, issue.body);
|
||||
core.info(`Reported Pulse version: ${reportedVersion || "not found"}`);
|
||||
@@ -265,7 +259,18 @@ async function syncLabels({ github, context, core }) {
|
||||
isBugLike,
|
||||
} = buildTriageState(issue, core, latestVersion);
|
||||
|
||||
if (hasAdditionalActionableTopics === true) {
|
||||
// A form declaration creates a review task only when it is new. A later
|
||||
// empty field cannot prove that comment topics were dispositioned, and an
|
||||
// unrelated edit must not restore a label Community deliberately cleared.
|
||||
const previousBody = context.payload.changes?.body?.from;
|
||||
const newlyDeclared = hasAdditionalActionableTopics === true && (
|
||||
context.payload.action === "opened" ||
|
||||
(context.payload.action === "edited" && previousBody !== undefined &&
|
||||
classifyAdditionalActionableTopics(previousBody) !== true)
|
||||
);
|
||||
if (newlyDeclared) {
|
||||
core.info("Issue newly declares additional actionable topics; decomposition is required.");
|
||||
nextLabels.add(NEEDS_DECOMPOSITION_LABEL);
|
||||
await ensureLabel(
|
||||
github,
|
||||
context,
|
||||
|
||||
@@ -69,11 +69,12 @@ function createGithub({
|
||||
return { github, calls };
|
||||
}
|
||||
|
||||
function createContext({ action = "opened", issue }) {
|
||||
function createContext({ action = "opened", issue, previousBody }) {
|
||||
return {
|
||||
payload: {
|
||||
action,
|
||||
issue,
|
||||
...(previousBody === undefined ? {} : { changes: { body: { from: previousBody } } }),
|
||||
},
|
||||
repo: {
|
||||
owner: "rcourtman",
|
||||
@@ -159,24 +160,52 @@ test("syncLabels marks declared secondary topics for decomposition", async () =>
|
||||
]);
|
||||
});
|
||||
|
||||
test("syncLabels clears decomposition after every declared topic has a disposition", async () => {
|
||||
const { github, calls } = createGithub({ latestVersion: "6.4.1" });
|
||||
const issue = {
|
||||
number: 1796,
|
||||
title: "Availability workflow feedback",
|
||||
body: "## Additional actionable topics\nNone.\n",
|
||||
labels: [{ name: "enhancement" }, { name: "needs-decomposition" }],
|
||||
};
|
||||
test("syncLabels preserves community decomposition on an empty form field", async () => {
|
||||
for (const action of ["edited", "reopened"]) {
|
||||
const { github, calls } = createGithub({ latestVersion: "6.4.1" });
|
||||
const issue = {
|
||||
number: 1796,
|
||||
title: "Availability workflow feedback",
|
||||
body: "## Additional actionable topics\nNone.\n",
|
||||
// A later comment may have raised a distinct topic after this form was filed.
|
||||
labels: [{ name: "enhancement" }, { name: "needs-decomposition" }],
|
||||
};
|
||||
|
||||
await triage.syncLabels({
|
||||
github,
|
||||
context: createContext({ action: "edited", issue }),
|
||||
core: createCore(),
|
||||
});
|
||||
await triage.syncLabels({
|
||||
github,
|
||||
context: createContext({ action, issue }),
|
||||
core: createCore(),
|
||||
});
|
||||
|
||||
assert.equal(calls.createLabel.length, 0);
|
||||
assert.equal(calls.addLabels.length, 0);
|
||||
assert.deepEqual(calls.removeLabel.map(call => call.name), ["needs-decomposition"]);
|
||||
assert.equal(calls.createLabel.length, 0);
|
||||
assert.equal(calls.addLabels.length, 0);
|
||||
assert.deepEqual(calls.removeLabel, []);
|
||||
}
|
||||
});
|
||||
|
||||
test("syncLabels adds newly declared topics but respects a completed disposition", async () => {
|
||||
const topicBody = "## Additional actionable topics\nAdd a storage filter.\n";
|
||||
const emptyBody = "## Additional actionable topics\nNone.\n";
|
||||
for (const { action, previousBody, expectedAdd } of [
|
||||
{ action: "edited", previousBody: emptyBody, expectedAdd: true },
|
||||
{ action: "edited", previousBody: topicBody, expectedAdd: false },
|
||||
{ action: "edited", expectedAdd: false },
|
||||
{ action: "reopened", expectedAdd: false },
|
||||
]) {
|
||||
const { github, calls } = createGithub();
|
||||
await triage.syncLabels({
|
||||
github,
|
||||
context: createContext({ action, previousBody, issue: {
|
||||
number: 1796,
|
||||
title: "Availability workflow feedback",
|
||||
body: topicBody,
|
||||
labels: [{ name: "enhancement" }],
|
||||
} }),
|
||||
core: createCore(),
|
||||
});
|
||||
assert.deepEqual(calls.addLabels.flatMap(call => call.labels),
|
||||
expectedAdd ? ["needs-decomposition"] : []);
|
||||
}
|
||||
});
|
||||
|
||||
test("additional topic classification is explicit and fail-quiet for legacy forms", () => {
|
||||
@@ -390,13 +419,13 @@ test("queued label sync cannot overwrite newer Community label decisions", async
|
||||
}
|
||||
});
|
||||
|
||||
test("classification removal tolerates an absent label but propagates access failures", async () => {
|
||||
test("version-label removal tolerates an absent label but propagates access failures", async () => {
|
||||
for (const status of [404, 403]) {
|
||||
const { github } = createGithub();
|
||||
github.rest.issues.removeLabel = async () => { throw Object.assign(new Error("API failure"), { status }); };
|
||||
const run = triage.syncLabels({ github, core: createCore(), context: createContext({ issue: {
|
||||
number: 1200, title: "Feedback", body: "## Additional actionable topics\nNone.\n",
|
||||
labels: [{ name: "enhancement" }, { name: "needs-decomposition" }],
|
||||
number: 1200, title: "Bug", body: "## Pulse version\n6.0.1\n",
|
||||
labels: [{ name: "bug" }, { name: "affects-6.0.0" }],
|
||||
} }) });
|
||||
if (status === 404) await assert.doesNotReject(run);
|
||||
else await assert.rejects(run, { status: 403 });
|
||||
|
||||
Reference in New Issue
Block a user