Skip to content
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
30 changes: 13 additions & 17 deletions apps/webapp/app/components/navigation/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -886,23 +886,6 @@ export function SideMenu({
});
}

if (user.admin || user.isImpersonating || featureFlags.hasWebhooksAccess) {
staticSections.push({
id: "webhooks",
title: "Webhooks",
items: [
{
id: "webhook-deliveries",
name: "Deliveries",
icon: WebhookIcon,
activeIconColor: "text-webhooks",
to: v3WebhooksPath(organization, project, environment),
dataAction: "webhook-deliveries",
},
],
});
}

staticSections.push({
id: "deployments",
title: "Deployments",
Expand Down Expand Up @@ -1204,6 +1187,19 @@ export function SideMenu({
isCollapsed={isCollapsed}
yieldActiveToFavorite
/>
{(user.admin || user.isImpersonating || featureFlags.hasWebhooksAccess) && (
<SideMenuItem
name="Webhooks"
icon={WebhookIcon}
activeIconColor="text-webhooks"
inactiveIconColor="text-text-dimmed"
to={v3WebhooksPath(organization, project, environment)}
data-action="webhooks"
badge={<NewBadge />}
isCollapsed={isCollapsed}
yieldActiveToFavorite
/>
)}
</div>

{orderedSectionIds.map((sectionId) => {
Expand Down
3 changes: 3 additions & 0 deletions apps/webapp/app/components/navigation/favoritePages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { TasksIcon } from "~/assets/icons/TasksIcon";
import { UsageIcon } from "~/assets/icons/UsageIcon";
import { UserGroupIcon } from "~/assets/icons/UserGroupIcon";
import { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon";
import { WebhookIcon } from "~/assets/icons/WebhookIcon";
import { VercelLogo } from "~/components/integrations/VercelLogo";
import { useOptionalUser } from "~/hooks/useUser";
import { type FavoritePage } from "~/services/dashboardPreferences.server";
Expand Down Expand Up @@ -71,6 +72,7 @@ const FAVORITE_PAGE_ICONS: Record<
"task-agent": { icon: CubeSparkleIcon, activeColor: "text-agents" },
runs: { icon: RunsIcon, activeColor: "text-runs" },
sessions: { icon: AIChatIcon, activeColor: "text-sessions" },
webhooks: { icon: WebhookIcon, activeColor: "text-webhooks" },
prompts: { icon: AIPenIcon, activeColor: "text-aiPrompts" },
models: { icon: Box3DIcon, activeColor: "text-models" },
logs: { icon: LogsIcon, activeColor: "text-logs" },
Expand Down Expand Up @@ -213,6 +215,7 @@ const ENV_PAGE_META: Record<string, PageMeta> = {
"": { icon: "tasks", name: "Tasks", singular: "Task" },
runs: { icon: "runs", name: "Runs", singular: "Run" },
sessions: { icon: "sessions", name: "Sessions", singular: "Session" },
webhooks: { icon: "webhooks", name: "Webhook deliveries" },
prompts: { icon: "prompts", name: "Prompts", singular: "Prompt" },
models: { icon: "models", name: "Models", singular: "Model" },
logs: { icon: "logs", name: "Logs" },
Expand Down
1 change: 0 additions & 1 deletion apps/webapp/app/components/navigation/sideMenuTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export const SideMenuSectionIdSchema = z.enum([
"deployments",
"project-settings",
"tasks",
"webhooks",
]);

// Inferred type from the schema
Expand Down
24 changes: 21 additions & 3 deletions apps/webapp/app/components/primitives/CopyableText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function CopyableText({
asChild,
variant,
hideTooltip,
truncate,
}: {
value: string;
copyValue?: string;
Expand All @@ -24,6 +25,12 @@ export function CopyableText({
* fire Radix's global "one tooltip open at a time" close and dismiss the parent.
*/
hideTooltip?: boolean;
/**
* Ellipsise the value rather than letting it overflow its column. For unbreakable strings
* (hashes, opaque ids) that offer no wrap opportunity. The copy button moves into a reserved
* right gutter so it stays visible instead of sitting outside the column.
*/
truncate?: boolean;
}) {
const [isHovered, setIsHovered] = useState(false);
const { copy, copied } = useCopy(copyValue ?? value);
Expand Down Expand Up @@ -51,15 +58,26 @@ export function CopyableText({

return (
<span
className={cn("group relative inline-flex h-6 items-center", className)}
className={cn(
"group relative inline-flex h-6 items-center",
truncate && "max-w-full pr-7",
className
)}
onMouseLeave={() => setIsHovered(false)}
>
<span onMouseEnter={() => setIsHovered(true)}>{value}</span>
<span
className={cn(truncate && "min-w-0 truncate")}
onMouseEnter={() => setIsHovered(true)}
>
{value}
</span>
<span
onClick={copy}
onMouseDown={(e) => e.stopPropagation()}
className={cn(
"absolute -right-6 top-0 z-10 size-6 font-sans",
"absolute top-0 z-10 size-6 font-sans",
// Truncated values reserve a right gutter, so the button sits inside it
truncate ? "right-0" : "-right-6",
isHovered ? "flex" : "hidden"
)}
>
Expand Down
13 changes: 11 additions & 2 deletions apps/webapp/app/components/run/RunTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -483,18 +483,25 @@ export type RunTimelineLineProps = {
state?: TimelineEventState;
variant?: TimelineLineVariant;
style?: TimelineStyle;
/**
* Round the top of a thick ("normal") line. Needed when the line itself starts the thick bar,
* as in the delivery timeline, where nothing above it supplies a `start-cap-thick`. The run
* timeline always precedes its thick line with that cap, so it leaves this off.
*/
roundedTop?: boolean;
};

export function RunTimelineLine({
title,
state,
variant = "normal",
style = "normal",
roundedTop = false,
}: RunTimelineLineProps) {
return (
<div className="grid h-6 grid-cols-[1.125rem_1fr] gap-1 text-xs">
<div className="flex items-stretch justify-center">
<LineMarker state={state} variant={variant} style={style} />
<LineMarker state={state} variant={variant} style={style} roundedTop={roundedTop} />
</div>
<div className="flex items-center justify-between gap-3">
<span className="text-text-dimmed">{title}</span>
Expand All @@ -507,10 +514,12 @@ function LineMarker({
state,
variant,
style,
roundedTop = false,
}: {
state?: TimelineEventState;
variant: TimelineLineVariant;
style?: TimelineStyle;
roundedTop?: boolean;
}) {
let containerClass = "bg-text-dimmed";
switch (state) {
Expand All @@ -532,7 +541,7 @@ function LineMarker({
switch (variant) {
case "normal":
return (
<div className={cn("relative w-1.75", containerClass)}>
<div className={cn("relative w-1.75", roundedTop && "rounded-t-xs", containerClass)}>
{state === "inprogress" && (
<div
className="absolute inset-0 h-full w-full animate-tile-scroll opacity-30"
Expand Down
40 changes: 24 additions & 16 deletions apps/webapp/app/components/webhookDeliveries/v1/DeliveriesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useLocation, useNavigation } from "@remix-run/react";
import { AIChatIcon } from "~/assets/icons/AIChatIcon";
import { RunsIcon } from "~/assets/icons/RunsIcon";
import { WebhookIcon } from "~/assets/icons/WebhookIcon";
import { Badge } from "~/components/primitives/Badge";
import { DateTime } from "~/components/primitives/DateTime";
import { MiddleTruncate } from "~/components/primitives/MiddleTruncate";
import { Paragraph } from "~/components/primitives/Paragraph";
Expand Down Expand Up @@ -121,28 +122,26 @@ export function DeliveriesTable({
<TableCell to={webhookPath}>
{delivery.webhook ? (
<span className="flex items-center gap-x-1">
<WebhookIcon className="size-4 text-webhooks" />
<WebhookIcon className="size-4.5 min-w-4.5 text-webhooks" />
{delivery.webhook.slug}
</span>
) : (
<span className="text-text-dimmed">Unknown</span>
<span className="text-text-dimmed group-hover/table-row:text-text-bright">
Unknown
</span>
)}
</TableCell>
)}
<TableCell to={deliveryPath}>
<span className="flex items-center gap-1.5">
<span className="font-mono text-xs">{delivery.friendlyId}</span>
{delivery.isTest ? (
<span className="rounded-sm bg-charcoal-700 px-1 py-0.5 text-xxs font-semibold uppercase tracking-wide text-text-dimmed">
Test
</span>
) : null}
{delivery.isTest ? <Badge variant="extra-small">Test</Badge> : null}
</span>
</TableCell>
<TableCell to={deliveryPath}>
<DeliveryStatusBadge status={delivery.status} />
</TableCell>
<TableCell>
<TableCell to={deliveryPath}>
{delivery.externalDeliveryId ? (
<div className="w-[24ch]">
<MiddleTruncate
Expand All @@ -151,10 +150,13 @@ export function DeliveriesTable({
/>
</div>
) : (
<span className="text-text-dimmed">None</span>
<span className="text-text-dimmed group-hover/table-row:text-text-bright">
None
</span>
)}
</TableCell>
<TableCell to={sessionPath ?? runPath}>
{/* Falls back to the delivery so the whole row stays clickable when there is no target */}
<TableCell to={sessionPath ?? runPath ?? deliveryPath}>
{delivery.session ? (
<span className="flex items-center gap-x-1">
<AIChatIcon className="size-4 text-sessions" />
Expand All @@ -166,20 +168,24 @@ export function DeliveriesTable({
<span className="font-mono text-xs">{delivery.run.friendlyId}</span>
</span>
) : (
<span className="text-text-dimmed">None</span>
<span className="text-text-dimmed group-hover/table-row:text-text-bright">
None
</span>
)}
</TableCell>
<TableCell>
<TableCell to={deliveryPath}>
<DateTime date={delivery.createdAt} />
</TableCell>
<TableCell>
<TableCell to={deliveryPath}>
{delivery.processedAt ? (
<DateTime date={delivery.processedAt} />
) : (
<span className="text-text-dimmed">None</span>
<span className="text-text-dimmed group-hover/table-row:text-text-bright">
None
</span>
)}
</TableCell>
<TableCell>
<TableCell to={deliveryPath}>
{delivery.status === "FAILED" && delivery.errorMessage ? (
<SimpleTooltip
content={delivery.errorMessage}
Expand All @@ -190,7 +196,9 @@ export function DeliveriesTable({
}
/>
) : (
<span className="text-text-dimmed">None</span>
<span className="text-text-dimmed group-hover/table-row:text-text-bright">
None
</span>
)}
</TableCell>
<DeliveryActionsCell
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export function DeliveryTimeline({ delivery, runPath, sessionPath }: DeliveryTim
key={item.id}
state={item.state}
variant={item.variant}
// "Received" is a thin start-cap, so a thick line here begins the bar and has to
// round its own top. Thin ("light") lines, as in the FILTERED case, need nothing.
roundedTop={item.variant === "normal"}
title={
<span className="flex items-center gap-1.5">
{item.to ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,20 +155,13 @@ function Menu(props: MenuProps) {
}
}

function MainMenu({ searchValue, trigger, clearSearchValue, setFilterType }: MenuProps) {
const filtered = useMemo(() => {
return filterTypes.filter((item) =>
item.title.toLowerCase().includes(searchValue.toLowerCase())
);
}, [searchValue]);

function MainMenu({ trigger, clearSearchValue, setFilterType }: MenuProps) {
return (
<SelectProvider virtualFocus={true}>
{trigger}
<SelectPopover>
<ComboBox placeholder={"Filter by..."} shortcut={moreFiltersShortcut} value={searchValue} />
<SelectList>
{filtered.map((type, index) => (
{filterTypes.map((type, index) => (
<SelectButtonItem
key={type.name}
onClick={() => {
Expand All @@ -190,12 +183,10 @@ function MainMenu({ searchValue, trigger, clearSearchValue, setFilterType }: Men
function StatusDropdown({
trigger,
clearSearchValue,
searchValue,
onClose,
}: {
trigger: ReactNode;
clearSearchValue: () => void;
searchValue: string;
onClose?: () => void;
}) {
const { values, replace } = useSearchParams();
Expand All @@ -205,12 +196,6 @@ function StatusDropdown({
replace({ statuses: values, cursor: undefined, direction: undefined });
};

const filtered = useMemo(() => {
return deliveryStatuses.filter((item) =>
item.title.toLowerCase().includes(searchValue.toLowerCase())
);
}, [searchValue]);

return (
<SelectProvider value={values("statuses")} setValue={handleChange} virtualFocus={true}>
{trigger}
Expand All @@ -225,9 +210,8 @@ function StatusDropdown({
return true;
}}
>
<ComboBox placeholder={"Filter by status..."} value={searchValue} />
<SelectList>
{filtered.map((item, index) => (
{deliveryStatuses.map((item, index) => (
<SelectItem
key={item.value}
value={item.value}
Expand Down Expand Up @@ -264,7 +248,7 @@ function PermanentStatusFilter() {

return (
<FilterMenuProvider>
{(search, setSearch) => (
{(_search, setSearch) => (
<StatusDropdown
trigger={
<Ariakit.TooltipProvider timeout={200}>
Expand Down Expand Up @@ -309,7 +293,6 @@ function PermanentStatusFilter() {
</Ariakit.Tooltip>
</Ariakit.TooltipProvider>
}
searchValue={search}
clearSearchValue={() => setSearch("")}
/>
)}
Expand Down Expand Up @@ -536,7 +519,7 @@ function PermanentTestFilter() {
/>
) : (
<div className="flex h-6 items-center gap-1.5 rounded border border-charcoal-600 bg-secondary pl-1 pr-2 text-xs text-text-bright transition group-hover:border-charcoal-550 group-hover:bg-charcoal-600">
<BeakerIcon className="size-4 text-text-dimmed" />
<BeakerIcon className="size-4 text-text-bright" />
<span>Test</span>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
type WebhookDeliveryListItem,
} from "./WebhookDetailPresenter.server";

const DELIVERIES_PAGE_SIZE = 25;
const DELIVERIES_PAGE_SIZE = 60;
type Direction = "forward" | "backward";

export type WebhookDeliveriesListResult = {
Expand Down
Loading
Loading