mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
Scope blocked-by summaries to sub-issue lists
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
6b4cacc484
commit
570fdce388
7 files changed
+22
-12
No files matched your search
@@ -939,7 +939,10 @@ describeEmbeddedPostgres("issueService.list participantAgentId", () => {
|
||||
type: "blocks",
|
||||
});
|
||||
|
||||
const result = await svc.list(companyId);
|
||||
const defaultResult = await svc.list(companyId);
|
||||
expect(defaultResult.find((issue) => issue.id === blockedId)?.blockedBy).toBeUndefined();
|
||||
|
||||
const result = await svc.list(companyId, { includeBlockedBy: true });
|
||||
const byId = new Map(result.map((issue) => [issue.id, issue]));
|
||||
|
||||
expect(byId.get(blockedId)?.blockedBy).toEqual([
|
||||
|
||||
@@ -949,6 +949,7 @@ export function issueRoutes(
|
||||
req.query.includeRoutineExecutions === "true" || req.query.includeRoutineExecutions === "1",
|
||||
excludeRoutineExecutions:
|
||||
req.query.excludeRoutineExecutions === "true" || req.query.excludeRoutineExecutions === "1",
|
||||
includeBlockedBy: req.query.includeBlockedBy === "true" || req.query.includeBlockedBy === "1",
|
||||
q: req.query.q as string | undefined,
|
||||
limit,
|
||||
});
|
||||
|
||||
@@ -101,6 +101,7 @@ export interface IssueFilters {
|
||||
originId?: string;
|
||||
includeRoutineExecutions?: boolean;
|
||||
excludeRoutineExecutions?: boolean;
|
||||
includeBlockedBy?: boolean;
|
||||
q?: string;
|
||||
limit?: number;
|
||||
}
|
||||
@@ -1303,10 +1304,11 @@ async function blockedByMapForIssues(
|
||||
): Promise<Map<string, IssueRelationIssueSummary[]>> {
|
||||
const map = new Map<string, IssueRelationIssueSummary[]>();
|
||||
const uniqueIssueIds = [...new Set(issueIds)];
|
||||
if (uniqueIssueIds.length === 0) return map;
|
||||
|
||||
for (const issueId of uniqueIssueIds) {
|
||||
map.set(issueId, []);
|
||||
}
|
||||
if (uniqueIssueIds.length === 0) return map;
|
||||
|
||||
for (const issueIdChunk of chunkList(uniqueIssueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
||||
const rows = await dbOrTx
|
||||
@@ -1840,6 +1842,7 @@ export function issueService(db: Db) {
|
||||
const inboxArchivedByUserId = filters?.inboxArchivedByUserId?.trim() || undefined;
|
||||
const unreadForUserId = filters?.unreadForUserId?.trim() || undefined;
|
||||
const contextUserId = unreadForUserId ?? touchedByUserId ?? inboxArchivedByUserId;
|
||||
const includeBlockedBy = filters?.includeBlockedBy === true;
|
||||
const rawSearch = filters?.q?.trim() ?? "";
|
||||
const hasSearch = rawSearch.length > 0;
|
||||
const escapedSearch = hasSearch ? escapeLikePattern(rawSearch) : "";
|
||||
@@ -1978,7 +1981,9 @@ export function issueService(db: Db) {
|
||||
? userReadStatsForIssues(db, companyId, contextUserId, issueIds)
|
||||
: Promise.resolve([]),
|
||||
lastActivityStatsForIssues(db, companyId, issueIds),
|
||||
blockedByMapForIssues(db, companyId, issueIds),
|
||||
includeBlockedBy
|
||||
? blockedByMapForIssues(db, companyId, issueIds)
|
||||
: Promise.resolve(new Map<string, IssueRelationIssueSummary[]>()),
|
||||
]);
|
||||
const statsByIssueId = new Map(statsRows.map((row) => [row.issueId, row]));
|
||||
const lastActivityByIssueId = new Map(lastActivityRows.map((row) => [row.issueId, row]));
|
||||
@@ -1994,7 +1999,7 @@ export function issueService(db: Db) {
|
||||
) ?? row.updatedAt;
|
||||
return {
|
||||
...row,
|
||||
blockedBy: blockedByMap.get(row.id) ?? [],
|
||||
...(includeBlockedBy ? { blockedBy: blockedByMap.get(row.id) ?? [] } : {}),
|
||||
lastActivityAt,
|
||||
...(blockerAttentionByIssueId.has(row.id) ? { blockerAttention: blockerAttentionByIssueId.get(row.id) } : {}),
|
||||
};
|
||||
@@ -2012,7 +2017,7 @@ export function issueService(db: Db) {
|
||||
) ?? row.updatedAt;
|
||||
return {
|
||||
...row,
|
||||
blockedBy: blockedByMap.get(row.id) ?? [],
|
||||
...(includeBlockedBy ? { blockedBy: blockedByMap.get(row.id) ?? [] } : {}),
|
||||
lastActivityAt,
|
||||
...(blockerAttentionByIssueId.has(row.id) ? { blockerAttention: blockerAttentionByIssueId.get(row.id) } : {}),
|
||||
...deriveIssueUserContext(row, contextUserId, {
|
||||
|
||||
@@ -25,10 +25,10 @@ describe("issuesApi.list", () => {
|
||||
});
|
||||
|
||||
it("passes descendantOf through to the company issues endpoint", async () => {
|
||||
await issuesApi.list("company-1", { descendantOf: "issue-root-1", limit: 25 });
|
||||
await issuesApi.list("company-1", { descendantOf: "issue-root-1", includeBlockedBy: true, limit: 25 });
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith(
|
||||
"/companies/company-1/issues?descendantOf=issue-root-1&limit=25",
|
||||
"/companies/company-1/issues?descendantOf=issue-root-1&includeBlockedBy=true&limit=25",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ export const issuesApi = {
|
||||
originId?: string;
|
||||
descendantOf?: string;
|
||||
includeRoutineExecutions?: boolean;
|
||||
includeBlockedBy?: boolean;
|
||||
q?: string;
|
||||
limit?: number;
|
||||
},
|
||||
@@ -66,6 +67,7 @@ export const issuesApi = {
|
||||
if (filters?.originId) params.set("originId", filters.originId);
|
||||
if (filters?.descendantOf) params.set("descendantOf", filters.descendantOf);
|
||||
if (filters?.includeRoutineExecutions) params.set("includeRoutineExecutions", "true");
|
||||
if (filters?.includeBlockedBy) params.set("includeBlockedBy", "true");
|
||||
if (filters?.q) params.set("q", filters.q);
|
||||
if (filters?.limit) params.set("limit", String(filters.limit));
|
||||
const qs = params.toString();
|
||||
|
||||
@@ -204,7 +204,7 @@ function saveIssueColumns(key: string, columns: InboxIssueColumn[]) {
|
||||
function sortIssues(issues: Issue[], state: IssueViewState): Issue[] {
|
||||
if (state.sortField === "workflow") {
|
||||
const ordered = workflowSort(issues);
|
||||
return state.sortDir === "desc" ? ordered.reverse() : ordered;
|
||||
return state.sortDir === "desc" ? [...ordered].reverse() : ordered;
|
||||
}
|
||||
const sorted = [...issues];
|
||||
const dir = state.sortDir === "asc" ? 1 : -1;
|
||||
@@ -848,9 +848,8 @@ export function IssuesList({
|
||||
const checklistAffordanceEnabled = useMemo(
|
||||
() =>
|
||||
defaultSortField === "workflow"
|
||||
&& viewState.sortField === "workflow"
|
||||
&& viewState.groupBy === "none",
|
||||
[defaultSortField, viewState.groupBy, viewState.sortField],
|
||||
[defaultSortField, viewState.groupBy],
|
||||
);
|
||||
const workflowChecklistMeta = useMemo(() => {
|
||||
if (!checklistAffordanceEnabled) return null;
|
||||
|
||||
@@ -1151,7 +1151,7 @@ export function IssueDetail() {
|
||||
issue?.id && resolvedCompanyId
|
||||
? queryKeys.issues.listByDescendantRoot(resolvedCompanyId, issue.id)
|
||||
: ["issues", "parent", "pending"],
|
||||
queryFn: () => issuesApi.list(resolvedCompanyId!, { descendantOf: issue!.id }),
|
||||
queryFn: () => issuesApi.list(resolvedCompanyId!, { descendantOf: issue!.id, includeBlockedBy: true }),
|
||||
enabled: !!resolvedCompanyId && !!issue?.id,
|
||||
placeholderData: keepPreviousDataForSameQueryTail<Issue[]>(issue?.id ?? "pending"),
|
||||
});
|
||||
@@ -3155,7 +3155,7 @@ export function IssueDetail() {
|
||||
projectId={issue.projectId ?? undefined}
|
||||
viewStateKey={`paperclip:issue-detail:${issue.id}:subissues-view`}
|
||||
issueLinkState={resolvedIssueDetailState ?? location.state}
|
||||
searchFilters={{ descendantOf: issue.id }}
|
||||
searchFilters={{ descendantOf: issue.id, includeBlockedBy: true }}
|
||||
searchWithinLoadedIssues
|
||||
baseCreateIssueDefaults={buildSubIssueDefaultsForViewer(issue, currentUserId)}
|
||||
createIssueLabel="Sub-issue"
|
||||
|
||||
Reference in new issue
Block a user