Skip to content

Commit

Permalink
Rework reconnect logic, add "Port could not be opened" modal (#1513)
Browse files Browse the repository at this point in the history
* move initialization into state machine

* use parallel states

* move reconnect logic into invoked actor

* modals

* update semgrepignore

* split machine into files

* make dialogs non-closable

* update tests

* fix emitting of `store.didReset`

* add debug logic

* progress

* adjustments

* use `ExternalLink`  everywhere

* more readable logic around "only one modal open"

* minor cleanup

* move `target="_blank" rel="noopener noreferrer"` into `ExternalLink`

* update more noopener usages

* suggestion from code review

* feedback from code review

* add .vscode extension

* Modals -> ErrorModals

* changesets
  • Loading branch information
phryneas authored Dec 10, 2024
1 parent 31394f0 commit f6305df
Show file tree
Hide file tree
Showing 35 changed files with 817 additions and 265 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-keys-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client-devtools-vscode": minor
---

Adds a modal for cases where the port could not be opened.
5 changes: 5 additions & 0 deletions .changeset/fair-windows-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"apollo-client-devtools": patch
---

Reworks the logic around the "could not connect" modal.
2 changes: 1 addition & 1 deletion .semgrepignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
src/extension/vscode/client.ts
src/application/components/ClientNotFoundModal.vscode.tsx
src/application/components/Modals/ClientNotFoundModal.vscode.tsx
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export default {
testEnvironment: "jsdom",
globals: {
VERSION: "0.0.0",
__IS_FIREFOX__: false,
__IS_CHROME__: true,
__IS_VSCODE__: false,
__IS_EXTENSION__: true,
},
testPathIgnorePatterns: [
"<rootDir>/build",
Expand Down
15 changes: 2 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 14 additions & 40 deletions src/application/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useEffect, useState } from "react";
import type { ReactNode } from "react";
import type { TypedDocumentNode } from "@apollo/client";
import { useReactiveVar, gql, useQuery } from "@apollo/client";
import { useMachine } from "@xstate/react";
import { ErrorBoundary } from "react-error-boundary";

import { currentScreen, Screens } from "./components/Layouts/Navigation";
Expand All @@ -23,8 +22,8 @@ import IconGitHubSolid from "@apollo/icons/small/IconGitHubSolid.svg";
import { SettingsModal } from "./components/Layouts/SettingsModal";
import Logo from "@apollo/icons/logos/LogoSymbol.svg";
import { BannerAlert } from "./components/BannerAlert";
import { devtoolsMachine } from "./machines/devtoolsMachine";
import { ClientNotFoundModal } from "./components/ClientNotFoundModal";
import { useDevToolsActorRef } from "./machines/devtoolsMachine";
import { ErrorModals } from "./components/ErrorModals/ErrorModals";
import { ButtonGroup } from "./components/ButtonGroup";
import {
GitHubIssueLink,
Expand All @@ -41,6 +40,7 @@ import { useActorEvent } from "./hooks/useActorEvent";
import { removeClient } from ".";
import { PageError } from "./components/PageError";
import { SidebarLayout } from "./components/Layouts/SidebarLayout";
import { ExternalLink } from "./components/ExternalLink";

const APP_QUERY: TypedDocumentNode<AppQuery, AppQueryVariables> = gql`
query AppQuery {
Expand Down Expand Up @@ -74,26 +74,13 @@ ${SECTIONS.devtoolsVersion}
`;

const stableEmptyClients: Required<AppQuery["clients"]> = [];
const noop = () => {};

export const App = () => {
const [snapshot, send] = useMachine(
devtoolsMachine.provide({
actions: {
resetStore: () => {
apolloClient.clearStore().catch(noop);
},
},
})
);
const {
data,
client: apolloClient,
refetch,
} = useQuery(APP_QUERY, { errorPolicy: "all" });
const { send } = useDevToolsActorRef();
const { data, refetch } = useQuery(APP_QUERY, { errorPolicy: "all" });

useActorEvent("registerClient", () => {
send({ type: "connect" });
send({ type: "client.register" });
// Unfortunately after we clear the store above, the query ends up "stuck"
// holding onto the old list of clients even if we manually write a cache
// update to properly resolve the list. Instead we refetch the list again to
Expand All @@ -102,17 +89,12 @@ export const App = () => {
});

useActorEvent("clientTerminated", (message) => {
// Disconnect if we are terminating the last client. We assume that 1 client
// means we are terminating the selected client
if (clients.length === 1) {
send({ type: "disconnect" });
}

send({ type: "client.terminated" });
removeClient(message.clientId);
});

useActorEvent("pageNavigated", () => {
send({ type: "disconnect" });
send({ type: "client.setCount", count: 0 });
});

const [settingsOpen, setSettingsOpen] = useState(false);
Expand Down Expand Up @@ -149,31 +131,25 @@ export const App = () => {

useEffect(() => {
if (clients.length) {
send({ type: "connect" });
send({ type: "client.setCount", count: clients.length });
}
}, [send, clients.length]);

return (
<>
<SettingsModal open={settingsOpen} onOpen={setSettingsOpen} />
<ClientNotFoundModal
open={snapshot.context.modalOpen}
onClose={() => send({ type: "closeModal" })}
onRetry={() => send({ type: "retry" })}
/>
<ErrorModals />
<BannerAlert />
<Tabs
value={selected}
onChange={(screen) => currentScreen(screen)}
className="flex flex-col h-screen bg-primary dark:bg-primary-dark"
>
<div className="flex items-center border-b border-b-primary dark:border-b-primary-dark gap-4 px-4">
<a
<ExternalLink
href="https://go.apollo.dev/c/docs"
target="_blank"
title="Apollo Client developer documentation"
className="block"
rel="noreferrer"
>
<Logo
role="img"
Expand All @@ -182,7 +158,7 @@ export const App = () => {
fill="currentColor"
className="text-icon-primary dark:text-icon-primary-dark"
/>
</a>
</ExternalLink>
<Divider orientation="vertical" />
<Tabs.List className="-mb-px">
<Tabs.Trigger value={Screens.Queries}>
Expand All @@ -197,21 +173,19 @@ export const App = () => {
<div className="ml-auto flex-1 justify-end flex items-center gap-2 h-full">
{client?.version && (
<GitHubReleaseHoverCard version={client.version}>
<a
<ExternalLink
className="no-underline"
href={
isSnapshotRelease(client.version)
? `https://github.com/apollographql/apollo-client/pull/${parseSnapshotRelease(client.version).prNumber}`
: `https://github.com/apollographql/apollo-client/releases/tag/v${client.version}`
}
target="_blank"
rel="noreferrer"
>
<Badge variant="info" className="cursor-pointer">
Apollo Client <span className="lowercase">v</span>
{client.version}
</Badge>
</a>
</ExternalLink>
</GitHubReleaseHoverCard>
)}
{clients.length > 1 && (
Expand Down
4 changes: 3 additions & 1 deletion src/application/__tests__/AppProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { render, waitFor, screen } from "@testing-library/react";
import matchMediaMock from "../utilities/testing/matchMedia";
import { Mode, colorTheme } from "../theme";
import { AppProvider } from "../index";
import { devtoolsMachine } from "../machines/devtoolsMachine";
import { createActor } from "xstate";

const matchMedia = matchMediaMock();

Expand All @@ -12,7 +14,7 @@ jest.mock("../App", () => ({

describe("<AppProvider />", () => {
test("changes the color theme", async () => {
render(<AppProvider />);
render(<AppProvider actor={createActor(devtoolsMachine).start()} />);
expect(screen.getByText("App")).toBeInTheDocument();

expect(colorTheme()).toEqual("light");
Expand Down
2 changes: 1 addition & 1 deletion src/application/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Spinner } from "./Spinner";

type NativeButtonProps = ComponentPropsWithoutRef<"button">;

type ButtonProps = AsChildProps<NativeButtonProps> &
export type ButtonProps = AsChildProps<NativeButtonProps> &
Variants & {
className?: string;
icon?: ReactElement<{ "aria-hidden": boolean; className?: string }>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
import { Button } from "./Button";
import { ButtonGroup } from "./ButtonGroup";
import { Disclosure } from "./Disclosure";
import { GitHubIssueLink, SECTIONS, LABELS } from "./GitHubIssueLink";
import { Modal } from "./Modal";
import { Button } from "../Button";
import { Disclosure } from "../Disclosure";
import { ExternalLink } from "../ExternalLink";
import { GitHubIssueLink, SECTIONS, LABELS } from "../GitHubIssueLink";
import { Modal } from "../Modal";
import IconGitHubSolid from "@apollo/icons/small/IconGitHubSolid.svg";

interface ClientNotFoundModalProps {
open: boolean;
onClose: () => void;
onRetry: () => void;
}

function ConnectToDevToolsOptionLink() {
return (
<a
rel="noreferrer noopener"
target="_blank"
href="https://www.apollographql.com/docs/react/api/core/ApolloClient#apolloclientoptions-connecttodevtools"
>
<ExternalLink href="https://www.apollographql.com/docs/react/api/core/ApolloClient#apolloclientoptions-connecttodevtools">
<code>connectToDevTools</code> option
</a>
</ExternalLink>
);
}

export function ClientNotFoundModal({
open,
onClose,
onRetry,
}: ClientNotFoundModalProps) {
return (
<Modal open={open} onClose={onClose} size="xl">
<Modal open={open} size="xl">
<Modal.Header>
<Modal.Title>Could not find client</Modal.Title>
<Modal.Description>
Expand Down Expand Up @@ -75,13 +69,9 @@ ${SECTIONS.devtoolsVersion}
<p className="mt-4">
You may need to tweak your bundler settings to set{" "}
<code>globalThis.__DEV__</code> correctly. See the{" "}
<a
rel="noreferrer noopener"
target="_blank"
href="https://www.apollographql.com/docs/react/development-testing/reducing-bundle-size/"
>
<ExternalLink href="https://www.apollographql.com/docs/react/development-testing/reducing-bundle-size/">
&quot;Reducing bundle size&quot;
</a>{" "}
</ExternalLink>{" "}
article for examples on configuring your bundler.
</p>
<p className="mt-4">
Expand Down Expand Up @@ -110,13 +100,9 @@ ${SECTIONS.devtoolsVersion}
<Disclosure.Panel>
Apollo Client Devtools does not currently support clients created
in iframes. Please follow{" "}
<a
rel="noreferrer noopener"
target="_blank"
href="https://github.com/apollographql/apollo-client-devtools/discussions/380"
>
<ExternalLink href="https://github.com/apollographql/apollo-client-devtools/discussions/380">
this discussion
</a>{" "}
</ExternalLink>{" "}
for updates on this feature.
</Disclosure.Panel>
</Disclosure>
Expand All @@ -131,13 +117,9 @@ ${SECTIONS.devtoolsVersion}
<code>window.__APOLLO_CLIENT__</code> variable for up to 10
seconds after a page load before giving up. When using
Apollo&apos;s{" "}
<a
rel="noreferrer noopener"
target="_blank"
href="https://github.com/apollographql/apollo-client-nextjs"
>
<ExternalLink href="https://github.com/apollographql/apollo-client-nextjs">
Next.js RSC integration
</a>
</ExternalLink>
, it is possible that Apollo Client Devtools will be unable to
connect to the client when the first loaded page does not render
any client components and no other client components are loaded
Expand Down Expand Up @@ -255,14 +237,9 @@ ${SECTIONS.devtoolsVersion}
<span>Create an issue</span>
</GitHubIssueLink>
</Button>
<ButtonGroup>
<Button type="button" size="md" variant="secondary" onClick={onClose}>
Close
</Button>
<Button size="md" variant="primary" onClick={onRetry}>
Retry
</Button>
</ButtonGroup>
<Button size="md" variant="primary" onClick={onRetry}>
Retry
</Button>
</Modal.Footer>
</Modal>
);
Expand Down
Loading

0 comments on commit f6305df

Please sign in to comment.