Skip to content

Show "root" runs by default, save the preference to a cookie #1512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/webapp/app/components/primitives/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const variations = {
"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",
root: "h-3 w-6",
thumb: "h-2.5 w-2.5 data-[state=checked]:translate-x-2.5 data-[state=unchecked]:translate-x-0",
text: "text-xs transition",
text: "text-xs",
},
};

Expand Down
30 changes: 14 additions & 16 deletions apps/webapp/app/components/runs/v3/RunFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const TaskRunListSearchFilters = z.object({
bulkId: z.string().optional(),
from: z.coerce.number().optional(),
to: z.coerce.number().optional(),
showChildTasks: z.coerce.boolean().optional(),
rootOnly: z.coerce.boolean().optional(),
batchId: z.string().optional(),
runId: z.string().optional(),
scheduleId: z.string().optional(),
Expand All @@ -119,6 +119,7 @@ type RunFiltersProps = {
type: BulkActionType;
createdAt: Date;
}[];
rootOnlyDefault: boolean;
hasFilters: boolean;
};

Expand All @@ -141,16 +142,12 @@ export function RunsFilters(props: RunFiltersProps) {
return (
<div className="flex flex-row flex-wrap items-center gap-1">
<FilterMenu {...props} />
<ShowChildTasksToggle />
<RootOnlyToggle defaultValue={props.rootOnlyDefault} />
<AppliedFilters {...props} />
{hasFilters && (
<Form className="h-6">
{searchParams.has("showChildTasks") && (
<input
type="hidden"
name="showChildTasks"
value={searchParams.get("showChildTasks") as string}
/>
{searchParams.has("rootOnly") && (
<input type="hidden" name="rootOnly" value={searchParams.get("rootOnly") as string} />
)}
<Button variant="minimal/small" LeadingIcon={TrashIcon}>
Clear all
Expand Down Expand Up @@ -707,26 +704,27 @@ function AppliedTagsFilter() {
);
}

function ShowChildTasksToggle() {
const { value, replace } = useSearchParams();

const showChildTasks = value("showChildTasks") === "true";
function RootOnlyToggle({ defaultValue }: { defaultValue: boolean }) {
const { value, values, replace } = useSearchParams();
const searchValue = value("rootOnly");
const rootOnly = searchValue !== undefined ? searchValue === "true" : defaultValue;

const batchId = value("batchId");
const runId = value("runId");
const scheduleId = value("scheduleId");
const tasks = values("tasks");

const disabled = !!batchId || !!runId || !!scheduleId;
const disabled = !!batchId || !!runId || !!scheduleId || tasks.length > 0;

return (
<Switch
disabled={disabled}
variant="small"
label="Show child runs"
checked={disabled ? true : showChildTasks}
label="Root only"
checked={disabled ? false : rootOnly}
onCheckedChange={(checked) => {
replace({
showChildTasks: checked ? "true" : undefined,
rootOnly: checked ? "true" : "false",
});
}}
/>
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/presenters/v3/RunListPresenter.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export class RunListPresenter extends BasePresenter {
}

//show all runs if we are filtering by batchId or runId
if (batchId || runId || scheduleId) {
if (batchId || runId || scheduleId || tasks?.length) {
rootOnly = false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ import { TaskRunsTable } from "~/components/runs/v3/TaskRunsTable";
import { BULK_ACTION_RUN_LIMIT } from "~/consts";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useUser } from "~/hooks/useUser";
import { findProjectBySlug } from "~/models/project.server";
import { RunListPresenter } from "~/presenters/v3/RunListPresenter.server";
import {
getRootOnlyFilterPreference,
setRootOnlyFilterPreference,
uiPreferencesStorage,
} from "~/services/preferences/uiPreferences.server";
import { requireUserId } from "~/services/session.server";
import { cn } from "~/utils/cn";
import {
Expand All @@ -54,6 +58,14 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const { projectParam, organizationSlug } = ProjectParamSchema.parse(params);

const url = new URL(request.url);

let rootOnlyValue = false;
if (url.searchParams.has("rootOnly")) {
rootOnlyValue = url.searchParams.get("rootOnly") === "true";
} else {
rootOnlyValue = await getRootOnlyFilterPreference(request);
}

const s = {
cursor: url.searchParams.get("cursor") ?? undefined,
direction: url.searchParams.get("direction") ?? undefined,
Expand All @@ -65,7 +77,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
tags: url.searchParams.getAll("tags").map((t) => decodeURIComponent(t)),
from: url.searchParams.get("from") ?? undefined,
to: url.searchParams.get("to") ?? undefined,
showChildTasks: url.searchParams.get("showChildTasks") === "true",
rootOnly: rootOnlyValue,
runId: url.searchParams.get("runId") ?? undefined,
batchId: url.searchParams.get("batchId") ?? undefined,
scheduleId: url.searchParams.get("scheduleId") ?? undefined,
Expand All @@ -82,7 +94,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
to,
cursor,
direction,
showChildTasks,
rootOnly,
runId,
batchId,
scheduleId,
Expand Down Expand Up @@ -110,22 +122,32 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
batchId,
runId,
scheduleId,
rootOnly: !showChildTasks,
rootOnly,
direction: direction,
cursor: cursor,
});

return typeddefer({
data: list,
});
const session = await setRootOnlyFilterPreference(rootOnlyValue, request);
const cookieValue = await uiPreferencesStorage.commitSession(session);

return typeddefer(
{
data: list,
rootOnlyDefault: rootOnlyValue,
},
{
headers: {
"Set-Cookie": cookieValue,
},
}
);
};

export default function Page() {
const { data } = useTypedLoaderData<typeof loader>();
const { data, rootOnlyDefault } = useTypedLoaderData<typeof loader>();
const navigation = useNavigation();
const isLoading = navigation.state !== "idle";
const project = useProject();
const user = useUser();

return (
<>
Expand Down Expand Up @@ -184,6 +206,7 @@ export default function Page() {
possibleTasks={list.possibleTasks}
bulkActions={list.bulkActions}
hasFilters={list.hasFilters}
rootOnlyDefault={rootOnlyDefault}
/>
<div className="flex items-center justify-end gap-x-2">
<ListPagination list={list} />
Expand Down
15 changes: 15 additions & 0 deletions apps/webapp/app/services/preferences/uiPreferences.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,18 @@ export async function setUsefulLinksPreference(show: boolean, request: Request)
session.set("showUsefulLinks", show);
return session;
}

export async function getRootOnlyFilterPreference(request: Request): Promise<boolean> {
const session = await getUiPreferencesSession(request);
const rootOnly = session.get("rootOnly");
if (rootOnly === undefined) {
return false;
}
return rootOnly;
}

export async function setRootOnlyFilterPreference(rootOnly: boolean, request: Request) {
const session = await getUiPreferencesSession(request);
session.set("rootOnly", rootOnly);
return session;
}