diff --git a/apps/webapp/app/components/DevPresence.tsx b/apps/webapp/app/components/DevPresence.tsx index 396c6ea9f6..7a99dab37a 100644 --- a/apps/webapp/app/components/DevPresence.tsx +++ b/apps/webapp/app/components/DevPresence.tsx @@ -1,8 +1,23 @@ +import { AnimatePresence, motion } from "framer-motion"; import { createContext, type ReactNode, useContext, useEffect, useMemo, useState } from "react"; +import { + CheckingConnectionIcon, + ConnectedIcon, + DisconnectedIcon, +} from "~/assets/icons/ConnectionIcons"; import { useEnvironment } from "~/hooks/useEnvironment"; import { useEventSource } from "~/hooks/useEventSource"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; +import { docsPath } from "~/utils/pathBuilder"; +import connectedImage from "../assets/images/cli-connected.png"; +import disconnectedImage from "../assets/images/cli-disconnected.png"; +import { InlineCode } from "./code/InlineCode"; +import { Button } from "./primitives/Buttons"; +import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "./primitives/Dialog"; +import { Paragraph } from "./primitives/Paragraph"; +import { TextLink } from "./primitives/TextLink"; +import { PackageManagerProvider, TriggerDevStepV3 } from "./SetupCommands"; // Define Context types type DevPresenceContextType = { @@ -77,3 +92,131 @@ export function useDevPresence() { } return context; } + +/** + * We need this for the legacy v1 engine, where we show the banner after a delay if there are no events. + */ +export function useCrossEngineIsConnected({ + isCompleted, + logCount, +}: { + isCompleted: boolean; + logCount: number; +}) { + const project = useProject(); + const environment = useEnvironment(); + const { isConnected } = useDevPresence(); + const [crossEngineIsConnected, setCrossEngineIsConnected] = useState( + undefined + ); + + useEffect(() => { + if (project.engine === "V2") { + setCrossEngineIsConnected(isConnected); + return; + } + + if (project.engine === "V1") { + if (isCompleted) { + setCrossEngineIsConnected(true); + return; + } + + if (logCount <= 1) { + const timer = setTimeout(() => { + setCrossEngineIsConnected(false); + }, 5000); + return () => clearTimeout(timer); + } else { + setCrossEngineIsConnected(true); + } + } + }, [environment.type, project.engine, logCount, isConnected, isCompleted]); + + return crossEngineIsConnected; +} + +export function ConnectionIcon({ isConnected }: { isConnected: boolean | undefined }) { + if (isConnected === undefined) { + return ; + } + return isConnected ? ( + + ) : ( + + ); +} + +export function DevPresencePanel({ isConnected }: { isConnected: boolean | undefined }) { + return ( + + + {isConnected === undefined + ? "Checking connection..." + : isConnected + ? "Your dev server is connected" + : "Your dev server is not connected"} + +
+
+ {isConnected + + {isConnected === undefined + ? "Checking connection..." + : isConnected + ? "Your local dev server is connected to Trigger.dev" + : "Your local dev server is not connected to Trigger.dev"} + +
+ {isConnected ? null : ( +
+ + + + + Run this CLI dev command to connect to + the Trigger.dev servers to start developing locally. Keep it running while you develop + to stay connected. Learn more in the{" "} + CLI docs. + +
+ )} +
+
+ ); +} + +export function DevDisconnectedBanner({ isConnected }: { isConnected: boolean | undefined }) { + return ( + + + {isConnected === false && ( + + + + + + )} + + + + ); +} diff --git a/apps/webapp/app/components/navigation/SideMenu.tsx b/apps/webapp/app/components/navigation/SideMenu.tsx index 18b9b7d5df..0551b80050 100644 --- a/apps/webapp/app/components/navigation/SideMenu.tsx +++ b/apps/webapp/app/components/navigation/SideMenu.tsx @@ -20,13 +20,9 @@ import { import { useNavigation } from "@remix-run/react"; import { useEffect, useRef, useState, type ReactNode } from "react"; import simplur from "simplur"; -import { - CheckingConnectionIcon, - ConnectedIcon, - DisconnectedIcon, -} from "~/assets/icons/ConnectionIcons"; -import { RunsIconExtraSmall, RunsIconSmall } from "~/assets/icons/RunsIcon"; +import { RunsIconExtraSmall } from "~/assets/icons/RunsIcon"; import { TaskIconSmall } from "~/assets/icons/TaskIcon"; +import { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon"; import { Avatar } from "~/components/primitives/Avatar"; import { type MatchedEnvironment } from "~/hooks/useEnvironment"; import { type MatchedOrganization } from "~/hooks/useOrganizations"; @@ -37,7 +33,6 @@ import { type FeedbackType } from "~/routes/resources.feedback"; import { cn } from "~/utils/cn"; import { accountPath, - docsPath, logoutPath, newOrganizationPath, newProjectPath, @@ -60,14 +55,11 @@ import { v3UsagePath, v3WaitpointTokensPath, } from "~/utils/pathBuilder"; -import connectedImage from "../../assets/images/cli-connected.png"; -import disconnectedImage from "../../assets/images/cli-disconnected.png"; import { FreePlanUsage } from "../billing/FreePlanUsage"; -import { InlineCode } from "../code/InlineCode"; -import { useDevPresence } from "../DevPresence"; +import { ConnectionIcon, DevPresencePanel, useDevPresence } from "../DevPresence"; import { ImpersonationBanner } from "../ImpersonationBanner"; import { Button, ButtonContent, LinkButton } from "../primitives/Buttons"; -import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "../primitives/Dialog"; +import { Dialog, DialogTrigger } from "../primitives/Dialog"; import { Paragraph } from "../primitives/Paragraph"; import { Popover, @@ -78,20 +70,17 @@ import { } from "../primitives/Popover"; import { TextLink } from "../primitives/TextLink"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip"; -import { PackageManagerProvider, TriggerDevStepV3 } from "../SetupCommands"; import { UserProfilePhoto } from "../UserProfilePhoto"; import { EnvironmentSelector } from "./EnvironmentSelector"; import { HelpAndFeedback } from "./HelpAndFeedbackPopover"; import { SideMenuHeader } from "./SideMenuHeader"; import { SideMenuItem } from "./SideMenuItem"; import { SideMenuSection } from "./SideMenuSection"; -import { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon"; -import { Spinner } from "../primitives/Spinner"; type SideMenuUser = Pick & { isImpersonating: boolean }; export type SideMenuProject = Pick< MatchedProject, - "id" | "name" | "slug" | "version" | "environments" + "id" | "name" | "slug" | "version" | "environments" | "engine" >; export type SideMenuEnvironment = MatchedEnvironment; @@ -115,6 +104,7 @@ export function SideMenu({ const borderRef = useRef(null); const [showHeaderDivider, setShowHeaderDivider] = useState(false); const currentPlan = useCurrentPlan(); + const { isConnected } = useDevPresence(); const isFreeUser = currentPlan?.v3Subscription?.isPaying === false; useEffect(() => { @@ -163,7 +153,33 @@ export function SideMenu({ project={project} environment={environment} /> - {environment.type === "DEVELOPMENT" && } + {environment.type === "DEVELOPMENT" && project.engine === "V2" && ( + + + + +
+ +
+
+ + {isConnected === undefined + ? "Checking connection..." + : isConnected + ? "Your dev server is connected" + : "Your dev server is not connected"} + +
+
+ +
+ )} @@ -522,81 +538,3 @@ function SelectorDivider() { ); } - -export function DevConnection() { - const { isConnected } = useDevPresence(); - - return ( - - - - -
- -
-
- - {isConnected === undefined - ? "Checking connection..." - : isConnected - ? "Your dev server is connected" - : "Your dev server is not connected"} - -
-
- - - {isConnected === undefined - ? "Checking connection..." - : isConnected - ? "Your dev server is connected" - : "Your dev server is not connected"} - -
-
- {isConnected - - {isConnected === undefined - ? "Checking connection..." - : isConnected - ? "Your local dev server is connected to Trigger.dev" - : "Your local dev server is not connected to Trigger.dev"} - -
- {isConnected ? null : ( -
- - - - - Run this CLI dev command to connect - to the Trigger.dev servers to start developing locally. Keep it running while you - develop to stay connected. Learn more in the{" "} - CLI docs. - -
- )} -
-
-
- ); -} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx index 8fd725ee3d..b0e6c981e9 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx @@ -21,13 +21,17 @@ import { tryCatch, } from "@trigger.dev/core/v3"; import { type RuntimeEnvironmentType } from "@trigger.dev/database"; -import { motion } from "framer-motion"; +import { AnimatePresence, motion } from "framer-motion"; import { useCallback, useEffect, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; import { DisconnectedIcon } from "~/assets/icons/ConnectionIcons"; import { ShowParentIcon, ShowParentIconSelected } from "~/assets/icons/ShowParentIcon"; import tileBgPath from "~/assets/images/error-banner-tile@2x.png"; -import { useDevPresence } from "~/components/DevPresence"; +import { + DevDisconnectedBanner, + useCrossEngineIsConnected, + useDevPresence, +} from "~/components/DevPresence"; import { AdminDebugTooltip } from "~/components/admin/debugTooltip"; import { PageBody } from "~/components/layout/AppLayout"; import { Badge } from "~/components/primitives/Badge"; @@ -184,6 +188,10 @@ export default function Page() { const organization = useOrganization(); const project = useProject(); const environment = useEnvironment(); + const isConnected = useCrossEngineIsConnected({ + logCount: trace?.events.length ?? 0, + isCompleted: run.completedAt !== null, + }); return ( <> @@ -195,6 +203,7 @@ export default function Page() { }} title={`Run #${run.number}`} /> + {environment.type === "DEVELOPMENT" && } @@ -666,9 +675,6 @@ function TasksTreeView({ - {!isCompleted && - environmentType === "DEVELOPMENT" && - index === displayEvents.length - 1 && } )} onScroll={(scrollTop) => { @@ -1270,32 +1276,6 @@ function CurrentTimeIndicator({ ); } -function ConnectedDevWarning() { - const { isConnected } = useDevPresence(); - - return ( -
- } - className="mt-2" - > -
- - Your local dev server is not connectedr. Check you're running the CLI: - - -
-
-
- ); -} - function KeyboardShortcuts({ expandAllBelowDepth, collapseAllBelowDepth, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx index 2bf0ef79af..bcb1447e40 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx @@ -8,6 +8,7 @@ import { ListChecks, ListX } from "lucide-react"; import { Suspense, useState } from "react"; import { TypedAwait, typeddefer, useTypedLoaderData } from "remix-typedjson"; import { TaskIcon } from "~/assets/icons/TaskIcon"; +import { DevDisconnectedBanner, useDevPresence } from "~/components/DevPresence"; import { StepContentContainer } from "~/components/StepContentContainer"; import { MainCenteredContainer, PageBody } from "~/components/layout/AppLayout"; import { Button, LinkButton } from "~/components/primitives/Buttons"; @@ -161,11 +162,17 @@ export default function Page() { const { data, rootOnlyDefault } = useTypedLoaderData(); const navigation = useNavigation(); const isLoading = navigation.state !== "idle"; + const { isConnected } = useDevPresence(); + const project = useProject(); + const environment = useEnvironment(); return ( <> + {environment.type === "DEVELOPMENT" && project.engine === "V2" && ( + + )}