render a ticket page again when its comments, patches or labels change
Signed-off-by: dawn <90008@klbr.net>
17 unmodified lines181920212222232425262728282929303132333431353637383937404142434426 unmodified lines71727374757677787922 unmodified lines10210310410510610710810911011111211311411511611711811912012112212312412512612712812917 unmodified lines17 unmodified lines } return chain;};
const MANIFEST = /^at:\/\/(did:[^/]+)\/org\.tangled\.repo\.manifest\/self$/;// a ticket, its comments and its patches name the repo as the authority of the ticket's uriconst AT_URI = /^at:\/\/(did:[^/]+)\//;
const repoOfParams = (params) => { if (!params) return null; if (params.repo?.startsWith("did:")) return params.repo; if (params.repoDid?.startsWith("did:")) return params.repoDid; if (typeof params.subject === "string") { const m = MANIFEST.exec(params.subject); for (const name of ["subject", "uri", "ticket"]) { const value = params[name]; if (typeof value !== "string") continue; if (value.startsWith("did:")) return value; const m = AT_URI.exec(value); if (m) return m[1]; if (params.subject.startsWith("did:")) return params.subject; } return null;};
const parse = async (files) => { const diffs = { entries: 0, exact: new Set(), defaults: new Set(), any: new Map(), resets: new Set(), repos: new Set(), global: false }; const diffs = { entries: 0, exact: new Set(), nsids: new Set(), defaults: new Set(), any: new Map(), resets: new Set(), repos: new Set(), global: false }; for (const file of files) { let text; try { text = await readFile(file, "utf8");26 unmodified lines if (repo) diffs.repos.add(repo); else diffs.global = true; // an added key is one a page may have read while the map default answered it diffs.exact.add(keyOf(entry.nsid, entry.params)); diffs.nsids.add(entry.nsid); } } } return diffs;22 unmodified lines } return params;};
// the knot lists every git read as spelled, but an appview map keys on only some of a// read's params (comments on subject and order, not limit), so a line naming fewer params// stands for every read that has thoseconst MAX_PARAMS = 6;
const touchedBySubset = (diffs, nsid, key) => { const pairs = Object.entries(splitKey(key)); if (pairs.length < 2 || pairs.length > MAX_PARAMS) return false; for (let mask = 1; mask < (1 << pairs.length) - 1; mask++) { const params = Object.fromEntries(pairs.filter((_, i) => mask & (1 << i))); if (diffs.exact.has(keyOf(nsid, params))) return true; } return false;};
const touched = (diffs, key) => { if (diffs.exact.has(key)) return true; const nsid = nsidOf(key); if (diffs.defaults.has(nsid)) return true; if (diffs.nsids.has(nsid) && !nsid.startsWith("org.tangled.git.") && touchedBySubset(diffs, nsid, key)) return true; const any = diffs.any.get(nsid); if (!any) return false; const params = splitKey(key); for (const name of any.names) delete params[name];17 unmodified lines98 unmodified lines9910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114227 unmodified lines98 unmodified lines assert.deepEqual([...dirtyPages(prior, diffs)].sort(), ["/o/r", "/o/r/issues"]); assert.equal(dirtyPages({ ...prior, did: "did:plc:z" }, diffs), "all");});
test("a comment or a state change dirties the ticket's page and the board, nothing else", async () => { const ticket = (rkey) => `at://did:plc:a/org.tangled.track.ticket/${rkey}`; const { hookPath, hook } = await chain([ ["g1", []], [ "g2", [ { op: "changed", nsid: "org.tangled.feed.listComments", params: { order: "desc", subject: ticket(3) }, old: "x", new: "y" }, { op: "changed", nsid: "org.tangled.track.getTicketView", params: { uri: ticket(5) }, old: "x", new: "y" }, { op: "changed", nsid: "org.tangled.search.query", params: { kind: "issue", repoDid: "did:plc:a" }, any: ["q", "state", "limit", "offset", "cursor"] }, { op: "changed", nsid: "org.tangled.git.getTree", params: { ref: "main", repo: "did:plc:a" }, old: "x", new: "y" } ] ] ]); const diffs = await readDiff(hookPath, hook, "g1"); assert.equal(diffs.global, false); assert.deepEqual([...diffs.repos], ["did:plc:a"]); const issue = (rkey) => page( `org.tangled.feed.listComments?limit=100&order=desc&subject=${ticket(rkey)}`, `org.tangled.track.getTicketView?uri=${ticket(rkey)}`, "org.tangled.label.listDefinitions?limit=100&subject=did:plc:a" ); const prior = { did: "did:plc:a", pages: { "/o/r/issues": page("org.tangled.search.query?kind=issue&repoDid=did:plc:a"), "/o/r/issues/3": issue(3), "/o/r/issues/4": issue(4), "/o/r/issues/5": issue(5), "/o/r/tree/main/src": page("org.tangled.git.getTree?path=src&ref=main&repo=did:plc:a") } }; assert.deepEqual([...dirtyPages(prior, diffs)].sort(), ["/o/r/issues", "/o/r/issues/3", "/o/r/issues/5"]);});
test("a shared value is named by its content", () => { const a = sharedOf({ ref: "main", files: [] }); assert.equal(a.hash, sharedOf({ ref: "main", files: [] }).hash); assert.notEqual(a.hash, sharedOf({ ref: "dev", files: [] }).hash);27 unmodified lines