Skip to content

Commit a5b9151

Browse files
samejrmatt-aitken
andauthored
Adds a "dev connected" banner to the top of the Run and Runs list pages (#1855)
* Adds DevPresenceBanner to the Run page * Adds DevPresenceBanner to the Run list page * Makes the DevConnection component reusable and moves components to the DevPresence.tsx file * SideMenu: only show dev presence when project engine === V2 * WIP on disconnected banner on v3 and v4 * v3 dev connection working. Made it slightly red * Only show the disconnected banner on v3 if the run is not completed --------- Co-authored-by: Matt Aitken <matt@mattaitken.com>
1 parent 2812124 commit a5b9151

File tree

4 files changed

+194
-126
lines changed
  • apps/webapp/app
    • components
    • routes
      • _app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam
      • _app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index

4 files changed

+194
-126
lines changed

apps/webapp/app/components/DevPresence.tsx

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
1+
import { AnimatePresence, motion } from "framer-motion";
12
import { createContext, type ReactNode, useContext, useEffect, useMemo, useState } from "react";
3+
import {
4+
CheckingConnectionIcon,
5+
ConnectedIcon,
6+
DisconnectedIcon,
7+
} from "~/assets/icons/ConnectionIcons";
28
import { useEnvironment } from "~/hooks/useEnvironment";
39
import { useEventSource } from "~/hooks/useEventSource";
410
import { useOrganization } from "~/hooks/useOrganizations";
511
import { useProject } from "~/hooks/useProject";
12+
import { docsPath } from "~/utils/pathBuilder";
13+
import connectedImage from "../assets/images/cli-connected.png";
14+
import disconnectedImage from "../assets/images/cli-disconnected.png";
15+
import { InlineCode } from "./code/InlineCode";
16+
import { Button } from "./primitives/Buttons";
17+
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "./primitives/Dialog";
18+
import { Paragraph } from "./primitives/Paragraph";
19+
import { TextLink } from "./primitives/TextLink";
20+
import { PackageManagerProvider, TriggerDevStepV3 } from "./SetupCommands";
621

722
// Define Context types
823
type DevPresenceContextType = {
@@ -77,3 +92,131 @@ export function useDevPresence() {
7792
}
7893
return context;
7994
}
95+
96+
/**
97+
* We need this for the legacy v1 engine, where we show the banner after a delay if there are no events.
98+
*/
99+
export function useCrossEngineIsConnected({
100+
isCompleted,
101+
logCount,
102+
}: {
103+
isCompleted: boolean;
104+
logCount: number;
105+
}) {
106+
const project = useProject();
107+
const environment = useEnvironment();
108+
const { isConnected } = useDevPresence();
109+
const [crossEngineIsConnected, setCrossEngineIsConnected] = useState<boolean | undefined>(
110+
undefined
111+
);
112+
113+
useEffect(() => {
114+
if (project.engine === "V2") {
115+
setCrossEngineIsConnected(isConnected);
116+
return;
117+
}
118+
119+
if (project.engine === "V1") {
120+
if (isCompleted) {
121+
setCrossEngineIsConnected(true);
122+
return;
123+
}
124+
125+
if (logCount <= 1) {
126+
const timer = setTimeout(() => {
127+
setCrossEngineIsConnected(false);
128+
}, 5000);
129+
return () => clearTimeout(timer);
130+
} else {
131+
setCrossEngineIsConnected(true);
132+
}
133+
}
134+
}, [environment.type, project.engine, logCount, isConnected, isCompleted]);
135+
136+
return crossEngineIsConnected;
137+
}
138+
139+
export function ConnectionIcon({ isConnected }: { isConnected: boolean | undefined }) {
140+
if (isConnected === undefined) {
141+
return <CheckingConnectionIcon className="size-5" />;
142+
}
143+
return isConnected ? (
144+
<ConnectedIcon className="size-5" />
145+
) : (
146+
<DisconnectedIcon className="size-5" />
147+
);
148+
}
149+
150+
export function DevPresencePanel({ isConnected }: { isConnected: boolean | undefined }) {
151+
return (
152+
<DialogContent>
153+
<DialogHeader>
154+
{isConnected === undefined
155+
? "Checking connection..."
156+
: isConnected
157+
? "Your dev server is connected"
158+
: "Your dev server is not connected"}
159+
</DialogHeader>
160+
<div className="mt-2 flex flex-col gap-3 px-2">
161+
<div className="flex flex-col items-center justify-center gap-6 px-6 py-10">
162+
<img
163+
src={isConnected === true ? connectedImage : disconnectedImage}
164+
alt={isConnected === true ? "Connected" : "Disconnected"}
165+
width={282}
166+
height={45}
167+
/>
168+
<Paragraph variant="small" className={isConnected ? "text-success" : "text-error"}>
169+
{isConnected === undefined
170+
? "Checking connection..."
171+
: isConnected
172+
? "Your local dev server is connected to Trigger.dev"
173+
: "Your local dev server is not connected to Trigger.dev"}
174+
</Paragraph>
175+
</div>
176+
{isConnected ? null : (
177+
<div className="space-y-3">
178+
<PackageManagerProvider>
179+
<TriggerDevStepV3 title="Run this command to connect" />
180+
</PackageManagerProvider>
181+
<Paragraph variant="small">
182+
Run this CLI <InlineCode variant="extra-small">dev</InlineCode> command to connect to
183+
the Trigger.dev servers to start developing locally. Keep it running while you develop
184+
to stay connected. Learn more in the{" "}
185+
<TextLink to={docsPath("cli-dev")}>CLI docs</TextLink>.
186+
</Paragraph>
187+
</div>
188+
)}
189+
</div>
190+
</DialogContent>
191+
);
192+
}
193+
194+
export function DevDisconnectedBanner({ isConnected }: { isConnected: boolean | undefined }) {
195+
return (
196+
<Dialog>
197+
<AnimatePresence>
198+
{isConnected === false && (
199+
<motion.div
200+
initial={{ opacity: 0 }}
201+
animate={{ opacity: 1 }}
202+
exit={{ opacity: 0 }}
203+
transition={{ duration: 0.3 }}
204+
className="flex"
205+
>
206+
<DialogTrigger asChild>
207+
<Button
208+
variant="minimal/small"
209+
className="border border-error/20 bg-error/10 py-1 pl-1 pr-2 group-hover/button:border-error/30 group-hover/button:bg-error/20"
210+
iconSpacing="gap-1"
211+
LeadingIcon={<ConnectionIcon isConnected={false} />}
212+
>
213+
Your local dev server is not connected to Trigger.dev
214+
</Button>
215+
</DialogTrigger>
216+
</motion.div>
217+
)}
218+
</AnimatePresence>
219+
<DevPresencePanel isConnected={isConnected} />
220+
</Dialog>
221+
);
222+
}

apps/webapp/app/components/navigation/SideMenu.tsx

Lines changed: 33 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,9 @@ import {
2020
import { useNavigation } from "@remix-run/react";
2121
import { useEffect, useRef, useState, type ReactNode } from "react";
2222
import simplur from "simplur";
23-
import {
24-
CheckingConnectionIcon,
25-
ConnectedIcon,
26-
DisconnectedIcon,
27-
} from "~/assets/icons/ConnectionIcons";
28-
import { RunsIconExtraSmall, RunsIconSmall } from "~/assets/icons/RunsIcon";
23+
import { RunsIconExtraSmall } from "~/assets/icons/RunsIcon";
2924
import { TaskIconSmall } from "~/assets/icons/TaskIcon";
25+
import { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon";
3026
import { Avatar } from "~/components/primitives/Avatar";
3127
import { type MatchedEnvironment } from "~/hooks/useEnvironment";
3228
import { type MatchedOrganization } from "~/hooks/useOrganizations";
@@ -37,7 +33,6 @@ import { type FeedbackType } from "~/routes/resources.feedback";
3733
import { cn } from "~/utils/cn";
3834
import {
3935
accountPath,
40-
docsPath,
4136
logoutPath,
4237
newOrganizationPath,
4338
newProjectPath,
@@ -60,14 +55,11 @@ import {
6055
v3UsagePath,
6156
v3WaitpointTokensPath,
6257
} from "~/utils/pathBuilder";
63-
import connectedImage from "../../assets/images/cli-connected.png";
64-
import disconnectedImage from "../../assets/images/cli-disconnected.png";
6558
import { FreePlanUsage } from "../billing/FreePlanUsage";
66-
import { InlineCode } from "../code/InlineCode";
67-
import { useDevPresence } from "../DevPresence";
59+
import { ConnectionIcon, DevPresencePanel, useDevPresence } from "../DevPresence";
6860
import { ImpersonationBanner } from "../ImpersonationBanner";
6961
import { Button, ButtonContent, LinkButton } from "../primitives/Buttons";
70-
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "../primitives/Dialog";
62+
import { Dialog, DialogTrigger } from "../primitives/Dialog";
7163
import { Paragraph } from "../primitives/Paragraph";
7264
import {
7365
Popover,
@@ -78,20 +70,17 @@ import {
7870
} from "../primitives/Popover";
7971
import { TextLink } from "../primitives/TextLink";
8072
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip";
81-
import { PackageManagerProvider, TriggerDevStepV3 } from "../SetupCommands";
8273
import { UserProfilePhoto } from "../UserProfilePhoto";
8374
import { EnvironmentSelector } from "./EnvironmentSelector";
8475
import { HelpAndFeedback } from "./HelpAndFeedbackPopover";
8576
import { SideMenuHeader } from "./SideMenuHeader";
8677
import { SideMenuItem } from "./SideMenuItem";
8778
import { SideMenuSection } from "./SideMenuSection";
88-
import { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon";
89-
import { Spinner } from "../primitives/Spinner";
9079

9180
type SideMenuUser = Pick<User, "email" | "admin"> & { isImpersonating: boolean };
9281
export type SideMenuProject = Pick<
9382
MatchedProject,
94-
"id" | "name" | "slug" | "version" | "environments"
83+
"id" | "name" | "slug" | "version" | "environments" | "engine"
9584
>;
9685
export type SideMenuEnvironment = MatchedEnvironment;
9786

@@ -115,6 +104,7 @@ export function SideMenu({
115104
const borderRef = useRef<HTMLDivElement>(null);
116105
const [showHeaderDivider, setShowHeaderDivider] = useState(false);
117106
const currentPlan = useCurrentPlan();
107+
const { isConnected } = useDevPresence();
118108
const isFreeUser = currentPlan?.v3Subscription?.isPaying === false;
119109

120110
useEffect(() => {
@@ -163,7 +153,33 @@ export function SideMenu({
163153
project={project}
164154
environment={environment}
165155
/>
166-
{environment.type === "DEVELOPMENT" && <DevConnection />}
156+
{environment.type === "DEVELOPMENT" && project.engine === "V2" && (
157+
<Dialog>
158+
<TooltipProvider disableHoverableContent={true}>
159+
<Tooltip>
160+
<TooltipTrigger asChild>
161+
<div className="inline-flex">
162+
<DialogTrigger asChild>
163+
<Button
164+
variant="minimal/small"
165+
className="aspect-square h-7 p-1"
166+
LeadingIcon={<ConnectionIcon isConnected={isConnected} />}
167+
/>
168+
</DialogTrigger>
169+
</div>
170+
</TooltipTrigger>
171+
<TooltipContent side="right" className={"text-xs"}>
172+
{isConnected === undefined
173+
? "Checking connection..."
174+
: isConnected
175+
? "Your dev server is connected"
176+
: "Your dev server is not connected"}
177+
</TooltipContent>
178+
</Tooltip>
179+
</TooltipProvider>
180+
<DevPresencePanel isConnected={isConnected} />
181+
</Dialog>
182+
)}
167183
</div>
168184
</div>
169185

@@ -522,81 +538,3 @@ function SelectorDivider() {
522538
</svg>
523539
);
524540
}
525-
526-
export function DevConnection() {
527-
const { isConnected } = useDevPresence();
528-
529-
return (
530-
<Dialog>
531-
<TooltipProvider disableHoverableContent={true}>
532-
<Tooltip>
533-
<TooltipTrigger asChild>
534-
<div className="inline-flex">
535-
<DialogTrigger asChild>
536-
<Button
537-
variant="minimal/small"
538-
className="aspect-square h-7 p-1"
539-
LeadingIcon={
540-
isConnected === undefined ? (
541-
<CheckingConnectionIcon className="size-5" />
542-
) : isConnected ? (
543-
<ConnectedIcon className="size-5" />
544-
) : (
545-
<DisconnectedIcon className="size-5" />
546-
)
547-
}
548-
/>
549-
</DialogTrigger>
550-
</div>
551-
</TooltipTrigger>
552-
<TooltipContent side="right" className={"text-xs"}>
553-
{isConnected === undefined
554-
? "Checking connection..."
555-
: isConnected
556-
? "Your dev server is connected"
557-
: "Your dev server is not connected"}
558-
</TooltipContent>
559-
</Tooltip>
560-
</TooltipProvider>
561-
<DialogContent>
562-
<DialogHeader>
563-
{isConnected === undefined
564-
? "Checking connection..."
565-
: isConnected
566-
? "Your dev server is connected"
567-
: "Your dev server is not connected"}
568-
</DialogHeader>
569-
<div className="mt-2 flex flex-col gap-3 px-2">
570-
<div className="flex flex-col items-center justify-center gap-6 px-6 py-10">
571-
<img
572-
src={isConnected === true ? connectedImage : disconnectedImage}
573-
alt={isConnected === true ? "Connected" : "Disconnected"}
574-
width={282}
575-
height={45}
576-
/>
577-
<Paragraph variant="small" className={isConnected ? "text-success" : "text-error"}>
578-
{isConnected === undefined
579-
? "Checking connection..."
580-
: isConnected
581-
? "Your local dev server is connected to Trigger.dev"
582-
: "Your local dev server is not connected to Trigger.dev"}
583-
</Paragraph>
584-
</div>
585-
{isConnected ? null : (
586-
<div className="space-y-3">
587-
<PackageManagerProvider>
588-
<TriggerDevStepV3 title="Run this command to connect" />
589-
</PackageManagerProvider>
590-
<Paragraph variant="small">
591-
Run this CLI <InlineCode variant="extra-small">dev</InlineCode> command to connect
592-
to the Trigger.dev servers to start developing locally. Keep it running while you
593-
develop to stay connected. Learn more in the{" "}
594-
<TextLink to={docsPath("cli-dev")}>CLI docs</TextLink>.
595-
</Paragraph>
596-
</div>
597-
)}
598-
</div>
599-
</DialogContent>
600-
</Dialog>
601-
);
602-
}

0 commit comments

Comments
 (0)