Skip to content

Commit

Permalink
Add search input for queries and mutations tabs (#1473)
Browse files Browse the repository at this point in the history
  • Loading branch information
jerelmiller committed Aug 19, 2024
1 parent 2df0b50 commit 1d622c4
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 43 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-chicken-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"apollo-client-devtools": minor
---

Add a search input to the "Queries" and "Mutations" tabs that filters the list of operations.
58 changes: 25 additions & 33 deletions src/application/components/Cache/Cache.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactNode } from "react";
import { Fragment, useState, useMemo, useSyncExternalStore } from "react";
import { useState, useMemo, useSyncExternalStore } from "react";
import type { TypedDocumentNode } from "@apollo/client";
import { gql, useQuery } from "@apollo/client";
import IconArrowLeft from "@apollo/icons/small/IconArrowLeft.svg";
Expand Down Expand Up @@ -87,38 +87,30 @@ export function Cache({ clientId }: CacheProps) {
return (
<SidebarLayout>
<Sidebar className="flex flex-col h-full">
{loading ? (
<Loading />
) : dataExists ? (
<Fragment>
<SearchField
className="mb-4"
placeholder="Search queries"
onChange={setSearchTerm}
value={searchTerm}
/>
<div className="overflow-auto h-full">
<List>
{cacheIds.map((id) => {
return (
<ListItem
key={id}
onClick={() => history.push(id)}
selected={id === cacheId}
className="font-code"
>
{searchTerm ? (
<HighlightMatch searchTerm={searchTerm} value={id} />
) : (
id
)}
</ListItem>
);
})}
</List>
</div>
</Fragment>
) : null}
<SearchField
className="mb-4"
placeholder="Search cache"
onChange={setSearchTerm}
value={searchTerm}
/>
<List className="h-full">
{cacheIds.map((id) => {
return (
<ListItem
key={id}
onClick={() => history.push(id)}
selected={id === cacheId}
className="font-code"
>
{searchTerm ? (
<HighlightMatch searchTerm={searchTerm} value={id} />
) : (
id
)}
</ListItem>
);
})}
</List>
</Sidebar>
<Main className="!overflow-auto flex flex-col">
{dataExists ? (
Expand Down
4 changes: 2 additions & 2 deletions src/application/components/Cache/__tests__/Cache.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ describe("Cache component tests", () => {
renderWithApolloClient(<Cache clientId="1" />);

const searchInput =
await screen.findByPlaceholderText<HTMLInputElement>("Search queries");
await screen.findByPlaceholderText<HTMLInputElement>("Search cache");
await act(() => user.type(searchInput, "Result"));

const sidebar = screen.getByRole("complementary");
Expand Down Expand Up @@ -197,7 +197,7 @@ describe("Cache component tests", () => {

renderWithApolloClient(<Cache clientId="1" />);

const searchInput = await screen.findByPlaceholderText("Search queries");
const searchInput = await screen.findByPlaceholderText("Search cache");
await act(() => user.type(searchInput, "Res"));

const sidebar = screen.getByRole("complementary");
Expand Down
35 changes: 31 additions & 4 deletions src/application/components/Mutations/Mutations.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useMemo, useState } from "react";
import type { TypedDocumentNode } from "@apollo/client";
import { gql, useQuery } from "@apollo/client";
import { List } from "../List";
Expand All @@ -20,6 +20,8 @@ import { AlertDisclosure } from "../AlertDisclosure";
import { SerializedErrorAlertDisclosureItem } from "../SerializedErrorAlertDisclosureItem";
import { ApolloErrorAlertDisclosurePanel } from "../ApolloErrorAlertDisclosurePanel";
import { useActorEvent } from "../../hooks/useActorEvent";
import { SearchField } from "../SearchField";
import HighlightMatch from "../HighlightMatch";

const GET_MUTATIONS: TypedDocumentNode<GetMutations, GetMutationsVariables> =
gql`
Expand Down Expand Up @@ -56,8 +58,14 @@ interface MutationsProps {
explorerIFrame: HTMLIFrameElement | null;
}

const STABLE_EMPTY_MUTATIONS: Array<
NonNullable<GetMutations["client"]>["mutations"]["items"][number]
> = [];

export const Mutations = ({ clientId, explorerIFrame }: MutationsProps) => {
const [selected, setSelected] = useState<number>(0);
const [searchTerm, setSearchTerm] = useState("");

const { data, startPolling, stopPolling } = useQuery(GET_MUTATIONS, {
variables: { id: clientId as string },
skip: clientId == null,
Expand All @@ -68,7 +76,7 @@ export const Mutations = ({ clientId, explorerIFrame }: MutationsProps) => {
useActorEvent("panelHidden", () => stopPolling());
useActorEvent("panelShown", () => startPolling(500));

const mutations = data?.client?.mutations?.items ?? [];
const mutations = data?.client?.mutations?.items ?? STABLE_EMPTY_MUTATIONS;
const selectedMutation = mutations.find(
(mutation) => Number(mutation.id) === selected
);
Expand All @@ -77,11 +85,26 @@ export const Mutations = ({ clientId, explorerIFrame }: MutationsProps) => {
setSelected(0);
}

const filteredMutations = useMemo(() => {
if (!searchTerm) {
return mutations;
}
const regex = new RegExp(searchTerm, "i");

return mutations.filter(({ name }) => name && regex.test(name));
}, [searchTerm, mutations]);

return (
<SidebarLayout>
<SidebarLayout.Sidebar>
<SearchField
className="mb-4"
placeholder="Search mutations"
onChange={setSearchTerm}
value={searchTerm}
/>
<List className="h-full">
{mutations.map(({ name, id, loading, error }) => {
{filteredMutations.map(({ name, id, loading, error }) => {
return (
<ListItem
key={`${name}-${id}`}
Expand All @@ -91,7 +114,11 @@ export const Mutations = ({ clientId, explorerIFrame }: MutationsProps) => {
>
<div className="w-full flex items-center justify-between gap-2">
<span className="flex-1 overflow-hidden text-ellipsis">
{name}
{searchTerm && name ? (
<HighlightMatch searchTerm={searchTerm} value={name} />
) : (
name
)}
</span>
{loading ? (
<Spinner size="xs" className="shrink-0" />
Expand Down
36 changes: 32 additions & 4 deletions src/application/components/Queries/Queries.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useMemo, useState } from "react";
import type { TypedDocumentNode } from "@apollo/client";
import { NetworkStatus, gql, useQuery } from "@apollo/client";
import { isNetworkRequestInFlight } from "@apollo/client/core/networkStatus";
Expand All @@ -22,6 +22,8 @@ import { AlertDisclosure } from "../AlertDisclosure";
import { Tooltip } from "../Tooltip";
import { ApolloErrorAlertDisclosurePanel } from "../ApolloErrorAlertDisclosurePanel";
import { useActorEvent } from "../../hooks/useActorEvent";
import { SearchField } from "../SearchField";
import HighlightMatch from "../HighlightMatch";

enum QueryTabs {
Variables = "Variables",
Expand Down Expand Up @@ -61,16 +63,22 @@ interface QueriesProps {
explorerIFrame: HTMLIFrameElement | null;
}

const STABLE_EMPTY_QUERIES: Array<
NonNullable<GetQueries["client"]>["queries"]["items"][number]
> = [];

export const Queries = ({ clientId, explorerIFrame }: QueriesProps) => {
const [selected, setSelected] = useState("1");
const [searchTerm, setSearchTerm] = useState("");

const { data, startPolling, stopPolling } = useQuery(GET_QUERIES, {
variables: { clientId: clientId as string },
skip: clientId == null,
fetchPolicy: "cache-and-network",
pollInterval: 500,
});

const queries = data?.client?.queries.items ?? [];
const queries = data?.client?.queries.items ?? STABLE_EMPTY_QUERIES;
const selectedQuery = queries.find((query) => query.id === selected);
const [currentTab, setCurrentTab] = useState<QueryTabs>(QueryTabs.Variables);
const copyButtonText = JSON.stringify(
Expand All @@ -88,11 +96,27 @@ export const Queries = ({ clientId, explorerIFrame }: QueriesProps) => {
setSelected(queries[0].id);
}

const filteredQueries = useMemo(() => {
if (!searchTerm) {
return queries;
}

const regex = new RegExp(searchTerm, "i");

return queries.filter((query) => query.name && regex.test(query.name));
}, [searchTerm, queries]);

return (
<SidebarLayout>
<SidebarLayout.Sidebar>
<SearchField
className="mb-4"
placeholder="Search queries"
onChange={setSearchTerm}
value={searchTerm}
/>
<List className="h-full">
{queries.map(({ name, id, networkStatus, pollInterval }) => {
{filteredQueries.map(({ name, id, networkStatus, pollInterval }) => {
return (
<ListItem
key={`${name}-${id}`}
Expand All @@ -102,7 +126,11 @@ export const Queries = ({ clientId, explorerIFrame }: QueriesProps) => {
>
<div className="w-full flex items-center justify-between gap-2">
<span className="flex-1 overflow-hidden text-ellipsis">
{name}
{searchTerm && name ? (
<HighlightMatch searchTerm={searchTerm} value={name} />
) : (
name
)}
</span>
<QueryStatusIcon
networkStatus={networkStatus}
Expand Down

0 comments on commit 1d622c4

Please sign in to comment.