Skip to content

Added default time period to Runs, Waitpoint and Batch list pages. Improved dev presence #1832

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

Merged
merged 14 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions apps/webapp/app/assets/icons/ConnectionIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,25 @@ export function DisconnectedIcon({ className }: { className?: string }) {
</svg>
);
}

export function CheckingConnectionIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect
x="0.5"
y="-0.5"
width="19"
height="1"
rx="0.5"
transform="matrix(1 0 0 -1 1.99998 20)"
stroke="#878C99"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M18 6.5L6.00001 6.5C5.72387 6.5 5.50001 6.72386 5.50001 7L5.50001 15C5.50001 15.2761 5.72387 15.5 6.00001 15.5L18 15.5C18.2762 15.5 18.5 15.2761 18.5 15V7C18.5 6.72386 18.2762 6.5 18 6.5ZM6.00001 5C4.89545 5 4.00001 5.89543 4.00001 7L4.00001 15C4.00001 16.1046 4.89545 17 6.00001 17L18 17C19.1046 17 20 16.1046 20 15V7C20 5.89543 19.1046 5 18 5L6.00001 5Z"
fill="#878C99"
/>
</svg>
);
}
35 changes: 13 additions & 22 deletions apps/webapp/app/components/DevPresence.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import { createContext, type ReactNode, useContext, useEffect, useMemo, useState } from "react";
import { useDebounce } from "~/hooks/useDebounce";
import { useEnvironment } from "~/hooks/useEnvironment";
import { useEventSource } from "~/hooks/useEventSource";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";

// Define Context types
type DevPresenceContextType = {
lastSeen: Date | null;
isConnected: boolean;
isConnected: boolean | undefined;
};

// Create Context with default values
const DevPresenceContext = createContext<DevPresenceContextType>({
lastSeen: null,
isConnected: false,
isConnected: undefined,
});

// Provider component with enabled prop
Expand All @@ -30,50 +27,44 @@ export function DevPresenceProvider({ children, enabled = true }: DevPresencePro

// Only subscribe to event source if enabled is true
const streamedEvents = useEventSource(
`/resources/orgs/${organization.slug}/projects/${project.slug}/env/${environment.slug}/dev/presence`,
`/resources/orgs/${organization.slug}/projects/${project.slug}/dev/presence`,
{
event: "presence",
disabled: !enabled,
}
);

const [lastSeen, setLastSeen] = useState<Date | null>(null);

const debouncer = useDebounce((seen: Date | null) => {
setLastSeen(seen);
}, 3_000);
const [isConnected, setIsConnected] = useState<boolean | undefined>(undefined);

useEffect(() => {
// If disabled or no events, set lastSeen to null
// If disabled or no events
if (!enabled || streamedEvents === null) {
debouncer(null);
setIsConnected(undefined);
return;
}

try {
const data = JSON.parse(streamedEvents) as any;
if ("lastSeen" in data && data.lastSeen) {
if ("isConnected" in data && data.isConnected) {
try {
const lastSeenDate = new Date(data.lastSeen);
debouncer(lastSeenDate);
setIsConnected(true);
} catch (error) {
console.log("DevPresence: Failed to parse lastSeen timestamp", { error });
debouncer(null);
setIsConnected(false);
}
} else {
debouncer(null);
setIsConnected(false);
}
} catch (error) {
console.log("DevPresence: Failed to parse presence message", { error });
debouncer(null);
setIsConnected(false);
}
}, [streamedEvents, enabled]);

// Calculate isConnected and memoize the context value
const contextValue = useMemo(() => {
const isConnected = enabled && lastSeen !== null && lastSeen > new Date(Date.now() - 120_000);
return { lastSeen, isConnected };
}, [lastSeen, enabled]);
return { isConnected };
}, [isConnected, enabled]);

return <DevPresenceContext.Provider value={contextValue}>{children}</DevPresenceContext.Provider>;
}
Expand Down
31 changes: 24 additions & 7 deletions apps/webapp/app/components/navigation/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import {
import { useNavigation } from "@remix-run/react";
import { useEffect, useRef, useState, type ReactNode } from "react";
import simplur from "simplur";
import { ConnectedIcon, DisconnectedIcon } from "~/assets/icons/ConnectionIcons";
import {
CheckingConnectionIcon,
ConnectedIcon,
DisconnectedIcon,
} from "~/assets/icons/ConnectionIcons";
import { RunsIconExtraSmall, RunsIconSmall } from "~/assets/icons/RunsIcon";
import { TaskIconSmall } from "~/assets/icons/TaskIcon";
import { Avatar } from "~/components/primitives/Avatar";
Expand Down Expand Up @@ -82,6 +86,7 @@ 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<User, "email" | "admin"> & { isImpersonating: boolean };
export type SideMenuProject = Pick<
Expand Down Expand Up @@ -532,7 +537,9 @@ export function DevConnection() {
variant="minimal/small"
className="aspect-square h-7 p-1"
LeadingIcon={
isConnected ? (
isConnected === undefined ? (
<CheckingConnectionIcon className="size-5" />
) : isConnected ? (
<ConnectedIcon className="size-5" />
) : (
<DisconnectedIcon className="size-5" />
Expand All @@ -543,24 +550,34 @@ export function DevConnection() {
</div>
</TooltipTrigger>
<TooltipContent side="right" className={"text-xs"}>
{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"}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<DialogContent>
<DialogHeader>
{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"}
</DialogHeader>
<div className="mt-2 flex flex-col gap-3 px-2">
<div className="flex flex-col items-center justify-center gap-6 px-6 py-10">
<img
src={isConnected ? connectedImage : disconnectedImage}
alt={isConnected ? "Connected" : "Disconnected"}
src={isConnected === true ? connectedImage : disconnectedImage}
alt={isConnected === true ? "Connected" : "Disconnected"}
width={282}
height={45}
/>
<Paragraph variant="small" className={isConnected ? "text-success" : "text-error"}>
{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"}
</Paragraph>
Expand Down
9 changes: 8 additions & 1 deletion apps/webapp/app/components/primitives/AppliedFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@ export function AppliedFilter({
}: AppliedFilterProps) {
const variantClassName = variants[variant];
return (
<div className={cn("flex items-center transition", variantClassName.box, className)}>
<div
className={cn(
"flex items-center transition",
variantClassName.box,
!removable && "pr-2",
className
)}
>
<div className="flex items-center gap-0.5">
<div className="text-text-dimmed">
<span>{label}</span>:
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/app/components/primitives/DateField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import { Button } from "./Buttons";

const variants = {
small: {
fieldStyles: "h-5 text-sm rounded-sm px-0.5",
fieldStyles: "h-5 text-xs rounded-sm px-0.5",
nowButtonVariant: "tertiary/small" as const,
clearButtonVariant: "tertiary/small" as const,
},
medium: {
fieldStyles: "h-7 text-base rounded px-1",
fieldStyles: "h-7 text-sm rounded px-1",
nowButtonVariant: "tertiary/medium" as const,
clearButtonVariant: "minimal/medium" as const,
},
Expand Down
6 changes: 3 additions & 3 deletions apps/webapp/app/components/primitives/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import * as Ariakit from "@ariakit/react";
import { SelectProps as AriaSelectProps } from "@ariakit/react";
import { type SelectProps as AriaSelectProps } from "@ariakit/react";
import { SelectValue } from "@ariakit/react-core/select/select-value";
import { Link } from "@remix-run/react";
import * as React from "react";
import { Fragment, useMemo, useState } from "react";
import { ShortcutDefinition, useShortcutKeys } from "~/hooks/useShortcutKeys";
import { type ShortcutDefinition, useShortcutKeys } from "~/hooks/useShortcutKeys";
import { cn } from "~/utils/cn";
import { ShortcutKey } from "./ShortcutKey";
import { ChevronDown } from "lucide-react";
import { MatchSorterOptions, matchSorter } from "match-sorter";
import { type MatchSorterOptions, matchSorter } from "match-sorter";

const sizes = {
small: {
Expand Down
25 changes: 18 additions & 7 deletions apps/webapp/app/components/primitives/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
import * as React from "react";
import * as SwitchPrimitives from "@radix-ui/react-switch";
import { cn } from "~/utils/cn";
import { ShortcutDefinition, useShortcutKeys } from "~/hooks/useShortcutKeys";
import { type ShortcutDefinition, useShortcutKeys } from "~/hooks/useShortcutKeys";

const small = {
container:
"flex items-center h-[1.5rem] gap-x-1.5 rounded hover:bg-tertiary disabled:hover:bg-transparent pr-1 py-[0.1rem] pl-1.5 transition focus-custom disabled:hover:text-charcoal-400 disabled:opacity-50 text-charcoal-400 hover:text-charcoal-200 disabled:hover:cursor-not-allowed hover:cursor-pointer",
root: "h-3 w-6",
thumb: "size-2.5 data-[state=checked]:translate-x-2.5 data-[state=unchecked]:translate-x-0",
text: "text-xs text-text-dimmed",
};

const variations = {
large: {
Expand All @@ -12,12 +20,15 @@ const variations = {
thumb: "size-5 data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
text: "text-sm text-text-dimmed",
},
small: {
container:
"flex items-center h-[1.5rem] gap-x-1.5 rounded hover:bg-tertiary disabled:hover:bg-transparent pr-1 py-[0.1rem] pl-1.5 transition focus-custom disabled:hover:text-charcoal-400 disabled:opacity-50 text-charcoal-400 hover:text-charcoal-200 disabled:hover:cursor-not-allowed hover:cursor-pointer",
root: "h-3 w-6",
thumb: "size-2.5 data-[state=checked]:translate-x-2.5 data-[state=unchecked]:translate-x-0",
text: "text-xs text-text-dimmed",
small,
"tertiary/small": {
container: small.container,
root: cn(
small.root,
"group-data-[state=unchecked]:bg-charcoal-600 group-data-[state=unchecked]:group-hover:bg-charcoal-500/50"
),
thumb: small.thumb,
text: cn(small.text, "transition group-hover:text-text-bright"),
},
};

Expand Down
28 changes: 4 additions & 24 deletions apps/webapp/app/components/runs/v3/BatchFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,7 @@ import {
batchStatusTitle,
descriptionForBatchStatus,
} from "./BatchStatus";
import {
AppliedCustomDateRangeFilter,
AppliedPeriodFilter,
appliedSummary,
CreatedAtDropdown,
CustomDateRangeDropdown,
FilterMenuProvider,
} from "./SharedFilters";
import { TimeFilter, appliedSummary, FilterMenuProvider } from "./SharedFilters";

export const BatchStatus = z.enum(allBatchStatuses);

Expand All @@ -54,8 +47,8 @@ export const BatchListFilters = z.object({
(value) => (typeof value === "string" ? [value] : value),
BatchStatus.array().optional()
),
period: z.preprocess((value) => (value === "all" ? undefined : value), z.string().optional()),
id: z.string().optional(),
period: z.preprocess((value) => (value === "all" ? undefined : value), z.string().optional()),
from: z.coerce.number().optional(),
to: z.coerce.number().optional(),
});
Expand All @@ -69,16 +62,12 @@ type BatchFiltersProps = {
export function BatchFilters(props: BatchFiltersProps) {
const location = useOptimisticLocation();
const searchParams = new URLSearchParams(location.search);
const hasFilters =
searchParams.has("statuses") ||
searchParams.has("id") ||
searchParams.has("period") ||
searchParams.has("from") ||
searchParams.has("to");
const hasFilters = searchParams.has("statuses") || searchParams.has("id");

return (
<div className="flex flex-row flex-wrap items-center gap-1">
<FilterMenu {...props} />
<TimeFilter />
<AppliedFilters />
{hasFilters && (
<Form className="h-6">
Expand All @@ -101,8 +90,6 @@ const filterTypes = [
</div>
),
},
{ name: "created", title: "Created", icon: <CalendarIcon className="size-4" /> },
{ name: "daterange", title: "Custom date range", icon: <CalendarIcon className="size-4" /> },
{ name: "batch", title: "Batch ID", icon: <Squares2X2Icon className="size-4" /> },
] as const;

Expand Down Expand Up @@ -148,8 +135,6 @@ function AppliedFilters() {
return (
<>
<AppliedStatusFilter />
<AppliedPeriodFilter />
<AppliedCustomDateRangeFilter />
<AppliedBatchIdFilter />
</>
);
Expand All @@ -169,10 +154,6 @@ function Menu(props: MenuProps) {
return <MainMenu {...props} />;
case "statuses":
return <StatusDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
case "created":
return <CreatedAtDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
case "daterange":
return <CustomDateRangeDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
case "batch":
return <BatchIdDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
}
Expand All @@ -181,7 +162,6 @@ function Menu(props: MenuProps) {
function MainMenu({ searchValue, trigger, clearSearchValue, setFilterType }: MenuProps) {
const filtered = useMemo(() => {
return filterTypes.filter((item) => {
if (item.name === "daterange") return false;
return item.title.toLowerCase().includes(searchValue.toLowerCase());
});
}, [searchValue]);
Expand Down
Loading