Skip to content

Commit 00f1103

Browse files
authored
Show "root" runs by default, save the preference to a cookie (#1512)
* “Root only” toggle, off by default. Disabled when filtering by tasks * Remove text transition on Switch because it didn’t match the container
1 parent c37622e commit 00f1103

File tree

5 files changed

+63
-27
lines changed

5 files changed

+63
-27
lines changed

apps/webapp/app/components/primitives/Switch.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const variations = {
1616
"flex items-center h-[1.5rem] gap-x-1.5 rounded hover:bg-tertiary disabled:hover:bg-transparent pr-1 py-[0.1rem] pl-1.5 transition focus-custom disabled:hover:text-charcoal-400 disabled:opacity-50 text-charcoal-400 hover:text-charcoal-200 disabled:hover:cursor-not-allowed hover:cursor-pointer",
1717
root: "h-3 w-6",
1818
thumb: "h-2.5 w-2.5 data-[state=checked]:translate-x-2.5 data-[state=unchecked]:translate-x-0",
19-
text: "text-xs transition",
19+
text: "text-xs",
2020
},
2121
};
2222

apps/webapp/app/components/runs/v3/RunFilters.tsx

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export const TaskRunListSearchFilters = z.object({
9999
bulkId: z.string().optional(),
100100
from: z.coerce.number().optional(),
101101
to: z.coerce.number().optional(),
102-
showChildTasks: z.coerce.boolean().optional(),
102+
rootOnly: z.coerce.boolean().optional(),
103103
batchId: z.string().optional(),
104104
runId: z.string().optional(),
105105
scheduleId: z.string().optional(),
@@ -119,6 +119,7 @@ type RunFiltersProps = {
119119
type: BulkActionType;
120120
createdAt: Date;
121121
}[];
122+
rootOnlyDefault: boolean;
122123
hasFilters: boolean;
123124
};
124125

@@ -141,16 +142,12 @@ export function RunsFilters(props: RunFiltersProps) {
141142
return (
142143
<div className="flex flex-row flex-wrap items-center gap-1">
143144
<FilterMenu {...props} />
144-
<ShowChildTasksToggle />
145+
<RootOnlyToggle defaultValue={props.rootOnlyDefault} />
145146
<AppliedFilters {...props} />
146147
{hasFilters && (
147148
<Form className="h-6">
148-
{searchParams.has("showChildTasks") && (
149-
<input
150-
type="hidden"
151-
name="showChildTasks"
152-
value={searchParams.get("showChildTasks") as string}
153-
/>
149+
{searchParams.has("rootOnly") && (
150+
<input type="hidden" name="rootOnly" value={searchParams.get("rootOnly") as string} />
154151
)}
155152
<Button variant="minimal/small" LeadingIcon={TrashIcon}>
156153
Clear all
@@ -707,26 +704,27 @@ function AppliedTagsFilter() {
707704
);
708705
}
709706

710-
function ShowChildTasksToggle() {
711-
const { value, replace } = useSearchParams();
712-
713-
const showChildTasks = value("showChildTasks") === "true";
707+
function RootOnlyToggle({ defaultValue }: { defaultValue: boolean }) {
708+
const { value, values, replace } = useSearchParams();
709+
const searchValue = value("rootOnly");
710+
const rootOnly = searchValue !== undefined ? searchValue === "true" : defaultValue;
714711

715712
const batchId = value("batchId");
716713
const runId = value("runId");
717714
const scheduleId = value("scheduleId");
715+
const tasks = values("tasks");
718716

719-
const disabled = !!batchId || !!runId || !!scheduleId;
717+
const disabled = !!batchId || !!runId || !!scheduleId || tasks.length > 0;
720718

721719
return (
722720
<Switch
723721
disabled={disabled}
724722
variant="small"
725-
label="Show child runs"
726-
checked={disabled ? true : showChildTasks}
723+
label="Root only"
724+
checked={disabled ? false : rootOnly}
727725
onCheckedChange={(checked) => {
728726
replace({
729-
showChildTasks: checked ? "true" : undefined,
727+
rootOnly: checked ? "true" : "false",
730728
});
731729
}}
732730
/>

apps/webapp/app/presenters/v3/RunListPresenter.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export class RunListPresenter extends BasePresenter {
183183
}
184184

185185
//show all runs if we are filtering by batchId or runId
186-
if (batchId || runId || scheduleId) {
186+
if (batchId || runId || scheduleId || tasks?.length) {
187187
rootOnly = false;
188188
}
189189

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs._index/route.tsx

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,13 @@ import { TaskRunsTable } from "~/components/runs/v3/TaskRunsTable";
3535
import { BULK_ACTION_RUN_LIMIT } from "~/consts";
3636
import { useOrganization } from "~/hooks/useOrganizations";
3737
import { useProject } from "~/hooks/useProject";
38-
import { useUser } from "~/hooks/useUser";
3938
import { findProjectBySlug } from "~/models/project.server";
4039
import { RunListPresenter } from "~/presenters/v3/RunListPresenter.server";
40+
import {
41+
getRootOnlyFilterPreference,
42+
setRootOnlyFilterPreference,
43+
uiPreferencesStorage,
44+
} from "~/services/preferences/uiPreferences.server";
4145
import { requireUserId } from "~/services/session.server";
4246
import { cn } from "~/utils/cn";
4347
import {
@@ -54,6 +58,14 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
5458
const { projectParam, organizationSlug } = ProjectParamSchema.parse(params);
5559

5660
const url = new URL(request.url);
61+
62+
let rootOnlyValue = false;
63+
if (url.searchParams.has("rootOnly")) {
64+
rootOnlyValue = url.searchParams.get("rootOnly") === "true";
65+
} else {
66+
rootOnlyValue = await getRootOnlyFilterPreference(request);
67+
}
68+
5769
const s = {
5870
cursor: url.searchParams.get("cursor") ?? undefined,
5971
direction: url.searchParams.get("direction") ?? undefined,
@@ -65,7 +77,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
6577
tags: url.searchParams.getAll("tags").map((t) => decodeURIComponent(t)),
6678
from: url.searchParams.get("from") ?? undefined,
6779
to: url.searchParams.get("to") ?? undefined,
68-
showChildTasks: url.searchParams.get("showChildTasks") === "true",
80+
rootOnly: rootOnlyValue,
6981
runId: url.searchParams.get("runId") ?? undefined,
7082
batchId: url.searchParams.get("batchId") ?? undefined,
7183
scheduleId: url.searchParams.get("scheduleId") ?? undefined,
@@ -82,7 +94,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
8294
to,
8395
cursor,
8496
direction,
85-
showChildTasks,
97+
rootOnly,
8698
runId,
8799
batchId,
88100
scheduleId,
@@ -110,22 +122,32 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
110122
batchId,
111123
runId,
112124
scheduleId,
113-
rootOnly: !showChildTasks,
125+
rootOnly,
114126
direction: direction,
115127
cursor: cursor,
116128
});
117129

118-
return typeddefer({
119-
data: list,
120-
});
130+
const session = await setRootOnlyFilterPreference(rootOnlyValue, request);
131+
const cookieValue = await uiPreferencesStorage.commitSession(session);
132+
133+
return typeddefer(
134+
{
135+
data: list,
136+
rootOnlyDefault: rootOnlyValue,
137+
},
138+
{
139+
headers: {
140+
"Set-Cookie": cookieValue,
141+
},
142+
}
143+
);
121144
};
122145

123146
export default function Page() {
124-
const { data } = useTypedLoaderData<typeof loader>();
147+
const { data, rootOnlyDefault } = useTypedLoaderData<typeof loader>();
125148
const navigation = useNavigation();
126149
const isLoading = navigation.state !== "idle";
127150
const project = useProject();
128-
const user = useUser();
129151

130152
return (
131153
<>
@@ -184,6 +206,7 @@ export default function Page() {
184206
possibleTasks={list.possibleTasks}
185207
bulkActions={list.bulkActions}
186208
hasFilters={list.hasFilters}
209+
rootOnlyDefault={rootOnlyDefault}
187210
/>
188211
<div className="flex items-center justify-end gap-x-2">
189212
<ListPagination list={list} />

apps/webapp/app/services/preferences/uiPreferences.server.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,18 @@ export async function setUsefulLinksPreference(show: boolean, request: Request)
2727
session.set("showUsefulLinks", show);
2828
return session;
2929
}
30+
31+
export async function getRootOnlyFilterPreference(request: Request): Promise<boolean> {
32+
const session = await getUiPreferencesSession(request);
33+
const rootOnly = session.get("rootOnly");
34+
if (rootOnly === undefined) {
35+
return false;
36+
}
37+
return rootOnly;
38+
}
39+
40+
export async function setRootOnlyFilterPreference(rootOnly: boolean, request: Request) {
41+
const session = await getUiPreferencesSession(request);
42+
session.set("rootOnly", rootOnly);
43+
return session;
44+
}

0 commit comments

Comments
 (0)