-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix(site/src/pages/AgentsPage): support archived URL query #24742
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -466,6 +466,7 @@ interface ChatTreeNodeProps { | |
| } | ||
|
|
||
| const ChatTreeNode: FC<ChatTreeNodeProps> = ({ chat, isChildNode }) => { | ||
| const location = useLocation(); | ||
| const { | ||
| chatTree, | ||
| chatById, | ||
|
|
@@ -645,7 +646,10 @@ const ChatTreeNode: FC<ChatTreeNodeProps> = ({ chat, isChildNode }) => { | |
| )} | ||
| </div> | ||
| <NavLink | ||
| to={`/agents/${chat.id}`} | ||
| to={{ | ||
| pathname: `/agents/${chat.id}`, | ||
| search: location.search, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3 [DEREM-17] Sibling navigation paths still drop
The sidebar NavLink fix here is correct and complete. But the same root cause (bare
Before this PR the filter was Could we address these in this PR, or are they better as a follow-up?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do as a follow-up. |
||
| }} | ||
| className="flex min-h-0 min-w-0 flex-1 items-start gap-2 rounded-[inherit] py-1 pr-0.5 text-inherit no-underline" | ||
| > | ||
| {({ isActive }) => ( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import { act, waitFor } from "@testing-library/react"; | ||
| import { renderHookWithAuth } from "#/testHelpers/hooks"; | ||
| import { useArchivedFilterParam } from "./useArchivedFilterParam"; | ||
|
|
||
| describe(useArchivedFilterParam.name, () => { | ||
| describe("parsing the URL param", () => { | ||
| it.each([ | ||
| { route: "/agents", expected: "active" }, | ||
| { route: "/agents?archived=active", expected: "active" }, | ||
| { route: "/agents?archived=archived", expected: "archived" }, | ||
| { route: "/agents?archived=garbage", expected: "active" }, | ||
| ])("returns $expected for $route", async ({ route, expected }) => { | ||
| const { result } = await renderHookWithAuth( | ||
| () => useArchivedFilterParam(), | ||
| { routingOptions: { path: "/agents", route } }, | ||
| ); | ||
|
|
||
| expect(result.current[0]).toEqual(expected); | ||
| }); | ||
| }); | ||
|
|
||
| describe("setting the filter", () => { | ||
| it("writes ?archived=archived when set to 'archived'", async () => { | ||
| const { result, getLocationSnapshot } = await renderHookWithAuth( | ||
| () => useArchivedFilterParam(), | ||
| { routingOptions: { path: "/agents", route: "/agents" } }, | ||
| ); | ||
|
|
||
| act(() => result.current[1]("archived")); | ||
| await waitFor(() => expect(result.current[0]).toEqual("archived")); | ||
|
|
||
| const { search } = getLocationSnapshot(); | ||
| expect(search.get("archived")).toEqual("archived"); | ||
| }); | ||
|
|
||
| it("removes the param when set to 'active' (does not write archived=active)", async () => { | ||
| const { result, getLocationSnapshot } = await renderHookWithAuth( | ||
| () => useArchivedFilterParam(), | ||
| { | ||
| routingOptions: { | ||
| path: "/agents", | ||
| route: "/agents?archived=archived", | ||
| }, | ||
| }, | ||
| ); | ||
|
|
||
| act(() => result.current[1]("active")); | ||
| await waitFor(() => expect(result.current[0]).toEqual("active")); | ||
|
|
||
| const { search } = getLocationSnapshot(); | ||
| expect(search.get("archived")).toEqual(null); | ||
| }); | ||
|
|
||
| it("removes the param when set to 'active' from a clean URL (idempotent)", async () => { | ||
| const { result, getLocationSnapshot } = await renderHookWithAuth( | ||
| () => useArchivedFilterParam(), | ||
| { routingOptions: { path: "/agents", route: "/agents" } }, | ||
| ); | ||
|
|
||
| act(() => result.current[1]("active")); | ||
| await waitFor(() => expect(result.current[0]).toEqual("active")); | ||
|
|
||
| const { search } = getLocationSnapshot(); | ||
| expect(search.get("archived")).toEqual(null); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { useSearchParamsKey } from "#/hooks/useSearchParamsKey"; | ||
|
|
||
| type ArchivedFilter = "active" | "archived"; | ||
|
|
||
| const toArchivedFilter = (value: string): ArchivedFilter => | ||
| value === "archived" ? "archived" : "active"; | ||
|
|
||
| /** | ||
| * Reads and writes the agents page's archived filter via the `?archived` URL | ||
| * search param. Unknown or missing values fall back to `"active"`. Setting the | ||
| * filter back to `"active"` removes the param from the URL so the default | ||
| * state has a single canonical URL (`/agents`). | ||
| */ | ||
| export const useArchivedFilterParam = (): readonly [ | ||
| ArchivedFilter, | ||
| (next: ArchivedFilter) => void, | ||
| ] => { | ||
| const param = useSearchParamsKey({ | ||
| key: "archived", | ||
| defaultValue: "active", | ||
| }); | ||
| const filter = toArchivedFilter(param.value); | ||
| const setFilter = (next: ArchivedFilter) => { | ||
| if (next === "active") { | ||
| param.deleteValue(); | ||
| return; | ||
| } | ||
| param.setValue(next); | ||
| }; | ||
| return [filter, setFilter] as const; | ||
| }; |
Uh oh!
There was an error while loading. Please reload this page.