Add live run issue filter

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-04-24 13:32:07 -05:00
1 parent 2e0111b11e
commit 4fb8e337e9
13 files changed
+266 -50

No files matched your search

@@ -79,5 +79,6 @@ describe("IssueFiltersPopover", () => {
element.className.includes("md:grid-cols-3"),
);
expect(layoutGrid?.className).toContain("grid-cols-1");
expect(popoverContent?.textContent).toContain("Live runs only");
});
});
+12 -5
View File
@@ -344,9 +344,16 @@ export function IssueFiltersPopover({
</div>
) : null}
{enableRoutineVisibilityFilter ? (
<div className="space-y-1">
<span className="text-xs text-muted-foreground">Visibility</span>
<div className="space-y-1">
<span className="text-xs text-muted-foreground">Visibility</span>
<label className="flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1 hover:bg-accent/50">
<Checkbox
checked={state.liveOnly}
onCheckedChange={(checked) => onChange({ liveOnly: checked === true })}
/>
<span className="text-sm">Live runs only</span>
</label>
{enableRoutineVisibilityFilter ? (
<label className="flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1 hover:bg-accent/50">
<Checkbox
checked={state.hideRoutineExecutions}
@@ -354,8 +361,8 @@ export function IssueFiltersPopover({
/>
<span className="text-sm">Hide routine runs</span>
</label>
</div>
) : null}
) : null}
</div>
</div>
</div>
</div>
+52 -6
View File
@@ -23,6 +23,7 @@ import {
issuePriorityOrder,
normalizeIssueFilterState,
resolveIssueFilterWorkspaceId,
shouldIncludeIssueFilterWorkspaceOption,
issueStatusOrder,
type IssueFilterState,
} from "../lib/issue-filters";
@@ -483,6 +484,10 @@ export function IssuesList({
}
return map;
}, [projects]);
const defaultProjectWorkspaceIds = useMemo(
() => new Set(defaultProjectWorkspaceIdByProjectId.values()),
[defaultProjectWorkspaceIdByProjectId],
);
const executionWorkspaceById = useMemo(() => {
const map = new Map<string, {
@@ -499,17 +504,27 @@ export function IssuesList({
}
return map;
}, [executionWorkspaces]);
const issueFilterWorkspaceContext = useMemo(() => ({
executionWorkspaceById,
defaultProjectWorkspaceIdByProjectId,
}), [defaultProjectWorkspaceIdByProjectId, executionWorkspaceById]);
const workspaceNameMap = useMemo(() => {
const map = new Map<string, string>();
for (const [workspaceId, workspace] of projectWorkspaceById) {
if (!shouldIncludeIssueFilterWorkspaceOption({ id: workspaceId }, defaultProjectWorkspaceIds)) continue;
map.set(workspaceId, workspace.name);
}
for (const [workspaceId, workspace] of executionWorkspaceById) {
if (!shouldIncludeIssueFilterWorkspaceOption({
id: workspaceId,
mode: workspace.mode,
projectWorkspaceId: workspace.projectWorkspaceId,
}, defaultProjectWorkspaceIds)) continue;
map.set(workspaceId, workspace.name);
}
return map;
}, [executionWorkspaceById, projectWorkspaceById]);
}, [defaultProjectWorkspaceIds, executionWorkspaceById, projectWorkspaceById]);
const workspaceOptions = useMemo(() => {
const options = new Map<string, string>();
@@ -635,9 +650,27 @@ export function IssuesList({
const searchScopedIssues = normalizedIssueSearch.length > 0 && searchWithinLoadedIssues
? sourceIssues.filter((issue) => issueMatchesLocalSearch(issue, normalizedIssueSearch))
: sourceIssues;
const filteredByControls = applyIssueFilters(searchScopedIssues, viewState, currentUserId, enableRoutineVisibilityFilter);
const filteredByControls = applyIssueFilters(
searchScopedIssues,
viewState,
currentUserId,
enableRoutineVisibilityFilter,
liveIssueIds,
issueFilterWorkspaceContext,
);
return sortIssues(filteredByControls, viewState);
}, [boardIssues, issues, searchedIssues, searchWithinLoadedIssues, viewState, normalizedIssueSearch, currentUserId, enableRoutineVisibilityFilter]);
}, [
boardIssues,
issues,
searchedIssues,
searchWithinLoadedIssues,
viewState,
normalizedIssueSearch,
currentUserId,
enableRoutineVisibilityFilter,
liveIssueIds,
issueFilterWorkspaceContext,
]);
const { data: labels } = useQuery({
queryKey: queryKeys.issues.labels(selectedCompanyId!),
@@ -664,7 +697,10 @@ export function IssuesList({
.map((p) => ({ key: p, label: issueFilterLabel(p), items: groups[p]! }));
}
if (viewState.groupBy === "workspace") {
const groups = groupBy(filtered, (issue) => resolveIssueFilterWorkspaceId(issue) ?? "__no_workspace");
const groups = groupBy(
filtered,
(issue) => resolveIssueFilterWorkspaceId(issue, issueFilterWorkspaceContext) ?? "__no_workspace",
);
return Object.keys(groups)
.sort((a, b) => {
// Groups with items first, "no workspace" last
@@ -708,7 +744,17 @@ export function IssuesList({
: (agentName(key) ?? key.slice(0, 8)),
items: groups[key]!,
}));
}, [filtered, viewState.groupBy, agents, agentName, currentUserId, workspaceNameMap, issueTitleMap, companyUserLabelMap]);
}, [
filtered,
issueFilterWorkspaceContext,
viewState.groupBy,
agents,
agentName,
currentUserId,
workspaceNameMap,
issueTitleMap,
companyUserLabelMap,
]);
useEffect(() => {
if (viewState.viewMode !== "list") return;
@@ -1125,7 +1171,7 @@ export function IssuesList({
columns={visibleTrailingIssueColumns}
projectName={issueProject?.name ?? null}
projectColor={issueProject?.color ?? null}
workspaceId={resolveIssueFilterWorkspaceId(issue)}
workspaceId={resolveIssueFilterWorkspaceId(issue, issueFilterWorkspaceContext)}
workspaceName={resolveIssueWorkspaceName(issue, {
executionWorkspaceById,
projectWorkspaceById,