Add inbox issue search fallback

This commit is contained in:
Dotta
2026-04-11 06:49:23 -05:00
parent 03a2cf5c8a
commit fcab770518
3 changed files with 140 additions and 6 deletions
+60
View File
@@ -20,6 +20,7 @@ import {
getApprovalsForTab,
getInboxWorkItems,
getInboxKeyboardSelectionIndex,
getInboxSearchFallbackIssues,
getRecentTouchedIssues,
getUnreadTouchedIssues,
groupInboxWorkItems,
@@ -611,6 +612,65 @@ describe("inbox helpers", () => {
).toEqual(["newer", "older"]);
});
it("uses remote issue results only when local inbox search has no matches", () => {
const remoteMatch = makeIssue("remote-match", false);
remoteMatch.status = "in_progress";
expect(
getInboxSearchFallbackIssues({
query: "pull/3303",
filteredWorkItems: [],
archivedSearchIssues: [],
remoteIssues: [remoteMatch],
issueFilters: {
statuses: ["in_progress"],
priorities: [],
assignees: [],
labels: [],
projects: [],
workspaces: [],
showRoutineExecutions: false,
},
}).map((issue) => issue.id),
).toEqual(["remote-match"]);
expect(
getInboxSearchFallbackIssues({
query: "pull/3303",
filteredWorkItems: [{ kind: "issue", timestamp: 1, issue: makeIssue("local", false) }],
archivedSearchIssues: [],
remoteIssues: [remoteMatch],
issueFilters: {
statuses: [],
priorities: [],
assignees: [],
labels: [],
projects: [],
workspaces: [],
showRoutineExecutions: false,
},
}),
).toEqual([]);
expect(
getInboxSearchFallbackIssues({
query: "pull/3303",
filteredWorkItems: [],
archivedSearchIssues: [makeIssue("archived", false)],
remoteIssues: [remoteMatch],
issueFilters: {
statuses: [],
priorities: [],
assignees: [],
labels: [],
projects: [],
workspaces: [],
showRoutineExecutions: false,
},
}),
).toEqual([]);
});
it("defaults the remembered inbox tab to mine and persists all", () => {
localStorage.clear();
expect(loadLastInboxTab()).toBe("mine");
+25
View File
@@ -7,6 +7,7 @@ import type {
JoinRequest,
} from "@paperclipai/shared";
import {
applyIssueFilters,
defaultIssueFilterState,
type IssueFilterState,
} from "./issue-filters";
@@ -370,6 +371,30 @@ export function getArchivedInboxSearchIssues({
.sort(sortIssuesByMostRecentActivity);
}
export function getInboxSearchFallbackIssues({
query,
filteredWorkItems,
archivedSearchIssues,
remoteIssues,
issueFilters,
currentUserId,
enableRoutineVisibilityFilter = false,
}: {
query: string;
filteredWorkItems: InboxWorkItem[];
archivedSearchIssues: Issue[];
remoteIssues: Issue[];
issueFilters: IssueFilterState;
currentUserId?: string | null;
enableRoutineVisibilityFilter?: boolean;
}): Issue[] {
const normalizedQuery = query.trim();
if (!normalizedQuery) return [];
if (filteredWorkItems.length > 0) return [];
if (archivedSearchIssues.length > 0) return [];
return applyIssueFilters(remoteIssues, issueFilters, currentUserId, enableRoutineVisibilityFilter);
}
export function resolveIssueWorkspaceName(
issue: Pick<Issue, "executionWorkspaceId" | "projectId" | "projectWorkspaceId">,
{
+55 -6
View File
@@ -98,6 +98,7 @@ import {
getArchivedInboxSearchIssues,
getInboxWorkItems,
getInboxKeyboardSelectionIndex,
getInboxSearchFallbackIssues,
getLatestFailedRunsByAgent,
matchesInboxIssueSearch,
getRecentTouchedIssues,
@@ -642,6 +643,7 @@ export function Inbox() {
retry: false,
});
const [searchQuery, setSearchQuery] = useState("");
const normalizedSearchQuery = searchQuery.trim();
const [filterPreferences, setFilterPreferences] = useState<InboxFilterPreferences>(
() => loadInboxFilterPreferences(selectedCompanyId),
);
@@ -945,7 +947,7 @@ export function Inbox() {
);
const filteredWorkItems = useMemo(() => {
const q = searchQuery.trim().toLowerCase();
const q = normalizedSearchQuery.toLowerCase();
if (!q) return workItemsToRender;
return workItemsToRender.filter((item) => {
if (item.kind === "issue") {
@@ -987,12 +989,12 @@ export function Inbox() {
});
}, [
workItemsToRender,
searchQuery,
agentById,
defaultProjectWorkspaceIdByProjectId,
executionWorkspaceById,
issueById,
isolatedWorkspacesEnabled,
normalizedSearchQuery,
projectWorkspaceById,
]);
@@ -1002,7 +1004,7 @@ export function Inbox() {
? getArchivedInboxSearchIssues({
visibleIssues: visibleMineIssues,
searchableIssues: visibleTouchedIssues,
query: searchQuery,
query: normalizedSearchQuery,
isolatedWorkspacesEnabled,
executionWorkspaceById,
projectWorkspaceById,
@@ -1013,13 +1015,60 @@ export function Inbox() {
defaultProjectWorkspaceIdByProjectId,
executionWorkspaceById,
isolatedWorkspacesEnabled,
normalizedSearchQuery,
projectWorkspaceById,
searchQuery,
tab,
visibleMineIssues,
visibleTouchedIssues,
],
);
const shouldUseIssueSearchFallback =
!!selectedCompanyId
&& normalizedSearchQuery.length > 0
&& filteredWorkItems.length === 0
&& archivedSearchIssues.length === 0;
const { data: remoteIssueSearchResults = [] } = useQuery({
queryKey: [
...queryKeys.issues.search(selectedCompanyId!, normalizedSearchQuery, undefined, 25),
"inbox-fallback",
issueFilters,
],
queryFn: () =>
issuesApi.list(selectedCompanyId!, {
q: normalizedSearchQuery,
limit: 25,
includeRoutineExecutions: true,
}),
enabled: shouldUseIssueSearchFallback,
placeholderData: (previousData) => previousData,
});
const issueSearchFallbackResults = useMemo(
() =>
getInboxSearchFallbackIssues({
query: normalizedSearchQuery,
filteredWorkItems,
archivedSearchIssues,
remoteIssues: remoteIssueSearchResults,
issueFilters,
currentUserId,
enableRoutineVisibilityFilter: true,
}),
[
archivedSearchIssues,
currentUserId,
filteredWorkItems,
issueFilters,
normalizedSearchQuery,
remoteIssueSearchResults,
],
);
const effectiveWorkItems = useMemo(
() =>
issueSearchFallbackResults.length > 0
? getInboxWorkItems({ issues: issueSearchFallbackResults, approvals: [] })
: filteredWorkItems,
[filteredWorkItems, issueSearchFallbackResults],
);
const archivedSearchIssueIds = useMemo(
() => new Set(archivedSearchIssues.map((issue) => issue.id)),
[archivedSearchIssues],
@@ -1037,14 +1086,14 @@ export function Inbox() {
}, []);
const [collapsedInboxParents, setCollapsedInboxParents] = useState<Set<string>>(new Set());
const groupedSections = useMemo<InboxGroupedSection[]>(() => [
...buildGroupedInboxSections(filteredWorkItems, groupBy, nestingEnabled),
...buildGroupedInboxSections(effectiveWorkItems, groupBy, nestingEnabled),
...buildGroupedInboxSections(
getInboxWorkItems({ issues: archivedSearchIssues, approvals: [] }),
groupBy,
nestingEnabled,
{ keyPrefix: "archived-search:", isArchivedSearch: true },
),
], [archivedSearchIssues, filteredWorkItems, groupBy, nestingEnabled]);
], [archivedSearchIssues, effectiveWorkItems, groupBy, nestingEnabled]);
const totalVisibleWorkItems = useMemo(
() => groupedSections.reduce((count, group) => count + group.displayItems.length, 0),
[groupedSections],