mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
Fix external object issue filtering
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
8e279837fc
commit
71d08a6e05
8 files changed
+317
-58
No files matched your search
@@ -355,6 +355,62 @@ describe("IssuesList", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("filters issue rows by external-object status summaries", async () => {
|
||||
const failedIssue = createIssue({ id: "issue-failed", identifier: "PAP-10", title: "Failed external object" });
|
||||
const freshIssue = createIssue({ id: "issue-fresh", identifier: "PAP-11", title: "Fresh external object" });
|
||||
const noObjectIssue = createIssue({ id: "issue-none", identifier: "PAP-12", title: "No external object" });
|
||||
localStorage.setItem("paperclip:test-issues:company-1", JSON.stringify({ externalObjectStatuses: ["failed"] }));
|
||||
mockExternalObjectsApi.getIssueSummaries.mockResolvedValue({
|
||||
summaries: {
|
||||
"issue-failed": {
|
||||
total: 1,
|
||||
byStatusCategory: { failed: 1 },
|
||||
byLiveness: { fresh: 1 },
|
||||
highestSeverity: "danger",
|
||||
staleCount: 0,
|
||||
authRequiredCount: 0,
|
||||
unreachableCount: 0,
|
||||
objects: [],
|
||||
},
|
||||
"issue-fresh": {
|
||||
total: 1,
|
||||
byStatusCategory: { succeeded: 1 },
|
||||
byLiveness: { fresh: 1 },
|
||||
highestSeverity: "success",
|
||||
staleCount: 0,
|
||||
authRequiredCount: 0,
|
||||
unreachableCount: 0,
|
||||
objects: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { root } = renderWithQueryClient(
|
||||
<IssuesList
|
||||
issues={[failedIssue, freshIssue, noObjectIssue]}
|
||||
agents={[]}
|
||||
projects={[]}
|
||||
viewStateKey="paperclip:test-issues"
|
||||
onUpdateIssue={() => undefined}
|
||||
/>,
|
||||
container,
|
||||
);
|
||||
|
||||
await waitForAssertion(() => {
|
||||
expect(mockExternalObjectsApi.getIssueSummaries).toHaveBeenCalledWith(
|
||||
"company-1",
|
||||
["issue-failed", "issue-fresh", "issue-none"],
|
||||
);
|
||||
expect(container.textContent).toContain("Failed external object");
|
||||
expect(container.textContent).not.toContain("Fresh external object");
|
||||
expect(container.textContent).not.toContain("No external object");
|
||||
});
|
||||
|
||||
act(() => {
|
||||
root.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders server search results instead of filtering the full issue list locally", async () => {
|
||||
const localIssue = createIssue({ id: "issue-local", identifier: "PAP-1", title: "Local issue" });
|
||||
const serverIssue = createIssue({ id: "issue-server", identifier: "PAP-2", title: "Server result" });
|
||||
|
||||
@@ -968,43 +968,57 @@ export function IssuesList({
|
||||
[boardIssueQueries, searchWithinLoadedIssues, viewState.viewMode],
|
||||
);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const sourceIssues = useMemo(() => {
|
||||
const useRemoteSearch = normalizedIssueSearch.length > 0 && !searchWithinLoadedIssues;
|
||||
const sourceIssues = boardIssues ?? (useRemoteSearch ? searchedIssues : issues);
|
||||
const searchScopedIssues = normalizedIssueSearch.length > 0 && searchWithinLoadedIssues
|
||||
return boardIssues ?? (useRemoteSearch ? searchedIssues : issues);
|
||||
}, [boardIssues, issues, normalizedIssueSearch, searchedIssues, searchWithinLoadedIssues]);
|
||||
|
||||
const searchScopedIssues = useMemo(
|
||||
() => normalizedIssueSearch.length > 0 && searchWithinLoadedIssues
|
||||
? sourceIssues.filter((issue) => issueMatchesLocalSearch(issue, normalizedIssueSearch))
|
||||
: sourceIssues;
|
||||
: sourceIssues,
|
||||
[normalizedIssueSearch, searchWithinLoadedIssues, sourceIssues],
|
||||
);
|
||||
const hasExternalObjectStatusFilters = viewState.externalObjectStatuses.length > 0;
|
||||
const issueIdsForExternalObjectSummaries = useMemo(
|
||||
() => (viewState.viewMode === "list" || hasExternalObjectStatusFilters
|
||||
? searchScopedIssues.map((issue) => issue.id)
|
||||
: []),
|
||||
[hasExternalObjectStatusFilters, searchScopedIssues, viewState.viewMode],
|
||||
);
|
||||
const {
|
||||
summaries: externalObjectSummaryByIssueId,
|
||||
isLoading: externalObjectSummariesLoading,
|
||||
isReady: externalObjectSummariesReady,
|
||||
} = useIssueExternalObjectSummaries(
|
||||
selectedCompanyId,
|
||||
issueIdsForExternalObjectSummaries,
|
||||
);
|
||||
const issueFilterContext = useMemo(() => ({
|
||||
...issueFilterWorkspaceContext,
|
||||
externalObjectSummaryByIssueId,
|
||||
externalObjectSummariesReady: externalObjectSummariesReady && !externalObjectSummariesLoading,
|
||||
}), [externalObjectSummariesLoading, externalObjectSummariesReady, externalObjectSummaryByIssueId, issueFilterWorkspaceContext]);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const filteredByControls = applyIssueFilters(
|
||||
searchScopedIssues,
|
||||
viewState,
|
||||
currentUserId,
|
||||
enableRoutineVisibilityFilter,
|
||||
liveIssueIds,
|
||||
issueFilterWorkspaceContext,
|
||||
issueFilterContext,
|
||||
);
|
||||
return sortIssues(filteredByControls, viewState);
|
||||
}, [
|
||||
boardIssues,
|
||||
issues,
|
||||
searchedIssues,
|
||||
searchWithinLoadedIssues,
|
||||
searchScopedIssues,
|
||||
viewState,
|
||||
normalizedIssueSearch,
|
||||
currentUserId,
|
||||
enableRoutineVisibilityFilter,
|
||||
liveIssueIds,
|
||||
issueFilterWorkspaceContext,
|
||||
issueFilterContext,
|
||||
]);
|
||||
|
||||
const issueIdsForExternalObjectSummaries = useMemo(
|
||||
() => (viewState.viewMode === "list" ? filtered.map((issue) => issue.id) : []),
|
||||
[filtered, viewState.viewMode],
|
||||
);
|
||||
const { summaries: externalObjectSummaryByIssueId } = useIssueExternalObjectSummaries(
|
||||
selectedCompanyId,
|
||||
issueIdsForExternalObjectSummaries,
|
||||
);
|
||||
|
||||
const progressSummary = useMemo(
|
||||
() => shouldRenderSubIssueProgressSummary(showProgressSummary, issues.length)
|
||||
? buildSubIssueProgressSummary(issues)
|
||||
|
||||
@@ -165,6 +165,7 @@ export function useIssueExternalObjectSummaries(
|
||||
): {
|
||||
summaries: Map<string, ExternalObjectSummary>;
|
||||
isLoading: boolean;
|
||||
isReady: boolean;
|
||||
} {
|
||||
const normalizedIssueIds = useMemo(
|
||||
() => [...new Set(issueIds.filter((issueId) => issueId.length > 0))].sort(),
|
||||
@@ -184,6 +185,7 @@ export function useIssueExternalObjectSummaries(
|
||||
return {
|
||||
summaries,
|
||||
isLoading: enabled && query.isLoading,
|
||||
isReady: !enabled || query.isSuccess,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
Approval,
|
||||
DashboardSummary,
|
||||
ExecutionWorkspace,
|
||||
ExternalObjectSummary,
|
||||
HeartbeatRun,
|
||||
Issue,
|
||||
JoinRequest,
|
||||
@@ -958,6 +959,58 @@ describe("inbox helpers", () => {
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("applies external-object filters to remote inbox search supplements", () => {
|
||||
const failedMatch = makeIssue("failed-match", false);
|
||||
const freshMatch = makeIssue("fresh-match", false);
|
||||
const summaries = new Map<string, ExternalObjectSummary>([
|
||||
["failed-match", {
|
||||
total: 1,
|
||||
byStatusCategory: { failed: 1 },
|
||||
byLiveness: { fresh: 1 },
|
||||
highestSeverity: "danger",
|
||||
staleCount: 0,
|
||||
authRequiredCount: 0,
|
||||
unreachableCount: 0,
|
||||
objects: [],
|
||||
}],
|
||||
["fresh-match", {
|
||||
total: 1,
|
||||
byStatusCategory: { succeeded: 1 },
|
||||
byLiveness: { fresh: 1 },
|
||||
highestSeverity: "success",
|
||||
staleCount: 0,
|
||||
authRequiredCount: 0,
|
||||
unreachableCount: 0,
|
||||
objects: [],
|
||||
}],
|
||||
]);
|
||||
|
||||
expect(
|
||||
getInboxSearchSupplementIssues({
|
||||
query: "github",
|
||||
filteredWorkItems: [],
|
||||
archivedSearchIssues: [],
|
||||
remoteIssues: [failedMatch, freshMatch],
|
||||
issueFilters: {
|
||||
statuses: [],
|
||||
priorities: [],
|
||||
assignees: [],
|
||||
creators: [],
|
||||
labels: [],
|
||||
projects: [],
|
||||
workspaces: [],
|
||||
liveOnly: false,
|
||||
externalObjectStatuses: ["failed"],
|
||||
hideRoutineExecutions: true,
|
||||
},
|
||||
issueFilterContext: {
|
||||
externalObjectSummaryByIssueId: summaries,
|
||||
externalObjectSummariesReady: true,
|
||||
},
|
||||
}).map((issue) => issue.id),
|
||||
).toEqual(["failed-match"]);
|
||||
});
|
||||
|
||||
it("keeps inbox search matches ahead of archived and other result sections", () => {
|
||||
const inboxIssue = makeIssue("inbox", false);
|
||||
inboxIssue.lastActivityAt = new Date("2026-03-11T04:00:00.000Z");
|
||||
|
||||
+11
-1
@@ -11,6 +11,7 @@ import {
|
||||
defaultIssueFilterState,
|
||||
normalizeIssueFilterState,
|
||||
type IssueFilterState,
|
||||
type IssueFilterWorkspaceContext,
|
||||
} from "./issue-filters";
|
||||
import { formatAssigneeUserLabel } from "./assignees";
|
||||
|
||||
@@ -462,6 +463,7 @@ export function getInboxSearchSupplementIssues({
|
||||
currentUserId,
|
||||
enableRoutineVisibilityFilter = false,
|
||||
liveIssueIds,
|
||||
issueFilterContext = {},
|
||||
}: {
|
||||
query: string;
|
||||
filteredWorkItems: InboxWorkItem[];
|
||||
@@ -471,6 +473,7 @@ export function getInboxSearchSupplementIssues({
|
||||
currentUserId?: string | null;
|
||||
enableRoutineVisibilityFilter?: boolean;
|
||||
liveIssueIds?: ReadonlySet<string>;
|
||||
issueFilterContext?: IssueFilterWorkspaceContext;
|
||||
}): Issue[] {
|
||||
const normalizedQuery = query.trim();
|
||||
if (!normalizedQuery) return [];
|
||||
@@ -480,7 +483,14 @@ export function getInboxSearchSupplementIssues({
|
||||
.map((item) => item.issue.id),
|
||||
...archivedSearchIssues.map((issue) => issue.id),
|
||||
]);
|
||||
return applyIssueFilters(remoteIssues, issueFilters, currentUserId, enableRoutineVisibilityFilter, liveIssueIds)
|
||||
return applyIssueFilters(
|
||||
remoteIssues,
|
||||
issueFilters,
|
||||
currentUserId,
|
||||
enableRoutineVisibilityFilter,
|
||||
liveIssueIds,
|
||||
issueFilterContext,
|
||||
)
|
||||
.filter((issue) => !visibleIssueIds.has(issue.id));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Issue } from "@paperclipai/shared";
|
||||
import type { ExternalObjectSummary, Issue } from "@paperclipai/shared";
|
||||
import {
|
||||
applyIssueFilters,
|
||||
countActiveIssueFilters,
|
||||
@@ -51,6 +51,20 @@ function makeIssue(overrides: Partial<Issue> = {}): Issue {
|
||||
};
|
||||
}
|
||||
|
||||
function makeExternalObjectSummary(overrides: Partial<ExternalObjectSummary> = {}): ExternalObjectSummary {
|
||||
return {
|
||||
total: 1,
|
||||
byStatusCategory: {},
|
||||
byLiveness: { fresh: 1 },
|
||||
highestSeverity: "neutral",
|
||||
staleCount: 0,
|
||||
authRequiredCount: 0,
|
||||
unreachableCount: 0,
|
||||
objects: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("issue filters", () => {
|
||||
it("filters issues by creator across agents and users", () => {
|
||||
const issues = [
|
||||
@@ -169,4 +183,62 @@ describe("issue filters", () => {
|
||||
new Set(["workspace-default"]),
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["failed", ["failed-issue", "blocked-issue"]],
|
||||
["auth_required", ["auth-issue"]],
|
||||
["stale", ["stale-issue"]],
|
||||
["none", ["none-issue"]],
|
||||
])("filters issues by external-object status token %s", (externalObjectStatus, expectedIssueIds) => {
|
||||
const issues = [
|
||||
makeIssue({ id: "failed-issue" }),
|
||||
makeIssue({ id: "blocked-issue" }),
|
||||
makeIssue({ id: "auth-issue" }),
|
||||
makeIssue({ id: "stale-issue" }),
|
||||
makeIssue({ id: "none-issue" }),
|
||||
makeIssue({ id: "fresh-issue" }),
|
||||
];
|
||||
const summaries = new Map<string, ExternalObjectSummary>([
|
||||
["failed-issue", makeExternalObjectSummary({ byStatusCategory: { failed: 1 }, highestSeverity: "danger" })],
|
||||
["blocked-issue", makeExternalObjectSummary({ byStatusCategory: { blocked: 1 }, highestSeverity: "danger" })],
|
||||
["auth-issue", makeExternalObjectSummary({
|
||||
byLiveness: { auth_required: 1 },
|
||||
highestSeverity: "danger",
|
||||
authRequiredCount: 1,
|
||||
})],
|
||||
["stale-issue", makeExternalObjectSummary({
|
||||
byLiveness: { stale: 1 },
|
||||
highestSeverity: "warning",
|
||||
staleCount: 1,
|
||||
})],
|
||||
["fresh-issue", makeExternalObjectSummary({ byStatusCategory: { succeeded: 1 }, highestSeverity: "success" })],
|
||||
]);
|
||||
|
||||
const filtered = applyIssueFilters(
|
||||
issues,
|
||||
{ ...defaultIssueFilterState, externalObjectStatuses: [externalObjectStatus] },
|
||||
null,
|
||||
false,
|
||||
undefined,
|
||||
{
|
||||
externalObjectSummaryByIssueId: summaries,
|
||||
externalObjectSummariesReady: true,
|
||||
},
|
||||
);
|
||||
|
||||
expect(filtered.map((issue) => issue.id)).toEqual(expectedIssueIds);
|
||||
});
|
||||
|
||||
it("does not apply external-object filters before summaries are ready", () => {
|
||||
const filtered = applyIssueFilters(
|
||||
[makeIssue({ id: "issue-1" })],
|
||||
{ ...defaultIssueFilterState, externalObjectStatuses: ["none"] },
|
||||
null,
|
||||
false,
|
||||
undefined,
|
||||
{ externalObjectSummaryByIssueId: new Map(), externalObjectSummariesReady: false },
|
||||
);
|
||||
|
||||
expect(filtered).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Issue } from "@paperclipai/shared";
|
||||
import type { ExternalObjectSummary, Issue } from "@paperclipai/shared";
|
||||
|
||||
export type IssueFilterWorkspaceLookup = {
|
||||
mode?: string | null;
|
||||
@@ -8,6 +8,8 @@ export type IssueFilterWorkspaceLookup = {
|
||||
export type IssueFilterWorkspaceContext = {
|
||||
executionWorkspaceById?: ReadonlyMap<string, IssueFilterWorkspaceLookup>;
|
||||
defaultProjectWorkspaceIdByProjectId?: ReadonlyMap<string, string>;
|
||||
externalObjectSummaryByIssueId?: ReadonlyMap<string, ExternalObjectSummary>;
|
||||
externalObjectSummariesReady?: boolean;
|
||||
};
|
||||
|
||||
export type IssueFilterState = {
|
||||
@@ -159,6 +161,39 @@ export function shouldIncludeIssueFilterWorkspaceOption(
|
||||
&& defaultProjectWorkspaceIds.has(workspace.projectWorkspaceId));
|
||||
}
|
||||
|
||||
function summaryRecordCount(record: Record<string, number> | undefined, key: string): number {
|
||||
return record?.[key] ?? 0;
|
||||
}
|
||||
|
||||
function issueMatchesExternalObjectStatusFilter(
|
||||
summary: ExternalObjectSummary | null | undefined,
|
||||
value: string,
|
||||
): boolean {
|
||||
const total = summary?.total ?? 0;
|
||||
switch (value) {
|
||||
case "failed":
|
||||
return summaryRecordCount(summary?.byStatusCategory, "failed") > 0
|
||||
|| summaryRecordCount(summary?.byStatusCategory, "blocked") > 0;
|
||||
case "waiting":
|
||||
return summaryRecordCount(summary?.byStatusCategory, "waiting") > 0;
|
||||
case "running":
|
||||
return summaryRecordCount(summary?.byStatusCategory, "running") > 0;
|
||||
case "auth_required":
|
||||
return (summary?.authRequiredCount ?? 0) > 0
|
||||
|| summaryRecordCount(summary?.byLiveness, "auth_required") > 0;
|
||||
case "unreachable":
|
||||
return (summary?.unreachableCount ?? 0) > 0
|
||||
|| summaryRecordCount(summary?.byLiveness, "unreachable") > 0;
|
||||
case "stale":
|
||||
return (summary?.staleCount ?? 0) > 0
|
||||
|| summaryRecordCount(summary?.byLiveness, "stale") > 0;
|
||||
case "none":
|
||||
return total === 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function applyIssueFilters(
|
||||
issues: Issue[],
|
||||
state: IssueFilterState,
|
||||
@@ -207,6 +242,16 @@ export function applyIssueFilters(
|
||||
return workspaceId != null && state.workspaces.includes(workspaceId);
|
||||
});
|
||||
}
|
||||
if (state.externalObjectStatuses.length > 0) {
|
||||
const summaries = workspaceContext.externalObjectSummaryByIssueId;
|
||||
if (!summaries || workspaceContext.externalObjectSummariesReady !== true) return [];
|
||||
result = result.filter((issue) => {
|
||||
const summary = summaries.get(issue.id) ?? null;
|
||||
return state.externalObjectStatuses.some((status) =>
|
||||
issueMatchesExternalObjectStatusFilter(summary, status),
|
||||
);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+42
-35
@@ -864,13 +864,49 @@ export function Inbox() {
|
||||
|
||||
const mineIssues = useMemo(() => getRecentTouchedIssues(mineIssuesRaw), [mineIssuesRaw]);
|
||||
const touchedIssues = useMemo(() => getRecentTouchedIssues(touchedIssuesRaw), [touchedIssuesRaw]);
|
||||
const shouldUseIssueSearchSupplement =
|
||||
!!selectedCompanyId
|
||||
&& normalizedSearchQuery.length > 0;
|
||||
const { data: remoteIssueSearchResults = [] } = useQuery({
|
||||
queryKey: [
|
||||
...queryKeys.issues.search(selectedCompanyId!, normalizedSearchQuery, undefined, 25),
|
||||
"inbox-supplement",
|
||||
],
|
||||
queryFn: () =>
|
||||
issuesApi.list(selectedCompanyId!, {
|
||||
q: normalizedSearchQuery,
|
||||
limit: 25,
|
||||
includeRoutineExecutions: true,
|
||||
}),
|
||||
enabled: shouldUseIssueSearchSupplement,
|
||||
placeholderData: (previousData) => previousData,
|
||||
});
|
||||
const inboxIssueIdsForExternalObjectSummaries = useMemo(() => {
|
||||
const issueIds = new Set<string>();
|
||||
for (const issue of mineIssues) issueIds.add(issue.id);
|
||||
for (const issue of touchedIssues) issueIds.add(issue.id);
|
||||
for (const issue of remoteIssueSearchResults) issueIds.add(issue.id);
|
||||
return [...issueIds];
|
||||
}, [mineIssues, remoteIssueSearchResults, touchedIssues]);
|
||||
const {
|
||||
summaries: externalObjectSummaryByIssueId,
|
||||
isLoading: externalObjectSummariesLoading,
|
||||
isReady: externalObjectSummariesReady,
|
||||
} = useIssueExternalObjectSummaries(
|
||||
selectedCompanyId,
|
||||
inboxIssueIdsForExternalObjectSummaries,
|
||||
);
|
||||
const issueFilterContext = useMemo(() => ({
|
||||
externalObjectSummaryByIssueId,
|
||||
externalObjectSummariesReady: externalObjectSummariesReady && !externalObjectSummariesLoading,
|
||||
}), [externalObjectSummariesLoading, externalObjectSummariesReady, externalObjectSummaryByIssueId]);
|
||||
const visibleMineIssues = useMemo(
|
||||
() => applyIssueFilters(mineIssues, issueFilters, currentUserId, true, liveIssueIds),
|
||||
[mineIssues, issueFilters, currentUserId, liveIssueIds],
|
||||
() => applyIssueFilters(mineIssues, issueFilters, currentUserId, true, liveIssueIds, issueFilterContext),
|
||||
[mineIssues, issueFilters, currentUserId, liveIssueIds, issueFilterContext],
|
||||
);
|
||||
const visibleTouchedIssues = useMemo(
|
||||
() => applyIssueFilters(touchedIssues, issueFilters, currentUserId, true, liveIssueIds),
|
||||
[touchedIssues, issueFilters, currentUserId, liveIssueIds],
|
||||
() => applyIssueFilters(touchedIssues, issueFilters, currentUserId, true, liveIssueIds, issueFilterContext),
|
||||
[touchedIssues, issueFilters, currentUserId, liveIssueIds, issueFilterContext],
|
||||
);
|
||||
const unreadTouchedIssues = useMemo(
|
||||
() => visibleTouchedIssues.filter((issue) => issue.isUnreadForMe),
|
||||
@@ -1161,23 +1197,6 @@ export function Inbox() {
|
||||
visibleTouchedIssues,
|
||||
],
|
||||
);
|
||||
const shouldUseIssueSearchSupplement =
|
||||
!!selectedCompanyId
|
||||
&& normalizedSearchQuery.length > 0;
|
||||
const { data: remoteIssueSearchResults = [] } = useQuery({
|
||||
queryKey: [
|
||||
...queryKeys.issues.search(selectedCompanyId!, normalizedSearchQuery, undefined, 25),
|
||||
"inbox-supplement",
|
||||
],
|
||||
queryFn: () =>
|
||||
issuesApi.list(selectedCompanyId!, {
|
||||
q: normalizedSearchQuery,
|
||||
limit: 25,
|
||||
includeRoutineExecutions: true,
|
||||
}),
|
||||
enabled: shouldUseIssueSearchSupplement,
|
||||
placeholderData: (previousData) => previousData,
|
||||
});
|
||||
const issueSearchSupplementResults = useMemo(
|
||||
() =>
|
||||
getInboxSearchSupplementIssues({
|
||||
@@ -1189,11 +1208,13 @@ export function Inbox() {
|
||||
currentUserId,
|
||||
enableRoutineVisibilityFilter: true,
|
||||
liveIssueIds,
|
||||
issueFilterContext,
|
||||
}),
|
||||
[
|
||||
archivedSearchIssues,
|
||||
currentUserId,
|
||||
filteredWorkItems,
|
||||
issueFilterContext,
|
||||
issueFilters,
|
||||
liveIssueIds,
|
||||
normalizedSearchQuery,
|
||||
@@ -1277,20 +1298,6 @@ export function Inbox() {
|
||||
if (!defaults) return;
|
||||
openNewIssue(defaults);
|
||||
}, [groupBy, inboxWorkspaceGrouping, openNewIssue]);
|
||||
|
||||
const inboxIssueIdsForExternalObjectSummaries = useMemo(() => {
|
||||
const issueIds = new Set<string>();
|
||||
for (const section of groupedSections) {
|
||||
for (const item of section.displayItems) {
|
||||
if (item.kind === "issue") issueIds.add(item.issue.id);
|
||||
}
|
||||
}
|
||||
return [...issueIds];
|
||||
}, [groupedSections]);
|
||||
const { summaries: externalObjectSummaryByIssueId } = useIssueExternalObjectSummaries(
|
||||
selectedCompanyId,
|
||||
inboxIssueIdsForExternalObjectSummaries,
|
||||
);
|
||||
const totalVisibleWorkItems = useMemo(
|
||||
() => groupedSections.reduce((count, group) => count + group.displayItems.length, 0),
|
||||
[groupedSections],
|
||||
|
||||
Reference in new issue
Block a user