-
+
Close
diff --git a/apps/webapp/app/components/primitives/SheetV3.tsx b/apps/webapp/app/components/primitives/SheetV3.tsx
index 2bac7e9cb9..bb205f8b42 100644
--- a/apps/webapp/app/components/primitives/SheetV3.tsx
+++ b/apps/webapp/app/components/primitives/SheetV3.tsx
@@ -1,9 +1,8 @@
+import { XMarkIcon } from "@heroicons/react/20/solid";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "~/utils/cn";
-import { Header2 } from "./Headers";
-import { NamedIcon } from "./NamedIcon";
import { ShortcutKey } from "./ShortcutKey";
const Sheet = SheetPrimitive.Root;
@@ -93,7 +92,7 @@ const SheetTitle = React.forwardRef<
{children}
-
+
Close
diff --git a/apps/webapp/app/components/primitives/Spinner.tsx b/apps/webapp/app/components/primitives/Spinner.tsx
index b9a6961236..a9182b46ce 100644
--- a/apps/webapp/app/components/primitives/Spinner.tsx
+++ b/apps/webapp/app/components/primitives/Spinner.tsx
@@ -72,3 +72,7 @@ export function ButtonSpinner() {
/>
);
}
+
+export function SpinnerWhite({ className }: { className?: string }) {
+ return
;
+}
diff --git a/apps/webapp/app/components/primitives/TextLink.tsx b/apps/webapp/app/components/primitives/TextLink.tsx
index e5c8e7d487..e4314d4b0f 100644
--- a/apps/webapp/app/components/primitives/TextLink.tsx
+++ b/apps/webapp/app/components/primitives/TextLink.tsx
@@ -1,6 +1,6 @@
import { Link } from "@remix-run/react";
-import { IconNamesOrString, NamedIcon } from "./NamedIcon";
import { cn } from "~/utils/cn";
+import { Icon, RenderIcon } from "./Icon";
const variations = {
primary:
@@ -13,7 +13,7 @@ type TextLinkProps = {
href?: string;
to?: string;
className?: string;
- trailingIcon?: IconNamesOrString;
+ trailingIcon?: RenderIcon;
trailingIconClassName?: string;
variant?: keyof typeof variations;
children: React.ReactNode;
@@ -33,16 +33,12 @@ export function TextLink({
return to ? (
{children}{" "}
- {trailingIcon && (
-
- )}
+ {trailingIcon &&
}
) : href ? (
{children}{" "}
- {trailingIcon && (
-
- )}
+ {trailingIcon && }
) : (
Need to define a path or href
diff --git a/apps/webapp/app/components/primitives/help-gradient.svg b/apps/webapp/app/components/primitives/help-gradient.svg
deleted file mode 100644
index 0b17ade449..0000000000
--- a/apps/webapp/app/components/primitives/help-gradient.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/apps/webapp/app/components/run/RunCard.tsx b/apps/webapp/app/components/run/RunCard.tsx
deleted file mode 100644
index 2dfcbf5f63..0000000000
--- a/apps/webapp/app/components/run/RunCard.tsx
+++ /dev/null
@@ -1,265 +0,0 @@
-import type { DisplayProperty, StyleName } from "@trigger.dev/core";
-import { formatDuration } from "@trigger.dev/core/v3";
-import { motion } from "framer-motion";
-import { HourglassIcon } from "lucide-react";
-import { ReactNode, useEffect, useState } from "react";
-import { CodeBlock } from "~/components/code/CodeBlock";
-import { Callout } from "~/components/primitives/Callout";
-import { LabelValueStack } from "~/components/primitives/LabelValueStack";
-import { NamedIcon } from "~/components/primitives/NamedIcon";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { cn } from "~/utils/cn";
-
-type RunPanelProps = {
- selected?: boolean;
- children: React.ReactNode;
- onClick?: () => void;
- className?: string;
- styleName?: StyleName;
-};
-
-export function RunPanel({
- selected = false,
- children,
- onClick,
- className,
- styleName = "normal",
-}: RunPanelProps) {
- return (
-
onClick && onClick()}
- >
- {children}
-
- );
-}
-
-type RunPanelHeaderProps = {
- icon: React.ReactNode;
- title: React.ReactNode;
- accessory?: React.ReactNode;
- styleName?: StyleName;
-};
-
-export function RunPanelHeader({
- icon,
- title,
- accessory,
- styleName = "normal",
-}: RunPanelHeaderProps) {
- return (
-
-
- {typeof icon === "string" ?
: icon}
- {typeof title === "string" ?
{title} : title}
-
-
{accessory}
-
- );
-}
-
-type RunPanelIconTitleProps = {
- icon?: string | null;
- title: string;
-};
-
-export function RunPanelIconTitle({ icon, title }: RunPanelIconTitleProps) {
- return (
-
- );
-}
-
-export function RunPanelBody({ children }: { children: React.ReactNode }) {
- return
{children}
;
-}
-
-const variantClasses: Record
= {
- log: "",
- error: "text-rose-500",
- warn: "text-yellow-500",
- info: "",
- debug: "",
-};
-
-export function RunPanelDescription({ text, variant }: { text: string; variant?: string }) {
- return (
-
- {text}
-
- );
-}
-
-export function RunPanelError({
- text,
- error,
- stackTrace,
-}: {
- text: string;
- error?: string;
- stackTrace?: string;
-}) {
- return (
-
-
- {text}
-
- {error && }
- {stackTrace && }
-
- );
-}
-
-export function RunPanelIconSection({
- children,
- className,
-}: {
- children: React.ReactNode;
- className?: string;
-}) {
- return {children}
;
-}
-
-export function RunPanelDivider() {
- return
;
-}
-
-export function RunPanelIconProperty({
- icon,
- label,
- value,
-}: {
- icon: ReactNode;
- label: string;
- value: ReactNode;
-}) {
- return (
-
-
- {typeof icon === "string" ? : icon}
-
-
-
- );
-}
-
-export function RunPanelProperties({
- properties,
- className,
- layout = "horizontal",
-}: {
- properties: DisplayProperty[];
- className?: string;
- layout?: "horizontal" | "vertical";
-}) {
- return (
-
- {properties.map(({ label, text, url }, index) => (
-
- ))}
-
- );
-}
-
-export function TaskSeparator({ depth }: { depth: number }) {
- return (
-
- );
-}
-
-const updateInterval = 100;
-
-export function UpdatingDuration({ start, end }: { start?: Date; end?: Date }) {
- const [now, setNow] = useState();
-
- useEffect(() => {
- if (end) return;
-
- const interval = setInterval(() => {
- setNow(new Date());
- }, updateInterval);
-
- return () => clearInterval(interval);
- }, [end]);
-
- return (
-
- {formatDuration(start, end || now, {
- style: "short",
- maxDecimalPoints: 0,
- })}
-
- );
-}
-
-export function UpdatingDelay({ delayUntil }: { delayUntil: Date }) {
- const [now, setNow] = useState();
-
- useEffect(() => {
- const interval = setInterval(() => {
- const date = new Date();
- if (date > delayUntil) {
- setNow(delayUntil);
- return;
- }
- setNow(date);
- }, updateInterval);
-
- return () => clearInterval(interval);
- }, [delayUntil]);
-
- return (
-
-
-
- }
- label="Delay finishes in"
- value={formatDuration(now, delayUntil, {
- style: "long",
- maxDecimalPoints: 0,
- })}
- />
- );
-}
diff --git a/apps/webapp/app/components/run/RunCompletedDetail.tsx b/apps/webapp/app/components/run/RunCompletedDetail.tsx
deleted file mode 100644
index f11a9bd563..0000000000
--- a/apps/webapp/app/components/run/RunCompletedDetail.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { CodeBlock } from "~/components/code/CodeBlock";
-import { DateTime } from "~/components/primitives/DateTime";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { RunStatusIcon, RunStatusLabel } from "~/components/runs/RunStatuses";
-import { MatchedRun } from "~/hooks/useRun";
-import {
- RunPanel,
- RunPanelBody,
- RunPanelDivider,
- RunPanelError,
- RunPanelHeader,
- RunPanelIconProperty,
- RunPanelIconSection,
-} from "./RunCard";
-import { formatDuration } from "@trigger.dev/core/v3";
-
-export function RunCompletedDetail({ run }: { run: MatchedRun }) {
- return (
-
- }
- title={
-
-
-
- }
- />
-
-
- {run.startedAt && (
- }
- />
- )}
- {run.completedAt && (
- }
- />
- )}
- {run.startedAt && run.completedAt && (
-
- )}
-
-
- {run.error && }
- {run.output ? (
-
- ) : (
- run.output === null && This run returned nothing
- )}
-
-
- );
-}
diff --git a/apps/webapp/app/components/run/RunOverview.tsx b/apps/webapp/app/components/run/RunOverview.tsx
deleted file mode 100644
index cdaf64f44f..0000000000
--- a/apps/webapp/app/components/run/RunOverview.tsx
+++ /dev/null
@@ -1,435 +0,0 @@
-import { conform, useForm } from "@conform-to/react";
-import { parse } from "@conform-to/zod";
-import { PlayIcon } from "@heroicons/react/20/solid";
-import { BoltIcon } from "@heroicons/react/24/solid";
-import {
- Form,
- Outlet,
- useActionData,
- useLocation,
- useNavigate,
- useNavigation,
-} from "@remix-run/react";
-import { RuntimeEnvironmentType, User } from "@trigger.dev/database";
-import { useMemo } from "react";
-import { usePathName } from "~/hooks/usePathName";
-import type { RunBasicStatus } from "~/models/jobRun.server";
-import { ViewRun } from "~/presenters/RunPresenter.server";
-import { cancelSchema } from "~/routes/resources.runs.$runId.cancel";
-import { schema } from "~/routes/resources.runs.$runId.rerun";
-import { cn } from "~/utils/cn";
-import { runCompletedPath, runTaskPath, runTriggerPath } from "~/utils/pathBuilder";
-import { CodeBlock } from "../code/CodeBlock";
-import { EnvironmentLabel } from "../environments/EnvironmentLabel";
-import { PageBody, PageContainer } from "../layout/AppLayout";
-import { Button } from "../primitives/Buttons";
-import { Callout } from "../primitives/Callout";
-import { DateTime } from "../primitives/DateTime";
-import { Header2 } from "../primitives/Headers";
-import { Icon } from "../primitives/Icon";
-import { NamedIcon } from "../primitives/NamedIcon";
-import {
- PageAccessories,
- NavBar,
- PageInfoGroup,
- PageInfoProperty,
- PageInfoRow,
- PageTitle,
-} from "../primitives/PageHeader";
-import { Paragraph } from "../primitives/Paragraph";
-import { Popover, PopoverContent, PopoverTrigger } from "../primitives/Popover";
-import { RunStatusIcon, RunStatusLabel, runStatusTitle } from "../runs/RunStatuses";
-import {
- RunPanel,
- RunPanelBody,
- RunPanelDivider,
- RunPanelError,
- RunPanelHeader,
- RunPanelIconProperty,
- RunPanelIconSection,
- RunPanelProperties,
-} from "./RunCard";
-import { TaskCard } from "./TaskCard";
-import { TaskCardSkeleton } from "./TaskCardSkeleton";
-import { formatDuration, formatDurationMilliseconds } from "@trigger.dev/core/v3";
-
-type RunOverviewProps = {
- run: ViewRun;
- trigger: {
- icon: string;
- title: string;
- };
- showRerun: boolean;
- paths: {
- back: string;
- run: string;
- runsPath: string;
- };
- currentUser: User;
-};
-
-const taskPattern = /\/tasks\/(.*)/;
-
-export function RunOverview({ run, trigger, showRerun, paths, currentUser }: RunOverviewProps) {
- const navigate = useNavigate();
- const pathName = usePathName();
-
- const selectedId = useMemo(() => {
- if (pathName.endsWith("/completed")) {
- return "completed";
- }
-
- if (pathName.endsWith("/trigger")) {
- return "trigger";
- }
-
- const taskMatch = pathName.match(taskPattern);
- const taskId = taskMatch ? taskMatch[1] : undefined;
- if (taskId) {
- return taskId;
- }
- }, [pathName]);
-
- const usernameForEnv =
- currentUser.id !== run.environment.userId ? run.environment.userName : undefined;
-
- return (
-
-
-
-
- {run.isTest && (
-
-
- Test run
-
- )}
- {showRerun && run.isFinished && (
-
- )}
- {!run.isFinished && }
-
-
-
-
-
-
- }
- label={"Status"}
- value={runStatusTitle(run.status)}
- />
- : "Not started yet"}
- />
-
- }
- />
-
- }
- label={"Execution Time"}
- value={formatDurationMilliseconds(run.executionDuration, { style: "short" })}
- />
- }
- label={"Execution Count"}
- value={<>{run.executionCount}>}
- />
-
-
-
- RUN ID: {run.id}
-
-
-
-
-
-
-
- {run.status === "SUCCESS" &&
- (run.tasks.length === 0 || run.tasks.every((t) => t.noop)) && (
-
- This Run completed but it did not use any Tasks – this can cause unpredictable
- results. Read the docs to view the solution.
-
- )}
- Trigger
- navigate(runTriggerPath(paths.run))}
- >
-
-
-
-
-
-
-
- Tasks
-
- {run.tasks.length > 0 ? (
- run.tasks.map((task, index) => {
- const isLast = index === run.tasks.length - 1;
-
- return (
- {
- navigate(runTaskPath(paths.run, taskId));
- }}
- isLast={isLast}
- depth={0}
- {...task}
- />
- );
- })
- ) : (
-
- )}
-
- {(run.basicStatus === "COMPLETED" || run.basicStatus === "FAILED") && (
-
-
Run Summary
-
navigate(runCompletedPath(paths.run))}
- >
- }
- title={
-
-
-
- }
- />
-
-
- {run.startedAt && (
- }
- />
- )}
- {run.completedAt && (
- }
- />
- )}
- {run.startedAt && run.completedAt && (
-
- )}
-
-
- {run.error && (
-
- )}
- {run.output ? (
-
- ) : (
- run.output === null && (
-
- This Run returned nothing.
-
- )
- )}
-
-
-
- )}
-
-
- {/* Detail view */}
-
- Details
- {selectedId ? : Select a task or trigger }
-
-
-
-
- );
-}
-
-function BlankTasks({ status }: { status: RunBasicStatus }) {
- switch (status) {
- default:
- case "COMPLETED":
- return There were no tasks for this run. ;
- case "FAILED":
- return No tasks were run. ;
- case "WAITING":
- case "PENDING":
- case "RUNNING":
- return (
-
-
- Waiting for tasks…
-
-
-
- );
- }
-}
-
-function RerunPopover({
- runId,
- runPath,
- runsPath,
- environmentType,
- status,
-}: {
- runId: string;
- runPath: string;
- runsPath: string;
- environmentType: RuntimeEnvironmentType;
- status: RunBasicStatus;
-}) {
- const lastSubmission = useActionData();
-
- const [form, { successRedirect, failureRedirect }] = useForm({
- id: "rerun",
- // TODO: type this
- lastSubmission: lastSubmission as any,
- onValidate({ formData }) {
- return parse(formData, { schema });
- },
- });
-
- return (
-
-
-
- Rerun Job
-
-
-
-
-
-
- );
-}
-
-export function CancelRun({ runId }: { runId: string }) {
- const lastSubmission = useActionData();
- const location = useLocation();
- const navigation = useNavigation();
-
- const [form, { redirectUrl }] = useForm({
- id: "cancel-run",
- // TODO: type this
- lastSubmission: lastSubmission as any,
- onValidate({ formData }) {
- return parse(formData, { schema: cancelSchema });
- },
- });
-
- const isLoading = navigation.state === "submitting" && navigation.formData !== undefined;
-
- return (
-
- );
-}
diff --git a/apps/webapp/app/components/run/TaskAttemptStatus.tsx b/apps/webapp/app/components/run/TaskAttemptStatus.tsx
deleted file mode 100644
index df87d16297..0000000000
--- a/apps/webapp/app/components/run/TaskAttemptStatus.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { CheckCircleIcon, ClockIcon, XCircleIcon } from "@heroicons/react/24/solid";
-import type { TaskAttemptStatus } from "@trigger.dev/database";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { Spinner } from "~/components/primitives/Spinner";
-import { cn } from "~/utils/cn";
-
-type TaskAttemptStatusProps = {
- status: TaskAttemptStatus;
- className?: string;
-};
-
-export function TaskAttemptStatusLabel({ status }: { status: TaskAttemptStatus }) {
- return (
-
-
-
- {taskAttemptStatusTitle(status)}
-
-
- );
-}
-
-export function TaskAttemptStatusIcon({ status, className }: TaskAttemptStatusProps) {
- switch (status) {
- case "COMPLETED":
- return ;
- case "PENDING":
- return ;
- case "STARTED":
- return ;
- case "ERRORED":
- return ;
- }
-}
-
-function taskAttemptStatusClassNameColor(status: TaskAttemptStatus): string {
- switch (status) {
- case "COMPLETED":
- return "text-green-500";
- case "PENDING":
- return "text-charcoal-400";
- case "STARTED":
- return "text-blue-500";
- case "ERRORED":
- return "text-rose-500";
- }
-}
-
-function taskAttemptStatusTitle(status: TaskAttemptStatus): string {
- switch (status) {
- case "COMPLETED":
- return "Complete";
- case "PENDING":
- return "Scheduled";
- case "STARTED":
- return "Running";
- case "ERRORED":
- return "Error";
- }
-}
diff --git a/apps/webapp/app/components/run/TaskCard.tsx b/apps/webapp/app/components/run/TaskCard.tsx
deleted file mode 100644
index a0ccc9e3ac..0000000000
--- a/apps/webapp/app/components/run/TaskCard.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import { ChevronDownIcon, Square2StackIcon } from "@heroicons/react/24/solid";
-import { AnimatePresence, motion } from "framer-motion";
-import { Fragment, useState } from "react";
-import simplur from "simplur";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { ViewTask } from "~/presenters/RunPresenter.server";
-import { cn } from "~/utils/cn";
-import {
- RunPanel,
- RunPanelBody,
- RunPanelDescription,
- RunPanelError,
- RunPanelHeader,
- RunPanelIconProperty,
- RunPanelIconSection,
- RunPanelIconTitle,
- RunPanelProperties,
- TaskSeparator,
- UpdatingDelay,
- UpdatingDuration,
-} from "./RunCard";
-import { TaskStatusIcon } from "./TaskStatus";
-import { formatDuration } from "@trigger.dev/core/v3";
-
-type TaskCardProps = ViewTask & {
- selectedId?: string;
- selectedTask: (id: string) => void;
- isLast: boolean;
- depth: number;
-};
-
-export function TaskCard({
- selectedId,
- selectedTask,
- isLast,
- depth,
- id,
- style,
- status,
- icon,
- name,
- startedAt,
- completedAt,
- description,
- displayKey,
- connection,
- properties,
- subtasks,
- error,
- delayUntil,
-}: TaskCardProps) {
- const [expanded, setExpanded] = useState(false);
- const isSelected = id === selectedId;
-
- return (
-
-
-
selectedTask(id)} styleName={style?.style}>
-
- )
- }
- title={status === "COMPLETED" ? name : }
- accessory={
-
-
-
- }
- styleName={style?.style}
- />
-
- {error && }
- {description && }
-
- {displayKey && }
- {delayUntil && !completedAt && (
- <>
-
- >
- )}
- {delayUntil && completedAt && (
-
- )}
- {connection && (
-
- )}
-
- {properties.length > 0 && (
-
- )}
-
- {subtasks && subtasks.length > 0 && (
- setExpanded((c) => !c)}
- >
-
-
-
- {simplur`${expanded ? "Hide" : "Show"} ${subtasks.length} subtask[|s]`}
-
-
-
-
-
-
- )}
-
-
- {(!isLast || expanded) && }
-
- {subtasks &&
- subtasks.length > 0 &&
- expanded &&
- subtasks.map((subtask, index) => (
-
-
-
-
-
- ))}
-
- );
-}
diff --git a/apps/webapp/app/components/run/TaskCardSkeleton.tsx b/apps/webapp/app/components/run/TaskCardSkeleton.tsx
deleted file mode 100644
index e3708eb87b..0000000000
--- a/apps/webapp/app/components/run/TaskCardSkeleton.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { RunPanel, RunPanelBody, RunPanelHeader } from "./RunCard";
-
-export function TaskCardSkeleton() {
- return (
-
- }
- />
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/components/run/TaskDetail.tsx b/apps/webapp/app/components/run/TaskDetail.tsx
deleted file mode 100644
index d09df85c0c..0000000000
--- a/apps/webapp/app/components/run/TaskDetail.tsx
+++ /dev/null
@@ -1,170 +0,0 @@
-import {
- RunPanel,
- RunPanelBody,
- RunPanelDescription,
- RunPanelDivider,
- RunPanelHeader,
- RunPanelIconProperty,
- RunPanelIconSection,
- RunPanelIconTitle,
- RunPanelProperties,
- UpdatingDelay,
- UpdatingDuration,
-} from "./RunCard";
-import { sensitiveDataReplacer } from "~/services/sensitiveDataReplacer";
-import { cn } from "~/utils/cn";
-import { CodeBlock } from "../code/CodeBlock";
-import { DateTime } from "../primitives/DateTime";
-import { Header3 } from "../primitives/Headers";
-import { Paragraph } from "../primitives/Paragraph";
-import {
- TableHeader,
- TableRow,
- TableHeaderCell,
- TableBody,
- TableCell,
- Table,
-} from "../primitives/Table";
-import { TaskAttemptStatusLabel } from "./TaskAttemptStatus";
-import { TaskStatusIcon } from "./TaskStatus";
-import { ClientOnly } from "remix-utils/client-only";
-import { Spinner } from "../primitives/Spinner";
-import type { DetailedTask } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route";
-import { formatDuration } from "@trigger.dev/core/v3";
-
-export function TaskDetail({ task }: { task: DetailedTask }) {
- const {
- name,
- description,
- icon,
- status,
- params,
- properties,
- output,
- outputIsUndefined,
- style,
- attempts,
- noop,
- } = task;
-
- const startedAt = task.startedAt ? new Date(task.startedAt) : undefined;
- const completedAt = task.completedAt ? new Date(task.completedAt) : undefined;
- const delayUntil = task.delayUntil ? new Date(task.delayUntil) : undefined;
-
- return (
-
- }
- title={ }
- accessory={
-
-
-
- }
- />
-
-
- {startedAt && (
- }
- />
- )}
- {completedAt && (
- }
- />
- )}
- {delayUntil && !completedAt && (
- <>
- }
- />
-
- >
- )}
- {delayUntil && completedAt && (
-
- )}
-
-
- {description && }
- {properties.length > 0 && (
-
- Properties
-
-
- )}
-
- {attempts.length > 1 && (
-
-
Retries
-
-
-
- Attempt
- Status
- Date
- Error
-
-
-
- {attempts.map((attempt) => (
-
- {attempt.number}
-
-
-
-
-
-
- {attempt.error}
-
- ))}
-
-
-
- )}
-
-
-
Input
- {params ? (
-
- ) : (
-
No input
- )}
-
- {!noop && (
-
-
Output
- {output && !outputIsUndefined ? (
-
}>
- {() =>
}
-
- ) : (
-
No output
- )}
-
- )}
-
-
- );
-}
diff --git a/apps/webapp/app/components/run/TaskStatus.tsx b/apps/webapp/app/components/run/TaskStatus.tsx
deleted file mode 100644
index 38a4703a72..0000000000
--- a/apps/webapp/app/components/run/TaskStatus.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { TaskStatus } from "@trigger.dev/core";
-import {
- CheckCircleIcon,
- CheckIcon,
- ClockIcon,
- NoSymbolIcon,
- XCircleIcon,
-} from "@heroicons/react/24/solid";
-import { Spinner } from "~/components/primitives/Spinner";
-import { cn } from "~/utils/cn";
-
-type TaskStatusIconProps = {
- status: TaskStatus;
- className: string;
- minimal?: boolean;
-};
-
-export function TaskStatusIcon({ status, className, minimal = false }: TaskStatusIconProps) {
- switch (status) {
- case "COMPLETED":
- return minimal ? (
-
- ) : (
-
- );
- case "PENDING":
- return ;
- case "WAITING":
- return ;
- case "RUNNING":
- return ;
- case "ERRORED":
- return ;
- case "CANCELED":
- return ;
- }
-}
-
-function taskStatusClassNameColor(status: TaskStatus): string {
- switch (status) {
- case "COMPLETED":
- return "text-green-500";
- case "PENDING":
- return "text-charcoal-500";
- case "RUNNING":
- return "text-blue-500";
- case "WAITING":
- return "text-blue-500";
- case "ERRORED":
- return "text-rose-500";
- case "CANCELED":
- return "text-charcoal-500";
- }
-}
diff --git a/apps/webapp/app/components/run/TriggerDetail.tsx b/apps/webapp/app/components/run/TriggerDetail.tsx
deleted file mode 100644
index 4c7a393936..0000000000
--- a/apps/webapp/app/components/run/TriggerDetail.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { DetailedEvent } from "~/presenters/TriggerDetailsPresenter.server";
-import { CodeBlock } from "../code/CodeBlock";
-import { DateTime } from "../primitives/DateTime";
-import { Header3 } from "../primitives/Headers";
-import {
- RunPanel,
- RunPanelBody,
- RunPanelDivider,
- RunPanelHeader,
- RunPanelIconProperty,
- RunPanelIconSection,
- RunPanelProperties,
-} from "./RunCard";
-import type { DisplayProperty } from "@trigger.dev/core";
-
-export function TriggerDetail({
- trigger,
- event,
- properties,
-}: {
- trigger: DetailedEvent;
- event: {
- title: string;
- icon: string;
- };
- properties: DisplayProperty[];
-}) {
- const { id, name, payload, context, timestamp, deliveredAt } = trigger;
-
- return (
-
-
-
-
- }
- />
- {deliveredAt && (
- }
- />
- )}
-
-
- {trigger.externalAccount && (
-
- )}
-
-
-
- {properties.length > 0 && (
-
- Properties
-
-
- )}
-
Payload
-
-
Context
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/components/runs/RunFilters.tsx b/apps/webapp/app/components/runs/RunFilters.tsx
deleted file mode 100644
index b70d2e9d4e..0000000000
--- a/apps/webapp/app/components/runs/RunFilters.tsx
+++ /dev/null
@@ -1,233 +0,0 @@
-import {
- CheckCircleIcon,
- ClockIcon,
- ExclamationTriangleIcon,
- NoSymbolIcon,
- PauseCircleIcon,
- TrashIcon,
- XCircleIcon,
- XMarkIcon,
-} from "@heroicons/react/20/solid";
-import { useNavigate } from "@remix-run/react";
-import { useOptimisticLocation } from "~/hooks/useOptimisticLocation";
-import { cn } from "~/utils/cn";
-import { EnvironmentLabel } from "../environments/EnvironmentLabel";
-import { Paragraph } from "../primitives/Paragraph";
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "../primitives/SimpleSelect";
-import { Spinner } from "../primitives/Spinner";
-import {
- FilterableEnvironment,
- FilterableStatus,
- RunListSearchSchema,
- environmentKeys,
- statusKeys,
-} from "./RunStatuses";
-import { TimeFrameFilter } from "./TimeFrameFilter";
-import { Button } from "../primitives/Buttons";
-import { useCallback } from "react";
-import assertNever from "assert-never";
-
-export function RunsFilters() {
- const navigate = useNavigate();
- const location = useOptimisticLocation();
- const searchParams = new URLSearchParams(location.search);
- const { environment, status, from, to } = RunListSearchSchema.parse(
- Object.fromEntries(searchParams.entries())
- );
-
- const handleFilterChange = useCallback((filterType: string, value: string | undefined) => {
- if (value) {
- searchParams.set(filterType, value);
- } else {
- searchParams.delete(filterType);
- }
- searchParams.delete("cursor");
- searchParams.delete("direction");
- navigate(`${location.pathname}?${searchParams.toString()}`);
- }, []);
-
- const handleStatusChange = useCallback((value: FilterableStatus | "ALL") => {
- handleFilterChange("status", value === "ALL" ? undefined : value);
- }, []);
-
- const handleEnvironmentChange = useCallback((value: FilterableEnvironment | "ALL") => {
- handleFilterChange("environment", value === "ALL" ? undefined : value);
- }, []);
-
- const handleTimeFrameChange = useCallback((range: { from?: number; to?: number }) => {
- if (range.from) {
- searchParams.set("from", range.from.toString());
- } else {
- searchParams.delete("from");
- }
-
- if (range.to) {
- searchParams.set("to", range.to.toString());
- } else {
- searchParams.delete("to");
- }
-
- searchParams.delete("cursor");
- searchParams.delete("direction");
- navigate(`${location.pathname}?${searchParams.toString()}`);
- }, []);
-
- const clearFilters = useCallback(() => {
- searchParams.delete("status");
- searchParams.delete("environment");
- searchParams.delete("from");
- searchParams.delete("to");
- navigate(`${location.pathname}?${searchParams.toString()}`);
- }, []);
-
- return (
-
-
-
-
-
-
-
-
-
- All environments
-
-
- {environmentKeys.map((env) => (
-
-
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
-
- All statuses
-
-
- {statusKeys.map((status) => (
-
- {
-
-
-
-
- }
-
- ))}
-
-
-
-
-
-
-
clearFilters()} LeadingIcon={TrashIcon} />
-
- );
-}
-
-export function FilterStatusLabel({ status }: { status: FilterableStatus }) {
- return {filterStatusTitle(status)} ;
-}
-
-export function FilterStatusIcon({
- status,
- className,
-}: {
- status: FilterableStatus;
- className: string;
-}) {
- switch (status) {
- case "COMPLETED":
- return ;
- case "WAITING":
- return ;
- case "QUEUED":
- return ;
- case "IN_PROGRESS":
- return ;
- case "TIMEDOUT":
- return (
-
- );
- case "CANCELED":
- return ;
- case "FAILED":
- return ;
- default: {
- assertNever(status);
- }
- }
-}
-
-export function filterStatusTitle(status: FilterableStatus): string {
- switch (status) {
- case "QUEUED":
- return "Queued";
- case "IN_PROGRESS":
- return "In progress";
- case "WAITING":
- return "Waiting";
- case "COMPLETED":
- return "Completed";
- case "FAILED":
- return "Failed";
- case "CANCELED":
- return "Canceled";
- case "TIMEDOUT":
- return "Timed out";
- default: {
- assertNever(status);
- }
- }
-}
-
-export function filterStatusClassNameColor(status: FilterableStatus): string {
- switch (status) {
- case "QUEUED":
- return "text-charcoal-500";
- case "IN_PROGRESS":
- return "text-blue-500";
- case "WAITING":
- return "text-blue-500";
- case "COMPLETED":
- return "text-green-500";
- case "FAILED":
- return "text-rose-500";
- case "CANCELED":
- return "text-charcoal-500";
- case "TIMEDOUT":
- return "text-amber-300";
- default: {
- assertNever(status);
- }
- }
-}
diff --git a/apps/webapp/app/components/runs/RunStatuses.tsx b/apps/webapp/app/components/runs/RunStatuses.tsx
deleted file mode 100644
index 8ef4d39828..0000000000
--- a/apps/webapp/app/components/runs/RunStatuses.tsx
+++ /dev/null
@@ -1,177 +0,0 @@
-import { NoSymbolIcon } from "@heroicons/react/20/solid";
-import {
- CheckCircleIcon,
- ClockIcon,
- ExclamationTriangleIcon,
- PauseCircleIcon,
- WrenchIcon,
- XCircleIcon,
-} from "@heroicons/react/24/solid";
-import type { JobRunStatus } from "@trigger.dev/database";
-import { cn } from "~/utils/cn";
-import { Spinner } from "../primitives/Spinner";
-import { z } from "zod";
-import assertNever from "assert-never";
-
-export function RunStatus({ status }: { status: JobRunStatus }) {
- return (
-
-
-
-
- );
-}
-
-export function RunStatusLabel({ status }: { status: JobRunStatus }) {
- return {runStatusTitle(status)} ;
-}
-
-export function RunStatusIcon({ status, className }: { status: JobRunStatus; className: string }) {
- switch (status) {
- case "SUCCESS":
- return ;
- case "PENDING":
- case "WAITING_TO_CONTINUE":
- return ;
- case "QUEUED":
- case "WAITING_TO_EXECUTE":
- return ;
- case "PREPROCESSING":
- case "STARTED":
- case "EXECUTING":
- return ;
- case "TIMED_OUT":
- return ;
- case "UNRESOLVED_AUTH":
- case "FAILURE":
- case "ABORTED":
- case "INVALID_PAYLOAD":
- return ;
- case "WAITING_ON_CONNECTIONS":
- return ;
- case "CANCELED":
- return ;
- default: {
- assertNever(status);
- }
- }
-}
-
-export function runStatusTitle(status: JobRunStatus): string {
- switch (status) {
- case "SUCCESS":
- return "Completed";
- case "PENDING":
- return "Not started";
- case "STARTED":
- return "In progress";
- case "QUEUED":
- case "WAITING_TO_EXECUTE":
- return "Queued";
- case "EXECUTING":
- return "Executing";
- case "WAITING_TO_CONTINUE":
- return "Waiting";
- case "FAILURE":
- return "Failed";
- case "TIMED_OUT":
- return "Timed out";
- case "WAITING_ON_CONNECTIONS":
- return "Waiting on connections";
- case "ABORTED":
- return "Aborted";
- case "PREPROCESSING":
- return "Preprocessing";
- case "CANCELED":
- return "Canceled";
- case "UNRESOLVED_AUTH":
- return "Unresolved auth";
- case "INVALID_PAYLOAD":
- return "Invalid payload";
- default: {
- assertNever(status);
- }
- }
-}
-
-export function runStatusClassNameColor(status: JobRunStatus): string {
- switch (status) {
- case "SUCCESS":
- return "text-green-500";
- case "PENDING":
- return "text-charcoal-500";
- case "STARTED":
- case "EXECUTING":
- case "WAITING_TO_CONTINUE":
- case "WAITING_TO_EXECUTE":
- return "text-blue-500";
- case "QUEUED":
- return "text-charcoal-500";
- case "FAILURE":
- case "UNRESOLVED_AUTH":
- case "INVALID_PAYLOAD":
- return "text-rose-500";
- case "TIMED_OUT":
- return "text-amber-300";
- case "WAITING_ON_CONNECTIONS":
- return "text-amber-300";
- case "ABORTED":
- return "text-rose-500";
- case "PREPROCESSING":
- return "text-blue-500";
- case "CANCELED":
- return "text-charcoal-500";
- default: {
- assertNever(status);
- }
- }
-}
-
-export const DirectionSchema = z.union([z.literal("forward"), z.literal("backward")]);
-export type Direction = z.infer;
-
-export const FilterableStatus = z.union([
- z.literal("QUEUED"),
- z.literal("IN_PROGRESS"),
- z.literal("WAITING"),
- z.literal("COMPLETED"),
- z.literal("FAILED"),
- z.literal("TIMEDOUT"),
- z.literal("CANCELED"),
-]);
-export type FilterableStatus = z.infer;
-
-export const FilterableEnvironment = z.union([
- z.literal("DEVELOPMENT"),
- z.literal("STAGING"),
- z.literal("PRODUCTION"),
-]);
-export type FilterableEnvironment = z.infer;
-export const environmentKeys: FilterableEnvironment[] = ["DEVELOPMENT", "STAGING", "PRODUCTION"];
-
-export const RunListSearchSchema = z.object({
- cursor: z.string().optional(),
- direction: DirectionSchema.optional(),
- status: FilterableStatus.optional(),
- environment: FilterableEnvironment.optional(),
- from: z
- .string()
- .transform((value) => parseInt(value))
- .optional(),
- to: z
- .string()
- .transform((value) => parseInt(value))
- .optional(),
-});
-
-export const filterableStatuses: Record = {
- QUEUED: ["QUEUED", "WAITING_TO_EXECUTE", "PENDING", "WAITING_ON_CONNECTIONS"],
- IN_PROGRESS: ["STARTED", "EXECUTING", "PREPROCESSING"],
- WAITING: ["WAITING_TO_CONTINUE"],
- COMPLETED: ["SUCCESS"],
- FAILED: ["FAILURE", "UNRESOLVED_AUTH", "INVALID_PAYLOAD", "ABORTED"],
- TIMEDOUT: ["TIMED_OUT"],
- CANCELED: ["CANCELED"],
-};
-
-export const statusKeys: FilterableStatus[] = Object.keys(filterableStatuses) as FilterableStatus[];
diff --git a/apps/webapp/app/components/runs/RunsTable.tsx b/apps/webapp/app/components/runs/RunsTable.tsx
deleted file mode 100644
index c194da9532..0000000000
--- a/apps/webapp/app/components/runs/RunsTable.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import { StopIcon } from "@heroicons/react/24/outline";
-import { CheckIcon } from "@heroicons/react/24/solid";
-import { JobRunStatus, RuntimeEnvironmentType, User } from "@trigger.dev/database";
-import { EnvironmentLabel } from "../environments/EnvironmentLabel";
-import { DateTime } from "../primitives/DateTime";
-import { Paragraph } from "../primitives/Paragraph";
-import { Spinner } from "../primitives/Spinner";
-import {
- Table,
- TableBlankRow,
- TableBody,
- TableCell,
- TableCellChevron,
- TableHeader,
- TableHeaderCell,
- TableRow,
-} from "../primitives/Table";
-import { RunStatus } from "./RunStatuses";
-import { formatDuration, formatDurationMilliseconds } from "@trigger.dev/core/v3";
-
-type RunTableItem = {
- id: string;
- number: number | null;
- environment: {
- type: RuntimeEnvironmentType;
- userId?: string;
- userName?: string;
- };
- job: { title: string; slug: string };
- status: JobRunStatus;
- startedAt: Date | null;
- completedAt: Date | null;
- createdAt: Date | null;
- executionDuration: number;
- version: string;
- isTest: boolean;
-};
-
-type RunsTableProps = {
- total: number;
- hasFilters: boolean;
- showJob?: boolean;
- runs: RunTableItem[];
- isLoading?: boolean;
- runsParentPath: string;
- currentUser: User;
-};
-
-export function RunsTable({
- total,
- hasFilters,
- runs,
- isLoading = false,
- showJob = false,
- runsParentPath,
- currentUser,
-}: RunsTableProps) {
- return (
-
-
-
- Run
- {showJob && Job }
- Env
- Status
- Started
- Duration
- Exec Time
- Test
- Version
- Created at
-
- Go to page
-
-
-
-
- {total === 0 && !hasFilters ? (
-
- {!isLoading && }
-
- ) : runs.length === 0 ? (
-
- {!isLoading && }
-
- ) : (
- runs.map((run) => {
- const path = showJob
- ? `${runsParentPath}/jobs/${run.job.slug}/runs/${run.id}/trigger`
- : `${runsParentPath}/${run.id}/trigger`;
- const usernameForEnv =
- currentUser.id !== run.environment.userId ? run.environment.userName : undefined;
- return (
-
-
- {typeof run.number === "number" ? `#${run.number}` : "-"}
-
- {showJob && {run.job.slug} }
-
-
-
-
-
-
-
- {run.startedAt ? : "–"}
-
-
- {formatDuration(run.startedAt, run.completedAt, {
- style: "short",
- })}
-
-
- {formatDurationMilliseconds(run.executionDuration, {
- style: "short",
- })}
-
-
- {run.isTest ? (
-
- ) : (
-
- )}
-
- {run.version}
-
- {run.createdAt ? : "–"}
-
-
-
- );
- })
- )}
- {isLoading && (
-
- Loading…
-
- )}
-
-
- );
-}
-
-function NoRuns({ title }: { title: string }) {
- return (
-
- );
-}
diff --git a/apps/webapp/app/components/runs/TimeFrameFilter.tsx b/apps/webapp/app/components/runs/TimeFrameFilter.tsx
deleted file mode 100644
index 87e6fe0f95..0000000000
--- a/apps/webapp/app/components/runs/TimeFrameFilter.tsx
+++ /dev/null
@@ -1,237 +0,0 @@
-import { ChevronDownIcon } from "lucide-react";
-import { useCallback, useState } from "react";
-import { cn } from "~/utils/cn";
-import { Button } from "../primitives/Buttons";
-import { ClientTabs, ClientTabsContent, ClientTabsWithUnderline } from "../primitives/ClientTabs";
-import { DateField } from "../primitives/DateField";
-import { formatDateTime } from "../primitives/DateTime";
-import { Paragraph } from "../primitives/Paragraph";
-import { Popover, PopoverContent, PopoverTrigger } from "../primitives/Popover";
-import { Label } from "../primitives/Label";
-
-type RunTimeFrameFilterProps = {
- from?: number;
- to?: number;
- onRangeChanged: (range: { from?: number; to?: number }) => void;
-};
-
-type Mode = "absolute" | "relative";
-
-export function TimeFrameFilter({ from, to, onRangeChanged }: RunTimeFrameFilterProps) {
- const [activeTab, setActiveTab] = useState("absolute");
- const [isOpen, setIsOpen] = useState(false);
- const [relativeTimeSeconds, setRelativeTimeSeconds] = useState();
-
- const fromDate = from ? new Date(from) : undefined;
- const toDate = to ? new Date(to) : undefined;
-
- const relativeTimeFrameChanged = useCallback((value: number) => {
- const to = new Date().getTime();
- const from = to - value;
- onRangeChanged({ from, to });
- setRelativeTimeSeconds(value);
- }, []);
-
- const absoluteTimeFrameChanged = useCallback(({ from, to }: { from?: Date; to?: Date }) => {
- setRelativeTimeSeconds(undefined);
- const fromTime = from?.getTime();
- const toTime = to?.getTime();
- onRangeChanged({ from: fromTime, to: toTime });
- }, []);
-
- return (
- setIsOpen(open)} open={isOpen} modal>
-
-
-
- {title(from, to, relativeTimeSeconds)}
-
-
-
-
-
-
- setActiveTab(v as Mode)}
- className="p-1"
- >
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-function title(
- from: number | undefined,
- to: number | undefined,
- relativeTimeSeconds: number | undefined
-): string {
- if (!from && !to) {
- return "All time periods";
- }
-
- if (relativeTimeSeconds !== undefined) {
- return timeFrameValues.find((t) => t.value === relativeTimeSeconds)?.label ?? "Timeframe";
- }
-
- let fromString = from ? formatDateTime(new Date(from), "UTC", ["en-US"], false, true) : undefined;
- let toString = to ? formatDateTime(new Date(to), "UTC", ["en-US"], false, true) : undefined;
- if (from && !to) {
- return `From ${fromString} (UTC)`;
- }
-
- if (!from && to) {
- return `To ${toString} (UTC)`;
- }
-
- return `${fromString} - ${toString} (UTC)`;
-}
-
-function RelativeTimeFrame({
- value,
- onValueChange,
-}: {
- value?: number;
- onValueChange: (value: number) => void;
-}) {
- return (
-
- {timeFrameValues.map((timeframe) => (
- {
- onValueChange(timeframe.value);
- }}
- >
- {timeframe.label}
-
- ))}
-
- );
-}
-
-const timeFrameValues = [
- {
- label: "5 mins",
- value: 5 * 60 * 1000,
- },
- {
- label: "15 mins",
- value: 15 * 60 * 1000,
- },
- {
- label: "30 mins",
- value: 30 * 60 * 1000,
- },
- {
- label: "1 hour",
- value: 60 * 60 * 1000,
- },
- {
- label: "3 hours",
- value: 3 * 60 * 60 * 1000,
- },
- {
- label: "6 hours",
- value: 6 * 60 * 60 * 1000,
- },
- {
- label: "1 day",
- value: 24 * 60 * 60 * 1000,
- },
- {
- label: "3 days",
- value: 3 * 24 * 60 * 60 * 1000,
- },
- {
- label: "7 days",
- value: 7 * 24 * 60 * 60 * 1000,
- },
- {
- label: "10 days",
- value: 10 * 24 * 60 * 60 * 1000,
- },
- {
- label: "14 days",
- value: 14 * 24 * 60 * 60 * 1000,
- },
- {
- label: "30 days",
- value: 30 * 24 * 60 * 60 * 1000,
- },
-];
-
-export type RelativeTimeFrameItem = (typeof timeFrameValues)[number];
-
-export function AbsoluteTimeFrame({
- from,
- to,
- onValueChange,
-}: {
- from?: Date;
- to?: Date;
- onValueChange: (value: { from?: Date; to?: Date }) => void;
-}) {
- return (
-
-
-
- From (UTC)
- {
- onValueChange({ from: value, to: to });
- }}
- granularity="second"
- showNowButton
- showClearButton
- utc
- />
-
-
- To (UTC)
- {
- onValueChange({ from: from, to: value });
- }}
- granularity="second"
- showNowButton
- showClearButton
- utc
- />
-
-
-
- );
-}
diff --git a/apps/webapp/app/components/runs/WebhookDeliveryRunsTable.tsx b/apps/webapp/app/components/runs/WebhookDeliveryRunsTable.tsx
deleted file mode 100644
index 7b97fcd022..0000000000
--- a/apps/webapp/app/components/runs/WebhookDeliveryRunsTable.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import { StopIcon } from "@heroicons/react/24/outline";
-import { CheckIcon } from "@heroicons/react/24/solid";
-import { RuntimeEnvironmentType } from "@trigger.dev/database";
-import { EnvironmentLabel } from "../environments/EnvironmentLabel";
-import { DateTime } from "../primitives/DateTime";
-import { Paragraph } from "../primitives/Paragraph";
-import { Spinner } from "../primitives/Spinner";
-import {
- Table,
- TableBlankRow,
- TableBody,
- TableCell,
- TableHeader,
- TableHeaderCell,
- TableRow,
-} from "../primitives/Table";
-import { RunStatus } from "./RunStatuses";
-import { formatDuration } from "@trigger.dev/core/v3";
-
-type RunTableItem = {
- id: string;
- number: number;
- environment: {
- type: RuntimeEnvironmentType;
- };
- error: string | null;
- createdAt: Date | null;
- deliveredAt: Date | null;
- verified: boolean;
-};
-
-type RunsTableProps = {
- total: number;
- hasFilters: boolean;
- runs: RunTableItem[];
- isLoading?: boolean;
- runsParentPath: string;
-};
-
-export function WebhookDeliveryRunsTable({
- total,
- hasFilters,
- runs,
- isLoading = false,
- runsParentPath,
-}: RunsTableProps) {
- return (
-
-
-
- Run
- Env
- Status
- Last Error
- Started
- Duration
- Verified
- Created at
-
-
-
- {total === 0 && !hasFilters ? (
-
-
-
- ) : runs.length === 0 ? (
-
-
-
- ) : (
- runs.map((run) => {
- return (
-
- #{run.number}
-
-
-
-
-
-
- {run.error?.slice(0, 30) ?? "–"}
- {run.createdAt ? : "–"}
-
- {formatDuration(run.createdAt, run.deliveredAt, {
- style: "short",
- })}
-
-
- {run.verified ? (
-
- ) : (
-
- )}
-
- {run.createdAt ? : "–"}
-
- );
- })
- )}
- {isLoading && (
-
- Loading…
-
- )}
-
-
- );
-}
-function NoRuns({ title }: { title: string }) {
- return (
-
- );
-}
diff --git a/apps/webapp/app/components/runs/v3/CancelRunDialog.tsx b/apps/webapp/app/components/runs/v3/CancelRunDialog.tsx
index 2d0439402f..facff746c5 100644
--- a/apps/webapp/app/components/runs/v3/CancelRunDialog.tsx
+++ b/apps/webapp/app/components/runs/v3/CancelRunDialog.tsx
@@ -1,16 +1,11 @@
-import { StopCircleIcon } from "@heroicons/react/20/solid";
import { NoSymbolIcon } from "@heroicons/react/24/solid";
import { DialogClose } from "@radix-ui/react-dialog";
-import { Form, useFetcher, useNavigation } from "@remix-run/react";
+import { Form, useNavigation } from "@remix-run/react";
import { Button } from "~/components/primitives/Buttons";
-import {
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
-} from "~/components/primitives/Dialog";
+import { DialogContent, DialogHeader } from "~/components/primitives/Dialog";
import { FormButtons } from "~/components/primitives/FormButtons";
import { Paragraph } from "~/components/primitives/Paragraph";
+import { SpinnerWhite } from "~/components/primitives/Spinner";
type CancelRunDialogProps = {
runFriendlyId: string;
@@ -38,7 +33,7 @@ export function CancelRunDialog({ runFriendlyId, redirectPath }: CancelRunDialog
name="redirectUrl"
value={redirectPath}
variant="danger/medium"
- LeadingIcon={isLoading ? "spinner-white" : NoSymbolIcon}
+ LeadingIcon={isLoading ? SpinnerWhite : NoSymbolIcon}
disabled={isLoading}
shortcut={{ modifiers: ["mod"], key: "enter" }}
>
diff --git a/apps/webapp/app/components/runs/v3/CheckBatchCompletionDialog.tsx b/apps/webapp/app/components/runs/v3/CheckBatchCompletionDialog.tsx
index 10df63a4de..1f5f07bbf7 100644
--- a/apps/webapp/app/components/runs/v3/CheckBatchCompletionDialog.tsx
+++ b/apps/webapp/app/components/runs/v3/CheckBatchCompletionDialog.tsx
@@ -4,6 +4,7 @@ import { Button } from "~/components/primitives/Buttons";
import { DialogContent, DialogHeader } from "~/components/primitives/Dialog";
import { FormButtons } from "~/components/primitives/FormButtons";
import { Paragraph } from "~/components/primitives/Paragraph";
+import { SpinnerWhite } from "~/components/primitives/Spinner";
type CheckBatchCompletionDialogProps = {
batchId: string;
@@ -37,7 +38,7 @@ export function CheckBatchCompletionDialog({
name="redirectUrl"
value={redirectPath}
variant="primary/medium"
- LeadingIcon={isLoading ? "spinner-white" : undefined}
+ LeadingIcon={isLoading ? SpinnerWhite : undefined}
disabled={isLoading}
shortcut={{ modifiers: ["mod"], key: "enter" }}
>
diff --git a/apps/webapp/app/components/runs/v3/RetryDeploymentIndexingDialog.tsx b/apps/webapp/app/components/runs/v3/RetryDeploymentIndexingDialog.tsx
index 6c1ca5fe92..8eb85f9946 100644
--- a/apps/webapp/app/components/runs/v3/RetryDeploymentIndexingDialog.tsx
+++ b/apps/webapp/app/components/runs/v3/RetryDeploymentIndexingDialog.tsx
@@ -8,6 +8,7 @@ import {
DialogFooter,
DialogHeader,
} from "~/components/primitives/Dialog";
+import { SpinnerWhite } from "~/components/primitives/Spinner";
type RetryDeploymentIndexingDialogProps = {
projectId: string;
@@ -46,7 +47,7 @@ export function RetryDeploymentIndexingDialog({
name="redirectUrl"
value={redirectPath}
variant="primary/medium"
- LeadingIcon={isLoading ? "spinner-white" : ArrowPathIcon}
+ LeadingIcon={isLoading ? SpinnerWhite : ArrowPathIcon}
disabled={isLoading}
shortcut={{ modifiers: ["mod"], key: "enter" }}
>
diff --git a/apps/webapp/app/components/runs/v3/RollbackDeploymentDialog.tsx b/apps/webapp/app/components/runs/v3/RollbackDeploymentDialog.tsx
index 76ae56e155..50df478098 100644
--- a/apps/webapp/app/components/runs/v3/RollbackDeploymentDialog.tsx
+++ b/apps/webapp/app/components/runs/v3/RollbackDeploymentDialog.tsx
@@ -8,6 +8,7 @@ import {
DialogFooter,
DialogHeader,
} from "~/components/primitives/Dialog";
+import { SpinnerWhite } from "~/components/primitives/Spinner";
type RollbackDeploymentDialogProps = {
projectId: string;
@@ -46,7 +47,7 @@ export function RollbackDeploymentDialog({
name="redirectUrl"
value={redirectPath}
variant="primary/medium"
- LeadingIcon={isLoading ? "spinner-white" : ArrowPathIcon}
+ LeadingIcon={isLoading ? SpinnerWhite : ArrowPathIcon}
disabled={isLoading}
shortcut={{ modifiers: ["mod"], key: "enter" }}
>
@@ -88,7 +89,7 @@ export function PromoteDeploymentDialog({
name="redirectUrl"
value={redirectPath}
variant="primary/medium"
- LeadingIcon={isLoading ? "spinner-white" : ArrowPathIcon}
+ LeadingIcon={isLoading ? SpinnerWhite : ArrowPathIcon}
disabled={isLoading}
shortcut={{ modifiers: ["mod"], key: "enter" }}
>
diff --git a/apps/webapp/app/components/runs/v3/RunIcon.tsx b/apps/webapp/app/components/runs/v3/RunIcon.tsx
index 57f1b05dbe..0e20333c97 100644
--- a/apps/webapp/app/components/runs/v3/RunIcon.tsx
+++ b/apps/webapp/app/components/runs/v3/RunIcon.tsx
@@ -7,9 +7,10 @@ import {
} from "@heroicons/react/20/solid";
import { AttemptIcon } from "~/assets/icons/AttemptIcon";
import { TaskIcon } from "~/assets/icons/TaskIcon";
-import { TaskCachedIcon } from "~/assets/icons/TaskCachedIcon";
-import { NamedIcon } from "~/components/primitives/NamedIcon";
import { cn } from "~/utils/cn";
+import { tablerIcons } from "~/utils/tablerIcons";
+import tablerSpritePath from "~/components/primitives/tabler-sprite.svg";
+import { TaskCachedIcon } from "~/assets/icons/TaskCachedIcon";
import { PauseIcon } from "~/assets/icons/PauseIcon";
type TaskIconProps = {
@@ -29,13 +30,16 @@ export function RunIcon({ name, className, spanName }: TaskIconProps) {
const spanNameIcon = spanNameIcons.find(({ matcher }) => matcher.test(spanName));
if (spanNameIcon) {
- return (
- }
- />
- );
+ if (tablerIcons.has("tabler-" + spanNameIcon.iconName)) {
+ return ;
+ } else if (
+ spanNameIcon.iconName.startsWith("tabler-") &&
+ tablerIcons.has(spanNameIcon.iconName)
+ ) {
+ return ;
+ }
+
+ ;
}
if (!name) return ;
@@ -68,11 +72,13 @@ export function RunIcon({ name, className, spanName }: TaskIconProps) {
return ;
}
+ return ;
+}
+
+function TablerIcon({ name, className }: { name: string; className?: string }) {
return (
- }
- />
+
+
+
);
}
diff --git a/apps/webapp/app/components/runs/v3/ScheduleFilters.tsx b/apps/webapp/app/components/runs/v3/ScheduleFilters.tsx
index 7cbb182e76..4e686bc7b6 100644
--- a/apps/webapp/app/components/runs/v3/ScheduleFilters.tsx
+++ b/apps/webapp/app/components/runs/v3/ScheduleFilters.tsx
@@ -1,4 +1,4 @@
-import { XMarkIcon } from "@heroicons/react/20/solid";
+import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/20/solid";
import { useNavigate } from "@remix-run/react";
import { type RuntimeEnvironment } from "@trigger.dev/database";
import { useCallback } from "react";
@@ -97,7 +97,7 @@ export function ScheduleFilters({ possibleEnvironments, possibleTasks }: Schedul
{
process.exit(1);
});
-const sqsEventConsumer = singleton("sqsEventConsumer", getSharedSqsEventConsumer);
-
singleton("RunEngineEventBusHandlers", registerRunEngineEventBusHandlers);
export { apiRateLimiter } from "./services/apiRateLimit.server";
diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts
index 45118358c3..84e3c52b91 100644
--- a/apps/webapp/app/env.server.ts
+++ b/apps/webapp/app/env.server.ts
@@ -42,7 +42,6 @@ const EnvironmentSchema = z.object({
TELEMETRY_TRIGGER_API_KEY: z.string().optional(),
TELEMETRY_TRIGGER_API_URL: z.string().optional(),
TRIGGER_TELEMETRY_DISABLED: z.string().optional(),
- HIGHLIGHT_PROJECT_ID: z.string().optional(),
AUTH_GITHUB_CLIENT_ID: z.string().optional(),
AUTH_GITHUB_CLIENT_SECRET: z.string().optional(),
EMAIL_TRANSPORT: z.enum(["resend", "smtp", "aws-ses"]).optional(),
diff --git a/apps/webapp/app/hooks/useFilterJobs.ts b/apps/webapp/app/hooks/useFilterJobs.ts
deleted file mode 100644
index 9d220952e8..0000000000
--- a/apps/webapp/app/hooks/useFilterJobs.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { ProjectJob } from "~/presenters/JobListPresenter.server";
-import { useTextFilter } from "./useTextFilter";
-import { useToggleFilter } from "./useToggleFilter";
-
-export function useFilterJobs(jobs: ProjectJob[], onlyActiveJobs = false) {
- const toggleFilterRes = useToggleFilter({
- items: jobs,
- filter: (job, onlyActiveJobs) => {
- if (onlyActiveJobs && job.status !== "ACTIVE") {
- return false;
- }
- return true;
- },
- defaultValue: onlyActiveJobs,
- });
-
- const textFilterRes = useTextFilter({
- items: toggleFilterRes.filteredItems,
- filter: (job, text) => {
- if (job.slug.toLowerCase().includes(text.toLowerCase())) return true;
- if (job.title.toLowerCase().includes(text.toLowerCase())) return true;
- if (job.event.title.toLowerCase().includes(text.toLowerCase())) return true;
- if (
- job.integrations.some((integration) =>
- integration.title.toLowerCase().includes(text.toLowerCase())
- )
- )
- return true;
- if (
- job.properties &&
- job.properties.some((property) => property.text.toLowerCase().includes(text.toLowerCase()))
- )
- return true;
-
- return false;
- },
- });
-
- return {
- filteredItems: textFilterRes.filteredItems,
- filterText: textFilterRes.filterText,
- setFilterText: textFilterRes.setFilterText,
- onlyActiveJobs: toggleFilterRes.isToggleActive,
- setOnlyActiveJobs: toggleFilterRes.setToggleActive,
- };
-}
diff --git a/apps/webapp/app/hooks/useHighlight.ts b/apps/webapp/app/hooks/useHighlight.ts
deleted file mode 100644
index 99a796101b..0000000000
--- a/apps/webapp/app/hooks/useHighlight.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { H } from "highlight.run";
-import { useUserChanged } from "./useUser";
-
-export function useHighlight() {
- useUserChanged((user) => {
- if (!user) {
- return;
- }
-
- H.identify(user.id, {
- email: user.email,
- });
- });
-}
diff --git a/apps/webapp/app/hooks/useIntegrationClient.tsx b/apps/webapp/app/hooks/useIntegrationClient.tsx
deleted file mode 100644
index bfe9fad244..0000000000
--- a/apps/webapp/app/hooks/useIntegrationClient.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { UIMatch } from "@remix-run/react";
-import { UseDataFunctionReturn } from "remix-typedjson";
-import invariant from "tiny-invariant";
-import type { loader } from "~/routes/_app.orgs.$organizationSlug.integrations_.$clientParam/route";
-import { useTypedMatchesData } from "./useTypedMatchData";
-
-export type MatchedClient = UseDataFunctionReturn["client"];
-
-export function useOptionalIntegrationClient(matches?: UIMatch[]) {
- const routeMatch = useTypedMatchesData({
- id: "routes/_app.orgs.$organizationSlug.integrations_.$clientParam",
- matches,
- });
-
- return routeMatch?.client;
-}
-
-export function useIntegrationClient(matches?: UIMatch[]) {
- const integration = useOptionalIntegrationClient(matches);
- invariant(integration, "Integration must be defined");
- return integration;
-}
diff --git a/apps/webapp/app/hooks/useJob.tsx b/apps/webapp/app/hooks/useJob.tsx
deleted file mode 100644
index 7064e8f30e..0000000000
--- a/apps/webapp/app/hooks/useJob.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { UseDataFunctionReturn } from "remix-typedjson";
-import invariant from "tiny-invariant";
-import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route";
-import { useChanged } from "./useChanged";
-import { UIMatch } from "@remix-run/react";
-import { useTypedMatchesData } from "./useTypedMatchData";
-
-export type MatchedJob = UseDataFunctionReturn["job"];
-
-export const jobMatchId =
- "routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam";
-
-export function useOptionalJob(matches?: UIMatch[]) {
- const routeMatch = useTypedMatchesData({
- id: jobMatchId,
- matches,
- });
-
- if (!routeMatch) {
- return undefined;
- }
-
- return routeMatch.job;
-}
-
-export function useJob(matches?: UIMatch[]) {
- const job = useOptionalJob(matches);
- invariant(job, "Job must be defined");
- return job;
-}
-
-export const useJobChanged = (action: (org: MatchedJob | undefined) => void) => {
- useChanged(useOptionalJob, action);
-};
diff --git a/apps/webapp/app/hooks/usePostHog.ts b/apps/webapp/app/hooks/usePostHog.ts
index 81c73baabf..99e2597dfe 100644
--- a/apps/webapp/app/hooks/usePostHog.ts
+++ b/apps/webapp/app/hooks/usePostHog.ts
@@ -4,7 +4,6 @@ import { useEffect, useRef } from "react";
import { useOrganizationChanged } from "./useOrganizations";
import { useOptionalUser, useUserChanged } from "./useUser";
import { useProjectChanged } from "./useProject";
-import { useJobChanged } from "./useJob";
export const usePostHog = (apiKey?: string, logging = false, debug = false): void => {
const postHogInitialized = useRef(false);
@@ -63,14 +62,6 @@ export const usePostHog = (apiKey?: string, logging = false, debug = false): voi
}
});
- useJobChanged((job) => {
- if (postHogInitialized.current === false) return;
- if (job) {
- if (logging) console.log(`Grouping by job`, job);
- posthog.group("job", job.id);
- }
- });
-
//page view
useEffect(() => {
if (postHogInitialized.current === false) return;
diff --git a/apps/webapp/app/hooks/useProjectSetupComplete.ts b/apps/webapp/app/hooks/useProjectSetupComplete.ts
deleted file mode 100644
index cbfb78b5cd..0000000000
--- a/apps/webapp/app/hooks/useProjectSetupComplete.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { useEffect } from "react";
-import { projectPath, projectStreamingPath } from "~/utils/pathBuilder";
-import { useProject } from "./useProject";
-import { useOrganization } from "./useOrganizations";
-import { useNavigate } from "@remix-run/react";
-import { useEventSource } from "./useEventSource";
-
-export function useProjectSetupComplete() {
- const project = useProject();
- const organization = useOrganization();
- const navigate = useNavigate();
- const events = useEventSource(projectStreamingPath(project.id), {
- event: "message",
- });
-
- useEffect(() => {
- if (events !== null) {
- // This uses https://www.npmjs.com/package/canvas-confetti
- if ("confetti" in window && typeof window.confetti !== "undefined") {
- const duration = 3.5 * 1000;
- const animationEnd = Date.now() + duration;
- const defaults = {
- startVelocity: 30,
- spread: 360,
- ticks: 60,
- zIndex: 0,
- colors: [
- "#E7FF52",
- "#41FF54",
- "rgb(245 158 11)",
- "rgb(22 163 74)",
- "rgb(37 99 235)",
- "rgb(67 56 202)",
- "rgb(219 39 119)",
- "rgb(225 29 72)",
- "rgb(217 70 239)",
- ],
- };
- function randomInRange(min: number, max: number): number {
- return Math.random() * (max - min) + min;
- }
- // @ts-ignore
- const interval = setInterval(function () {
- const timeLeft = animationEnd - Date.now();
-
- if (timeLeft <= 0) {
- return clearInterval(interval);
- }
-
- const particleCount = 60 * (timeLeft / duration);
- // since particles fall down, start a bit higher than random
- // @ts-ignore
- window.confetti(
- Object.assign({}, defaults, {
- particleCount,
- origin: { x: randomInRange(0.1, 0.4), y: Math.random() - 0.2 },
- })
- );
- // @ts-ignore
- window.confetti(
- Object.assign({}, defaults, {
- particleCount,
- origin: { x: randomInRange(0.6, 0.9), y: Math.random() - 0.2 },
- })
- );
- }, 250);
- }
-
- navigate(projectPath(organization, project));
- }
- // WARNING Don't put the revalidator in the useEffect deps array or bad things will happen
- }, [events]); // eslint-disable-line react-hooks/exhaustive-deps
-}
diff --git a/apps/webapp/app/hooks/useRun.ts b/apps/webapp/app/hooks/useRun.ts
deleted file mode 100644
index 40c2516c96..0000000000
--- a/apps/webapp/app/hooks/useRun.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { UIMatch } from "@remix-run/react";
-import { UseDataFunctionReturn } from "remix-typedjson";
-import invariant from "tiny-invariant";
-import type { loader as runLoader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route";
-import { useOptionalProject } from "./useProject";
-import { useTypedMatchesData } from "./useTypedMatchData";
-
-export type MatchedRun = UseDataFunctionReturn["run"];
-
-export function useOptionalRun(matches?: UIMatch[]) {
- const project = useOptionalProject(matches);
- const routeMatch = useTypedMatchesData({
- id: "routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam",
- matches,
- });
-
- if (!project || !routeMatch || !routeMatch.run) {
- return undefined;
- }
-
- return routeMatch.run;
-}
-
-export function useRun(matches?: UIMatch[]) {
- const run = useOptionalRun(matches);
- invariant(run, "Run must be present");
- return run;
-}
diff --git a/apps/webapp/app/models/endpoint.server.ts b/apps/webapp/app/models/endpoint.server.ts
deleted file mode 100644
index 56f4925855..0000000000
--- a/apps/webapp/app/models/endpoint.server.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { VERCEL_RESPONSE_TIMEOUT_STATUS_CODES } from "~/consts";
-import { prisma } from "~/db.server";
-import { Prettify } from "~/lib.es5";
-
-export type ExtendedEndpoint = Prettify>>;
-
-export async function findEndpoint(id: string) {
- return await prisma.endpoint.findUniqueOrThrow({
- where: {
- id,
- },
- include: {
- environment: {
- include: {
- project: true,
- organization: true,
- },
- },
- },
- });
-}
-
-export function detectResponseIsTimeout(rawBody: string, response?: Response) {
- if (!response) {
- return false;
- }
-
- return (
- isResponseVercelTimeout(response) ||
- isResponseCloudfrontTimeout(response) ||
- isResponseDenoDeployTimeout(rawBody, response) ||
- isResponseCloudflareTimeout(rawBody, response)
- );
-}
-
-function isResponseCloudflareTimeout(rawBody: string, response: Response) {
- return (
- response.status === 503 &&
- rawBody.includes("Worker exceeded resource limits") &&
- typeof response.headers.get("cf-ray") === "string"
- );
-}
-
-function isResponseVercelTimeout(response: Response) {
- return (
- VERCEL_RESPONSE_TIMEOUT_STATUS_CODES.includes(response.status) ||
- response.headers.get("x-vercel-error") === "FUNCTION_INVOCATION_TIMEOUT"
- );
-}
-
-function isResponseDenoDeployTimeout(rawBody: string, response: Response) {
- return response.status === 502 && rawBody.includes("TIME_LIMIT");
-}
-
-function isResponseCloudfrontTimeout(response: Response) {
- return response.status === 504 && typeof response.headers.get("x-amz-cf-id") === "string";
-}
diff --git a/apps/webapp/app/models/eventDispatcher.server.ts b/apps/webapp/app/models/eventDispatcher.server.ts
deleted file mode 100644
index 70de2ac6af..0000000000
--- a/apps/webapp/app/models/eventDispatcher.server.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { z } from "zod";
-
-export const JobVersionDispatchableSchema = z.object({
- type: z.literal("JOB_VERSION"),
- id: z.string(),
-});
-
-export const DynamicTriggerDispatchableSchema = z.object({
- type: z.literal("DYNAMIC_TRIGGER"),
- id: z.string(),
-});
-
-export const EphemeralDispatchableSchema = z.object({
- type: z.literal("EPHEMERAL"),
- url: z.string(),
-});
-
-export const DispatchableSchema = z.discriminatedUnion("type", [
- JobVersionDispatchableSchema,
- DynamicTriggerDispatchableSchema,
- EphemeralDispatchableSchema,
-]);
diff --git a/apps/webapp/app/models/job.server.ts b/apps/webapp/app/models/job.server.ts
deleted file mode 100644
index 66cc310ec4..0000000000
--- a/apps/webapp/app/models/job.server.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { prisma } from "~/db.server";
-export type { Job, JobRunStatus } from "@trigger.dev/database";
-
-export function findJobByParams({
- userId,
- slug,
- projectSlug,
- organizationSlug,
-}: {
- userId: string;
- slug: string;
- projectSlug: string;
- organizationSlug: string;
-}) {
- //just the very basic info because we already fetched it for the Jobs list
- return prisma.job.findFirst({
- select: { id: true, title: true },
- where: {
- slug,
- project: { slug: projectSlug },
- organization: { slug: organizationSlug, members: { some: { userId } } },
- },
- });
-}
diff --git a/apps/webapp/app/models/jobRun.server.ts b/apps/webapp/app/models/jobRun.server.ts
deleted file mode 100644
index 6b5ea0bc90..0000000000
--- a/apps/webapp/app/models/jobRun.server.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import type { JobRun, JobRunStatus } from "@trigger.dev/database";
-
-const COMPLETED_STATUSES: Array = [
- "CANCELED",
- "ABORTED",
- "SUCCESS",
- "TIMED_OUT",
- "INVALID_PAYLOAD",
- "FAILURE",
- "UNRESOLVED_AUTH",
-];
-
-export function isRunCompleted(status: JobRunStatus) {
- return COMPLETED_STATUSES.includes(status);
-}
-
-export type RunBasicStatus = "WAITING" | "PENDING" | "RUNNING" | "COMPLETED" | "FAILED";
-
-export function runBasicStatus(status: JobRunStatus): RunBasicStatus {
- switch (status) {
- case "WAITING_ON_CONNECTIONS":
- case "QUEUED":
- case "PREPROCESSING":
- case "PENDING":
- return "PENDING";
- case "STARTED":
- case "EXECUTING":
- case "WAITING_TO_CONTINUE":
- case "WAITING_TO_EXECUTE":
- return "RUNNING";
- case "FAILURE":
- case "TIMED_OUT":
- case "UNRESOLVED_AUTH":
- case "CANCELED":
- case "ABORTED":
- case "INVALID_PAYLOAD":
- return "FAILED";
- case "SUCCESS":
- return "COMPLETED";
- default: {
- const _exhaustiveCheck: never = status;
- throw new Error(`Non-exhaustive match for value: ${status}`);
- }
- }
-}
-
-export function runOriginalStatus(status: JobRunStatus) {
- switch (status) {
- case "EXECUTING":
- case "WAITING_TO_CONTINUE":
- case "WAITING_TO_EXECUTE":
- return "STARTED";
- default:
- return status;
- }
-}
diff --git a/apps/webapp/app/models/runConnection.server.ts b/apps/webapp/app/models/runConnection.server.ts
deleted file mode 100644
index 732781ecc7..0000000000
--- a/apps/webapp/app/models/runConnection.server.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import type { Integration, RunConnection } from "@trigger.dev/database";
-import type { ConnectionAuth } from "@trigger.dev/core";
-import type { ConnectionWithSecretReference } from "~/services/externalApis/integrationAuthRepository.server";
-import { integrationAuthRepository } from "~/services/externalApis/integrationAuthRepository.server";
-
-export type ResolvableRunConnection = RunConnection & {
- integration: Integration;
- connection: ConnectionWithSecretReference | null;
-};
-
-export async function resolveRunConnections(
- connections: Array
-): Promise<{ auth: Record; success: boolean }> {
- let allResolved = true;
-
- const result: Record = {};
-
- for (const connection of connections) {
- if (connection.integration.authSource !== "HOSTED") {
- continue;
- }
-
- const auth = await resolveRunConnection(connection);
-
- if (!auth) {
- allResolved = false;
- continue;
- }
-
- result[connection.key] = auth;
- }
-
- return { auth: result, success: allResolved };
-}
-
-export async function resolveRunConnection(
- connection: ResolvableRunConnection
-): Promise {
- if (!connection.connection) {
- return;
- }
-
- const response = await integrationAuthRepository.getCredentials(connection.connection);
-
- if (!response) {
- return;
- }
-
- return {
- type: "oauth2",
- scopes: response.scopes,
- accessToken: response.accessToken,
- };
-}
-
-export async function resolveApiConnection(
- connection?: ConnectionWithSecretReference
-): Promise {
- if (!connection) {
- return;
- }
-
- const response = await integrationAuthRepository.getCredentials(connection);
-
- if (!response) {
- return;
- }
-
- return {
- type: "oauth2",
- scopes: response.scopes,
- accessToken: response.accessToken,
- };
-}
diff --git a/apps/webapp/app/models/sourceConnection.server.ts b/apps/webapp/app/models/sourceConnection.server.ts
deleted file mode 100644
index 8ca6aec116..0000000000
--- a/apps/webapp/app/models/sourceConnection.server.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import { ExternalAccount, Integration, TriggerSource } from "@trigger.dev/database";
-import { ConnectionAuth } from "@trigger.dev/core";
-import { PrismaClientOrTransaction } from "~/db.server";
-import { integrationAuthRepository } from "~/services/externalApis/integrationAuthRepository.server";
-import { logger } from "~/services/logger.server";
-
-type ResolvableTriggerSource = TriggerSource & {
- integration: Integration;
- externalAccount: ExternalAccount | null;
-};
-
-export async function resolveSourceConnection(
- tx: PrismaClientOrTransaction,
- source: ResolvableTriggerSource
-): Promise {
- if (source.integration.authSource !== "HOSTED") return;
-
- const connection = await getConnection(tx, source);
-
- if (!connection) {
- logger.error(
- `Integration connection not found for source ${source.id}, integration ${source.integration.id}`
- );
- return;
- }
-
- const response = await integrationAuthRepository.getCredentials(connection);
-
- if (!response) {
- return;
- }
-
- return {
- type: "oauth2",
- scopes: response.scopes,
- accessToken: response.accessToken,
- };
-}
-
-function getConnection(tx: PrismaClientOrTransaction, source: ResolvableTriggerSource) {
- if (source.externalAccount) {
- return tx.integrationConnection.findFirst({
- where: {
- integrationId: source.integration.id,
- externalAccountId: source.externalAccount.id,
- },
- include: {
- dataReference: true,
- },
- });
- }
-
- return tx.integrationConnection.findFirst({
- where: {
- integrationId: source.integration.id,
- },
- include: {
- dataReference: true,
- },
- });
-}
diff --git a/apps/webapp/app/models/task.server.ts b/apps/webapp/app/models/task.server.ts
index 079dd66bdc..0d0791ac7e 100644
--- a/apps/webapp/app/models/task.server.ts
+++ b/apps/webapp/app/models/task.server.ts
@@ -1,123 +1,6 @@
-import type { JobRun, Task, TaskAttempt, TaskTriggerSource } from "@trigger.dev/database";
-import { CachedTask, ServerTask } from "@trigger.dev/core";
+import type { TaskTriggerSource } from "@trigger.dev/database";
import { PrismaClientOrTransaction, sqlDatabaseSchema } from "~/db.server";
-export type TaskWithAttempts = Task & {
- attempts: TaskAttempt[];
- run: { forceYieldImmediately: boolean };
-};
-
-export function taskWithAttemptsToServerTask(task: TaskWithAttempts): ServerTask {
- return {
- id: task.id,
- name: task.name,
- icon: task.icon,
- noop: task.noop,
- startedAt: task.startedAt,
- completedAt: task.completedAt,
- delayUntil: task.delayUntil,
- status: task.status,
- description: task.description,
- params: task.params as any,
- output: task.outputIsUndefined ? undefined : (task.output as any),
- context: task.context as any,
- properties: task.properties as any,
- style: task.style as any,
- error: task.error,
- parentId: task.parentId,
- attempts: task.attempts.length,
- idempotencyKey: task.idempotencyKey,
- operation: task.operation,
- callbackUrl: task.callbackUrl,
- forceYield: task.run.forceYieldImmediately,
- childExecutionMode: task.childExecutionMode,
- };
-}
-
-export type TaskForCaching = Pick<
- Task,
- "id" | "status" | "idempotencyKey" | "noop" | "output" | "parentId" | "outputIsUndefined"
->;
-
-export function prepareTasksForCaching(
- possibleTasks: TaskForCaching[],
- maxSize: number
-): {
- tasks: CachedTask[];
- cursor: string | undefined;
-} {
- const tasks = possibleTasks.filter((task) => task.status === "COMPLETED" && !task.noop);
-
- // Select tasks using greedy approach
- const tasksToRun: CachedTask[] = [];
- let remainingSize = maxSize;
-
- for (const task of tasks) {
- const cachedTask = prepareTaskForCaching(task);
- const size = calculateCachedTaskSize(cachedTask);
-
- if (size <= remainingSize) {
- tasksToRun.push(cachedTask);
- remainingSize -= size;
- }
- }
-
- return {
- tasks: tasksToRun,
- cursor: tasks.length > tasksToRun.length ? tasks[tasksToRun.length].id : undefined,
- };
-}
-
-export function prepareTasksForCachingLegacy(
- possibleTasks: TaskForCaching[],
- maxSize: number
-): {
- tasks: CachedTask[];
- cursor: string | undefined;
-} {
- const tasks = possibleTasks.filter((task) => task.status === "COMPLETED");
-
- // Prepare tasks and calculate their sizes
- const availableTasks = tasks.map((task) => {
- const cachedTask = prepareTaskForCaching(task);
- return { task: cachedTask, size: calculateCachedTaskSize(cachedTask) };
- });
-
- // Sort tasks in ascending order by size
- availableTasks.sort((a, b) => a.size - b.size);
-
- // Select tasks using greedy approach
- const tasksToRun: CachedTask[] = [];
- let remainingSize = maxSize;
-
- for (const { task, size } of availableTasks) {
- if (size <= remainingSize) {
- tasksToRun.push(task);
- remainingSize -= size;
- }
- }
-
- return {
- tasks: tasksToRun,
- cursor: undefined,
- };
-}
-
-function prepareTaskForCaching(task: TaskForCaching): CachedTask {
- return {
- id: task.idempotencyKey, // We should eventually move this back to task.id
- status: task.status,
- idempotencyKey: task.idempotencyKey,
- noop: task.noop,
- output: task.outputIsUndefined ? undefined : (task.output as any),
- parentId: task.parentId,
- };
-}
-
-function calculateCachedTaskSize(task: CachedTask): number {
- return JSON.stringify(task).length;
-}
-
/**
*
* @param prisma An efficient query to get all task identifiers for a project.
diff --git a/apps/webapp/app/presenters/ApiRunPresenter.server.ts b/apps/webapp/app/presenters/ApiRunPresenter.server.ts
deleted file mode 100644
index a3d18f9e2b..0000000000
--- a/apps/webapp/app/presenters/ApiRunPresenter.server.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { Job } from "@trigger.dev/database";
-import { PrismaClient, prisma } from "~/db.server";
-
-type ApiRunOptions = {
- runId: Job["id"];
- maxTasks?: number;
- taskDetails?: boolean;
- subTasks?: boolean;
- cursor?: string;
-};
-
-export class ApiRunPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- runId,
- maxTasks = 20,
- taskDetails = false,
- subTasks = false,
- cursor,
- }: ApiRunOptions) {
- const take = Math.min(maxTasks, 50);
-
- return await prisma.jobRun.findFirst({
- where: {
- id: runId,
- },
- select: {
- id: true,
- status: true,
- startedAt: true,
- updatedAt: true,
- completedAt: true,
- environmentId: true,
- output: true,
- tasks: {
- select: {
- id: true,
- parentId: true,
- displayKey: true,
- status: true,
- name: true,
- icon: true,
- startedAt: true,
- completedAt: true,
- params: taskDetails,
- output: taskDetails,
- },
- where: {
- parentId: subTasks ? undefined : null,
- },
- orderBy: {
- id: "asc",
- },
- take: take + 1,
- cursor: cursor
- ? {
- id: cursor,
- }
- : undefined,
- },
- statuses: {
- select: { key: true, label: true, state: true, data: true, history: true },
- },
- },
- });
- }
-}
diff --git a/apps/webapp/app/presenters/EnvironmentsPresenter.server.ts b/apps/webapp/app/presenters/EnvironmentsPresenter.server.ts
deleted file mode 100644
index 50a22425e8..0000000000
--- a/apps/webapp/app/presenters/EnvironmentsPresenter.server.ts
+++ /dev/null
@@ -1,246 +0,0 @@
-import { PrismaClient, prisma } from "~/db.server";
-import { Project } from "~/models/project.server";
-import { User } from "~/models/user.server";
-import type {
- Endpoint,
- EndpointIndex,
- EndpointIndexStatus,
- RuntimeEnvironment,
- RuntimeEnvironmentType,
-} from "@trigger.dev/database";
-import {
- EndpointIndexError,
- EndpointIndexErrorSchema,
- IndexEndpointStats,
- parseEndpointIndexStats,
-} from "@trigger.dev/core";
-import { sortEnvironments } from "~/utils/environmentSort";
-
-export type Client = {
- slug: string;
- endpoints: {
- DEVELOPMENT: ClientEndpoint;
- PRODUCTION: ClientEndpoint;
- STAGING?: ClientEndpoint;
- };
-};
-
-export type ClientEndpoint =
- | {
- state: "unconfigured";
- environment: {
- id: string;
- apiKey: string;
- type: RuntimeEnvironmentType;
- };
- }
- | {
- state: "configured";
- id: string;
- slug: string;
- url: string | null;
- indexWebhookPath: string;
- latestIndex?: {
- status: EndpointIndexStatus;
- source: string;
- updatedAt: Date;
- stats?: IndexEndpointStats;
- error?: EndpointIndexError;
- };
- environment: {
- id: string;
- apiKey: string;
- type: RuntimeEnvironmentType;
- };
- };
-
-export class EnvironmentsPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- projectSlug,
- baseUrl,
- }: {
- userId: User["id"];
- projectSlug: Project["slug"];
- baseUrl: string;
- }) {
- const environments = await this.#prismaClient.runtimeEnvironment.findMany({
- select: {
- id: true,
- apiKey: true,
- pkApiKey: true,
- type: true,
- slug: true,
- orgMember: {
- select: {
- userId: true,
- },
- },
- endpoints: {
- select: {
- id: true,
- slug: true,
- url: true,
- indexingHookIdentifier: true,
- indexings: {
- select: {
- status: true,
- source: true,
- updatedAt: true,
- stats: true,
- error: true,
- },
- take: 1,
- orderBy: {
- updatedAt: "desc",
- },
- },
- },
- where: {
- url: {
- not: null,
- },
- },
- },
- },
- where: {
- project: {
- slug: projectSlug,
- },
- organization: {
- members: {
- some: {
- userId,
- },
- },
- },
- },
- });
-
- //filter out environments the only development ones belong to the current user
- const filtered = environments.filter((environment) => {
- if (environment.type === "DEVELOPMENT") {
- return environment.orgMember?.userId === userId;
- }
- return true;
- });
-
- //get all the possible client slugs
- const clientSlugs = new Set();
- for (const environment of filtered) {
- for (const endpoint of environment.endpoints) {
- clientSlugs.add(endpoint.slug);
- }
- }
-
- //build up list of clients for display, with endpoints by type
- const clients: Client[] = [];
- for (const slug of clientSlugs) {
- const developmentEnvironment = filtered.find(
- (environment) => environment.type === "DEVELOPMENT"
- );
- if (!developmentEnvironment) {
- throw new Error("Development environment not found, this should not happen");
- }
-
- const stagingEnvironment = filtered.find((environment) => environment.type === "STAGING");
-
- const productionEnvironment = filtered.find(
- (environment) => environment.type === "PRODUCTION"
- );
- if (!productionEnvironment) {
- throw new Error("Production environment not found, this should not happen");
- }
-
- const client: Client = {
- slug,
- endpoints: {
- DEVELOPMENT: {
- state: "unconfigured",
- environment: developmentEnvironment,
- },
- PRODUCTION: {
- state: "unconfigured",
- environment: productionEnvironment,
- },
- STAGING: stagingEnvironment
- ? { state: "unconfigured", environment: stagingEnvironment }
- : undefined,
- },
- };
-
- const devEndpoint = developmentEnvironment.endpoints.find(
- (endpoint) => endpoint.slug === slug
- );
- if (devEndpoint) {
- client.endpoints.DEVELOPMENT = endpointClient(devEndpoint, developmentEnvironment, baseUrl);
- }
-
- if (stagingEnvironment) {
- const stagingEndpoint = stagingEnvironment.endpoints.find(
- (endpoint) => endpoint.slug === slug
- );
-
- if (stagingEndpoint) {
- client.endpoints.STAGING = endpointClient(stagingEndpoint, stagingEnvironment, baseUrl);
- }
- }
-
- const prodEndpoint = productionEnvironment.endpoints.find(
- (endpoint) => endpoint.slug === slug
- );
- if (prodEndpoint) {
- client.endpoints.PRODUCTION = endpointClient(prodEndpoint, productionEnvironment, baseUrl);
- }
-
- clients.push(client);
- }
-
- return {
- environments: sortEnvironments(
- filtered.map((environment) => ({
- id: environment.id,
- apiKey: environment.apiKey,
- pkApiKey: environment.pkApiKey,
- type: environment.type,
- slug: environment.slug,
- }))
- ),
- clients,
- };
- }
-}
-
-function endpointClient(
- endpoint: Pick & {
- indexings: Pick[];
- },
- environment: Pick,
- baseUrl: string
-): ClientEndpoint {
- return {
- state: "configured" as const,
- id: endpoint.id,
- slug: endpoint.slug,
- url: endpoint.url,
- indexWebhookPath: `${baseUrl}/api/v1/endpoints/${environment.id}/${endpoint.slug}/index/${endpoint.indexingHookIdentifier}`,
- latestIndex: endpoint.indexings[0]
- ? {
- status: endpoint.indexings[0].status,
- source: endpoint.indexings[0].source,
- updatedAt: endpoint.indexings[0].updatedAt,
- stats: parseEndpointIndexStats(endpoint.indexings[0].stats),
- error: endpoint.indexings[0].error
- ? EndpointIndexErrorSchema.parse(endpoint.indexings[0].error)
- : undefined,
- }
- : undefined,
- environment: environment,
- };
-}
diff --git a/apps/webapp/app/presenters/EnvironmentsStreamPresenter.server.ts b/apps/webapp/app/presenters/EnvironmentsStreamPresenter.server.ts
deleted file mode 100644
index 3d7bf8877c..0000000000
--- a/apps/webapp/app/presenters/EnvironmentsStreamPresenter.server.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import { PrismaClient, prisma } from "~/db.server";
-import { Project } from "~/models/project.server";
-import { User } from "~/models/user.server";
-import { sse } from "~/utils/sse.server";
-
-type EnvironmentSignalsMap = {
- [x: string]: {
- lastUpdatedAt: number;
- lastTotalEndpointUpdatedTime: number;
- lastTotalIndexingUpdatedTime: number;
- };
-};
-
-export class EnvironmentsStreamPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- request,
- userId,
- projectSlug,
- }: {
- request: Request;
- userId: User["id"];
- projectSlug: Project["slug"];
- }) {
- let lastEnvironmentSignals: EnvironmentSignalsMap;
-
- return sse({
- request,
- run: async (send, stop) => {
- const nextEnvironmentSignals = await this.#runForUpdates({
- userId,
- projectSlug,
- });
-
- if (!nextEnvironmentSignals) {
- return stop();
- }
-
- const lastEnvironmentIds = lastEnvironmentSignals
- ? Object.keys(lastEnvironmentSignals)
- : [];
- const nextEnvironmentIds = Object.keys(nextEnvironmentSignals);
-
- if (
- //push update if the number of environments is different
- nextEnvironmentIds.length !== lastEnvironmentIds.length ||
- //push update if the list of ids is different
- lastEnvironmentIds.some((id) => !nextEnvironmentSignals[id]) ||
- nextEnvironmentIds.some((id) => !lastEnvironmentSignals[id]) ||
- //push update if any signals changed
- nextEnvironmentIds.some(
- (id) =>
- nextEnvironmentSignals[id].lastUpdatedAt !==
- lastEnvironmentSignals[id].lastUpdatedAt ||
- nextEnvironmentSignals[id].lastTotalEndpointUpdatedTime !==
- lastEnvironmentSignals[id].lastTotalEndpointUpdatedTime ||
- nextEnvironmentSignals[id].lastTotalIndexingUpdatedTime !==
- lastEnvironmentSignals[id].lastTotalIndexingUpdatedTime
- )
- ) {
- send({ data: new Date().toISOString() });
- }
-
- lastEnvironmentSignals = nextEnvironmentSignals;
- },
- });
- }
-
- async #runForUpdates({
- userId,
- projectSlug,
- }: {
- userId: User["id"];
- projectSlug: Project["slug"];
- }) {
- const environments = await this.#prismaClient.runtimeEnvironment.findMany({
- select: {
- id: true,
- updatedAt: true,
- endpoints: {
- select: {
- updatedAt: true,
- indexings: {
- select: {
- updatedAt: true,
- },
- },
- },
- },
- },
- where: {
- project: {
- slug: projectSlug,
- },
- organization: {
- members: {
- some: {
- userId,
- },
- },
- },
- },
- });
-
- if (!environments) return null;
-
- const environmentSignalsMap = environments.reduce((acc, environment) => {
- const lastUpdatedAt = environment.updatedAt.getTime();
- const lastTotalEndpointUpdatedTime = environment.endpoints.reduce(
- (prev, endpoint) => prev + endpoint.updatedAt.getTime(),
- 0
- );
- const lastTotalIndexingUpdatedTime = environment.endpoints.reduce(
- (prev, endpoint) =>
- prev +
- endpoint.indexings.reduce((prev, indexing) => prev + indexing.updatedAt.getTime(), 0),
- 0
- );
-
- return {
- ...acc,
- [environment.id]: {
- lastUpdatedAt,
- lastTotalEndpointUpdatedTime,
- lastTotalIndexingUpdatedTime,
- },
- };
- }, {});
-
- return environmentSignalsMap;
- }
-}
diff --git a/apps/webapp/app/presenters/EventPresenter.server.ts b/apps/webapp/app/presenters/EventPresenter.server.ts
deleted file mode 100644
index 8c569ceca0..0000000000
--- a/apps/webapp/app/presenters/EventPresenter.server.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { PrismaClient, prisma } from "~/db.server";
-
-export type Event = NonNullable>>;
-
-export class EventPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- projectSlug,
- organizationSlug,
- eventId,
- }: {
- userId: string;
- projectSlug: string;
- organizationSlug: string;
- eventId: string;
- }) {
- // Find the organization that the user is a member of
- const organization = await this.#prismaClient.organization.findFirstOrThrow({
- where: {
- slug: organizationSlug,
- members: { some: { userId } },
- },
- });
-
- // Find the project scoped to the organization
- const project = await this.#prismaClient.project.findFirstOrThrow({
- where: {
- slug: projectSlug,
- organizationId: organization.id,
- },
- });
-
- const event = await this.#prismaClient.eventRecord.findFirst({
- select: {
- id: true,
- name: true,
- payload: true,
- context: true,
- timestamp: true,
- deliveredAt: true,
- },
- where: {
- id: eventId,
- projectId: project.id,
- organizationId: organization.id,
- },
- });
-
- if (!event) {
- throw new Error("Could not find Event");
- }
-
- return {
- id: event.id,
- name: event.name,
- timestamp: event.timestamp,
- payload: JSON.stringify(event.payload, null, 2),
- context: JSON.stringify(event.context, null, 2),
- deliveredAt: event.deliveredAt,
- };
- }
-}
diff --git a/apps/webapp/app/presenters/HttpEndpointPresenter.server.ts b/apps/webapp/app/presenters/HttpEndpointPresenter.server.ts
deleted file mode 100644
index 242d6478a9..0000000000
--- a/apps/webapp/app/presenters/HttpEndpointPresenter.server.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import { z } from "zod";
-import { PrismaClient, prisma } from "~/db.server";
-import { sortEnvironments } from "~/utils/environmentSort";
-import { httpEndpointUrl } from "~/services/httpendpoint/HandleHttpEndpointService.server";
-import { getSecretStore } from "~/services/secrets/secretStore.server";
-import { projectPath } from "~/utils/pathBuilder";
-
-export class HttpEndpointPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- projectSlug,
- organizationSlug,
- httpEndpointKey,
- }: {
- userId: string;
- projectSlug: string;
- organizationSlug: string;
- httpEndpointKey: string;
- }) {
- const httpEndpoint = await this.#prismaClient.triggerHttpEndpoint.findFirst({
- select: {
- id: true,
- key: true,
- icon: true,
- title: true,
- updatedAt: true,
- projectId: true,
- secretReference: {
- select: {
- key: true,
- provider: true,
- },
- },
- httpEndpointEnvironments: {
- select: {
- id: true,
- immediateResponseFilter: true,
- skipTriggeringRuns: true,
- source: true,
- active: true,
- updatedAt: true,
- environment: {
- select: {
- type: true,
- orgMember: {
- select: {
- userId: true,
- },
- },
- },
- },
- },
- },
- webhook: {
- select: {
- id: true,
- key: true,
- },
- },
- },
- where: {
- key: httpEndpointKey,
- project: {
- slug: projectSlug,
- organization: {
- members: {
- some: {
- userId,
- },
- },
- },
- },
- },
- });
-
- if (!httpEndpoint) {
- throw new Error("Could not find http endpoint");
- }
-
- const environments = await this.#prismaClient.runtimeEnvironment.findMany({
- select: {
- id: true,
- type: true,
- slug: true,
- shortcode: true,
- orgMember: {
- select: {
- userId: true,
- },
- },
- },
- where: {
- projectId: httpEndpoint.projectId,
- },
- });
-
- const relevantEnvironments = sortEnvironments(
- environments
- .filter(
- (environment) => environment.orgMember === null || environment.orgMember.userId === userId
- )
- .map((environment) => ({
- ...environment,
- webhookUrl: httpEndpointUrl({ httpEndpointId: httpEndpoint.id, environment }),
- }))
- );
-
- //get the secret
- const secretStore = getSecretStore(httpEndpoint.secretReference.provider);
- let secret: string | undefined;
- try {
- const secretData = await secretStore.getSecretOrThrow(
- z.object({ secret: z.string() }),
- httpEndpoint.secretReference.key
- );
- secret = secretData.secret;
- } catch (e) {
- let error = e instanceof Error ? e.message : JSON.stringify(e);
- throw new Error(`Could not retrieve secret: ${error}`);
- }
- if (!secret) {
- throw new Error("Could not find secret");
- }
-
- const httpEndpointEnvironments = httpEndpoint.httpEndpointEnvironments
- .filter(
- (httpEndpointEnvironment) =>
- httpEndpointEnvironment.environment.orgMember === null ||
- httpEndpointEnvironment.environment.orgMember.userId === userId
- )
- .map((endpointEnv) => ({
- ...endpointEnv,
- immediateResponseFilter: endpointEnv.immediateResponseFilter != null,
- environment: {
- type: endpointEnv.environment.type,
- },
- webhookUrl: relevantEnvironments.find((e) => e.type === endpointEnv.environment.type)
- ?.webhookUrl,
- }));
-
- const projectRootPath = projectPath({ slug: organizationSlug }, { slug: projectSlug });
-
- return {
- httpEndpoint: {
- ...httpEndpoint,
- httpEndpointEnvironments,
- webhookLink: httpEndpoint.webhook
- ? `${projectRootPath}/triggers/webhooks/${httpEndpoint.webhook.id}`
- : undefined,
- },
- environments: relevantEnvironments,
- unconfiguredEnvironments: relevantEnvironments.filter(
- (e) => httpEndpointEnvironments.find((h) => h.environment.type === e.type) === undefined
- ),
- secret,
- };
- }
-}
diff --git a/apps/webapp/app/presenters/HttpEndpointsPresenter.server.ts b/apps/webapp/app/presenters/HttpEndpointsPresenter.server.ts
deleted file mode 100644
index 8f4d3134f9..0000000000
--- a/apps/webapp/app/presenters/HttpEndpointsPresenter.server.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { PrismaClient, prisma } from "~/db.server";
-import { Project } from "~/models/project.server";
-import { User } from "~/models/user.server";
-
-export class HttpEndpointsPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- slug,
- }: Pick & {
- userId: User["id"];
- }) {
- const httpEndpoints = await this.#prismaClient.triggerHttpEndpoint.findMany({
- select: {
- id: true,
- key: true,
- icon: true,
- title: true,
- updatedAt: true,
- httpEndpointEnvironments: {
- select: {
- id: true,
- environment: {
- select: {
- type: true,
- orgMember: {
- select: {
- userId: true,
- },
- },
- },
- },
- },
- },
- },
- where: {
- project: {
- slug,
- organization: {
- members: {
- some: {
- userId,
- },
- },
- },
- },
- },
- });
-
- return httpEndpoints.map((httpEndpoint) => ({
- ...httpEndpoint,
- httpEndpointEnvironments: httpEndpoint.httpEndpointEnvironments.filter(
- (httpEndpointEnvironment) =>
- httpEndpointEnvironment.environment.orgMember === null ||
- httpEndpointEnvironment.environment.orgMember.userId === userId
- ),
- }));
- }
-}
diff --git a/apps/webapp/app/presenters/IntegrationClientConnectionsPresenter.server.ts b/apps/webapp/app/presenters/IntegrationClientConnectionsPresenter.server.ts
deleted file mode 100644
index 3e02167ec7..0000000000
--- a/apps/webapp/app/presenters/IntegrationClientConnectionsPresenter.server.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { User } from "@trigger.dev/database";
-import { PrismaClient, prisma } from "~/db.server";
-import { Organization } from "~/models/organization.server";
-import { ConnectionMetadataSchema } from "~/services/externalApis/types";
-
-export class IntegrationClientConnectionsPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- organizationSlug,
- clientSlug,
- }: {
- userId: User["id"];
- organizationSlug: Organization["slug"];
- clientSlug: string;
- }) {
- const connections = await this.#prismaClient.integrationConnection.findMany({
- select: {
- id: true,
- expiresAt: true,
- metadata: true,
- connectionType: true,
- createdAt: true,
- updatedAt: true,
- _count: {
- select: {
- runConnections: true,
- },
- },
- },
- where: {
- organization: {
- slug: organizationSlug,
- members: {
- some: {
- userId,
- },
- },
- },
- integration: {
- slug: clientSlug,
- },
- },
- orderBy: {
- createdAt: "desc",
- },
- });
-
- return {
- connections: connections.map((c) => ({
- id: c.id,
- expiresAt: c.expiresAt,
- metadata: c.metadata != null ? ConnectionMetadataSchema.parse(c.metadata) : null,
- type: c.connectionType,
- createdAt: c.createdAt,
- updatedAt: c.updatedAt,
- runCount: c._count.runConnections,
- })),
- };
- }
-}
diff --git a/apps/webapp/app/presenters/IntegrationClientPresenter.server.ts b/apps/webapp/app/presenters/IntegrationClientPresenter.server.ts
deleted file mode 100644
index ae88353fec..0000000000
--- a/apps/webapp/app/presenters/IntegrationClientPresenter.server.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import { User } from "@trigger.dev/database";
-import { PrismaClient, prisma } from "~/db.server";
-import { env } from "~/env.server";
-import { Organization } from "~/models/organization.server";
-import { HelpSchema, OAuthClientSchema } from "~/services/externalApis/types";
-import { getSecretStore } from "~/services/secrets/secretStore.server";
-
-export class IntegrationClientPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- organizationSlug,
- clientSlug,
- }: {
- userId: User["id"];
- organizationSlug: Organization["slug"];
- clientSlug: string;
- }) {
- const integration = await this.#prismaClient.integration.findFirst({
- select: {
- id: true,
- title: true,
- slug: true,
- authMethod: {
- select: {
- key: true,
- type: true,
- name: true,
- help: true,
- },
- },
- authSource: true,
- definition: {
- select: {
- id: true,
- name: true,
- packageName: true,
- icon: true,
- },
- },
- connectionType: true,
- customClientReference: {
- select: {
- key: true,
- },
- },
- createdAt: true,
- _count: {
- select: {
- jobIntegrations: {
- where: {
- job: {
- organization: {
- slug: organizationSlug,
- },
- internal: false,
- deletedAt: null,
- },
- },
- },
- },
- },
- },
- where: {
- organization: {
- slug: organizationSlug,
- members: {
- some: {
- userId,
- },
- },
- },
- slug: clientSlug,
- },
- });
-
- if (!integration) {
- return undefined;
- }
-
- const secretStore = getSecretStore(env.SECRET_STORE, {
- prismaClient: this.#prismaClient,
- });
-
- let clientId: String | undefined = undefined;
- if (integration.customClientReference) {
- const clientConfig = await secretStore.getSecret(
- OAuthClientSchema,
- integration.customClientReference.key
- );
- clientId = clientConfig?.id;
- }
-
- const help = integration.authMethod?.help
- ? HelpSchema.parse(integration.authMethod?.help)
- : undefined;
-
- return {
- id: integration.id,
- title: integration.title ?? integration.slug,
- slug: integration.slug,
- integrationIdentifier: integration.definition.id,
- jobCount: integration._count.jobIntegrations,
- createdAt: integration.createdAt,
- customClientId: clientId,
- type: integration.connectionType,
- integration: {
- identifier: integration.definition.id,
- name: integration.definition.name,
- packageName: integration.definition.packageName,
- icon: integration.definition.icon,
- },
- authMethod: {
- type:
- integration.authMethod?.type ??
- (integration.authSource === "RESOLVER" ? "resolver" : "local"),
- name:
- integration.authMethod?.name ??
- (integration.authSource === "RESOLVER" ? "Auth Resolver" : "Local Auth"),
- },
- help,
- };
- }
-}
diff --git a/apps/webapp/app/presenters/IntegrationClientScopesPresenter.server.ts b/apps/webapp/app/presenters/IntegrationClientScopesPresenter.server.ts
deleted file mode 100644
index 25bbc783a0..0000000000
--- a/apps/webapp/app/presenters/IntegrationClientScopesPresenter.server.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { User } from "@trigger.dev/database";
-import { PrismaClient, prisma } from "~/db.server";
-import { Organization } from "~/models/organization.server";
-import { Project } from "~/models/project.server";
-
-import { Scope } from "~/services/externalApis/types";
-
-export class IntegrationClientScopesPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- organizationSlug,
- clientSlug,
- }: {
- userId: User["id"];
- organizationSlug: Organization["slug"];
- clientSlug: string;
- }) {
- const integration = await this.#prismaClient.integration.findFirst({
- select: {
- authMethod: true,
- scopes: true,
- },
- where: {
- organization: {
- slug: organizationSlug,
- members: {
- some: {
- userId,
- },
- },
- },
- slug: clientSlug,
- },
- });
-
- if (!integration) {
- throw new Error("Client not found");
- }
-
- const authMethodScopes = (integration.authMethod?.scopes ?? []) as Scope[];
-
- return {
- scopes: integration.scopes.map((s) => {
- const matchingScope = authMethodScopes.find((scope) => scope.name === s);
-
- return {
- name: s,
- description: matchingScope?.description,
- };
- }),
- };
- }
-}
diff --git a/apps/webapp/app/presenters/IntegrationsPresenter.server.ts b/apps/webapp/app/presenters/IntegrationsPresenter.server.ts
deleted file mode 100644
index 5b3c003fbc..0000000000
--- a/apps/webapp/app/presenters/IntegrationsPresenter.server.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-import { User } from "@trigger.dev/database";
-import { PrismaClient, prisma } from "~/db.server";
-import { env } from "~/env.server";
-import { Organization } from "~/models/organization.server";
-import { Project } from "~/models/project.server";
-import { Api, apisList } from "~/services/externalApis/apis.server";
-import { integrationCatalog } from "~/services/externalApis/integrationCatalog.server";
-import { Integration, OAuthClientSchema } from "~/services/externalApis/types";
-import { getSecretStore } from "~/services/secrets/secretStore.server";
-
-export type IntegrationOrApi =
- | ({
- type: "integration";
- } & Integration)
- | ({ type: "api" } & Api & { voted: boolean });
-
-export type Client = Awaited>["clients"][number];
-
-export class IntegrationsPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- organizationSlug,
- }: {
- userId: User["id"];
- organizationSlug: Organization["slug"];
- }) {
- const clients = await this.#prismaClient.integration.findMany({
- select: {
- id: true,
- title: true,
- slug: true,
- description: true,
- setupStatus: true,
- authMethod: {
- select: {
- type: true,
- name: true,
- },
- },
- definition: {
- select: {
- id: true,
- name: true,
- icon: true,
- },
- },
- authSource: true,
- connectionType: true,
- scopes: true,
- customClientReference: {
- select: {
- key: true,
- },
- },
- createdAt: true,
- _count: {
- select: {
- connections: true,
- jobIntegrations: {
- where: {
- job: {
- organization: {
- slug: organizationSlug,
- },
- internal: false,
- deletedAt: null,
- },
- },
- },
- },
- },
- },
- where: {
- organization: {
- slug: organizationSlug,
- members: {
- some: {
- userId,
- },
- },
- },
- },
- orderBy: {
- title: "asc",
- },
- });
-
- const secretStore = getSecretStore(env.SECRET_STORE, {
- prismaClient: this.#prismaClient,
- });
-
- const enrichedClients = await Promise.all(
- clients.map(async (c) => {
- let clientId: String | undefined = undefined;
- if (c.customClientReference) {
- const clientConfig = await secretStore.getSecret(
- OAuthClientSchema,
- c.customClientReference.key
- );
- clientId = clientConfig?.id;
- }
-
- return {
- id: c.id,
- title: c.title ?? c.slug,
- icon: c.definition.icon ?? c.definition.id,
- slug: c.slug,
- integrationIdentifier: c.definition.id,
- description: c.description,
- scopesCount: c.scopes.length,
- connectionsCount: c._count.connections,
- jobCount: c._count.jobIntegrations,
- createdAt: c.createdAt,
- customClientId: clientId,
- integration: {
- identifier: c.definition.id,
- name: c.definition.name,
- },
- authMethod: {
- type: c.authMethod?.type ?? (c.authSource === "RESOLVER" ? "resolver" : "local"),
- name:
- c.authMethod?.name ?? (c.authSource === "RESOLVER" ? "Auth Resolver" : "Local Only"),
- },
- authSource: c.authSource,
- setupStatus: c.setupStatus,
- };
- })
- );
-
- const setupClients = enrichedClients.filter((c) => c.setupStatus === "COMPLETE");
- const clientMissingFields = enrichedClients.filter((c) => c.setupStatus === "MISSING_FIELDS");
-
- const integrations = Object.values(integrationCatalog.getIntegrations()).map((i) => ({
- type: "integration" as const,
- ...i,
- }));
-
- //get all apis, some don't have integrations yet.
- //get whether the user has voted for them or not
- const votes = await this.#prismaClient.apiIntegrationVote.findMany({
- select: {
- apiIdentifier: true,
- },
- where: {
- userId,
- },
- });
-
- const apis = apisList
- .filter((a) => !integrations.some((i) => i.identifier === a.identifier))
- .map((a) => ({
- type: "api" as const,
- ...a,
- voted: votes.some((v) => v.apiIdentifier === a.identifier),
- }));
-
- const options = [...integrations, ...apis].sort((a, b) => a.name.localeCompare(b.name));
-
- return {
- clients: setupClients,
- clientMissingFields,
- options,
- callbackUrl: `${env.APP_ORIGIN}/oauth2/callback`,
- };
- }
-}
diff --git a/apps/webapp/app/presenters/JobListPresenter.server.ts b/apps/webapp/app/presenters/JobListPresenter.server.ts
deleted file mode 100644
index 0edbe39346..0000000000
--- a/apps/webapp/app/presenters/JobListPresenter.server.ts
+++ /dev/null
@@ -1,195 +0,0 @@
-import {
- DisplayProperty,
- DisplayPropertySchema,
- EventSpecificationSchema,
-} from "@trigger.dev/core";
-import { PrismaClient, Prisma, prisma, sqlDatabaseSchema } from "~/db.server";
-import { Organization } from "~/models/organization.server";
-import { Project } from "~/models/project.server";
-import { User } from "~/models/user.server";
-import { z } from "zod";
-import { projectPath } from "~/utils/pathBuilder";
-import { JobRunStatus } from "@trigger.dev/database";
-import { BasePresenter } from "./v3/basePresenter.server";
-
-export type ProjectJob = Awaited>[0];
-
-export class JobListPresenter extends BasePresenter {
-
-
- public async call({
- userId,
- projectSlug,
- organizationSlug,
- integrationSlug,
- }: {
- userId: User["id"];
- projectSlug?: Project["slug"];
- organizationSlug: Organization["slug"];
- integrationSlug?: string;
- }) {
- const orgWhere: Prisma.JobWhereInput["organization"] = organizationSlug
- ? { slug: organizationSlug, members: { some: { userId } } }
- : { members: { some: { userId } } };
-
- const integrationsWhere: Prisma.JobWhereInput["integrations"] = integrationSlug
- ? { some: { integration: { slug: integrationSlug } } }
- : {};
-
- const jobs = await this._replica.job.findMany({
- select: {
- id: true,
- slug: true,
- title: true,
- integrations: {
- select: {
- key: true,
- integration: {
- select: {
- slug: true,
- definition: true,
- setupStatus: true,
- },
- },
- },
- },
- versions: {
- select: {
- version: true,
- eventSpecification: true,
- properties: true,
- status: true,
- triggerLink: true,
- triggerHelp: true,
- environment: {
- select: {
- type: true,
- },
- },
- },
- orderBy: [{ updatedAt: "desc" }],
- take: 1,
- },
- dynamicTriggers: {
- select: {
- type: true,
- },
- },
- project: {
- select: {
- slug: true,
- },
- },
- },
- where: {
- internal: false,
- deletedAt: null,
- organization: orgWhere,
- project: projectSlug
- ? {
- slug: projectSlug,
- }
- : undefined,
- integrations: integrationsWhere,
- },
- orderBy: [{ title: "asc" }],
- });
-
- let latestRuns = [] as {
- createdAt: Date;
- status: JobRunStatus;
- jobId: string;
- rn: BigInt;
- }[];
-
- if (jobs.length > 0) {
- latestRuns = await this._replica.$queryRaw<
- {
- createdAt: Date;
- status: JobRunStatus;
- jobId: string;
- rn: BigInt;
- }[]
- >`
- SELECT * FROM (
- SELECT
- "id",
- "createdAt",
- "status",
- "jobId",
- ROW_NUMBER() OVER(PARTITION BY "jobId" ORDER BY "createdAt" DESC) as rn
- FROM
- ${sqlDatabaseSchema}."JobRun"
- WHERE
- "jobId" IN (${Prisma.join(jobs.map((j) => j.id))})
- ) t
- WHERE rn = 1;`;
- }
-
- return jobs
- .flatMap((job) => {
- const version = job.versions.at(0);
- if (!version) {
- return [];
- }
-
- const eventSpecification = EventSpecificationSchema.parse(version.eventSpecification);
-
- const integrations = job.integrations.map((integration) => ({
- key: integration.key,
- title: integration.integration.slug,
- icon: integration.integration.definition.icon ?? integration.integration.definition.id,
- setupStatus: integration.integration.setupStatus,
- }));
-
- //deduplicate integrations
- const uniqueIntegrations = new Map();
- integrations.forEach((i) => {
- uniqueIntegrations.set(i.key, i);
- });
-
- let properties: DisplayProperty[] = [];
-
- if (eventSpecification.properties) {
- properties = [...properties, ...eventSpecification.properties];
- }
-
- if (version.properties) {
- const versionProperties = z.array(DisplayPropertySchema).parse(version.properties);
- properties = [...properties, ...versionProperties];
- }
-
- const latestRun = latestRuns.find((r) => r.jobId === job.id);
-
- return [
- {
- id: job.id,
- slug: job.slug,
- title: job.title,
- version: version.version,
- status: version.status,
- dynamic: job.dynamicTriggers.length > 0,
- event: {
- title: eventSpecification.title,
- icon: eventSpecification.icon,
- source: eventSpecification.source,
- link: projectSlug
- ? `${projectPath({ slug: organizationSlug }, { slug: projectSlug })}/${
- version.triggerLink
- }`
- : undefined,
- },
- integrations: Array.from(uniqueIntegrations.values()),
- hasIntegrationsRequiringAction: integrations.some(
- (i) => i.setupStatus === "MISSING_FIELDS"
- ),
- environment: version.environment,
- lastRun: latestRun,
- properties,
- projectSlug: job.project.slug,
- },
- ];
- })
- .filter(Boolean);
- }
-}
diff --git a/apps/webapp/app/presenters/JobPresenter.server.ts b/apps/webapp/app/presenters/JobPresenter.server.ts
deleted file mode 100644
index 6db5ca3044..0000000000
--- a/apps/webapp/app/presenters/JobPresenter.server.ts
+++ /dev/null
@@ -1,210 +0,0 @@
-import {
- DisplayProperty,
- DisplayPropertySchema,
- EventSpecificationSchema,
- TriggerHelpSchema,
-} from "@trigger.dev/core";
-import { PrismaClient, Prisma, prisma } from "~/db.server";
-import { Organization } from "~/models/organization.server";
-import { Project } from "~/models/project.server";
-import { User } from "~/models/user.server";
-import { z } from "zod";
-import { projectPath } from "~/utils/pathBuilder";
-import { Job } from "@trigger.dev/database";
-import { BasePresenter } from "./v3/basePresenter.server";
-
-export class JobPresenter extends BasePresenter {
- public async call({
- userId,
- jobSlug,
- projectSlug,
- organizationSlug,
- }: {
- userId: User["id"];
- jobSlug: Job["slug"];
- projectSlug: Project["slug"];
- organizationSlug: Organization["slug"];
- }) {
- const job = await this._replica.job.findFirst({
- select: {
- id: true,
- slug: true,
- title: true,
- aliases: {
- select: {
- version: {
- select: {
- version: true,
- eventSpecification: true,
- properties: true,
- status: true,
- concurrencyLimit: true,
- concurrencyLimitGroup: {
- select: {
- name: true,
- concurrencyLimit: true,
- },
- },
- runs: {
- select: {
- createdAt: true,
- status: true,
- },
- take: 1,
- orderBy: [{ createdAt: "desc" }],
- },
- integrations: {
- select: {
- key: true,
- integration: {
- select: {
- slug: true,
- definition: true,
- setupStatus: true,
- },
- },
- },
- },
- triggerLink: true,
- triggerHelp: true,
- },
- },
- environment: {
- select: {
- type: true,
- orgMember: {
- select: {
- userId: true,
- },
- },
- },
- },
- },
- where: {
- name: "latest",
- },
- },
- dynamicTriggers: {
- select: {
- type: true,
- },
- },
- project: {
- select: {
- slug: true,
- },
- },
- },
- where: {
- slug: jobSlug,
- deletedAt: null,
- organization: {
- members: {
- some: {
- userId,
- },
- },
- },
- project: {
- slug: projectSlug,
- },
- },
- });
-
- if (!job) {
- return undefined;
- }
-
- //the best alias to select:
- // 1. Logged-in user dev
- // 2. Prod
- // 3. Any other user's dev
- const sortedAliases = job.aliases.sort((a, b) => {
- if (a.environment.type === "DEVELOPMENT" && a.environment.orgMember?.userId === userId) {
- return -1;
- }
-
- if (b.environment.type === "DEVELOPMENT" && b.environment.orgMember?.userId === userId) {
- return 1;
- }
-
- if (a.environment.type === "PRODUCTION") {
- return -1;
- }
-
- if (b.environment.type === "PRODUCTION") {
- return 1;
- }
-
- return 0;
- });
-
- const alias = sortedAliases.at(0);
-
- if (!alias) {
- throw new Error(`No aliases found for job ${job.id}, this should never happen.`);
- }
-
- const eventSpecification = EventSpecificationSchema.parse(alias.version.eventSpecification);
-
- const lastRuns = job.aliases
- .map((alias) => alias.version.runs.at(0))
- .filter(Boolean)
- .sort((a, b) => {
- return b.createdAt.getTime() - a.createdAt.getTime();
- });
-
- const lastRun = lastRuns.at(0);
-
- const integrations = alias.version.integrations.map((integration) => ({
- key: integration.key,
- title: integration.integration.slug,
- icon: integration.integration.definition.icon ?? integration.integration.definition.id,
- setupStatus: integration.integration.setupStatus,
- }));
-
- let properties: DisplayProperty[] = [];
-
- if (eventSpecification.properties) {
- properties = [...properties, ...eventSpecification.properties];
- }
-
- if (alias.version.properties) {
- const versionProperties = z.array(DisplayPropertySchema).parse(alias.version.properties);
- properties = [...properties, ...versionProperties];
- }
-
- const environments = job.aliases.map((alias) => ({
- type: alias.environment.type,
- enabled: alias.version.status === "ACTIVE",
- lastRun: alias.version.runs.at(0)?.createdAt,
- version: alias.version.version,
- concurrencyLimit: alias.version.concurrencyLimit,
- concurrencyLimitGroup: alias.version.concurrencyLimitGroup,
- }));
-
- const projectRootPath = projectPath({ slug: organizationSlug }, { slug: projectSlug });
-
- return {
- id: job.id,
- slug: job.slug,
- title: job.title,
- version: alias.version.version,
- status: alias.version.status,
- dynamic: job.dynamicTriggers.length > 0,
- event: {
- title: eventSpecification.title,
- icon: eventSpecification.icon,
- source: eventSpecification.source,
- link: alias.version.triggerLink
- ? `${projectRootPath}/${alias.version.triggerLink}`
- : undefined,
- },
- integrations,
- hasIntegrationsRequiringAction: integrations.some((i) => i.setupStatus === "MISSING_FIELDS"),
- lastRun,
- properties,
- environments,
- };
- }
-}
diff --git a/apps/webapp/app/presenters/OrgBillingPlanPresenter.ts b/apps/webapp/app/presenters/OrgBillingPlanPresenter.ts
deleted file mode 100644
index 7b3132cdb3..0000000000
--- a/apps/webapp/app/presenters/OrgBillingPlanPresenter.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { BillingService } from "../services/billing.v2.server";
-import { BasePresenter } from "./v3/basePresenter.server";
-
-export class OrgBillingPlanPresenter extends BasePresenter {
- public async call({ slug, isManagedCloud }: { slug: string; isManagedCloud: boolean }) {
- const billingPresenter = new BillingService(isManagedCloud);
- const plans = await billingPresenter.getPlans();
-
- if (plans === undefined) {
- return;
- }
-
- const organization = await this._replica.organization.findFirst({
- where: {
- slug,
- },
- });
-
- if (!organization) {
- return;
- }
-
- const maxConcurrency = await this._replica.$queryRaw<
- { organization_id: string; max_concurrent_runs: BigInt }[]
- >`WITH events AS (
- SELECT
- re.event_time,
- re.organization_id,
- re.event_type,
- SUM(re.event_type) OVER (PARTITION BY re.organization_id ORDER BY re.event_time) AS running_total
- FROM
- triggerdotdev_events.run_executions re
- WHERE
- re.organization_id = ${organization.id}
- AND re.event_time >= DATE_TRUNC('month',
- CURRENT_DATE)
- )
- SELECT
- organization_id, MAX(running_total) AS max_concurrent_runs
- FROM
- events
- GROUP BY
- organization_id;`;
-
- return {
- plans,
- maxConcurrency:
- maxConcurrency.at(0) !== undefined
- ? Number(maxConcurrency[0].max_concurrent_runs)
- : undefined,
- };
- }
-}
diff --git a/apps/webapp/app/presenters/OrgUsagePresenter.server.ts b/apps/webapp/app/presenters/OrgUsagePresenter.server.ts
deleted file mode 100644
index 4c64c520eb..0000000000
--- a/apps/webapp/app/presenters/OrgUsagePresenter.server.ts
+++ /dev/null
@@ -1,280 +0,0 @@
-import { estimate } from "@trigger.dev/platform/v2";
-import { sqlDatabaseSchema } from "~/db.server";
-import { featuresForRequest } from "~/features.server";
-import { BillingService } from "~/services/billing.v2.server";
-import { BasePresenter } from "./v3/basePresenter.server";
-
-export class OrgUsagePresenter extends BasePresenter {
- public async call({ userId, slug, request }: { userId: string; slug: string; request: Request }) {
- const organization = await this._replica.organization.findFirst({
- where: {
- slug,
- members: {
- some: {
- userId,
- },
- },
- },
- });
-
- if (!organization) {
- throw new Error("Organization not found");
- }
-
- // Get count of runs since the start of the current month
- const runsCount = await this._replica.jobRun.count({
- where: {
- organizationId: organization.id,
- createdAt: {
- gte: new Date(new Date().getFullYear(), new Date().getMonth(), 1),
- },
- internal: false,
- },
- });
-
- // Get the count of the runs for the last 6 months, by month. So for example we want the data shape to be:
- // [
- // { month: "2021-01", count: 10 },
- // { month: "2021-02", count: 20 },
- // { month: "2021-03", count: 30 },
- // { month: "2021-04", count: 40 },
- // { month: "2021-05", count: 50 },
- // { month: "2021-06", count: 60 },
- // ]
- // This will be used to generate the chart on the usage page
- // Use prisma queryRaw for this since prisma doesn't support grouping by month
- const monthlyRunsDataRaw = await this._replica.$queryRaw<
- {
- month: string;
- count: number;
- }[]
- >`SELECT TO_CHAR("createdAt", 'YYYY-MM') as month, COUNT(*) as count FROM ${sqlDatabaseSchema}."JobRun" WHERE "organizationId" = ${organization.id} AND "createdAt" >= NOW() - INTERVAL '6 months' AND "internal" = FALSE GROUP BY month ORDER BY month ASC`;
-
- const hasMonthlyRunData = monthlyRunsDataRaw.length > 0;
- const monthlyRunsData = monthlyRunsDataRaw.map((obj) => ({
- name: obj.month,
- total: Number(obj.count), // Convert BigInt to Number
- }));
-
- const monthlyRunsDataDisplay = fillInMissingRunMonthlyData(monthlyRunsData, 6);
-
- // Max concurrency each day over past 30 days
- const concurrencyChartRawData = await this._replica.$queryRaw<
- { day: Date; max_concurrent_runs: BigInt }[]
- >`
- WITH time_boundaries AS (
- SELECT generate_series(
- NOW() - interval '30 days',
- NOW(),
- interval '1 day'
- ) AS day_start
- ),
- events AS (
- SELECT
- day_start,
- event_time,
- event_type,
- SUM(event_type) OVER (ORDER BY event_time) AS running_total
- FROM
- time_boundaries
- JOIN
- triggerdotdev_events.run_executions
- ON
- event_time >= day_start AND event_time < day_start + interval '1 day'
- WHERE triggerdotdev_events.run_executions.organization_id = ${organization.id}
- ),
- max_concurrent_per_day AS (
- SELECT
- date_trunc('day', event_time) AS day,
- MAX(running_total) AS max_concurrent_runs
- FROM
- events
- GROUP BY day
- )
- SELECT
- day,
- max_concurrent_runs
- FROM
- max_concurrent_per_day
- ORDER BY
- day;`;
-
- const ThirtyDaysAgo = new Date();
- ThirtyDaysAgo.setDate(ThirtyDaysAgo.getDate() - 30);
- ThirtyDaysAgo.setUTCHours(0, 0, 0, 0);
-
- const hasConcurrencyData = concurrencyChartRawData.length > 0;
- const concurrencyChartRawDataFilledIn = fillInMissingConcurrencyDays(
- ThirtyDaysAgo,
- 31,
- concurrencyChartRawData
- );
-
- const dailyRunsRawData = await this._replica.$queryRaw<
- { day: Date; runs: BigInt }[]
- >`SELECT date_trunc('day', "createdAt") as day, COUNT(*) as runs FROM ${sqlDatabaseSchema}."JobRun" WHERE "organizationId" = ${organization.id} AND "createdAt" >= NOW() - INTERVAL '30 days' AND "internal" = FALSE GROUP BY day`;
-
- const hasDailyRunsData = dailyRunsRawData.length > 0;
- const dailyRunsDataFilledIn = fillInMissingDailyRuns(ThirtyDaysAgo, 31, dailyRunsRawData);
-
- const endOfMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1);
- endOfMonth.setDate(endOfMonth.getDate() - 1);
- const projectedRunsCount = Math.round(
- runsCount / (new Date().getDate() / endOfMonth.getDate())
- );
-
- const { isManagedCloud } = featuresForRequest(request);
- const billingPresenter = new BillingService(isManagedCloud);
- const plans = await billingPresenter.getPlans();
-
- let runCostEstimation: number | undefined = undefined;
- let projectedRunCostEstimation: number | undefined = undefined;
-
- if (plans) {
- const estimationResult = estimate({
- usage: { runs: runsCount },
- plans: [plans.free, plans.paid],
- });
- runCostEstimation = estimationResult?.cost.runsCost;
-
- const projectedEstimationResult = estimate({
- usage: { runs: projectedRunsCount },
- plans: [plans.free, plans.paid],
- });
- projectedRunCostEstimation = projectedEstimationResult?.cost.runsCost;
- }
-
- const periodStart = new Date();
- periodStart.setDate(1);
- periodStart.setUTCHours(0, 0, 0, 0);
-
- const periodEnd = new Date();
- periodEnd.setDate(1);
- periodEnd.setMonth(periodEnd.getMonth() + 1);
- periodEnd.setUTCHours(0, 0, 0, 0);
-
- return {
- id: organization.id,
- runsCount,
- projectedRunsCount,
- monthlyRunsData: monthlyRunsDataDisplay,
- hasMonthlyRunData,
- concurrencyData: concurrencyChartRawDataFilledIn,
- hasConcurrencyData,
- dailyRunsData: dailyRunsDataFilledIn,
- hasDailyRunsData,
- runCostEstimation,
- projectedRunCostEstimation,
- periodStart,
- periodEnd,
- };
- }
-}
-
-// This will fill in missing chart data with zeros
-// So for example, if data is [{ name: "2021-01", total: 10 }, { name: "2021-03", total: 30 }] and the totalNumberOfMonths is 6
-// And the current month is "2021-04", then this function will return:
-// [{ name: "2020-11", total: 0 }, { name: "2020-12", total: 0 }, { name: "2021-01", total: 10 }, { name: "2021-02", total: 0 }, { name: "2021-03", total: 30 }, { name: "2021-04", total: 0 }]
-function fillInMissingRunMonthlyData(
- data: Array<{ name: string; total: number }>,
- totalNumberOfMonths: number
-): Array<{ name: string; total: number }> {
- const currentMonth = new Date().toISOString().slice(0, 7);
-
- const startMonth = new Date(
- new Date(currentMonth).getFullYear(),
- new Date(currentMonth).getMonth() - (totalNumberOfMonths - 2),
- 1
- )
- .toISOString()
- .slice(0, 7);
-
- const months = getMonthsBetween(startMonth, currentMonth);
-
- let completeData = months.map((month) => {
- let foundData = data.find((d) => d.name === month);
- return foundData ? { ...foundData } : { name: month, total: 0 };
- });
-
- return completeData;
-}
-
-function fillInMissingConcurrencyDays(
- startDate: Date,
- days: number,
- data: Array<{ day: Date; max_concurrent_runs: BigInt }>
-) {
- const outputData: Array<{ date: Date; maxConcurrentRuns: number }> = [];
- for (let i = 0; i < days; i++) {
- const date = new Date(startDate);
- date.setDate(date.getDate() + i);
-
- const foundData = data.find((d) => d.day.toISOString() === date.toISOString());
- if (!foundData) {
- outputData.push({
- date,
- maxConcurrentRuns: 0,
- });
- } else {
- outputData.push({
- date,
- maxConcurrentRuns: Number(foundData.max_concurrent_runs),
- });
- }
- }
-
- return outputData;
-}
-
-function fillInMissingDailyRuns(
- startDate: Date,
- days: number,
- data: Array<{ day: Date; runs: BigInt }>
-) {
- const outputData: Array<{ date: Date; runs: number }> = [];
- for (let i = 0; i < days; i++) {
- const date = new Date(startDate);
- date.setDate(date.getDate() + i);
-
- const foundData = data.find((d) => d.day.toISOString() === date.toISOString());
- if (!foundData) {
- outputData.push({
- date,
- runs: 0,
- });
- } else {
- outputData.push({
- date,
- runs: Number(foundData.runs),
- });
- }
- }
-
- return outputData;
-}
-
-// Start month will be like 2023-03 and endMonth will be like 2023-10
-// The result should be an array of months between these two months, including the start and end month
-// So for example, if startMonth is 2023-03 and endMonth is 2023-10, the result should be:
-// ["2023-03", "2023-04", "2023-05", "2023-06", "2023-07", "2023-08", "2023-09", "2023-10"]
-function getMonthsBetween(startMonth: string, endMonth: string): string[] {
- // Initialize result array
- const result: string[] = [];
-
- // Parse the year and month from startMonth and endMonth
- let [startYear, startMonthNum] = startMonth.split("-").map(Number);
- let [endYear, endMonthNum] = endMonth.split("-").map(Number);
-
- // Loop through each month between startMonth and endMonth
- for (let year = startYear; year <= endYear; year++) {
- let monthStart = year === startYear ? startMonthNum : 1;
- let monthEnd = year === endYear ? endMonthNum : 12;
-
- for (let month = monthStart; month <= monthEnd; month++) {
- // Format the month into a string and add it to the result array
- result.push(`${year}-${String(month).padStart(2, "0")}`);
- }
- }
-
- return result;
-}
diff --git a/apps/webapp/app/presenters/RunListPresenter.server.ts b/apps/webapp/app/presenters/RunListPresenter.server.ts
deleted file mode 100644
index 91c1e5a98f..0000000000
--- a/apps/webapp/app/presenters/RunListPresenter.server.ts
+++ /dev/null
@@ -1,196 +0,0 @@
-import {
- Direction,
- FilterableEnvironment,
- FilterableStatus,
- filterableStatuses,
-} from "~/components/runs/RunStatuses";
-import { getUsername } from "~/utils/username";
-import { BasePresenter } from "./v3/basePresenter.server";
-
-type RunListOptions = {
- userId: string;
- eventId?: string;
- jobSlug?: string;
- organizationSlug: string;
- projectSlug: string;
- direction?: Direction;
- filterStatus?: FilterableStatus;
- filterEnvironment?: FilterableEnvironment;
- cursor?: string;
- pageSize?: number;
- from?: number;
- to?: number;
-};
-
-const DEFAULT_PAGE_SIZE = 20;
-
-export type RunList = Awaited>;
-
-export class RunListPresenter extends BasePresenter {
- public async call({
- userId,
- eventId,
- jobSlug,
- organizationSlug,
- projectSlug,
- filterEnvironment,
- filterStatus,
- direction = "forward",
- cursor,
- pageSize = DEFAULT_PAGE_SIZE,
- from,
- to,
- }: RunListOptions) {
- const filterStatuses = filterStatus ? filterableStatuses[filterStatus] : undefined;
-
- const directionMultiplier = direction === "forward" ? 1 : -1;
-
- // Find the organization that the user is a member of
- const organization = await this._replica.organization.findFirstOrThrow({
- select: {
- id: true,
- },
- where: {
- slug: organizationSlug,
- members: { some: { userId } },
- },
- });
-
- // Find the project scoped to the organization
- const project = await this._replica.project.findFirstOrThrow({
- select: {
- id: true,
- },
- where: {
- slug: projectSlug,
- organizationId: organization.id,
- },
- });
-
- const job = jobSlug
- ? await this._replica.job.findFirstOrThrow({
- where: {
- slug: jobSlug,
- projectId: project.id,
- },
- })
- : undefined;
-
- const event = eventId
- ? await this._replica.eventRecord.findUnique({ where: { id: eventId } })
- : undefined;
-
- const runs = await this._replica.jobRun.findMany({
- select: {
- id: true,
- number: true,
- startedAt: true,
- completedAt: true,
- createdAt: true,
- executionDuration: true,
- isTest: true,
- status: true,
- environment: {
- select: {
- type: true,
- slug: true,
- orgMember: {
- select: {
- user: {
- select: {
- id: true,
- name: true,
- displayName: true,
- },
- },
- },
- },
- },
- },
- version: {
- select: {
- version: true,
- },
- },
- job: {
- select: {
- slug: true,
- title: true,
- },
- },
- },
- where: {
- eventId: event?.id,
- jobId: job?.id,
- projectId: project.id,
- organizationId: organization.id,
- status: filterStatuses ? { in: filterStatuses } : undefined,
- environment: filterEnvironment ? { type: filterEnvironment } : undefined,
- startedAt: {
- gte: from ? new Date(from).toISOString() : undefined,
- lte: to ? new Date(to).toISOString() : undefined,
- },
- },
- orderBy: [{ id: "desc" }],
- //take an extra record to tell if there are more
- take: directionMultiplier * (pageSize + 1),
- //skip the cursor if there is one
- skip: cursor ? 1 : 0,
- cursor: cursor
- ? {
- id: cursor,
- }
- : undefined,
- });
-
- const hasMore = runs.length > pageSize;
-
- //get cursors for next and previous pages
- let next: string | undefined;
- let previous: string | undefined;
- switch (direction) {
- case "forward":
- previous = cursor ? runs.at(0)?.id : undefined;
- if (hasMore) {
- next = runs[pageSize - 1]?.id;
- }
- break;
- case "backward":
- if (hasMore) {
- previous = runs[1]?.id;
- next = runs[pageSize]?.id;
- } else {
- next = runs[pageSize - 1]?.id;
- }
- break;
- }
-
- const runsToReturn =
- direction === "backward" && hasMore ? runs.slice(1, pageSize + 1) : runs.slice(0, pageSize);
-
- return {
- runs: runsToReturn.map((run) => ({
- id: run.id,
- number: run.number,
- startedAt: run.startedAt,
- completedAt: run.completedAt,
- createdAt: run.createdAt,
- executionDuration: run.executionDuration,
- isTest: run.isTest,
- status: run.status,
- version: run.version?.version ?? "unknown",
- environment: {
- type: run.environment.type,
- slug: run.environment.slug,
- userId: run.environment.orgMember?.user.id,
- userName: getUsername(run.environment.orgMember?.user),
- },
- job: run.job,
- })),
- pagination: {
- next,
- previous,
- },
- };
- }
-}
diff --git a/apps/webapp/app/presenters/RunPresenter.server.ts b/apps/webapp/app/presenters/RunPresenter.server.ts
deleted file mode 100644
index 34a1e6f59a..0000000000
--- a/apps/webapp/app/presenters/RunPresenter.server.ts
+++ /dev/null
@@ -1,242 +0,0 @@
-import {
- ErrorWithStack,
- ErrorWithStackSchema,
- EventSpecificationSchema,
- StyleSchema,
-} from "@trigger.dev/core";
-import { $replica, PrismaClient, prisma } from "~/db.server";
-import { isRunCompleted, runBasicStatus } from "~/models/jobRun.server";
-import { mergeProperties } from "~/utils/mergeProperties.server";
-import { taskListToTree } from "~/utils/taskListToTree";
-import { getUsername } from "~/utils/username";
-
-type RunOptions = {
- id: string;
- userId: string;
-};
-
-export type ViewRun = NonNullable>>;
-export type ViewTask = NonNullable>>["tasks"][number];
-export type ViewEvent = NonNullable>>["event"];
-
-type QueryEvent = NonNullable>>["event"];
-type QueryTask = NonNullable>>["tasks"][number];
-
-export class RunPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({ id, userId }: RunOptions) {
- const run = await this.query({ id, userId });
-
- if (!run) {
- return undefined;
- }
-
- const eventSpecification = EventSpecificationSchema.parse(run.version.eventSpecification);
-
- const runProperties = mergeProperties(
- run.version.properties,
- run.properties,
- eventSpecification.properties
- );
-
- //enrich tasks then group subtasks under their parents
- const enrichedTasks = this.enrichTasks(run.tasks);
- const tasks = taskListToTree(enrichedTasks);
-
- let runError: ErrorWithStack | undefined = undefined;
- let runOutput: string | null | undefined = run.output
- ? JSON.stringify(run.output, null, 2)
- : null;
-
- if (run.status === "FAILURE") {
- const error = ErrorWithStackSchema.safeParse(run.output);
-
- if (error.success) {
- runError = error.data;
- runOutput = null;
- } else {
- runError = { message: "Unknown error" };
- runOutput = null;
- }
- }
-
- return {
- id: run.id,
- number: run.number,
- status: run.status,
- basicStatus: runBasicStatus(run.status),
- isFinished: isRunCompleted(run.status),
- startedAt: run.startedAt,
- completedAt: run.completedAt,
- isTest: run.isTest,
- version: run.version.version,
- output: runOutput,
- properties: runProperties,
- environment: {
- type: run.environment.type,
- slug: run.environment.slug,
- userId: run.environment.orgMember?.user.id,
- userName: getUsername(run.environment.orgMember?.user),
- },
- event: this.#prepareEventData(run.event),
- tasks,
- runConnections: run.runConnections,
- missingConnections: run.missingConnections,
- error: runError,
- executionDuration: run.executionDuration,
- executionCount: run.executionCount,
- };
- }
-
- #prepareEventData(event: QueryEvent) {
- return {
- id: event.eventId,
- name: event.name,
- payload: JSON.stringify(event.payload),
- context: JSON.stringify(event.context),
- timestamp: event.timestamp,
- deliveredAt: event.deliveredAt,
- externalAccount: event.externalAccount
- ? {
- identifier: event.externalAccount.identifier,
- }
- : undefined,
- };
- }
-
- query({ id, userId }: RunOptions) {
- return $replica.jobRun.findFirst({
- select: {
- id: true,
- number: true,
- status: true,
- startedAt: true,
- completedAt: true,
- isTest: true,
- properties: true,
- output: true,
- executionCount: true,
- executionDuration: true,
- version: {
- select: {
- version: true,
- properties: true,
- eventSpecification: true,
- },
- },
- environment: {
- select: {
- type: true,
- slug: true,
- orgMember: {
- select: {
- user: {
- select: {
- id: true,
- name: true,
- displayName: true,
- },
- },
- },
- },
- },
- },
- event: {
- select: {
- eventId: true,
- name: true,
- payload: true,
- context: true,
- timestamp: true,
- deliveredAt: true,
- externalAccount: {
- select: {
- identifier: true,
- },
- },
- },
- },
- tasks: {
- select: {
- id: true,
- displayKey: true,
- name: true,
- icon: true,
- status: true,
- delayUntil: true,
- description: true,
- properties: true,
- outputProperties: true,
- error: true,
- startedAt: true,
- completedAt: true,
- style: true,
- parentId: true,
- noop: true,
- runConnection: {
- select: {
- integration: {
- select: {
- definitionId: true,
- title: true,
- slug: true,
- definition: {
- select: {
- icon: true,
- },
- },
- },
- },
- },
- },
- },
- orderBy: {
- createdAt: "asc",
- },
- take: 1000,
- },
- runConnections: {
- select: {
- id: true,
- key: true,
- integration: {
- select: {
- title: true,
- slug: true,
- description: true,
- scopes: true,
- definition: true,
- },
- },
- },
- },
- missingConnections: true,
- },
- where: {
- id,
- organization: {
- members: {
- some: {
- userId,
- },
- },
- },
- },
- });
- }
-
- enrichTasks(tasks: QueryTask[]) {
- return tasks.map((task) => ({
- ...task,
- error: task.error ? ErrorWithStackSchema.parse(task.error) : undefined,
- connection: task.runConnection,
- properties: mergeProperties(task.properties, task.outputProperties),
- style: task.style ? StyleSchema.parse(task.style) : undefined,
- }));
- }
-}
diff --git a/apps/webapp/app/presenters/RunStreamPresenter.server.ts b/apps/webapp/app/presenters/RunStreamPresenter.server.ts
deleted file mode 100644
index f7c943dcca..0000000000
--- a/apps/webapp/app/presenters/RunStreamPresenter.server.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { JobRun } from "@trigger.dev/database";
-import { PrismaClient, prisma } from "~/db.server";
-import { sse } from "~/utils/sse.server";
-
-export class RunStreamPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({ request, runId }: { request: Request; runId: JobRun["id"] }) {
- const run = await this.#runForUpdates(runId);
-
- if (!run) {
- return new Response("Not found", { status: 404 });
- }
-
- let lastUpdatedAt: number = run.updatedAt.getTime();
- let lastTotalTaskUpdatedTime = run.tasks.reduce(
- (prev, task) => prev + task.updatedAt.getTime(),
- 0
- );
-
- return sse({
- request,
- run: async (send, stop) => {
- const result = await this.#runForUpdates(runId);
- if (!result) {
- return stop();
- }
-
- if (result.completedAt) {
- send({ data: new Date().toISOString() });
- return stop();
- }
-
- const totalRunUpdated = result.tasks.reduce(
- (prev, task) => prev + task.updatedAt.getTime(),
- 0
- );
-
- if (lastUpdatedAt !== result.updatedAt.getTime()) {
- send({ data: result.updatedAt.toISOString() });
- } else if (lastTotalTaskUpdatedTime !== totalRunUpdated) {
- send({ data: new Date().toISOString() });
- }
-
- lastUpdatedAt = result.updatedAt.getTime();
- lastTotalTaskUpdatedTime = totalRunUpdated;
- },
- });
- }
-
- #runForUpdates(id: string) {
- return this.#prismaClient.jobRun.findUnique({
- where: {
- id,
- },
- select: {
- updatedAt: true,
- completedAt: true,
- tasks: {
- select: {
- updatedAt: true,
- },
- },
- },
- });
- }
-}
diff --git a/apps/webapp/app/presenters/ScheduledTriggersPresenter.server.ts b/apps/webapp/app/presenters/ScheduledTriggersPresenter.server.ts
deleted file mode 100644
index bf72bdd675..0000000000
--- a/apps/webapp/app/presenters/ScheduledTriggersPresenter.server.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import { ScheduleMetadataSchema } from "@trigger.dev/core";
-import { User } from "@trigger.dev/database";
-import { Organization } from "~/models/organization.server";
-import { Project } from "~/models/project.server";
-import { calculateNextScheduledEvent } from "~/services/schedules/nextScheduledEvent.server";
-import { BasePresenter } from "./v3/basePresenter.server";
-
-const DEFAULT_PAGE_SIZE = 20;
-
-export class ScheduledTriggersPresenter extends BasePresenter {
- public async call({
- userId,
- projectSlug,
- organizationSlug,
- direction = "forward",
- pageSize = DEFAULT_PAGE_SIZE,
- cursor,
- }: {
- userId: User["id"];
- projectSlug: Project["slug"];
- organizationSlug: Organization["slug"];
- direction?: "forward" | "backward";
- pageSize?: number;
- cursor?: string;
- }) {
- const organization = await this._replica.organization.findFirstOrThrow({
- select: {
- id: true,
- },
- where: {
- slug: organizationSlug,
- members: { some: { userId } },
- },
- });
-
- // Find the project scoped to the organization
- const project = await this._replica.project.findFirstOrThrow({
- select: {
- id: true,
- },
- where: {
- slug: projectSlug,
- organizationId: organization.id,
- },
- });
-
- const directionMultiplier = direction === "forward" ? 1 : -1;
-
- const scheduled = await this._replica.scheduleSource.findMany({
- select: {
- id: true,
- key: true,
- active: true,
- schedule: true,
- lastEventTimestamp: true,
- environment: {
- select: {
- type: true,
- },
- },
- createdAt: true,
- updatedAt: true,
- metadata: true,
- dynamicTrigger: true,
- },
- where: {
- environment: {
- OR: [
- {
- orgMember: null,
- },
- {
- orgMember: {
- userId,
- },
- },
- ],
- projectId: project.id,
- },
- },
- orderBy: [{ id: "desc" }],
- //take an extra record to tell if there are more
- take: directionMultiplier * (pageSize + 1),
- //skip the cursor if there is one
- skip: cursor ? 1 : 0,
- cursor: cursor
- ? {
- id: cursor,
- }
- : undefined,
- });
-
- const hasMore = scheduled.length > pageSize;
-
- //get cursors for next and previous pages
- let next: string | undefined;
- let previous: string | undefined;
- switch (direction) {
- case "forward":
- previous = cursor ? scheduled.at(0)?.id : undefined;
- if (hasMore) {
- next = scheduled[pageSize - 1]?.id;
- }
- break;
- case "backward":
- if (hasMore) {
- previous = scheduled[1]?.id;
- next = scheduled[pageSize]?.id;
- } else {
- next = scheduled[pageSize - 1]?.id;
- }
- break;
- }
-
- const scheduledToReturn =
- direction === "backward" && hasMore
- ? scheduled.slice(1, pageSize + 1)
- : scheduled.slice(0, pageSize);
-
- return {
- scheduled: scheduledToReturn.map((s) => {
- const schedule = ScheduleMetadataSchema.parse(s.schedule);
- const nextEventTimestamp = s.active
- ? calculateNextScheduledEvent(schedule, s.lastEventTimestamp)
- : undefined;
-
- return {
- ...s,
- schedule,
- nextEventTimestamp,
- };
- }),
- pagination: {
- next,
- previous,
- },
- };
- }
-}
diff --git a/apps/webapp/app/presenters/SelectBestProjectPresenter.server.ts b/apps/webapp/app/presenters/SelectBestProjectPresenter.server.ts
index e87239cc53..32f666b077 100644
--- a/apps/webapp/app/presenters/SelectBestProjectPresenter.server.ts
+++ b/apps/webapp/app/presenters/SelectBestProjectPresenter.server.ts
@@ -22,7 +22,7 @@ export class SelectBestProjectPresenter {
}
}
- //failing that, we pick the project with the most jobs
+ //failing that, we pick the most recently modified project
const projects = await this.#prismaClient.project.findMany({
include: {
organization: true,
@@ -34,9 +34,7 @@ export class SelectBestProjectPresenter {
},
},
orderBy: {
- jobs: {
- _count: "desc",
- },
+ updatedAt: "desc",
},
take: 1,
});
diff --git a/apps/webapp/app/presenters/TaskDetailsPresenter.server.ts b/apps/webapp/app/presenters/TaskDetailsPresenter.server.ts
deleted file mode 100644
index 483f3934f3..0000000000
--- a/apps/webapp/app/presenters/TaskDetailsPresenter.server.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { RedactSchema, StyleSchema } from "@trigger.dev/core";
-import { $replica, PrismaClient, prisma } from "~/db.server";
-import { mergeProperties } from "~/utils/mergeProperties.server";
-import { Redactor } from "~/utils/redactor";
-
-type DetailsProps = {
- id: string;
- userId: string;
-};
-
-export class TaskDetailsPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({ id, userId }: DetailsProps) {
- const task = await $replica.task.findFirst({
- select: {
- id: true,
- displayKey: true,
- runConnection: {
- select: {
- id: true,
- key: true,
- connection: {
- select: {
- metadata: true,
- connectionType: true,
- integration: {
- select: {
- title: true,
- slug: true,
- description: true,
- scopes: true,
- definition: true,
- authMethod: {
- select: {
- type: true,
- name: true,
- },
- },
- },
- },
- },
- },
- },
- },
- name: true,
- icon: true,
- status: true,
- delayUntil: true,
- noop: true,
- description: true,
- properties: true,
- outputProperties: true,
- params: true,
- output: true,
- outputIsUndefined: true,
- error: true,
- startedAt: true,
- completedAt: true,
- style: true,
- parentId: true,
- redact: true,
- attempts: {
- select: {
- number: true,
- status: true,
- error: true,
- runAt: true,
- updatedAt: true,
- },
- orderBy: {
- number: "asc",
- },
- },
- },
- where: {
- id,
- },
- });
-
- if (!task) {
- return undefined;
- }
-
- return {
- ...task,
- redact: undefined,
- output: JSON.stringify(
- this.#stringifyOutputWithRedactions(task.output, task.redact),
- null,
- 2
- ),
- connection: task.runConnection,
- params: task.params as Record,
- properties: mergeProperties(task.properties, task.outputProperties),
- style: task.style ? StyleSchema.parse(task.style) : undefined,
- };
- }
-
- #stringifyOutputWithRedactions(output: any, redact: unknown): any {
- if (!output) {
- return output;
- }
-
- const parsedRedact = RedactSchema.safeParse(redact);
-
- if (!parsedRedact.success) {
- return output;
- }
-
- const paths = parsedRedact.data.paths;
-
- const redactor = new Redactor(paths);
-
- return redactor.redact(output);
- }
-}
diff --git a/apps/webapp/app/presenters/TestJobPresenter.server.ts b/apps/webapp/app/presenters/TestJobPresenter.server.ts
deleted file mode 100644
index cb66659943..0000000000
--- a/apps/webapp/app/presenters/TestJobPresenter.server.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-import { User } from "@trigger.dev/database";
-import { replacements } from "@trigger.dev/core";
-import { PrismaClient, prisma } from "~/db.server";
-import { Job } from "~/models/job.server";
-import { Organization } from "~/models/organization.server";
-import { Project } from "~/models/project.server";
-import { EventExample } from "@trigger.dev/core";
-
-export class TestJobPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- organizationSlug,
- projectSlug,
- jobSlug,
- }: {
- userId: User["id"];
- organizationSlug: Organization["slug"];
- projectSlug: Project["slug"];
- jobSlug: Job["slug"];
- }) {
- const job = await this.#prismaClient.job.findFirst({
- select: {
- aliases: {
- select: {
- version: {
- select: {
- id: true,
- version: true,
- examples: {
- select: {
- id: true,
- name: true,
- icon: true,
- payload: true,
- },
- },
- integrations: {
- select: {
- integration: {
- select: {
- authSource: true,
- },
- },
- },
- },
- },
- },
- environment: {
- select: {
- id: true,
- type: true,
- slug: true,
- orgMember: {
- select: {
- userId: true,
- },
- },
- },
- },
- },
- where: {
- name: "latest",
- environment: {
- OR: [
- {
- orgMember: null,
- },
- {
- orgMember: {
- userId,
- },
- },
- ],
- },
- },
- },
- runs: {
- select: {
- id: true,
- createdAt: true,
- number: true,
- status: true,
- event: {
- select: {
- payload: true,
- },
- },
- },
- orderBy: {
- createdAt: "desc",
- },
- take: 5,
- },
- },
- where: {
- organization: {
- slug: organizationSlug,
- members: {
- some: {
- userId,
- },
- },
- },
- project: {
- slug: projectSlug,
- },
- slug: jobSlug,
- },
- });
-
- if (!job) {
- throw new Error("Job not found");
- }
-
- //collect together the examples, we don't care about the environments
- const examples = job.aliases.flatMap((alias) =>
- alias.version.examples.map((example) => ({
- ...example,
- icon: example.icon ?? undefined,
- payload: example.payload ? JSON.stringify(example.payload, exampleReplacer, 2) : undefined,
- }))
- );
-
- return {
- environments: job.aliases.map((alias) => ({
- id: alias.environment.id,
- type: alias.environment.type,
- slug: alias.environment.slug,
- userId: alias.environment.orgMember?.userId,
- versionId: alias.version.id,
- hasAuthResolver: alias.version.integrations.some(
- (i) => i.integration.authSource === "RESOLVER"
- ),
- })),
- examples,
- runs: job.runs.map((r) => ({
- id: r.id,
- number: r.number,
- status: r.status,
- created: r.createdAt,
- payload: r.event.payload ? JSON.stringify(r.event.payload, null, 2) : undefined,
- })),
- };
- }
-}
-
-function exampleReplacer(key: string, value: any) {
- replacements.forEach((replacement) => {
- if (value === replacement.marker) {
- value = replacement.replace({
- match: {
- key,
- value,
- },
- data: { now: new Date() },
- });
- }
- });
-
- return value;
-}
diff --git a/apps/webapp/app/presenters/TriggerDetailsPresenter.server.ts b/apps/webapp/app/presenters/TriggerDetailsPresenter.server.ts
deleted file mode 100644
index 7a93e77ac9..0000000000
--- a/apps/webapp/app/presenters/TriggerDetailsPresenter.server.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { PrismaClient, prisma } from "~/db.server";
-
-export type DetailedEvent = NonNullable>>;
-
-export class TriggerDetailsPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call(runId: string) {
- const { event } = await this.#prismaClient.jobRun.findUniqueOrThrow({
- where: {
- id: runId,
- },
- select: {
- event: {
- select: {
- eventId: true,
- name: true,
- payload: true,
- context: true,
- timestamp: true,
- deliveredAt: true,
- externalAccount: {
- select: {
- identifier: true,
- },
- },
- },
- },
- },
- });
-
- return {
- id: event.eventId,
- name: event.name,
- payload: JSON.stringify(event.payload, null, 2),
- context: JSON.stringify(event.context, null, 2),
- timestamp: event.timestamp,
- deliveredAt: event.deliveredAt,
- externalAccount: event.externalAccount
- ? {
- identifier: event.externalAccount.identifier,
- }
- : undefined,
- };
- }
-}
diff --git a/apps/webapp/app/presenters/TriggerSourcePresenter.server.ts b/apps/webapp/app/presenters/TriggerSourcePresenter.server.ts
deleted file mode 100644
index 12552d6848..0000000000
--- a/apps/webapp/app/presenters/TriggerSourcePresenter.server.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import { TriggerSource, User } from "@trigger.dev/database";
-import { PrismaClient, prisma } from "~/db.server";
-import { Organization } from "~/models/organization.server";
-import { Project } from "~/models/project.server";
-import { RunList, RunListPresenter } from "./RunListPresenter.server";
-import { Direction } from "~/components/runs/RunStatuses";
-
-export class TriggerSourcePresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- projectSlug,
- organizationSlug,
- triggerSourceId,
- direction = "forward",
- cursor,
- }: {
- userId: User["id"];
- projectSlug: Project["slug"];
- organizationSlug: Organization["slug"];
- triggerSourceId: TriggerSource["id"];
- direction?: Direction;
- cursor?: string;
- }) {
- const trigger = await this.#prismaClient.triggerSource.findUnique({
- select: {
- id: true,
- active: true,
- integration: {
- select: {
- id: true,
- title: true,
- slug: true,
- definitionId: true,
- setupStatus: true,
- definition: {
- select: {
- icon: true,
- },
- },
- },
- },
- environment: {
- select: {
- type: true,
- },
- },
- createdAt: true,
- updatedAt: true,
- params: true,
- sourceRegistrationJob: {
- select: {
- job: {
- select: {
- id: true,
- slug: true,
- },
- },
- },
- },
- dynamicTrigger: {
- select: {
- id: true,
- slug: true,
- sourceRegistrationJob: {
- select: {
- job: {
- select: {
- id: true,
- slug: true,
- },
- },
- },
- },
- },
- },
- },
- where: {
- id: triggerSourceId,
- },
- });
-
- if (!trigger) {
- throw new Error("Trigger source not found");
- }
-
- const runListPresenter = new RunListPresenter(this.#prismaClient);
- const jobSlug = getJobSlug(
- trigger.sourceRegistrationJob?.job.slug,
- trigger.dynamicTrigger?.sourceRegistrationJob?.job.slug
- );
-
- const runList = jobSlug
- ? await runListPresenter.call({
- userId,
- jobSlug,
- organizationSlug,
- projectSlug,
- direction,
- cursor,
- })
- : undefined;
-
- return {
- trigger: {
- id: trigger.id,
- active: trigger.active,
- integration: trigger.integration,
- environment: trigger.environment,
- createdAt: trigger.createdAt,
- updatedAt: trigger.updatedAt,
- params: trigger.params,
- registrationJob: trigger.sourceRegistrationJob?.job,
- runList,
- dynamic: trigger.dynamicTrigger
- ? { id: trigger.dynamicTrigger.id, slug: trigger.dynamicTrigger.slug }
- : undefined,
- },
- };
- }
-}
-
-function getJobSlug(
- sourceRegistrationJobSlug: string | undefined,
- dynamicSourceRegistrationJobSlug: string | undefined
-) {
- if (sourceRegistrationJobSlug) {
- return sourceRegistrationJobSlug;
- }
-
- return dynamicSourceRegistrationJobSlug;
-}
diff --git a/apps/webapp/app/presenters/TriggersPresenter.server.ts b/apps/webapp/app/presenters/TriggersPresenter.server.ts
deleted file mode 100644
index 8c13e92e30..0000000000
--- a/apps/webapp/app/presenters/TriggersPresenter.server.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { User } from "@trigger.dev/database";
-import { PrismaClient, prisma } from "~/db.server";
-import { Organization } from "~/models/organization.server";
-import { Project } from "~/models/project.server";
-
-export class TriggersPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- projectSlug,
- organizationSlug,
- }: {
- userId: User["id"];
- projectSlug: Project["slug"];
- organizationSlug: Organization["slug"];
- }) {
- const triggers = await this.#prismaClient.triggerSource.findMany({
- select: {
- id: true,
- active: true,
- dynamicTrigger: {
- select: {
- id: true,
- slug: true,
- },
- },
- integration: {
- select: {
- id: true,
- title: true,
- slug: true,
- definitionId: true,
- setupStatus: true,
- definition: {
- select: {
- icon: true,
- },
- },
- },
- },
- environment: {
- select: {
- type: true,
- },
- },
- createdAt: true,
- updatedAt: true,
- params: true,
- registrations: true,
- sourceRegistrationJob: true,
- },
- where: {
- organization: {
- slug: organizationSlug,
- members: {
- some: {
- userId,
- },
- },
- },
- environment: {
- OR: [
- {
- orgMember: null,
- },
- {
- orgMember: {
- userId,
- },
- },
- ],
- },
- project: {
- slug: projectSlug,
- },
- },
- });
-
- return {
- triggers,
- };
- }
-}
diff --git a/apps/webapp/app/presenters/WebhookDeliveryListPresenter.server.ts b/apps/webapp/app/presenters/WebhookDeliveryListPresenter.server.ts
deleted file mode 100644
index c83a7aeb42..0000000000
--- a/apps/webapp/app/presenters/WebhookDeliveryListPresenter.server.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import { Direction } from "~/components/runs/RunStatuses";
-import { PrismaClient, prisma } from "~/db.server";
-
-type RunListOptions = {
- userId: string;
- webhookId: string;
- direction?: Direction;
- cursor?: string;
-};
-
-const PAGE_SIZE = 20;
-
-export type WebhookDeliveryList = Awaited>;
-
-export class WebhookDeliveryListPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({ userId, webhookId, direction = "forward", cursor }: RunListOptions) {
- const directionMultiplier = direction === "forward" ? 1 : -1;
-
- const runs = await this.#prismaClient.webhookRequestDelivery.findMany({
- select: {
- id: true,
- number: true,
- createdAt: true,
- deliveredAt: true,
- verified: true,
- error: true,
- environment: {
- select: {
- type: true,
- slug: true,
- orgMember: {
- select: {
- userId: true,
- },
- },
- },
- },
- },
- where: {
- webhookId,
- environment: {
- OR: [
- {
- orgMember: null,
- },
- {
- orgMember: {
- userId,
- },
- },
- ],
- },
- },
- orderBy: [{ id: "desc" }],
- //take an extra page to tell if there are more
- take: directionMultiplier * (PAGE_SIZE + 1),
- //skip the cursor if there is one
- skip: cursor ? 1 : 0,
- cursor: cursor
- ? {
- id: cursor,
- }
- : undefined,
- });
-
- const hasMore = runs.length > PAGE_SIZE;
-
- //get cursors for next and previous pages
- let next: string | undefined;
- let previous: string | undefined;
- switch (direction) {
- case "forward":
- previous = cursor ? runs.at(0)?.id : undefined;
- if (hasMore) {
- next = runs[PAGE_SIZE - 1]?.id;
- }
- break;
- case "backward":
- if (hasMore) {
- previous = runs[1]?.id;
- next = runs[PAGE_SIZE]?.id;
- } else {
- next = runs[PAGE_SIZE - 1]?.id;
- }
- break;
- }
-
- const runsToReturn =
- direction === "backward" && hasMore ? runs.slice(1, PAGE_SIZE + 1) : runs.slice(0, PAGE_SIZE);
-
- return {
- runs: runsToReturn.map((run) => ({
- id: run.id,
- number: run.number,
- createdAt: run.createdAt,
- deliveredAt: run.deliveredAt,
- verified: run.verified,
- error: run.error,
- environment: {
- type: run.environment.type,
- slug: run.environment.slug,
- userId: run.environment.orgMember?.userId,
- },
- })),
- pagination: {
- next,
- previous,
- },
- };
- }
-}
diff --git a/apps/webapp/app/presenters/WebhookDeliveryPresenter.server.ts b/apps/webapp/app/presenters/WebhookDeliveryPresenter.server.ts
deleted file mode 100644
index cb831834b1..0000000000
--- a/apps/webapp/app/presenters/WebhookDeliveryPresenter.server.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { User, Webhook } from "@trigger.dev/database";
-import { PrismaClient, prisma } from "~/db.server";
-import { Organization } from "~/models/organization.server";
-import { Project } from "~/models/project.server";
-import { organizationPath, projectPath } from "~/utils/pathBuilder";
-import { WebhookDeliveryListPresenter } from "./WebhookDeliveryListPresenter.server";
-import { Direction } from "~/components/runs/RunStatuses";
-
-export class WebhookDeliveryPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- projectSlug,
- organizationSlug,
- webhookId,
- direction = "forward",
- cursor,
- }: {
- userId: User["id"];
- projectSlug: Project["slug"];
- organizationSlug: Organization["slug"];
- webhookId: Webhook["id"];
- direction?: Direction;
- cursor?: string;
- }) {
- const webhook = await this.#prismaClient.webhook.findUnique({
- select: {
- id: true,
- key: true,
- active: true,
- integration: {
- select: {
- id: true,
- title: true,
- slug: true,
- definitionId: true,
- setupStatus: true,
- definition: {
- select: {
- icon: true,
- },
- },
- },
- },
- httpEndpoint: {
- select: {
- key: true,
- },
- },
- createdAt: true,
- updatedAt: true,
- params: true,
- },
- where: {
- id: webhookId,
- },
- });
-
- if (!webhook) {
- throw new Error("Webhook source not found");
- }
-
- const deliveryListPresenter = new WebhookDeliveryListPresenter(this.#prismaClient);
-
- const orgRootPath = organizationPath({ slug: organizationSlug });
- const projectRootPath = projectPath({ slug: organizationSlug }, { slug: projectSlug });
-
- const requestDeliveries = await deliveryListPresenter.call({
- userId,
- webhookId: webhook.id,
- direction,
- cursor,
- });
-
- return {
- webhook: {
- id: webhook.id,
- key: webhook.key,
- active: webhook.active,
- integration: webhook.integration,
- integrationLink: `${orgRootPath}/integrations/${webhook.integration.slug}`,
- httpEndpoint: webhook.httpEndpoint,
- httpEndpointLink: `${projectRootPath}/http-endpoints/${webhook.httpEndpoint.key}`,
- createdAt: webhook.createdAt,
- updatedAt: webhook.updatedAt,
- params: webhook.params,
- requestDeliveries,
- },
- };
- }
-}
diff --git a/apps/webapp/app/presenters/WebhookSourcePresenter.server.ts b/apps/webapp/app/presenters/WebhookSourcePresenter.server.ts
deleted file mode 100644
index 536ad4eb59..0000000000
--- a/apps/webapp/app/presenters/WebhookSourcePresenter.server.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import { User, Webhook } from "@trigger.dev/database";
-import { PrismaClient, prisma } from "~/db.server";
-import { Organization } from "~/models/organization.server";
-import { Project } from "~/models/project.server";
-import { RunListPresenter } from "./RunListPresenter.server";
-import { organizationPath, projectPath } from "~/utils/pathBuilder";
-import { Direction } from "~/components/runs/RunStatuses";
-
-export class WebhookSourcePresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- projectSlug,
- organizationSlug,
- webhookId,
- direction = "forward",
- cursor,
- getDeliveryRuns = false,
- }: {
- userId: User["id"];
- projectSlug: Project["slug"];
- organizationSlug: Organization["slug"];
- webhookId: Webhook["id"];
- direction?: Direction;
- cursor?: string;
- getDeliveryRuns?: boolean;
- }) {
- const webhook = await this.#prismaClient.webhook.findUnique({
- select: {
- id: true,
- key: true,
- active: true,
- integration: {
- select: {
- id: true,
- title: true,
- slug: true,
- definitionId: true,
- setupStatus: true,
- definition: {
- select: {
- icon: true,
- },
- },
- },
- },
- httpEndpoint: {
- select: {
- key: true,
- },
- },
- createdAt: true,
- updatedAt: true,
- params: true,
- },
- where: {
- id: webhookId,
- },
- });
-
- if (!webhook) {
- throw new Error("Webhook source not found");
- }
-
- const runListPresenter = new RunListPresenter(this.#prismaClient);
- const jobSlug = getDeliveryRuns
- ? getDeliveryJobSlug(webhook.key)
- : getRegistrationJobSlug(webhook.key);
-
- const runList = await runListPresenter.call({
- userId,
- jobSlug,
- organizationSlug,
- projectSlug,
- direction,
- cursor,
- });
-
- const orgRootPath = organizationPath({ slug: organizationSlug });
- const projectRootPath = projectPath({ slug: organizationSlug }, { slug: projectSlug });
-
- return {
- trigger: {
- id: webhook.id,
- key: webhook.key,
- active: webhook.active,
- integration: webhook.integration,
- integrationLink: `${orgRootPath}/integrations/${webhook.integration.slug}`,
- httpEndpoint: webhook.httpEndpoint,
- httpEndpointLink: `${projectRootPath}/http-endpoints/${webhook.httpEndpoint.key}`,
- createdAt: webhook.createdAt,
- updatedAt: webhook.updatedAt,
- params: webhook.params,
- runList,
- },
- };
- }
-}
-
-const getRegistrationJobSlug = (key: string) => `webhook.register.${key}`;
-
-const getDeliveryJobSlug = (key: string) => `webhook.deliver.${key}`;
diff --git a/apps/webapp/app/presenters/WebhookTriggersPresenter.server.ts b/apps/webapp/app/presenters/WebhookTriggersPresenter.server.ts
deleted file mode 100644
index a9e76cae54..0000000000
--- a/apps/webapp/app/presenters/WebhookTriggersPresenter.server.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { Organization, User } from "@trigger.dev/database";
-import { PrismaClient, prisma } from "~/db.server";
-import { Project } from "~/models/project.server";
-
-export class WebhookTriggersPresenter {
- #prismaClient: PrismaClient;
-
- constructor(prismaClient: PrismaClient = prisma) {
- this.#prismaClient = prismaClient;
- }
-
- public async call({
- userId,
- projectSlug,
- organizationSlug,
- }: {
- userId: User["id"];
- projectSlug: Project["slug"];
- organizationSlug: Organization["slug"];
- }) {
- const webhooks = await this.#prismaClient.webhook.findMany({
- select: {
- id: true,
- key: true,
- active: true,
- params: true,
- integration: {
- select: {
- id: true,
- title: true,
- slug: true,
- definitionId: true,
- setupStatus: true,
- definition: {
- select: {
- icon: true,
- },
- },
- },
- },
- webhookEnvironments: {
- select: {
- id: true,
- environment: {
- select: {
- type: true
- }
- }
- }
- },
- createdAt: true,
- updatedAt: true,
- },
- where: {
- project: {
- slug: projectSlug,
- organization: {
- slug: organizationSlug,
- members: {
- some: {
- userId,
- },
- },
- },
- },
- },
- });
-
- return { webhooks };
- }
-}
diff --git a/apps/webapp/app/presenters/v3/BatchListPresenter.server.ts b/apps/webapp/app/presenters/v3/BatchListPresenter.server.ts
index ef7d3f2dd4..855c75a8ba 100644
--- a/apps/webapp/app/presenters/v3/BatchListPresenter.server.ts
+++ b/apps/webapp/app/presenters/v3/BatchListPresenter.server.ts
@@ -1,9 +1,9 @@
import { BatchTaskRunStatus, Prisma } from "@trigger.dev/database";
import parse from "parse-duration";
-import { type Direction } from "~/components/runs/RunStatuses";
import { sqlDatabaseSchema } from "~/db.server";
import { displayableEnvironment } from "~/models/runtimeEnvironment.server";
import { BasePresenter } from "./basePresenter.server";
+import { Direction } from "~/components/ListPagination";
export type BatchListOptions = {
userId?: string;
diff --git a/apps/webapp/app/presenters/v3/RunListPresenter.server.ts b/apps/webapp/app/presenters/v3/RunListPresenter.server.ts
index 208466544c..590e499a77 100644
--- a/apps/webapp/app/presenters/v3/RunListPresenter.server.ts
+++ b/apps/webapp/app/presenters/v3/RunListPresenter.server.ts
@@ -1,11 +1,11 @@
import { Prisma, type TaskRunStatus } from "@trigger.dev/database";
import parse from "parse-duration";
-import { type Direction } from "~/components/runs/RunStatuses";
import { sqlDatabaseSchema } from "~/db.server";
import { displayableEnvironment } from "~/models/runtimeEnvironment.server";
import { isCancellableRunStatus, isFinalRunStatus } from "~/v3/taskStatus";
import { BasePresenter } from "./basePresenter.server";
import { getAllTaskIdentifiers } from "~/models/task.server";
+import { Direction } from "~/components/ListPagination";
export type RunListOptions = {
userId?: string;
diff --git a/apps/webapp/app/root.tsx b/apps/webapp/app/root.tsx
index 409fd120f0..e1418968de 100644
--- a/apps/webapp/app/root.tsx
+++ b/apps/webapp/app/root.tsx
@@ -7,12 +7,10 @@ import type { ToastMessage } from "~/models/message.server";
import { commitSession, getSession } from "~/models/message.server";
import tailwindStylesheetUrl from "~/tailwind.css";
import { RouteErrorDisplay } from "./components/ErrorDisplay";
-import { HighlightInit } from "./components/HighlightInit";
import { AppContainer, MainCenteredContainer } from "./components/layout/AppLayout";
import { Toast } from "./components/primitives/Toast";
import { env } from "./env.server";
import { featuresForRequest } from "./features.server";
-import { useHighlight } from "./hooks/useHighlight";
import { usePostHog } from "./hooks/usePostHog";
import { getUser } from "./services/session.server";
import { appEnvTitleTag } from "./utils";
@@ -40,7 +38,6 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
const session = await getSession(request.headers.get("cookie"));
const toastMessage = session.get("toastMessage") as ToastMessage;
const posthogProjectKey = env.POSTHOG_PROJECT_KEY;
- const highlightProjectId = env.HIGHLIGHT_PROJECT_ID;
const features = featuresForRequest(request);
return typedjson(
@@ -48,7 +45,6 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
user: await getUser(request),
toastMessage,
posthogProjectKey,
- highlightProjectId,
features,
appEnv: env.APP_ENV,
appOrigin: env.APP_ORIGIN,
@@ -91,19 +87,11 @@ export function ErrorBoundary() {
}
function App() {
- const { posthogProjectKey, highlightProjectId } = useTypedLoaderData();
+ const { posthogProjectKey } = useTypedLoaderData();
usePostHog(posthogProjectKey);
- useHighlight();
return (
<>
- {highlightProjectId && (
-
- )}
diff --git a/apps/webapp/app/routes/_app._index/route.tsx b/apps/webapp/app/routes/_app._index/route.tsx
index dc9a9a9dc1..041f8aee11 100644
--- a/apps/webapp/app/routes/_app._index/route.tsx
+++ b/apps/webapp/app/routes/_app._index/route.tsx
@@ -3,7 +3,12 @@ import { prisma } from "~/db.server";
import { getUsersInvites } from "~/models/member.server";
import { SelectBestProjectPresenter } from "~/presenters/SelectBestProjectPresenter.server";
import { requireUser } from "~/services/session.server";
-import { invitesPath, newOrganizationPath, newProjectPath, projectPath } from "~/utils/pathBuilder";
+import {
+ invitesPath,
+ newOrganizationPath,
+ newProjectPath,
+ v3ProjectPath,
+} from "~/utils/pathBuilder";
//this loader chooses the best project to redirect you to, ideally based on the cookie
export const loader = async ({ request }: LoaderFunctionArgs) => {
@@ -19,7 +24,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
try {
const { project, organization } = await presenter.call({ userId: user.id, request });
//redirect them to the most appropriate project
- return redirect(projectPath(organization, project));
+ return redirect(v3ProjectPath(organization, project));
} catch (e) {
const organization = await prisma.organization.findFirst({
where: {
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationId.subscription.canceled/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationId.subscription.canceled/route.tsx
deleted file mode 100644
index a2f85995aa..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationId.subscription.canceled/route.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
-import { z } from "zod";
-import { prisma } from "~/db.server";
-import { redirectWithErrorMessage } from "~/models/message.server";
-import { plansPath } from "~/utils/pathBuilder";
-
-const ParamsSchema = z.object({
- organizationId: z.string(),
-});
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const { organizationId } = ParamsSchema.parse(params);
-
- const org = await prisma.organization.findUnique({
- select: {
- slug: true,
- },
- where: {
- id: organizationId,
- },
- });
-
- if (!org) {
- throw new Response(null, { status: 404 });
- }
-
- return redirectWithErrorMessage(
- `${plansPath({ slug: org.slug })}`,
- request,
- "You didn't complete your details on Stripe. Please try again."
- );
-};
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationId.subscription.complete/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationId.subscription.complete/route.tsx
deleted file mode 100644
index 0282294290..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationId.subscription.complete/route.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { z } from "zod";
-import { prisma } from "~/db.server";
-import { redirectWithSuccessMessage } from "~/models/message.server";
-import { subscribedPath } from "~/utils/pathBuilder";
-
-const ParamsSchema = z.object({
- organizationId: z.string(),
-});
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const { organizationId } = ParamsSchema.parse(params);
-
- const org = await prisma.organization.findUnique({
- select: {
- slug: true,
- },
- where: {
- id: organizationId,
- },
- });
-
- if (!org) {
- throw new Response(null, { status: 404 });
- }
-
- return redirectWithSuccessMessage(
- `${subscribedPath({ slug: org.slug })}`,
- request,
- "You are now subscribed to Trigger.dev"
- );
-};
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationId.subscription.failed/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationId.subscription.failed/route.tsx
deleted file mode 100644
index efe9ddb578..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationId.subscription.failed/route.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { z } from "zod";
-import { prisma } from "~/db.server";
-import { redirectWithErrorMessage } from "~/models/message.server";
-import { plansPath } from "~/utils/pathBuilder";
-
-const ParamsSchema = z.object({
- organizationId: z.string(),
-});
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const { organizationId } = ParamsSchema.parse(params);
-
- const org = await prisma.organization.findUnique({
- select: {
- slug: true,
- },
- where: {
- id: organizationId,
- },
- });
-
- if (!org) {
- throw new Response(null, { status: 404 });
- }
-
- const url = new URL(request.url);
- const searchParams = new URLSearchParams(url.search);
- const reason = searchParams.get("reason");
-
- let errorMessage = reason ? decodeURIComponent(reason) : "Subscribing failed to complete";
-
- return redirectWithErrorMessage(`${plansPath({ slug: org.slug })}`, request, errorMessage);
-};
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug._index/route.tsx
index 85c1780ffa..34f098a241 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug._index/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug._index/route.tsx
@@ -1,13 +1,13 @@
+import { FolderIcon } from "@heroicons/react/20/solid";
import { Link, MetaFunction } from "@remix-run/react";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
import { Badge } from "~/components/primitives/Badge";
import { LinkButton } from "~/components/primitives/Buttons";
import { Header3 } from "~/components/primitives/Headers";
-import { NamedIcon } from "~/components/primitives/NamedIcon";
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
import { Paragraph } from "~/components/primitives/Paragraph";
import { useOrganization } from "~/hooks/useOrganizations";
-import { newProjectPath, projectPath } from "~/utils/pathBuilder";
+import { newProjectPath, v3ProjectPath } from "~/utils/pathBuilder";
export const meta: MetaFunction = () => {
return [
@@ -45,9 +45,9 @@ export default function Page() {
-
+
{project.name}
{project.version}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing._index/route.tsx
deleted file mode 100644
index 2bcc152517..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing._index/route.tsx
+++ /dev/null
@@ -1,235 +0,0 @@
-import { ArrowRightIcon } from "@heroicons/react/20/solid";
-import { ArrowUpCircleIcon } from "@heroicons/react/24/outline";
-import { Await, useLoaderData } from "@remix-run/react";
-import { DataFunctionArgs, defer } from "@remix-run/server-runtime";
-import { Suspense } from "react";
-import { Bar, BarChart, ResponsiveContainer, Tooltip, TooltipProps, XAxis, YAxis } from "recharts";
-import { ConcurrentRunsChart } from "~/components/billing/v2/ConcurrentRunsChart";
-import { UsageBar } from "~/components/billing/v2/UsageBar";
-import { LinkButton } from "~/components/primitives/Buttons";
-import { Callout } from "~/components/primitives/Callout";
-import { DailyRunsChart } from "~/components/billing/v2/DailyRunsChat";
-import { DateTime } from "~/components/primitives/DateTime";
-import { Header2, Header3 } from "~/components/primitives/Headers";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { Spinner } from "~/components/primitives/Spinner";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { OrgUsagePresenter } from "~/presenters/OrgUsagePresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { formatCurrency, formatNumberCompact } from "~/utils/numberFormatter";
-import { OrganizationParamsSchema, plansPath } from "~/utils/pathBuilder";
-import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
-
-export async function loader({ request, params }: DataFunctionArgs) {
- const userId = await requireUserId(request);
- const { organizationSlug } = OrganizationParamsSchema.parse(params);
-
- const presenter = new OrgUsagePresenter();
- const usageData = presenter.call({ userId, slug: organizationSlug, request });
- return defer({ usageData });
-}
-
-const CustomTooltip = ({ active, payload, label }: TooltipProps
) => {
- if (active && payload) {
- return (
-
-
{label}:
-
{payload[0].value}
-
- );
- }
-
- return null;
-};
-
-export default function Page() {
- const organization = useOrganization();
- const { usageData } = useLoaderData();
- const currentPlan = useCurrentPlan();
-
- const hitsRunLimit = currentPlan?.usage?.runCountCap
- ? currentPlan.usage.currentRunCount > currentPlan.usage.runCountCap
- : false;
-
- return (
-
-
-
-
- >
- }
- >
- There was a problem loading your usage data.}
- >
- {(data) => {
- const hitConcurrencyLimit = currentPlan?.subscription?.limits.concurrentRuns
- ? data.concurrencyData.some(
- (c) =>
- c.maxConcurrentRuns >=
- (currentPlan.subscription?.limits.concurrentRuns ?? Infinity)
- )
- : false;
-
- return (
- <>
-
-
Concurrent runs
-
- {hitConcurrencyLimit && (
-
- Increase concurrent runs
-
- }
- >
- {`Some of your runs are being queued because the number of concurrent runs is limited to
- ${currentPlan?.subscription?.limits.concurrentRuns}.`}
-
- )}
-
-
-
-
-
-
Runs
-
- {hitsRunLimit && (
-
- Upgrade
-
- }
- >
-
- You have exceeded the monthly{" "}
- {formatNumberCompact(currentPlan?.subscription?.limits.runs ?? 0)} runs
- limit. Upgrade to a paid plan before{" "}
-
- .
-
-
- )}
-
-
- {data.runCostEstimation !== undefined &&
- data.projectedRunCostEstimation !== undefined && (
-
-
-
Month-to-date
-
- {formatCurrency(data.runCostEstimation, false)}
-
-
-
-
-
Projected
-
- {formatCurrency(data.projectedRunCostEstimation, false)}
-
-
-
- )}
-
-
-
-
Monthly runs
- {!data.hasMonthlyRunData && (
-
- No runs to show
-
- )}
-
-
-
- `${value}`}
- />
- }
- />
-
-
-
-
-
-
- Daily runs
-
-
-
-
- >
- );
- }}
-
-
-
- );
-}
-
-function LoadingElement({ title }: { title: string }) {
- return (
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing.plans/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing.plans/route.tsx
deleted file mode 100644
index 5406cb0c85..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing.plans/route.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
-import { PricingCalculator } from "~/components/billing/v2/PricingCalculator";
-import { PricingTiers } from "~/components/billing/v2/PricingTiers";
-import { RunsVolumeDiscountTable } from "~/components/billing/v2/RunsVolumeDiscountTable";
-import { Callout } from "~/components/primitives/Callout";
-import { Header2 } from "~/components/primitives/Headers";
-import { featuresForRequest } from "~/features.server";
-import { OrgBillingPlanPresenter } from "~/presenters/OrgBillingPlanPresenter";
-import { formatNumberCompact } from "~/utils/numberFormatter";
-import { OrganizationParamsSchema, organizationBillingPath } from "~/utils/pathBuilder";
-import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
-
-export async function loader({ params, request }: LoaderFunctionArgs) {
- const { organizationSlug } = OrganizationParamsSchema.parse(params);
-
- const { isManagedCloud } = featuresForRequest(request);
- if (!isManagedCloud) {
- return redirect(organizationBillingPath({ slug: organizationSlug }));
- }
-
- const presenter = new OrgBillingPlanPresenter();
- const result = await presenter.call({ slug: organizationSlug, isManagedCloud });
- if (!result) {
- throw new Response(null, { status: 404 });
- }
-
- return typedjson({
- plans: result.plans,
- maxConcurrency: result.maxConcurrency,
- organizationSlug,
- });
-}
-
-export default function Page() {
- const { plans, maxConcurrency, organizationSlug } = useTypedLoaderData();
- const currentPlan = useCurrentPlan();
-
- const hitConcurrencyLimit =
- currentPlan?.subscription?.limits.concurrentRuns && maxConcurrency
- ? maxConcurrency >= currentPlan.subscription!.limits.concurrentRuns!
- : false;
-
- const hitRunLimit = currentPlan?.usage?.runCountCap
- ? currentPlan.usage.currentRunCount > currentPlan.usage.runCountCap
- : false;
-
- return (
-
- {hitConcurrencyLimit && (
-
- Some of your runs are being queued because your run concurrency is limited to{" "}
- {currentPlan?.subscription?.limits.concurrentRuns}.
-
- )}
- {hitRunLimit && (
-
- {`You have exceeded the monthly
- ${formatNumberCompact(currentPlan!.subscription!.limits.runs!)} runs limit. Upgrade so you
- can continue to perform runs.`}
-
- )}
-
-
-
Estimate your usage
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx
deleted file mode 100644
index 0c91c510e2..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx
+++ /dev/null
@@ -1,146 +0,0 @@
-import { CalendarDaysIcon, ReceiptRefundIcon } from "@heroicons/react/20/solid";
-import { ArrowUpCircleIcon } from "@heroicons/react/24/outline";
-import { Outlet } from "@remix-run/react";
-import { ActiveSubscription } from "@trigger.dev/platform/v2";
-import { formatDurationInDays } from "@trigger.dev/core/v3";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { LinkButton } from "~/components/primitives/Buttons";
-import { DateTime } from "~/components/primitives/DateTime";
-import {
- PageAccessories,
- NavBar,
- PageInfoGroup,
- PageInfoProperty,
- PageInfoRow,
- PageTabs,
- PageTitle,
-} from "~/components/primitives/PageHeader";
-import { useFeatures } from "~/hooks/useFeatures";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { plansPath, stripePortalPath, usagePath } from "~/utils/pathBuilder";
-import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
-import { Callout } from "~/components/primitives/Callout";
-
-function planLabel(subscription: ActiveSubscription | undefined, periodEnd: Date) {
- if (!subscription) {
- return "You're currently on the Free plan";
- }
- if (!subscription.isPaying) {
- return `You're currently on the ${subscription.plan.title} plan`;
- }
- const costDescription = subscription.plan.concurrentRuns.pricing
- ? `\$${subscription.plan.concurrentRuns.pricing.tierCost}/mo`
- : "";
- if (subscription.canceledAt) {
- return (
- <>
- You're on the {costDescription} {subscription.plan.title} plan until{" "}
- when you'll be on the Free plan
- >
- );
- }
-
- return `You're currently on the ${costDescription} ${subscription.plan.title} plan`;
-}
-
-export default function Page() {
- const organization = useOrganization();
- const { isManagedCloud } = useFeatures();
- const currentPlan = useCurrentPlan();
-
- const hasV3Project = organization.projects.some((p) => p.version === "V3");
- const hasV2Project = organization.projects.some((p) => p.version === "V2");
- const allV3Projects = organization.projects.every((p) => p.version === "V3");
-
- return (
-
-
-
-
- {isManagedCloud && (
- <>
- {currentPlan?.subscription?.isPaying && (
- <>
-
- Invoices
-
-
- Manage card details
-
- >
- )}
- {hasV2Project && (
-
- Upgrade
-
- )}
- >
- )}
-
-
-
-
-
- {hasV3Project ? (
-
- This organization has a mix of v2 and v3 projects. They have separate subscriptions,
- this is the usage and billing for v2.
-
- ) : null}
- {hasV2Project && (
-
-
- {currentPlan?.subscription && currentPlan.usage && (
- }
- value={planLabel(currentPlan.subscription, currentPlan.usage.periodEnd)}
- />
- )}
- {currentPlan?.subscription?.isPaying && currentPlan.usage && (
- }
- label={"Billing period"}
- value={
- <>
- to{" "}
- (
- {formatDurationInDays(currentPlan.usage.periodRemainingDuration)}{" "}
- remaining)
- >
- }
- />
- )}
-
-
- )}
- {hasV2Project && isManagedCloud && (
-
- )}
-
- {hasV2Project && (
-
-
-
- )}
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations/route.tsx
deleted file mode 100644
index 6a790b7171..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations/route.tsx
+++ /dev/null
@@ -1,470 +0,0 @@
-import { ChevronRightIcon } from "@heroicons/react/24/solid";
-import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { useState } from "react";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { IntegrationIcon } from "~/assets/icons/IntegrationIcon";
-import { Feedback } from "~/components/Feedback";
-import { HowToConnectAnIntegration } from "~/components/helpContent/HelpContentText";
-import { ConnectToIntegrationSheet } from "~/components/integrations/ConnectToIntegrationSheet";
-import { IntegrationWithMissingFieldSheet } from "~/components/integrations/IntegrationWithMissingFieldSheet";
-import { NoIntegrationSheet } from "~/components/integrations/NoIntegrationSheet";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { LinkButton } from "~/components/primitives/Buttons";
-import { Callout } from "~/components/primitives/Callout";
-import { DateTime } from "~/components/primitives/DateTime";
-import { DetailCell } from "~/components/primitives/DetailCell";
-import { Header2 } from "~/components/primitives/Headers";
-import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help";
-import { Input } from "~/components/primitives/Input";
-import { NamedIcon } from "~/components/primitives/NamedIcon";
-import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
-import { Switch } from "~/components/primitives/Switch";
-import {
- Table,
- TableBlankRow,
- TableBody,
- TableCell,
- TableCellChevron,
- TableHeader,
- TableHeaderCell,
- TableRow,
-} from "~/components/primitives/Table";
-import { SimpleTooltip } from "~/components/primitives/Tooltip";
-import { MatchedOrganization, useOrganization } from "~/hooks/useOrganizations";
-import { useTextFilter } from "~/hooks/useTextFilter";
-import {
- Client,
- IntegrationOrApi,
- IntegrationsPresenter,
-} from "~/presenters/IntegrationsPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { OrganizationParamsSchema, docsPath, integrationClientPath } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug } = OrganizationParamsSchema.parse(params);
-
- const presenter = new IntegrationsPresenter();
- const data = await presenter.call({
- userId,
- organizationSlug,
- });
-
- return typedjson(data);
-};
-
-export default function Integrations() {
- const { clients, clientMissingFields, options, callbackUrl } =
- useTypedLoaderData();
- const organization = useOrganization();
-
- return (
-
-
-
-
-
- Integrations documentation
-
-
-
-
-
-
-
-
- {clientMissingFields.length > 0 && (
-
- )}
-
-
-
-
-
- );
-}
-
-function PossibleIntegrationsList({
- options,
- organizationId,
- callbackUrl,
-}: {
- options: IntegrationOrApi[];
- organizationId: string;
- callbackUrl: string;
-}) {
- const [onlyShowIntegrations, setOnlyShowIntegrations] = useState(false);
- const optionsToShow = onlyShowIntegrations
- ? options.filter((o) => o.type === "integration")
- : options;
- const { filterText, setFilterText, filteredItems } = useTextFilter({
- items: optionsToShow,
- filter: (integration, text) => integration.name.toLowerCase().includes(text.toLowerCase()),
- });
-
- return (
-
-
-
- Connect an API
-
- Trigger.dev Integrations
-
- }
- />
-
-
setFilterText(e.target.value)}
- />
-
- {filteredItems.map((option) => {
- switch (option.type) {
- case "integration":
- return (
-
- }
- />
- );
- case "api":
- return (
-
- }
- />
- );
- }
- })}
-
-
Missing an API?
-
-
-
- }
- defaultValue="feature"
- />
-
- Create an Integration
-
-
-
-
-
- );
-}
-
-function ConnectedIntegrationsList({
- clients,
- organization,
-}: {
- clients: Client[];
- organization: MatchedOrganization;
-}) {
- const { filterText, setFilterText, filteredItems } = useTextFilter({
- items: clients,
- filter: (client, text) => {
- if (client.title.toLowerCase().includes(text.toLowerCase())) {
- return true;
- }
-
- if (
- client.customClientId &&
- client.customClientId.toLowerCase().includes(text.toLowerCase())
- ) {
- return true;
- }
-
- if (client.integration.name.toLowerCase().includes(text.toLowerCase())) {
- return true;
- }
-
- if (client.authMethod.name.toLowerCase().includes(text.toLowerCase())) {
- return true;
- }
-
- return false;
- },
- });
-
- return (
-
-
-
-
-
- Your connected Integrations
-
- {clients.length > 0 && (
-
-
- setFilterText(e.target.value)}
- />
-
-
-
-
-
- Name
- API
- ID
- Type
- Jobs
- Scopes
- Client id
- Connections
- Added
- Go to page
-
-
-
- {filteredItems.length === 0 ? (
-
-
-
- No connected Integrations match your filters.
-
-
-
- ) : (
- <>
- {filteredItems.map((client) => {
- const path = integrationClientPath(organization, client);
- return (
-
- {client.title}
-
-
-
- {client.integration.name}
-
-
- {client.slug}
- {client.authMethod.name}
- {client.jobCount}
-
- {client.authSource === "LOCAL" ? "–" : client.scopesCount}
-
-
- {client.authSource === "LOCAL" ? (
- "–"
- ) : (
- Auto
- )
- }
- content={
- client.customClientId
- ? client.customClientId
- : "This uses the Trigger.dev OAuth client"
- }
- />
- )}
-
-
- {client.authSource === "LOCAL" ? "–" : client.connectionsCount}
-
-
-
-
-
-
- );
- })}
- >
- )}
-
-
-
- )}
-
- );
-}
-
-function IntegrationsWithMissingFields({
- clients,
- organizationId,
- callbackUrl,
- options,
-}: {
- clients: Client[];
- organizationId: string;
- callbackUrl: string;
- options: IntegrationOrApi[];
-}) {
- const integrationsList = options.flatMap((o) => (o.type === "integration" ? [o] : []));
-
- return (
-
-
-
- Integrations requiring configuration
-
-
-
-
-
- Name
- API
- Added
- Go to page
-
-
-
- {clients.map((client) => {
- const integration = integrationsList.find(
- (i) => i.identifier === client.integrationIdentifier
- );
-
- if (!integration) {
- return Can't find matching integration
;
- }
-
- return (
-
-
-
- {client.title}
-
- }
- callbackUrl={callbackUrl}
- existingIntegration={client}
- className="flex w-full cursor-pointer justify-start"
- />
-
-
-
-
- {client.integration.name}
-
- }
- callbackUrl={callbackUrl}
- existingIntegration={client}
- className="flex w-full cursor-pointer justify-start"
- />
-
-
- }
- callbackUrl={callbackUrl}
- existingIntegration={client}
- className="flex w-full cursor-pointer justify-start"
- />
-
-
-
- }
- callbackUrl={callbackUrl}
- existingIntegration={client}
- className="flex w-full cursor-pointer justify-end"
- />
-
-
- );
- })}
-
-
-
- );
-}
-
-function AddIntegrationConnection({
- identifier,
- name,
- isIntegration,
- icon,
-}: {
- identifier: string;
- name: string;
- isIntegration: boolean;
- icon?: string;
-}) {
- return (
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam._index/route.tsx
deleted file mode 100644
index ddd9b1dd46..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam._index/route.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { HowToUseThisIntegration } from "~/components/helpContent/HelpContentText";
-import { JobsTable } from "~/components/jobs/JobsTable";
-import { Callout } from "~/components/primitives/Callout";
-import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help";
-import { Input } from "~/components/primitives/Input";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { useFilterJobs } from "~/hooks/useFilterJobs";
-import { useIntegrationClient } from "~/hooks/useIntegrationClient";
-import { JobListPresenter } from "~/presenters/JobListPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { cn } from "~/utils/cn";
-import { IntegrationClientParamSchema, docsIntegrationPath } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, clientParam } = IntegrationClientParamSchema.parse(params);
-
- const jobsPresenter = new JobListPresenter();
-
- const jobs = await jobsPresenter.call({
- userId,
- organizationSlug,
- integrationSlug: clientParam,
- });
-
- return typedjson({ jobs });
-};
-
-export default function Page() {
- const { jobs } = useTypedLoaderData();
- const client = useIntegrationClient();
-
- const { filterText, setFilterText, filteredItems } = useFilterJobs(jobs);
-
- return (
-
- {(open) => (
-
-
-
- {jobs.length !== 0 && (
- setFilterText(e.target.value)}
- />
- )}
-
-
- {jobs.length === 0 ? (
-
-
Jobs using this Integration will appear here.
-
- ) : (
-
- )}
-
-
-
-
- View the docs to learn more about using the {client.integration.name} Integration.
-
-
-
- )}
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam.connections/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam.connections/route.tsx
deleted file mode 100644
index 30a12e0223..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam.connections/route.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { connectionType } from "~/components/integrations/connectionType";
-import { DateTime } from "~/components/primitives/DateTime";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import {
- Table,
- TableBlankRow,
- TableBody,
- TableCell,
- TableHeader,
- TableHeaderCell,
- TableRow,
-} from "~/components/primitives/Table";
-import { IntegrationClientConnectionsPresenter } from "~/presenters/IntegrationClientConnectionsPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { IntegrationClientParamSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, clientParam } = IntegrationClientParamSchema.parse(params);
-
- const presenter = new IntegrationClientConnectionsPresenter();
- const { connections } = await presenter.call({
- userId: userId,
- organizationSlug,
- clientSlug: clientParam,
- });
-
- return typedjson({ connections });
-};
-
-export default function Page() {
- const { connections } = useTypedLoaderData();
-
- return (
-
-
-
- ID
- Type
- Run count
- Account
- Expires
- Created
- Updated
-
-
-
- {connections.length > 0 ? (
- connections.map((connection) => {
- return (
-
- {connection.id}
- {connectionType(connection.type)}
- {connection.runCount}
- {connection.metadata?.account ?? "–"}
-
-
-
- { }
- { }
-
- );
- })
- ) : (
-
-
- No connections
-
-
- )}
-
-
- );
-}
-
-function ExpiresAt({ expiresAt }: { expiresAt: Date | null }) {
- if (!expiresAt) return <>–>;
-
- const inPast = expiresAt < new Date();
-
- return (
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam.scopes/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam.scopes/route.tsx
deleted file mode 100644
index 41b3b3249a..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam.scopes/route.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { IntegrationClientScopesPresenter } from "~/presenters/IntegrationClientScopesPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { IntegrationClientParamSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, clientParam } = IntegrationClientParamSchema.parse(params);
-
- const presenter = new IntegrationClientScopesPresenter();
- const { scopes } = await presenter.call({
- userId: userId,
- organizationSlug,
- clientSlug: clientParam,
- });
-
- return typedjson({ scopes });
-};
-
-export default function Page() {
- const { scopes } = useTypedLoaderData();
-
- return (
-
- {scopes.map((scope) => (
-
- {scope.name}
- {scope.description}
-
- ))}
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam/route.tsx
deleted file mode 100644
index 1ba642eb9c..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam/route.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import { Outlet } from "@remix-run/react";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { connectionType } from "~/components/integrations/connectionType";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { ClipboardField } from "~/components/primitives/ClipboardField";
-import { DateTime } from "~/components/primitives/DateTime";
-import {
- NavBar,
- PageInfoGroup,
- PageInfoProperty,
- PageInfoRow,
- PageTabs,
- PageTitle,
-} from "~/components/primitives/PageHeader";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { IntegrationClientPresenter } from "~/presenters/IntegrationClientPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import {
- IntegrationClientParamSchema,
- integrationClientConnectionsPath,
- integrationClientPath,
- integrationClientScopesPath,
- organizationIntegrationsPath,
-} from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, clientParam } = IntegrationClientParamSchema.parse(params);
-
- const presenter = new IntegrationClientPresenter();
- const client = await presenter.call({
- userId,
- organizationSlug,
- clientSlug: clientParam,
- });
-
- if (!client) {
- throw new Response("Not found", { status: 404 });
- }
-
- return typedjson({ client });
-};
-
-export default function Integrations() {
- const { client } = useTypedLoaderData();
- const organization = useOrganization();
-
- let tabs = [
- {
- label: "Jobs",
- to: integrationClientPath(organization, client),
- },
- ];
-
- if (client.authMethod.type !== "local") {
- tabs.push({
- label: "Connections",
- to: integrationClientConnectionsPath(organization, client),
- });
- tabs.push({
- label: "Scopes",
- to: integrationClientScopesPath(organization, client),
- });
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
index cf60e81862..2c91928129 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx
@@ -1,6 +1,6 @@
import { conform, list, requestIntent, useFieldList, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
-import { LockOpenIcon } from "@heroicons/react/20/solid";
+import { EnvelopeIcon, LockOpenIcon, UserPlusIcon } from "@heroicons/react/20/solid";
import type { ActionFunction, LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
@@ -143,7 +143,7 @@ export default function Page() {
}
title="Invite team members"
description={`Invite new team members to ${organization.title}.`}
/>
@@ -172,7 +172,7 @@ export default function Page() {
{
fieldValues.current[index] = e.target.value;
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx
deleted file mode 100644
index fa47283bb3..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx
+++ /dev/null
@@ -1,273 +0,0 @@
-import { ArrowUpIcon } from "@heroicons/react/24/solid";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { FrameworkSelector } from "~/components/frameworks/FrameworkSelector";
-import { JobsTable } from "~/components/jobs/JobsTable";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { Callout } from "~/components/primitives/Callout";
-import { Header2 } from "~/components/primitives/Headers";
-import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help";
-import { Input } from "~/components/primitives/Input";
-import { NamedIcon } from "~/components/primitives/NamedIcon";
-import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { Switch } from "~/components/primitives/Switch";
-import { TextLink } from "~/components/primitives/TextLink";
-import { useFilterJobs } from "~/hooks/useFilterJobs";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { JobListPresenter } from "~/presenters/JobListPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { cn } from "~/utils/cn";
-import { ProjectParamSchema, organizationIntegrationsPath } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
-
- try {
- const presenter = new JobListPresenter();
- const jobs = await presenter.call({ userId, organizationSlug, projectSlug: projectParam });
-
- return typedjson({
- jobs,
- });
- } catch (error) {
- console.error(error);
- throw new Response(undefined, {
- status: 400,
- statusText: "Something went wrong, if this problem persists please contact support.",
- });
- }
-};
-
-export default function Page() {
- const organization = useOrganization();
- const project = useProject();
- const { jobs } = useTypedLoaderData
();
- const { filterText, setFilterText, filteredItems, onlyActiveJobs, setOnlyActiveJobs } =
- useFilterJobs(jobs);
- const totalJobs = jobs.length;
- const hasJobs = totalJobs > 0;
- const activeJobCount = jobs.filter((j) => j.status === "ACTIVE").length;
-
- return (
-
-
-
-
-
-
- {(open) => (
-
-
- {hasJobs ? (
- <>
- {jobs.some((j) => j.hasIntegrationsRequiringAction) && (
-
- Some of your Jobs have Integrations that have not been configured.
-
- )}
-
-
- setFilterText(e.target.value)}
- autoFocus
- />
-
-
-
-
-
- {jobs.length === 1 &&
- jobs.every((r) => r.lastRun === undefined) &&
- jobs.every((i) => i.hasIntegrationsRequiringAction === false) && (
-
- )}
- >
- ) : (
-
- )}
-
-
-
-
-
- )}
-
-
-
- );
-}
-
-function RunYourJobPrompt() {
- return (
-
-
-
- Your Job is ready to run! Click it to run it now.
-
-
- );
-}
-
-function ExampleJobs() {
- return (
- <>
- Video walk-through
-
- Watch Matt, CEO of Trigger.dev create a GitHub issue reminder in Slack using Trigger.dev.
- (10 mins)
-
- VIDEO
- How to create a Job
-
- Our docs are a great way to learn how to create Jobs with each type of Trigger, from
- webhooks, to delays, to triggering Jobs on a schedule.{" "}
-
-
-
-
- How to create a Job
-
-
- Check out some example Jobs in code
-
- If you're looking for inspiration for your next Job, check out our{" "}
- examples repo . Or jump
- straight into an example repo from the list below:
-
-
- >
- );
-}
-
-const iconStyles = "h-7 w-7 mr-2 pl-2 min-w-[28px]";
-
-const examples = [
- {
- icon: ,
- title: "Basic delay",
- description: "Logs a message to the console, waits 5 minutes, and then logs another message.",
- codeLink: "https://github.com/triggerdotdev/examples/blob/main/delays/src/jobs/delayJob.ts",
- },
- {
- icon: ,
- title: "Basic interval",
- description: "This Job runs every 60 seconds, starting 60 seconds after it is first indexed.",
- codeLink: "https://github.com/triggerdotdev/examples/blob/main/scheduled/src/jobs/interval.ts",
- },
- {
- icon: ,
- title: "Cron scheduled interval",
- description: "A scheduled Job which runs at 2:30pm every Monday.",
- codeLink:
- "https://github.com/triggerdotdev/examples/blob/main/scheduled/src/jobs/cronScheduled.ts",
- },
- {
- icon: ,
- title: "OpenAI text summarizer",
- description:
- "Summarizes a block of text, pulling out the most unique / helpful points using OpenAI.",
- codeLink:
- "https://github.com/triggerdotdev/examples/blob/main/openai-text-summarizer/src/jobs/textSummarizer.ts",
- },
- {
- icon: ,
- title: "Tell me a joke using OpenAI",
- description: "Generates a random joke using OpenAI GPT 3.5.",
- codeLink: "https://github.com/triggerdotdev/examples/blob/main/openai/src/jobs/tellMeAJoke.ts",
- },
- {
- icon: ,
- title: "Generate a random image using OpenAI",
- description: "Generates a random image of a hedgehog using OpenAI DALL-E.",
- codeLink:
- "https://github.com/triggerdotdev/examples/blob/main/openai/src/jobs/generateHedgehogImages.ts",
- },
- {
- icon: ,
- title: "Send an email using Resend",
- description: "Send a basic email using Resend.",
- codeLink:
- "https://github.com/triggerdotdev/examples/blob/main/resend/src/jobs/resendBasicEmail.ts",
- },
- {
- icon: ,
- title: "GitHub issue reminder",
- description: "Sends a Slack message if a GitHub issue is left for 24h.",
- codeLink:
- "https://github.com/triggerdotdev/examples/blob/main/github-issue-reminder/jobs/githubIssue.ts",
- },
- {
- icon: ,
- title: "Github new star alert in Slack",
- description: "When a repo is starred, a message is sent to a Slack.",
- codeLink:
- "https://github.com/triggerdotdev/examples/blob/main/github/src/jobs/newStarToSlack.ts",
- },
- {
- icon: ,
- title: "Add a custom label to a GitHub issue",
- description: "When a new GitHub issue is opened it adds a “Bug” label to it.",
- codeLink:
- "https://github.com/triggerdotdev/examples/blob/main/github/src/jobs/onIssueOpened.ts",
- },
- {
- icon: ,
- title: "GitHub new star alert",
- description: "When a repo is starred a message is logged with the new Stargazers count.",
- codeLink: "https://github.com/triggerdotdev/examples/blob/main/github/src/jobs/newStarAlert.ts",
- },
- {
- icon: ,
- title: "Send a Slack message",
- description: "Sends a Slack message to a specific channel when an event is received.",
- codeLink:
- "https://github.com/triggerdotdev/examples/blob/main/slack/src/jobs/sendSlackMessage.ts",
- },
-];
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments.stream/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments.stream/route.tsx
deleted file mode 100644
index 0cf19ac245..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments.stream/route.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { EnvironmentsStreamPresenter } from "~/presenters/EnvironmentsStreamPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { ProjectParamSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { projectParam } = ProjectParamSchema.parse(params);
-
- const presenter = new EnvironmentsStreamPresenter();
- return await presenter.call({
- request,
- userId,
- projectSlug: projectParam,
- });
-};
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/ConfigureEndpointSheet.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/ConfigureEndpointSheet.tsx
deleted file mode 100644
index cc241ea180..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/ConfigureEndpointSheet.tsx
+++ /dev/null
@@ -1,214 +0,0 @@
-import { conform, useForm } from "@conform-to/react";
-import { parse } from "@conform-to/zod";
-import { useFetcher, useRevalidator } from "@remix-run/react";
-import { useEffect } from "react";
-import { useEventSource } from "~/hooks/useEventSource";
-import { InlineCode } from "~/components/code/InlineCode";
-import {
- EndpointIndexStatusIcon,
- EndpointIndexStatusLabel,
-} from "~/components/environments/EndpointIndexStatus";
-import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
-import { Button } from "~/components/primitives/Buttons";
-import { Callout, CalloutVariant } from "~/components/primitives/Callout";
-import { ClipboardField } from "~/components/primitives/ClipboardField";
-import { DateTime } from "~/components/primitives/DateTime";
-import { FormError } from "~/components/primitives/FormError";
-import { Header1, Header2 } from "~/components/primitives/Headers";
-import { Hint } from "~/components/primitives/Hint";
-import { Input } from "~/components/primitives/Input";
-import { InputGroup } from "~/components/primitives/InputGroup";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { Sheet, SheetBody, SheetContent, SheetHeader } from "~/components/primitives/Sheet";
-import { ClientEndpoint } from "~/presenters/EnvironmentsPresenter.server";
-import { endpointStreamingPath } from "~/utils/pathBuilder";
-import { EndpointIndexStatus, RuntimeEnvironmentType } from "@trigger.dev/database";
-import { bodySchema } from "../resources.environments.$environmentParam.endpoint";
-
-type ConfigureEndpointSheetProps = {
- slug: string;
- endpoint: ClientEndpoint;
- type: RuntimeEnvironmentType;
- onClose: () => void;
-};
-
-export function ConfigureEndpointSheet({ slug, endpoint, onClose }: ConfigureEndpointSheetProps) {
- const setEndpointUrlFetcher = useFetcher();
-
- const [form, { url, clientSlug }] = useForm({
- id: "endpoint-url",
- lastSubmission: setEndpointUrlFetcher.data as any,
- onValidate({ formData }) {
- return parse(formData, { schema: bodySchema });
- },
- });
- const loadingEndpointUrl = setEndpointUrlFetcher.state !== "idle";
-
- const refreshEndpointFetcher = useFetcher();
- const refreshingEndpoint = refreshEndpointFetcher.state !== "idle";
-
- const deleteEndpointFetcher = useFetcher();
- const deletingEndpoint = deleteEndpointFetcher.state !== "idle";
-
- const revalidator = useRevalidator();
- const events = useEventSource(endpointStreamingPath({ id: endpoint.environment.id }), {
- event: "message",
- });
-
- useEffect(() => {
- if (events !== null) {
- revalidator.revalidate();
- }
- // WARNING Don't put the revalidator in the useEffect deps array or bad things will happen
- }, [events]); // eslint-disable-line react-hooks/exhaustive-deps
-
- return (
- {
- if (!open) {
- onClose();
- }
- }}
- >
-
-
-
-
-
-
- Configure endpoint
-
-
- {endpoint.state === "configured" && (
-
-
-
- {deletingEndpoint ? "Deleting" : "Delete"}
-
-
- )}
-
-
-
-
-
- Endpoint URL
-
-
-
-
- {loadingEndpointUrl ? "Saving" : "Save"}
-
-
- {url.error}
- {form.error}
-
- This is the URL of your Trigger API route, Typically this would be:{" "}
- https://yourdomain.com/api/trigger .
-
-
-
-
- {endpoint.state === "configured" && (
-
-
-
Status
-
- We connect to your endpoint and refresh your Jobs.
-
-
-
-
- }
- className="justiy-between items-center"
- >
-
-
-
- Last refreshed:{" "}
- {endpoint.latestIndex ? (
- <>
-
- >
- ) : (
- "–"
- )}
-
-
-
-
- {refreshingEndpoint ? "Refreshing" : "Refresh now"}
-
-
- {endpoint.latestIndex?.error && (
-
- {endpoint.latestIndex.error.message}
-
- )}
-
-
-
-
Automatic refreshing
-
- Use this webhook URL so your Jobs get automatically refreshed when you deploy. You
- just need to hit this URL (POST) and we will refresh your Jobs.
-
-
-
-
- )}
-
-
-
- );
-}
-
-function calloutVariantFromStatus(status: EndpointIndexStatus): CalloutVariant {
- switch (status) {
- case "PENDING":
- return "pending";
- case "STARTED":
- return "pending";
- case "SUCCESS":
- return "success";
- case "FAILURE":
- return "error";
- }
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/FirstEndpointSheet.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/FirstEndpointSheet.tsx
deleted file mode 100644
index 45dc829097..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/FirstEndpointSheet.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import { conform, useForm, useInputEvent } from "@conform-to/react";
-import { parse } from "@conform-to/zod";
-import { useFetcher } from "@remix-run/react";
-import { InlineCode } from "~/components/code/InlineCode";
-import { Button, ButtonContent } from "~/components/primitives/Buttons";
-import { FormError } from "~/components/primitives/FormError";
-import { Header1, Header2 } from "~/components/primitives/Headers";
-import { Hint } from "~/components/primitives/Hint";
-import { Input } from "~/components/primitives/Input";
-import { InputGroup } from "~/components/primitives/InputGroup";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import {
- Sheet,
- SheetBody,
- SheetContent,
- SheetHeader,
- SheetTrigger,
-} from "~/components/primitives/Sheet";
-import { TextLink } from "~/components/primitives/TextLink";
-import { docsPath } from "~/utils/pathBuilder";
-import { bodySchema } from "../resources.projects.$projectId.endpoint";
-import { RuntimeEnvironment, RuntimeEnvironmentType } from "@trigger.dev/database";
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "~/components/primitives/SimpleSelect";
-import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
-
-type FirstEndpointSheetProps = {
- projectId: string;
- environments: { id: string; type: RuntimeEnvironmentType }[];
-};
-
-export function FirstEndpointSheet({ projectId, environments }: FirstEndpointSheetProps) {
- const setEndpointUrlFetcher = useFetcher();
- const [form, { url, environmentId }] = useForm({
- id: "new-endpoint-url",
- // TODO: type this
- lastSubmission: setEndpointUrlFetcher.data as any,
- onValidate({ formData }) {
- return parse(formData, { schema: bodySchema });
- },
- });
-
- const loadingEndpointUrl = setEndpointUrlFetcher.state !== "idle";
-
- return (
-
-
- Add your first endpoint
-
-
-
-
-
Add your first endpoint
-
- We recommend you{" "}
- use the CLI when
- working in development.
-
-
-
-
-
-
- Environment type
-
-
-
- Environment
-
-
- {environments.map((environment) => (
-
-
-
- ))}
-
-
-
- {environmentId.error}
-
-
- Endpoint URL
-
-
-
- {loadingEndpointUrl ? "Saving" : "Save"}
-
-
- {url.error}
- {form.error}
-
- This is the URL of your Trigger API route, Typically this would be:{" "}
- https://yourdomain.com/api/trigger .
-
-
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx
deleted file mode 100644
index 94f97f085e..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx
+++ /dev/null
@@ -1,318 +0,0 @@
-import { useRevalidator } from "@remix-run/react";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { useEffect, useMemo, useState } from "react";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import {
- EndpointIndexStatusIcon,
- EndpointIndexStatusLabel,
-} from "~/components/environments/EndpointIndexStatus";
-import { EnvironmentLabel, environmentTitle } from "~/components/environments/EnvironmentLabel";
-import { RegenerateApiKeyModal } from "~/components/environments/RegenerateApiKeyModal";
-import { HowToUseApiKeysAndEndpoints } from "~/components/helpContent/HelpContentText";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { Badge } from "~/components/primitives/Badge";
-import { ButtonContent, LinkButton } from "~/components/primitives/Buttons";
-import { ClipboardField } from "~/components/primitives/ClipboardField";
-import { DateTime } from "~/components/primitives/DateTime";
-import { Header2, Header3 } from "~/components/primitives/Headers";
-import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help";
-import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import {
- Table,
- TableBody,
- TableCell,
- TableCellChevron,
- TableHeader,
- TableHeaderCell,
- TableRow,
-} from "~/components/primitives/Table";
-import { useEventSource } from "~/hooks/useEventSource";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { ClientEndpoint, EnvironmentsPresenter } from "~/presenters/EnvironmentsPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { cn } from "~/utils/cn";
-import {
- ProjectParamSchema,
- docsPath,
- projectEnvironmentsStreamingPath,
-} from "~/utils/pathBuilder";
-import { requestUrl } from "~/utils/requestUrl.server";
-import { RuntimeEnvironmentType } from "@trigger.dev/database";
-import { ConfigureEndpointSheet } from "./ConfigureEndpointSheet";
-import { FirstEndpointSheet } from "./FirstEndpointSheet";
-import { BookOpenIcon } from "@heroicons/react/20/solid";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { projectParam } = ProjectParamSchema.parse(params);
-
- try {
- const url = requestUrl(request);
- const baseUrl = `${url.protocol}//${url.host}`;
- const presenter = new EnvironmentsPresenter();
- const { environments, clients } = await presenter.call({
- userId,
- projectSlug: projectParam,
- baseUrl,
- });
-
- return typedjson({
- environments,
- clients,
- });
- } catch (error) {
- console.error(error);
- throw new Response(undefined, {
- status: 400,
- statusText: "Something went wrong, if this problem persists please contact support.",
- });
- }
-};
-
-export default function Page() {
- const { environments, clients } = useTypedLoaderData();
- const [selected, setSelected] = useState<
- { client: string; type: RuntimeEnvironmentType } | undefined
- >();
-
- const selectedEndpoint = useMemo(() => {
- if (!selected) return undefined;
-
- const client = clients.find((c) => c.slug === selected.client);
- if (!client) return undefined;
-
- if (selected.type === "PREVIEW") {
- throw new Error("PREVIEW is not yet supported");
- }
-
- return {
- clientSlug: selected.client,
- type: selected.type,
- endpoint: client.endpoints[selected.type],
- };
- }, [selected, clients]);
-
- const isAnyClientFullyConfigured = clients.some((client) => {
- const { DEVELOPMENT, PRODUCTION, STAGING } = client.endpoints;
- return (
- PRODUCTION.state === "configured" ||
- DEVELOPMENT.state === "configured" ||
- (STAGING && STAGING.state === "configured")
- );
- });
-
- const organization = useOrganization();
- const project = useProject();
-
- const revalidator = useRevalidator();
- const events = useEventSource(projectEnvironmentsStreamingPath(organization, project), {
- event: "message",
- });
-
- useEffect(() => {
- if (events !== null) {
- revalidator.revalidate();
- }
- // WARNING Don't put the revalidator in the useEffect deps array or bad things will happen
- }, [events]); // eslint-disable-line react-hooks/exhaustive-deps
-
- return (
-
-
-
-
-
- Environments documentation
-
-
-
-
-
- {(open) => (
-
-
-
- API Keys
-
-
-
-
- Server API keys should be used on your server – they give full API access.{" "}
-
- Public API keys should be used in your frontend – they have limited read-only
- access.
-
-
- {environments.map((environment) => (
-
-
-
- Environment
-
-
-
-
- Server}
- />
- Public}
- />
-
-
- ))}
-
-
-
-
Endpoints
-
- {clients.length > 0 ? (
- clients.map((client) => (
-
-
{client.slug}
-
-
-
- Environment
- Url
- Last refreshed
- Last refresh Status
- Jobs
- Go to page
-
-
-
-
- setSelected({
- client: client.slug,
- type: "DEVELOPMENT",
- })
- }
- />
- {client.endpoints.STAGING && (
-
- setSelected({
- client: client.slug,
- type: "STAGING",
- })
- }
- />
- )}
-
- setSelected({
- client: client.slug,
- type: "PRODUCTION",
- })
- }
- />
-
-
-
- ))
- ) : (
-
-
-
- )}
-
- {selectedEndpoint && selectedEndpoint.endpoint && (
-
setSelected(undefined)}
- />
- )}
-
-
-
-
-
- )}
-
-
-
- );
-}
-
-function EndpointRow({
- endpoint,
- type,
- onClick,
-}: {
- endpoint: ClientEndpoint;
- type: RuntimeEnvironmentType;
- onClick?: () => void;
-}) {
- switch (endpoint.state) {
- case "unconfigured":
- return (
-
-
-
-
-
-
-
-
-
- The {environmentTitle({ type })} environment is not configured
-
- Configure
-
-
-
- );
- case "configured":
- return (
-
-
-
-
-
-
- {endpoint.url}
-
- {endpoint.latestIndex ? : "–"}
-
-
- {endpoint.latestIndex ? (
-
-
-
-
- ) : (
- "–"
- )}
-
- {endpoint.latestIndex?.stats?.jobs ?? "–"}
-
-
- );
- }
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.events.$eventParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.events.$eventParam/route.tsx
deleted file mode 100644
index b488ae4315..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.events.$eventParam/route.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import { useNavigation } from "@remix-run/react";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { EventDetail } from "~/components/event/EventDetail";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { RunsFilters } from "~/components/runs/RunFilters";
-import { RunListSearchSchema } from "~/components/runs/RunStatuses";
-import { RunsTable } from "~/components/runs/RunsTable";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useUser } from "~/hooks/useUser";
-import { EventPresenter } from "~/presenters/EventPresenter.server";
-import { RunListPresenter } from "~/presenters/RunListPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { EventParamSchema, projectPath } from "~/utils/pathBuilder";
-import { ListPagination } from "../../components/ListPagination";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { eventParam, projectParam, organizationSlug } = EventParamSchema.parse(params);
-
- const url = new URL(request.url);
- const s = Object.fromEntries(url.searchParams.entries());
- const searchParams = RunListSearchSchema.parse(s);
-
- const presenter = new EventPresenter();
- try {
- const event = await presenter.call({
- userId,
- projectSlug: projectParam,
- organizationSlug,
- eventId: eventParam,
- });
-
- if (!event) {
- throw new Response("Not Found", { status: 404 });
- }
-
- const runsPresenter = new RunListPresenter();
-
- const list = await runsPresenter.call({
- userId,
- filterEnvironment: searchParams.environment,
- filterStatus: searchParams.status,
- eventId: event.id,
- projectSlug: projectParam,
- organizationSlug,
- direction: searchParams.direction,
- cursor: searchParams.cursor,
- from: searchParams.from,
- to: searchParams.to,
- });
-
- return typedjson({ event, list });
- } catch (e) {
- console.log(e);
- throw new Response(e instanceof Error ? e.message : JSON.stringify(e), { status: 404 });
- }
-};
-
-export default function Page() {
- const { event, list } = useTypedLoaderData();
- const navigation = useNavigation();
- const isLoading = navigation.state !== "idle";
- const organization = useOrganization();
- const project = useProject();
- const user = useUser();
-
- return (
-
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.events/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.events/route.tsx
deleted file mode 100644
index 5aca454de0..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.events/route.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { Outlet } from "@remix-run/react";
-
-export default function Page() {
- return ;
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints.$httpEndpointParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints.$httpEndpointParam/route.tsx
deleted file mode 100644
index b8c9a1bb8b..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints.$httpEndpointParam/route.tsx
+++ /dev/null
@@ -1,234 +0,0 @@
-import { BookOpenIcon, CheckIcon } from "@heroicons/react/20/solid";
-import { StopIcon } from "@heroicons/react/24/outline";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
-import { HowToConnectHttpEndpoint } from "~/components/helpContent/HelpContentText";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { LinkButton } from "~/components/primitives/Buttons";
-import { ClipboardField } from "~/components/primitives/ClipboardField";
-import { DateTime } from "~/components/primitives/DateTime";
-import { Header1 } from "~/components/primitives/Headers";
-import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help";
-import {
- PageAccessories,
- NavBar,
- PageInfoGroup,
- PageInfoProperty,
- PageInfoRow,
- PageTitle,
-} from "~/components/primitives/PageHeader";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import {
- Table,
- TableBody,
- TableCell,
- TableHeader,
- TableHeaderCell,
- TableRow,
-} from "~/components/primitives/Table";
-import { TextLink } from "~/components/primitives/TextLink";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { HttpEndpointPresenter } from "~/presenters/HttpEndpointPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { cn } from "~/utils/cn";
-import { HttpEndpointParamSchema, docsPath, projectHttpEndpointsPath } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { projectParam, organizationSlug, httpEndpointParam } =
- HttpEndpointParamSchema.parse(params);
-
- const presenter = new HttpEndpointPresenter();
- try {
- const result = await presenter.call({
- userId,
- projectSlug: projectParam,
- organizationSlug,
- httpEndpointKey: httpEndpointParam,
- });
-
- if (!result) {
- throw new Response("Not Found", { status: 404 });
- }
-
- return typedjson(result);
- } catch (e) {
- console.log(e);
- throw new Response(e instanceof Error ? e.message : JSON.stringify(e), { status: 404 });
- }
-};
-
-export default function Page() {
- const { httpEndpoint, unconfiguredEnvironments, secret } = useTypedLoaderData();
- const organization = useOrganization();
- const project = useProject();
-
- return (
-
-
-
-
-
- HTTP endpoints documentation
-
-
-
- {httpEndpoint.webhook && (
-
-
-
-
-
- )}
-
-
-
- {(open) => (
-
-
-
-
- {httpEndpoint.httpEndpointEnvironments.length > 0
- ? "Ready to receive data"
- : "Not deployed"}
-
-
-
- {httpEndpoint.httpEndpointEnvironments.length > 0 && (
-
-
-
-
- Environment
- Endpoint URL
- Secret
- Source
- Respond to request?
- Updated
-
-
-
- {httpEndpoint.httpEndpointEnvironments.map((httpEnvironment) => {
- return (
-
-
-
-
-
-
-
-
-
-
- {httpEnvironment.source}
-
- {httpEnvironment.immediateResponseFilter ? (
-
- ) : (
-
- )}
-
-
-
-
-
- );
- })}
-
-
-
- )}
-
- {unconfiguredEnvironments.length > 0 && (
- <>
-
Not deployed
-
- You need to deploy your code for the following environments to receive
- webhooks –{" "}
-
- read our deployment guide
-
- .
-
-
-
-
-
- Environment
- Endpoint URL
- Secret
-
-
-
- {unconfiguredEnvironments.map((environment) => {
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
- })}
-
-
-
- >
- )}
-
-
-
-
-
- )}
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints._index/route.tsx
deleted file mode 100644
index 5887a111fa..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints._index/route.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import { BookOpenIcon } from "@heroicons/react/20/solid";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
-import { WhatAreHttpEndpoints } from "~/components/helpContent/HelpContentText";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { LinkButton } from "~/components/primitives/Buttons";
-import { DateTime } from "~/components/primitives/DateTime";
-import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help";
-import { Icon } from "~/components/primitives/Icon";
-import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import {
- Table,
- TableBlankRow,
- TableBody,
- TableCell,
- TableCellChevron,
- TableHeader,
- TableHeaderCell,
- TableRow,
-} from "~/components/primitives/Table";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { HttpEndpointsPresenter } from "~/presenters/HttpEndpointsPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { cn } from "~/utils/cn";
-import { ProjectParamSchema, docsPath, projectHttpEndpointPath } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { projectParam } = ProjectParamSchema.parse(params);
-
- const presenter = new HttpEndpointsPresenter();
- const httpEndpoints = await presenter.call({ userId, slug: projectParam });
-
- return typedjson({ httpEndpoints });
-};
-
-export default function Page() {
- const { httpEndpoints } = useTypedLoaderData();
- const organization = useOrganization();
- const project = useProject();
-
- return (
-
-
-
-
-
- HTTP endpoints documentation
-
-
-
-
-
- {(open) => (
-
-
-
-
-
-
-
-
-
- ID
- Title
- Updated
- Environments
- Go to page
-
-
-
- {httpEndpoints.length > 0 ? (
- httpEndpoints.map((httpEndpoint) => {
- const path = projectHttpEndpointPath(organization, project, httpEndpoint);
- return (
-
-
-
-
- {httpEndpoint.key}
-
-
- {httpEndpoint.title ?? "–"}
-
-
-
-
-
- {httpEndpoint.httpEndpointEnvironments.map((environment) => (
-
- ))}
-
-
-
-
- );
- })
- ) : (
-
- No HTTP endpoints
-
- )}
-
-
-
-
-
-
-
-
- )}
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints/route.tsx
deleted file mode 100644
index 5aca454de0..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints/route.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { Outlet } from "@remix-run/react";
-
-export default function Page() {
- return ;
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx
deleted file mode 100644
index 24729fc55b..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { useNavigation } from "@remix-run/react";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import simplur from "simplur";
-import { z } from "zod";
-import { HowToRunYourJob } from "~/components/helpContent/HelpContentText";
-import { Callout } from "~/components/primitives/Callout";
-import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help";
-import { RunsTable } from "~/components/runs/RunsTable";
-import { useJob } from "~/hooks/useJob";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useUser } from "~/hooks/useUser";
-import { RunListPresenter } from "~/presenters/RunListPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { cn } from "~/utils/cn";
-import {
- JobParamsSchema,
- jobRunsParentPath,
- organizationIntegrationsPath,
-} from "~/utils/pathBuilder";
-import { ListPagination } from "../../components/ListPagination";
-import { RunListSearchSchema } from "~/components/runs/RunStatuses";
-import { RunsFilters } from "~/components/runs/RunFilters";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { jobParam, projectParam, organizationSlug } = JobParamsSchema.parse(params);
-
- const url = new URL(request.url);
- const s = Object.fromEntries(url.searchParams.entries());
- const searchParams = RunListSearchSchema.parse(s);
-
- const presenter = new RunListPresenter();
- const list = await presenter.call({
- userId,
- filterEnvironment: searchParams.environment,
- filterStatus: searchParams.status,
- jobSlug: jobParam,
- projectSlug: projectParam,
- organizationSlug,
- direction: searchParams.direction,
- cursor: searchParams.cursor,
- from: searchParams.from,
- to: searchParams.to,
- });
-
- return typedjson({
- list,
- });
-};
-
-export default function Page() {
- const { list } = useTypedLoaderData();
- const navigation = useNavigation();
- const isLoading = navigation.state !== "idle";
- const organization = useOrganization();
- const project = useProject();
- const job = useJob();
- const user = useUser();
-
- return (
-
- {job.hasIntegrationsRequiringAction && (
-
- {simplur`This Job has ${
- job.integrations.filter((j) => j.setupStatus === "MISSING_FIELDS").length
- } Integration[|s] that [has|have] not been configured.`}
-
- )}
-
- {(open) => (
-
- )}
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.completed/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.completed/route.tsx
deleted file mode 100644
index 3c567b5bbf..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.completed/route.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { RunCompletedDetail } from "~/components/run/RunCompletedDetail";
-import { useRun } from "~/hooks/useRun";
-
-export default function RunCompletedPage() {
- const run = useRun();
-
- return ;
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.stream/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.stream/route.tsx
deleted file mode 100644
index 287ba86a4c..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.stream/route.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { z } from "zod";
-import { RunStreamPresenter } from "~/presenters/RunStreamPresenter.server";
-import { requireUserId } from "~/services/session.server";
-
-export async function loader({ request, params }: LoaderFunctionArgs) {
- await requireUserId(request);
-
- const { runParam } = z.object({ runParam: z.string() }).parse(params);
-
- const presenter = new RunStreamPresenter();
- return presenter.call({ request, runId: runParam });
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route.tsx
deleted file mode 100644
index 0f114b1b82..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { UseDataFunctionReturn, typedjson, useTypedLoaderData } from "remix-typedjson";
-import { Spinner } from "~/components/primitives/Spinner";
-import { TaskDetail } from "~/components/run/TaskDetail";
-import { TaskDetailsPresenter } from "~/presenters/TaskDetailsPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { TaskParamsSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { taskParam } = TaskParamsSchema.parse(params);
-
- const presenter = new TaskDetailsPresenter();
- const task = await presenter.call({
- userId,
- id: taskParam,
- });
-
- return typedjson({
- task,
- });
-};
-
-export type DetailedTask = NonNullable["task"]>;
-
-export default function Page() {
- const { task } = useTypedLoaderData();
-
- if (!task) {
- return (
-
-
-
- );
- }
-
- return ;
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.trigger/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.trigger/route.tsx
deleted file mode 100644
index 7f86d26b72..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.trigger/route.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { TriggerDetail } from "~/components/run/TriggerDetail";
-import { useJob } from "~/hooks/useJob";
-import { useRun } from "~/hooks/useRun";
-import { TriggerDetailsPresenter } from "~/presenters/TriggerDetailsPresenter.server";
-import { RunParamsSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const { runParam } = RunParamsSchema.parse(params);
-
- const presenter = new TriggerDetailsPresenter();
- const trigger = await presenter.call(runParam);
-
- if (!trigger) {
- throw new Response(null, {
- status: 404,
- });
- }
-
- return typedjson({
- trigger,
- });
-};
-
-export default function Page() {
- const { trigger } = useTypedLoaderData();
- const job = useJob();
- const run = useRun();
-
- return ;
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx
deleted file mode 100644
index 4cc14090f4..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { useRevalidator } from "@remix-run/react";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { useEffect } from "react";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { RunOverview } from "~/components/run/RunOverview";
-import { useEventSource } from "~/hooks/useEventSource";
-import { useJob } from "~/hooks/useJob";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useUser } from "~/hooks/useUser";
-import { RunPresenter } from "~/presenters/RunPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import {
- RunParamsSchema,
- jobPath,
- jobRunsParentPath,
- runPath,
- runStreamingPath,
-} from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { runParam } = RunParamsSchema.parse(params);
-
- const presenter = new RunPresenter();
- const run = await presenter.call({
- userId,
- id: runParam,
- });
-
- if (!run) {
- throw new Response(null, {
- status: 404,
- });
- }
-
- return typedjson({
- run,
- });
-};
-
-export default function Page() {
- const { run } = useTypedLoaderData();
- const organization = useOrganization();
- const project = useProject();
- const job = useJob();
- const user = useUser();
-
- const revalidator = useRevalidator();
- const events = useEventSource(runStreamingPath(organization, project, job, run), {
- event: "message",
- disabled: !!run.completedAt || run.tasks.length > 100,
- });
- useEffect(() => {
- if (events !== null) {
- revalidator.revalidate();
- }
- // WARNING Don't put the revalidator in the useEffect deps array or bad things will happen
- }, [events]); // eslint-disable-line react-hooks/exhaustive-deps
-
- return (
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.settings/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.settings/route.tsx
deleted file mode 100644
index e15428acdb..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.settings/route.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { JobStatusTable } from "~/components/JobsStatusTable";
-import { HowToDisableAJob } from "~/components/helpContent/HelpContentText";
-import { DeleteJobDialogContent } from "~/components/jobs/DeleteJobModalContent";
-import { Button } from "~/components/primitives/Buttons";
-import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "~/components/primitives/Dialog";
-import { Header2 } from "~/components/primitives/Headers";
-import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { useJob } from "~/hooks/useJob";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { cn } from "~/utils/cn";
-import { projectJobsPath } from "~/utils/pathBuilder";
-
-export default function Page() {
- const job = useJob();
- const organization = useOrganization();
- const project = useProject();
-
- return (
-
- {(open) => (
-
-
-
- Environments
-
-
-
-
- {job.status === "ACTIVE" && (
-
- Disable this Job in all environments before deleting
-
- )}
-
-
-
-
- I want to delete this Job
-
-
-
- Delete Job
-
-
-
-
-
-
-
-
-
- )}
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.test/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.test/route.tsx
deleted file mode 100644
index a4e2902f80..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.test/route.tsx
+++ /dev/null
@@ -1,375 +0,0 @@
-import { useForm } from "@conform-to/react";
-import { parse } from "@conform-to/zod";
-import { BeakerIcon } from "@heroicons/react/20/solid";
-import { ClockIcon, CodeBracketIcon } from "@heroicons/react/24/outline";
-import { Form, useActionData, useSubmit } from "@remix-run/react";
-import { ActionFunction, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
-import { useCallback, useRef, useState } from "react";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { z } from "zod";
-import { JSONEditor } from "~/components/code/JSONEditor";
-import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
-import { Button, LinkButton } from "~/components/primitives/Buttons";
-import { Callout } from "~/components/primitives/Callout";
-import { DateTime } from "~/components/primitives/DateTime";
-import { DetailCell } from "~/components/primitives/DetailCell";
-import { FormError } from "~/components/primitives/FormError";
-import { Header2 } from "~/components/primitives/Headers";
-import { Hint } from "~/components/primitives/Hint";
-import { Input } from "~/components/primitives/Input";
-import { InputGroup } from "~/components/primitives/InputGroup";
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "~/components/primitives/SimpleSelect";
-import { TextLink } from "~/components/primitives/TextLink";
-import { runStatusClassNameColor, runStatusTitle } from "~/components/runs/RunStatuses";
-import { redirectBackWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
-import { TestJobPresenter } from "~/presenters/TestJobPresenter.server";
-import { TestJobService } from "~/services/jobs/testJob.server";
-import { requireUserId } from "~/services/session.server";
-import { isValidIcon } from "~/utils/icon";
-import { JobParamsSchema, docsPath, jobRunDashboardPath } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, projectParam, jobParam } = JobParamsSchema.parse(params);
-
- const presenter = new TestJobPresenter();
- const { environments, runs, examples } = await presenter.call({
- userId,
- organizationSlug,
- projectSlug: projectParam,
- jobSlug: jobParam,
- });
-
- return typedjson({ environments, runs, examples });
-};
-
-const schema = z.object({
- payload: z.string().transform((payload, ctx) => {
- try {
- const data = JSON.parse(payload);
- return data as any;
- } catch (e) {
- console.log("parsing error", e);
-
- if (e instanceof Error) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: e.message,
- });
- } else {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: "This is invalid JSON",
- });
- }
- }
- }),
- environmentId: z.string(),
- versionId: z.string(),
- accountId: z.string().optional(),
-});
-
-//todo save the chosen environment to a cookie (for that user), use it to default the env dropdown
-export const action: ActionFunction = async ({ request, params }) => {
- const { organizationSlug, projectParam, jobParam } = JobParamsSchema.parse(params);
-
- const formData = await request.formData();
-
- const submission = parse(formData, { schema });
-
- if (!submission.value) {
- return json(submission);
- }
-
- const testService = new TestJobService();
- const run = await testService.call(submission.value);
-
- if (!run) {
- return redirectBackWithErrorMessage(
- request,
- "Unable to start a test run: Something went wrong"
- );
- }
-
- return redirectWithSuccessMessage(
- jobRunDashboardPath(
- { slug: organizationSlug },
- { slug: projectParam },
- { slug: jobParam },
- { id: run.id }
- ),
- request,
- "Test run created"
- );
-};
-
-const startingJson = "{\n\n}";
-
-export default function Page() {
- const { environments, runs, examples } = useTypedLoaderData();
-
- //form submission
- const submit = useSubmit();
- const lastSubmission = useActionData();
-
- //examples
- const [selectedCodeSampleId, setSelectedCodeSampleId] = useState(
- examples.at(0)?.id ?? runs.at(0)?.id
- );
- const selectedCodeSample =
- examples.find((e) => e.id === selectedCodeSampleId)?.payload ??
- runs.find((r) => r.id === selectedCodeSampleId)?.payload;
-
- const [defaultJson, setDefaultJson] = useState(selectedCodeSample ?? startingJson);
- const setCode = useCallback((code: string) => {
- setDefaultJson(code);
- }, []);
-
- const [selectedEnvironmentId, setSelectedEnvironmentId] = useState(environments.at(0)?.id);
- const selectedEnvironment = environments.find((e) => e.id === selectedEnvironmentId);
-
- const currentJson = useRef(defaultJson);
- const [currentAccountId, setCurrentAccountId] = useState(undefined);
-
- const submitForm = useCallback(
- (e: React.FormEvent) => {
- if (!selectedEnvironmentId) {
- return;
- }
-
- submit(
- {
- payload: currentJson.current,
- environmentId: selectedEnvironmentId,
- versionId: selectedEnvironment?.versionId ?? "",
- ...(currentAccountId ? { accountId: currentAccountId } : {}),
- },
- {
- action: "",
- method: "post",
- }
- );
- e.preventDefault();
- },
- [currentJson, selectedEnvironmentId, currentAccountId]
- );
-
- const [form, { environmentId, payload, accountId }] = useForm({
- id: "test-job",
- // TODO: type this
- lastSubmission: lastSubmission as any,
- onValidate({ formData }) {
- return parse(formData, { schema });
- },
- });
-
- if (environments.length === 0) {
- return (
-
-
- There are no environments that you can test this job with – you can't run Tests against
- your teammates' Dev environments. You should run the code locally (using the CLI) so that
- this Job will be associated with your Dev environment. This also means that this Job
- hasn't been deployed to Staging or Prod yet.
-
-
-
Useful guides
-
-
- Using the CLI
-
-
- Deploying your Jobs
-
-
-
-
- );
- }
-
- return (
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.trigger/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.trigger/route.tsx
deleted file mode 100644
index 2ffa0fe602..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.trigger/route.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { ComingSoon } from "~/components/ComingSoon";
-
-export default function Page() {
- return (
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route.tsx
deleted file mode 100644
index f207c4ba16..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import { Outlet, useLocation } from "@remix-run/react";
-import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson } from "remix-typedjson";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { LinkButton } from "~/components/primitives/Buttons";
-import { Callout } from "~/components/primitives/Callout";
-import { NamedIcon } from "~/components/primitives/NamedIcon";
-import {
- PageAccessories,
- NavBar,
- PageInfoGroup,
- PageInfoProperty,
- PageInfoRow,
- PageTabs,
- PageTitle,
-} from "~/components/primitives/PageHeader";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { useJob } from "~/hooks/useJob";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useOptionalRun } from "~/hooks/useRun";
-import { JobPresenter } from "~/presenters/JobPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { titleCase } from "~/utils";
-import { JobParamsSchema, jobPath, jobSettingsPath, jobTestPath } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { jobParam, projectParam, organizationSlug } = JobParamsSchema.parse(params);
-
- const presenter = new JobPresenter();
- const job = await presenter.call({
- userId,
- jobSlug: jobParam,
- organizationSlug,
- projectSlug: projectParam,
- });
-
- if (!job) {
- throw new Response("Not Found", {
- status: 404,
- statusText: `There is no Job ${jobParam} in this Project.`,
- });
- }
-
- return typedjson({
- job,
- });
-};
-
-export default function Job() {
- const organization = useOrganization();
- const project = useProject();
- const job = useJob();
- const run = useOptionalRun();
- const renderHeader = run === undefined;
- const location = useLocation();
-
- const isTestPage = location.pathname.endsWith("/test");
-
- return renderHeader ? (
-
-
-
- {!isTestPage && (
-
-
- Test
-
-
- )}
-
-
-
-
-
-
- {job.dynamic && }
-
- {job.properties &&
- job.properties.map((property, index) => (
-
- ))}
- {job.integrations.length > 0 && (
-
- {job.integrations.map((integration, index) => (
-
- ))}
-
- }
- />
- )}
-
-
-
-
- UID: {job.id}
-
-
-
-
-
-
-
-
-
- ) : (
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx
deleted file mode 100644
index ba87f37262..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import { Await, useLoaderData, useNavigation } from "@remix-run/react";
-import { LoaderFunctionArgs, defer } from "@remix-run/server-runtime";
-import { Suspense } from "react";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { LinkButton } from "~/components/primitives/Buttons";
-import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
-import { RunsFilters } from "~/components/runs/RunFilters";
-import { RunListSearchSchema } from "~/components/runs/RunStatuses";
-import { RunsTable } from "~/components/runs/RunsTable";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useUser } from "~/hooks/useUser";
-import { RunListPresenter } from "~/presenters/RunListPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { ProjectParamSchema, docsPath, projectPath } from "~/utils/pathBuilder";
-import { ListPagination } from "../../components/ListPagination";
-import { BookOpenIcon } from "@heroicons/react/20/solid";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { projectParam, organizationSlug } = ProjectParamSchema.parse(params);
-
- const url = new URL(request.url);
- const s = Object.fromEntries(url.searchParams.entries());
- const searchParams = RunListSearchSchema.parse(s);
-
- const presenter = new RunListPresenter();
-
- const list = presenter.call({
- userId,
- filterEnvironment: searchParams.environment,
- filterStatus: searchParams.status,
- projectSlug: projectParam,
- organizationSlug,
- direction: searchParams.direction,
- cursor: searchParams.cursor,
- pageSize: 25,
- from: searchParams.from,
- to: searchParams.to,
- });
-
- return defer({
- list,
- });
-};
-
-export default function Page() {
- const { list } = useLoaderData();
- const navigation = useNavigation();
- const isLoading = navigation.state !== "idle";
- const organization = useOrganization();
- const project = useProject();
- const user = useUser();
-
- return (
-
-
-
-
-
- Run documentation
-
-
-
-
-
-
-
-
-
>}>
- {(data) => }
-
-
-
- }
- >
-
- {(data) => {
- const runs = data.runs.map((run) => ({
- ...run,
- startedAt: run.startedAt ? new Date(run.startedAt) : null,
- completedAt: run.completedAt ? new Date(run.completedAt) : null,
- createdAt: new Date(run.createdAt),
- }));
-
- return (
- <>
-
-
- >
- );
- }}
-
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.settings/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.settings/route.tsx
deleted file mode 100644
index d28182a3e3..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.settings/route.tsx
+++ /dev/null
@@ -1,260 +0,0 @@
-import { conform, useForm } from "@conform-to/react";
-import { parse } from "@conform-to/zod";
-import { Form, useActionData, useNavigation } from "@remix-run/react";
-import { ActionFunction, json } from "@remix-run/server-runtime";
-import { redirect } from "remix-typedjson";
-import { z } from "zod";
-import { InlineCode } from "~/components/code/InlineCode";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { Button } from "~/components/primitives/Buttons";
-import { Fieldset } from "~/components/primitives/Fieldset";
-import { FormButtons } from "~/components/primitives/FormButtons";
-import { FormError } from "~/components/primitives/FormError";
-import { Header2 } from "~/components/primitives/Headers";
-import { Hint } from "~/components/primitives/Hint";
-import { Input } from "~/components/primitives/Input";
-import { InputGroup } from "~/components/primitives/InputGroup";
-import { Label } from "~/components/primitives/Label";
-import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
-import { prisma } from "~/db.server";
-import { useProject } from "~/hooks/useProject";
-import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
-import {
- clearCurrentProjectId,
- commitCurrentProjectSession,
-} from "~/services/currentProject.server";
-import { DeleteProjectService } from "~/services/deleteProject.server";
-import { logger } from "~/services/logger.server";
-import { requireUserId } from "~/services/session.server";
-import { organizationPath, projectPath } from "~/utils/pathBuilder";
-
-export function createSchema(
- constraints: {
- getSlugMatch?: (slug: string) => { isMatch: boolean; projectSlug: string };
- } = {}
-) {
- return z.discriminatedUnion("action", [
- z.object({
- action: z.literal("rename"),
- projectName: z.string().min(3, "Project name must have at least 3 characters").max(50),
- }),
- z.object({
- action: z.literal("delete"),
- projectSlug: z.string().superRefine((slug, ctx) => {
- if (constraints.getSlugMatch === undefined) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: conform.VALIDATION_UNDEFINED,
- });
- } else {
- const { isMatch, projectSlug } = constraints.getSlugMatch(slug);
- if (isMatch) {
- return;
- }
-
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `The slug must match ${projectSlug}`,
- });
- }
- }),
- }),
- ]);
-}
-
-export const action: ActionFunction = async ({ request, params }) => {
- const userId = await requireUserId(request);
- const { organizationSlug, projectParam } = params;
- if (!organizationSlug || !projectParam) {
- return json({ errors: { body: "organizationSlug is required" } }, { status: 400 });
- }
-
- const formData = await request.formData();
-
- const schema = createSchema({
- getSlugMatch: (slug) => {
- return { isMatch: slug === projectParam, projectSlug: projectParam };
- },
- });
- const submission = parse(formData, { schema });
-
- if (!submission.value || submission.intent !== "submit") {
- return json(submission);
- }
-
- try {
- switch (submission.value.action) {
- case "rename": {
- await prisma.project.update({
- where: {
- slug: projectParam,
- organization: {
- members: {
- some: {
- userId,
- },
- },
- },
- },
- data: {
- name: submission.value.projectName,
- },
- });
-
- return redirectWithSuccessMessage(
- projectPath({ slug: organizationSlug }, { slug: projectParam }),
- request,
- `Project renamed to ${submission.value.projectName}`
- );
- }
- case "delete": {
- const deleteProjectService = new DeleteProjectService();
- try {
- await deleteProjectService.call({ projectSlug: projectParam, userId });
-
- //we need to clear the project from the session
- const removeProjectIdSession = await clearCurrentProjectId(request);
- return redirect(organizationPath({ slug: organizationSlug }), {
- headers: { "Set-Cookie": await commitCurrentProjectSession(removeProjectIdSession) },
- });
- } catch (error: unknown) {
- logger.error("Project could not be deleted", {
- error: error instanceof Error ? error.message : JSON.stringify(error),
- });
- return redirectWithErrorMessage(
- organizationPath({ slug: organizationSlug }),
- request,
- `Project ${projectParam} could not be deleted`
- );
- }
- }
- }
- } catch (error: any) {
- return json({ errors: { body: error.message } }, { status: 400 });
- }
-};
-
-export default function Page() {
- const project = useProject();
- const lastSubmission = useActionData();
- const navigation = useNavigation();
-
- const [renameForm, { projectName }] = useForm({
- id: "rename-project",
- // TODO: type this
- lastSubmission: lastSubmission as any,
- shouldRevalidate: "onSubmit",
- onValidate({ formData }) {
- return parse(formData, {
- schema: createSchema(),
- });
- },
- });
-
- const [deleteForm, { projectSlug }] = useForm({
- id: "delete-project",
- // TODO: type this
- lastSubmission: lastSubmission as any,
- shouldValidate: "onInput",
- shouldRevalidate: "onSubmit",
- onValidate({ formData }) {
- return parse(formData, {
- schema: createSchema({
- getSlugMatch: (slug) => ({ isMatch: slug === project.slug, projectSlug: project.slug }),
- }),
- });
- },
- });
-
- const isRenameLoading =
- navigation.formData?.get("action") === "rename" &&
- (navigation.state === "submitting" || navigation.state === "loading");
-
- const isDeleteLoading =
- navigation.formData?.get("action") === "delete" &&
- (navigation.state === "submitting" || navigation.state === "loading");
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- Danger zone
-
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup._index/route.tsx
deleted file mode 100644
index 8d2aa05f03..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup._index/route.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { FrameworkSelector } from "~/components/frameworks/FrameworkSelector";
-
-export default function Page() {
- return (
- <>
-
- >
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.astro/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.astro/route.tsx
deleted file mode 100644
index f2aa69f43f..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.astro/route.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
-import { AstroLogo } from "~/assets/logos/AstroLogo";
-import { Feedback } from "~/components/Feedback";
-import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands";
-import { StepContentContainer } from "~/components/StepContentContainer";
-import { InlineCode } from "~/components/code/InlineCode";
-import { Button, LinkButton } from "~/components/primitives/Buttons";
-import { Callout } from "~/components/primitives/Callout";
-import { Header1 } from "~/components/primitives/Headers";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { StepNumber } from "~/components/primitives/StepNumber";
-import { useAppOrigin } from "~/hooks/useAppOrigin";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
-import { projectSetupPath } from "~/utils/pathBuilder";
-import { useV2OnboardingApiKey } from "../_app.orgs.$organizationSlug.projects.$projectParam.setup/route";
-
-export default function SetUpAstro() {
- const organization = useOrganization();
- const project = useProject();
- useProjectSetupComplete();
- const { apiKey } = useV2OnboardingApiKey();
- const appOrigin = useAppOrigin();
-
- return (
-
-
-
-
- Get setup in 5 minutes
-
-
-
- Choose a different framework
-
-
- I'm stuck!
-
- }
- defaultValue="help"
- />
-
-
-
-
- Trigger.dev has full support for serverless. We will be adding support for long-running
- servers soon.
-
-
-
-
-
-
-
- You’ll notice a new folder in your project called 'jobs'. We’ve added a very simple
- example Job in example.ts to help you
- get started.
-
-
-
-
-
-
-
-
-
-
-
-
- This page will automatically refresh.
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx
deleted file mode 100644
index ede841fdd8..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
-import { ExpressLogo } from "~/assets/logos/ExpressLogo";
-import { Feedback } from "~/components/Feedback";
-import { RunDevCommand, TriggerDevStep } from "~/components/SetupCommands";
-import { StepContentContainer } from "~/components/StepContentContainer";
-import { Badge } from "~/components/primitives/Badge";
-import { Button, LinkButton } from "~/components/primitives/Buttons";
-import { Callout } from "~/components/primitives/Callout";
-import { ClipboardField } from "~/components/primitives/ClipboardField";
-import { Header1 } from "~/components/primitives/Headers";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { StepNumber } from "~/components/primitives/StepNumber";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
-import { projectSetupPath } from "~/utils/pathBuilder";
-import { useV2OnboardingApiKey } from "../_app.orgs.$organizationSlug.projects.$projectParam.setup/route";
-
-export default function Page() {
- const organization = useOrganization();
- const project = useProject();
- useProjectSetupComplete();
- const { apiKey } = useV2OnboardingApiKey();
-
- return (
-
-
-
-
-
-
- Get setup in 5 minutes
-
-
-
- Choose a different framework
-
-
- I'm stuck!
-
- }
- defaultValue="help"
- />
-
-
-
-
- Trigger.dev has full support for serverless. We will be adding support for long-running
- servers soon.
-
-
-
-
- Copy your server API Key to your clipboard:
-
- Server}
- />
-
- Now follow this guide:
-
- Manual installation guide
-
-
-
-
-
-
- You may be using the `start` script instead, in which case substitute `dev` in the
- above commands.
-
-
-
-
-
-
-
-
- This page will automatically refresh.
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.fastify/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.fastify/route.tsx
deleted file mode 100644
index d9cb3fbeb3..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.fastify/route.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { FastifyLogo } from "~/assets/logos/FastifyLogo";
-import { FrameworkComingSoon } from "~/components/frameworks/FrameworkComingSoon";
-
-export default function Page() {
- return (
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nestjs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nestjs/route.tsx
deleted file mode 100644
index e4929d27dd..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nestjs/route.tsx
+++ /dev/null
@@ -1,212 +0,0 @@
-import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
-import { NestjsLogo } from "~/assets/logos/NestjsLogo";
-import { Feedback } from "~/components/Feedback";
-import { TriggerDevStep } from "~/components/SetupCommands";
-import { StepContentContainer } from "~/components/StepContentContainer";
-import { InlineCode } from "~/components/code/InlineCode";
-import { InstallPackages } from "~/components/code/InstallPackages";
-import { Button, LinkButton } from "~/components/primitives/Buttons";
-import { Header1 } from "~/components/primitives/Headers";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { StepNumber } from "~/components/primitives/StepNumber";
-import { useAppOrigin } from "~/hooks/useAppOrigin";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
-import { projectSetupPath } from "~/utils/pathBuilder";
-import { CodeBlock } from "../../components/code/CodeBlock";
-import { useV2OnboardingApiKey } from "../_app.orgs.$organizationSlug.projects.$projectParam.setup/route";
-
-const AppModuleCode = `
-import { Module } from '@nestjs/common';
-import { ConfigModule, ConfigService } from '@nestjs/config';
-import { TriggerDevModule } from '@trigger.dev/nestjs';
-
-@Module({
- imports: [
- ConfigModule.forRoot({
- isGlobal: true,
- }),
- TriggerDevModule.registerAsync({
- inject: [ConfigService],
- useFactory: (config: ConfigService) => ({
- id: 'my-nest-app',
- apiKey: config.getOrThrow('TRIGGER_API_KEY'),
- apiUrl: config.getOrThrow('TRIGGER_API_URL'),
- verbose: false,
- ioLogLocalEnabled: true,
- }),
- }),
- ],
-})
-export class AppModule {}
-`;
-
-const JobControllerCode = `
-import { Controller, Get } from '@nestjs/common';
-import { InjectTriggerDevClient } from '@trigger.dev/nestjs';
-import { eventTrigger, TriggerClient } from '@trigger.dev/sdk';
-
-@Controller()
-export class JobController {
- constructor(
- @InjectTriggerDevClient() private readonly client: TriggerClient,
- ) {
- this.client.defineJob({
- id: 'test-job',
- name: 'Test Job One',
- version: '0.0.1',
- trigger: eventTrigger({
- name: 'test.event',
- }),
- run: async (payload, io, ctx) => {
- await io.logger.info('Hello world!', { payload });
-
- return {
- message: 'Hello world!',
- };
- },
- });
- }
-
- @Get()
- getHello(): string {
- return \`Running Trigger.dev with client-id \${this.client.id}\`;
- }
-}`;
-
-const AppModuleWithControllerCode = `
-import { Module } from '@nestjs/common';
-import { ConfigModule, ConfigService } from '@nestjs/config';
-import { TriggerDevModule } from '@trigger.dev/nestjs';
-import { JobController } from './job.controller';
-
-@Module({
- imports: [
- ConfigModule.forRoot({
- isGlobal: true,
- }),
- TriggerDevModule.registerAsync({
- inject: [ConfigService],
- useFactory: (config: ConfigService) => ({
- id: 'my-nest-app',
- apiKey: config.getOrThrow('TRIGGER_API_KEY'),
- apiUrl: config.getOrThrow('TRIGGER_API_URL'),
- verbose: false,
- ioLogLocalEnabled: true,
- }),
- }),
- ],
- controllers: [
- //...existingControllers,
- JobController
- ],
-})
-export class AppModule {}
-`;
-
-const packageJsonCode = `"trigger.dev": {
- "endpointId": "my-nest-app"
-}`;
-
-export default function SetupNestJS() {
- const organization = useOrganization();
- const project = useProject();
- useProjectSetupComplete();
- const { apiKey } = useV2OnboardingApiKey();
- const appOrigin = useAppOrigin();
-
- return (
-
-
-
-
-
-
- Get setup in 5 minutes
-
-
-
- Choose a different framework
-
-
- I'm stuck!
-
- }
- defaultValue="help"
- />
-
-
- <>
-
-
-
-
-
-
-
- Inside your .env file, create the following env variables:
-
-
-
-
-
-
- Now, go to your app.module.ts and add the{" "}
- TriggerDevModule :
-
-
-
-
-
-
- Create a controller called{" "}
- job.controller.ts and add the following code:
-
-
-
-
-
-
- Now, add the new controller to your{" "}
- app.module.ts :
-
-
-
-
-
-
- Now, add this to the top-level of your package.json :
-
-
-
-
-
-
- Finally, run your project with npm run start :
-
-
-
-
-
-
-
-
- This page will automatically refresh.
-
- >
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nextjs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nextjs/route.tsx
deleted file mode 100644
index c01c599b60..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nextjs/route.tsx
+++ /dev/null
@@ -1,205 +0,0 @@
-import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
-import { useState } from "react";
-import { NextjsLogo } from "~/assets/logos/NextjsLogo";
-import { Feedback } from "~/components/Feedback";
-import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands";
-import { StepContentContainer } from "~/components/StepContentContainer";
-import { InlineCode } from "~/components/code/InlineCode";
-import { Button, LinkButton } from "~/components/primitives/Buttons";
-import { Callout } from "~/components/primitives/Callout";
-import {
- ClientTabs,
- ClientTabsContent,
- ClientTabsList,
- ClientTabsTrigger,
-} from "~/components/primitives/ClientTabs";
-import { ClipboardField } from "~/components/primitives/ClipboardField";
-import { Header1 } from "~/components/primitives/Headers";
-import { NamedIcon } from "~/components/primitives/NamedIcon";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { RadioGroup, RadioGroupItem } from "~/components/primitives/RadioButton";
-import { StepNumber } from "~/components/primitives/StepNumber";
-import { useAppOrigin } from "~/hooks/useAppOrigin";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
-import { projectSetupPath } from "~/utils/pathBuilder";
-import { useV2OnboardingApiKey } from "../_app.orgs.$organizationSlug.projects.$projectParam.setup/route";
-
-type SelectionChoices = "use-existing-project" | "create-new-next-app";
-
-export default function SetupNextjs() {
- const organization = useOrganization();
- const project = useProject();
- useProjectSetupComplete();
- const { apiKey } = useV2OnboardingApiKey();
- const appOrigin = useAppOrigin();
- const [selectedValue, setSelectedValue] = useState(null);
-
- return (
-
-
-
-
-
-
- Get setup in {selectedValue === "create-new-next-app" ? "5" : "2"} minutes
-
-
-
- Choose a different framework
-
-
- I'm stuck!
-
- }
- defaultValue="help"
- />
-
-
-
setSelectedValue(value as SelectionChoices)}
- >
- }
- />
- }
- />
-
- {selectedValue && (
- <>
-
- Trigger.dev has full support for serverless. We will be adding support for long-running
- servers soon.
-
- {selectedValue === "create-new-next-app" ? (
- <>
-
-
-
-
- npm
- pnpm
- yarn
-
-
-
-
-
-
-
-
-
-
-
-
-
- Trigger.dev works with either the Pages or App Router configuration.
-
-
-
-
-
- You have now created a new Next.js project. Let’s cd into
- it using the project name you just provided:
-
-
-
-
-
-
-
- You’ll notice a new folder in your project called 'jobs'. We’ve added a very
- simple example Job in examples.ts {" "}
- to help you get started.
-
-
-
-
-
-
-
-
-
-
-
-
- This page will automatically refresh.
-
- >
- ) : (
- <>
-
-
-
-
-
- You’ll notice a new folder in your project called 'jobs'. We’ve added a very
- simple example Job in examples.ts {" "}
- to help you get started.
-
-
-
-
-
-
-
-
-
-
-
-
- This page will automatically refresh.
-
- >
- )}
- >
- )}
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nuxt/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nuxt/route.tsx
deleted file mode 100644
index 1c767168c1..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nuxt/route.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { NuxtLogo } from "~/assets/logos/NuxtLogo";
-import { FrameworkComingSoon } from "~/components/frameworks/FrameworkComingSoon";
-
-export default function Page() {
- return (
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.redwood/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.redwood/route.tsx
deleted file mode 100644
index 2a4ccf3543..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.redwood/route.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { RedwoodLogo } from "~/assets/logos/RedwoodLogo";
-import { FrameworkComingSoon } from "~/components/frameworks/FrameworkComingSoon";
-
-export default function Page() {
- return (
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.remix/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.remix/route.tsx
deleted file mode 100644
index 620a09c4cd..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.remix/route.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
-import { RemixLogo } from "~/assets/logos/RemixLogo";
-import { Feedback } from "~/components/Feedback";
-import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands";
-import { StepContentContainer } from "~/components/StepContentContainer";
-import { InlineCode } from "~/components/code/InlineCode";
-import { Button, LinkButton } from "~/components/primitives/Buttons";
-import { Callout } from "~/components/primitives/Callout";
-import { Header1 } from "~/components/primitives/Headers";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { StepNumber } from "~/components/primitives/StepNumber";
-import { useAppOrigin } from "~/hooks/useAppOrigin";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
-import { projectSetupPath } from "~/utils/pathBuilder";
-import { useV2OnboardingApiKey } from "../_app.orgs.$organizationSlug.projects.$projectParam.setup/route";
-
-export default function SetUpRemix() {
- const organization = useOrganization();
- const project = useProject();
- useProjectSetupComplete();
- const { apiKey } = useV2OnboardingApiKey();
- const appOrigin = useAppOrigin();
-
- return (
-
-
-
-
-
-
- Get setup in 5 minutes
-
-
-
- Choose a different framework
-
-
- I'm stuck!
-
- }
- defaultValue="help"
- />
-
-
-
-
- Trigger.dev has full support for serverless. We will be adding support for long-running
- servers soon.
-
-
-
-
-
-
-
- You’ll notice a new folder in your project called 'jobs'. We’ve added a very simple
- example Job in example.server.ts to
- help you get started.
-
-
-
-
-
-
-
-
-
-
-
-
- This page will automatically refresh.
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.sveltekit/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.sveltekit/route.tsx
deleted file mode 100644
index 47da85ab79..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.sveltekit/route.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
-import { SvelteKitLogo } from "~/assets/logos/SveltekitLogo";
-import { Feedback } from "~/components/Feedback";
-import { RunDevCommand, TriggerDevStep } from "~/components/SetupCommands";
-import { StepContentContainer } from "~/components/StepContentContainer";
-import { Badge } from "~/components/primitives/Badge";
-import { Button, LinkButton } from "~/components/primitives/Buttons";
-import { Callout } from "~/components/primitives/Callout";
-import { ClipboardField } from "~/components/primitives/ClipboardField";
-import { Header1 } from "~/components/primitives/Headers";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { StepNumber } from "~/components/primitives/StepNumber";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
-import { projectSetupPath } from "~/utils/pathBuilder";
-import { useV2OnboardingApiKey } from "../_app.orgs.$organizationSlug.projects.$projectParam.setup/route";
-
-export default function SetUpSveltekit() {
- const organization = useOrganization();
- const project = useProject();
- useProjectSetupComplete();
- const { apiKey } = useV2OnboardingApiKey();
-
- return (
-
-
-
-
-
-
- Get setup in 5 minutes
-
-
-
- Choose a different framework
-
-
- I'm stuck!
-
- }
- defaultValue="help"
- />
-
-
-
-
- Trigger.dev has full support for serverless. We will be adding support for long-running
- servers soon.
-
-
-
-
- Copy your server API Key to your clipboard:
-
- Server}
- />
-
- Now follow this guide:
-
- Manual installation guide
-
-
-
-
-
-
-
-
-
-
-
-
-
- This page will automatically refresh.
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup/route.tsx
deleted file mode 100644
index 031e790ac3..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup/route.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { Outlet } from "@remix-run/react";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson } from "remix-typedjson";
-import { prisma } from "~/db.server";
-import { useTypedMatchData, useTypedMatchesData } from "~/hooks/useTypedMatchData";
-import { requireUserId } from "~/services/session.server";
-import { ProjectParamSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
-
- const environment = await prisma.runtimeEnvironment.findFirst({
- where: {
- organization: {
- slug: organizationSlug,
- },
- project: {
- slug: projectParam,
- },
- orgMember: {
- userId,
- },
- },
- });
-
- if (!environment) {
- throw new Response("Not Found", { status: 404 });
- }
-
- return typedjson({
- apiKey: environment.apiKey,
- });
-};
-
-export function useV2OnboardingApiKey() {
- const routeMatch = useTypedMatchesData({
- id: "routes/_app.orgs.$organizationSlug.projects.$projectParam.setup",
- });
- if (!routeMatch) {
- throw new Error("Route match not found");
- }
-
- return routeMatch;
-}
-
-export default function Page() {
- return (
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx
deleted file mode 100644
index 8903add2e8..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
-import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
-import { LabelValueStack } from "~/components/primitives/LabelValueStack";
-import { NamedIcon } from "~/components/primitives/NamedIcon";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import {
- Table,
- TableBlankRow,
- TableBody,
- TableCell,
- TableCellChevron,
- TableHeader,
- TableHeaderCell,
- TableRow,
-} from "~/components/primitives/Table";
-import { SimpleTooltip } from "~/components/primitives/Tooltip";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { TriggersPresenter } from "~/presenters/TriggersPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { cn } from "~/utils/cn";
-import { ProjectParamSchema, externalTriggerPath } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
-
- const presenter = new TriggersPresenter();
- const data = await presenter.call({
- userId,
- organizationSlug,
- projectSlug: projectParam,
- });
-
- return typedjson(data);
-};
-
-export default function Integrations() {
- const { triggers } = useTypedLoaderData();
- const organization = useOrganization();
- const project = useProject();
-
- return (
- <>
-
- External Triggers get registered with external APIs, for example a webhook.
-
-
-
-
- Integration
- Dynamic
- Properties
- Environment
- Active
- Go to page
-
-
-
- {triggers.length > 0 ? (
- triggers.map((t) => {
- const path = externalTriggerPath(organization, project, t);
- return (
-
-
-
-
-
-
-
-
- {t.dynamicTrigger ? (
-
-
- {t.dynamicTrigger.slug}
-
- ) : (
- –
- )}
-
-
- {t.params && (
-
- {Object.entries(t.params).map(([label, value], index) => (
-
- ))}
-
- }
- content={
-
- {Object.entries(t.params).map(([label, value], index) => (
-
- ))}
-
- }
- />
- )}
-
-
-
-
-
-
-
- {t.active ? (
-
- ) : (
-
- )}
-
-
-
- );
- })
- ) : (
-
- No External triggers
-
- )}
-
-
- >
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx
deleted file mode 100644
index 245bd7392f..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-import { NoSymbolIcon } from "@heroicons/react/20/solid";
-import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
-import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { z } from "zod";
-import { ListPagination } from "~/components/ListPagination";
-import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
-import { DateTime } from "~/components/primitives/DateTime";
-import { LabelValueStack } from "~/components/primitives/LabelValueStack";
-import { NamedIcon } from "~/components/primitives/NamedIcon";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import {
- Table,
- TableBlankRow,
- TableBody,
- TableCell,
- TableHeader,
- TableHeaderCell,
- TableRow,
-} from "~/components/primitives/Table";
-import { TextLink } from "~/components/primitives/TextLink";
-import { DirectionSchema } from "~/components/runs/RunStatuses";
-import { ScheduledTriggersPresenter } from "~/presenters/ScheduledTriggersPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { ProjectParamSchema, docsPath } from "~/utils/pathBuilder";
-
-const SearchSchema = z.object({
- cursor: z.string().optional(),
- direction: DirectionSchema.optional(),
-});
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
-
- const url = new URL(request.url);
- const s = Object.fromEntries(url.searchParams.entries());
- const searchParams = SearchSchema.parse(s);
-
- const presenter = new ScheduledTriggersPresenter();
- const data = await presenter.call({
- userId,
- organizationSlug,
- projectSlug: projectParam,
- direction: searchParams.direction,
- cursor: searchParams.cursor,
- });
-
- return typedjson(data);
-};
-
-export default function Route() {
- const { scheduled, pagination } = useTypedLoaderData();
-
- return (
- <>
-
- A Scheduled Trigger runs a Job on a repeated schedule. The schedule can use a CRON
- expression or an interval.
-
-
- {scheduled.length > 0 && (
-
- )}
-
-
-
-
- ID
- Schedule
- Environment
- Active
- Dynamic
- Last run
- Next run
-
-
-
- {scheduled.length > 0 ? (
- scheduled.map((t) => {
- return (
-
- {t.key}
-
-
- {t.schedule.type === "cron" ? (
- <>
-
-
- >
- ) : (
- <>
-
-
- >
- )}
-
-
-
-
-
-
-
-
- {t.active ? (
-
- ) : t.environment.type === "DEVELOPMENT" ? (
-
-
-
-
- Won't run in DEV
-
-
-
- ) : (
-
- )}
-
-
- {t.dynamicTrigger ? (
-
-
- {t.dynamicTrigger.slug}
-
- ) : (
- –
- )}
-
-
- {t.lastEventTimestamp ? : "–"}
-
-
- {t.nextEventTimestamp ? : "–"}
-
-
- );
- })
- ) : (
-
- No Scheduled triggers
-
- )}
-
-
- >
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.webhooks/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.webhooks/route.tsx
deleted file mode 100644
index fec9b9ddcb..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.webhooks/route.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
-import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
-import { LabelValueStack } from "~/components/primitives/LabelValueStack";
-import { NamedIcon } from "~/components/primitives/NamedIcon";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import {
- Table,
- TableBlankRow,
- TableBody,
- TableCell,
- TableCellChevron,
- TableHeader,
- TableHeaderCell,
- TableRow,
-} from "~/components/primitives/Table";
-import { SimpleTooltip } from "~/components/primitives/Tooltip";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { WebhookTriggersPresenter } from "~/presenters/WebhookTriggersPresenter.server";
-import { requireUser } from "~/services/session.server";
-import { cn } from "~/utils/cn";
-import { ProjectParamSchema, webhookTriggerPath } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const user = await requireUser(request);
- const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
-
- const presenter = new WebhookTriggersPresenter();
- const data = await presenter.call({
- userId: user.id,
- organizationSlug,
- projectSlug: projectParam,
- });
-
- return typedjson(data);
-};
-
-export default function Integrations() {
- const { webhooks } = useTypedLoaderData();
- const organization = useOrganization();
- const project = useProject();
-
- return (
- <>
-
- A Webhook Trigger runs a Job when it receives a matching payload at a registered HTTP
- Endpoint.
-
-
-
-
-
- Key
- Integration
- Properties
- Environment
- Active
- Go to page
-
-
-
- {webhooks.length > 0 ? (
- webhooks.map((w) => {
- const path = webhookTriggerPath(organization, project, w);
- return (
-
- {w.key}
-
-
-
-
-
-
-
- {w.params && (
-
- {Object.entries(w.params).map(([label, value], index) => (
-
- ))}
-
- }
- content={
-
- {Object.entries(w.params).map(([label, value], index) => (
-
- ))}
-
- }
- />
- )}
-
-
-
- {w.webhookEnvironments.map((env) => (
-
- ))}
-
-
-
- {w.active ? (
-
- ) : (
-
- )}
-
-
-
- );
- })
- ) : (
-
- No External triggers
-
- )}
-
-
- >
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers/route.tsx
deleted file mode 100644
index 4c8ae081d8..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers/route.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { BookOpenIcon } from "@heroicons/react/20/solid";
-import { Outlet } from "@remix-run/react";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { LinkButton } from "~/components/primitives/Buttons";
-import { PageAccessories, NavBar, PageTabs, PageTitle } from "~/components/primitives/PageHeader";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import {
- docsPath,
- projectScheduledTriggersPath,
- projectTriggersPath,
- projectWebhookTriggersPath,
-} from "~/utils/pathBuilder";
-
-export default function Page() {
- const organization = useOrganization();
- const project = useProject();
-
- return (
-
-
-
-
-
- Triggers documentation
-
-
-
-
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx
deleted file mode 100644
index 8da3bae012..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx
+++ /dev/null
@@ -1,221 +0,0 @@
-import { conform, useForm } from "@conform-to/react";
-import { parse } from "@conform-to/zod";
-import { json } from "@remix-run/node";
-import { Form, useActionData, useNavigation } from "@remix-run/react";
-import type { ActionFunction, LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { z } from "zod";
-import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { Button } from "~/components/primitives/Buttons";
-import { Callout, variantClasses } from "~/components/primitives/Callout";
-import { Header2 } from "~/components/primitives/Headers";
-import { NamedIcon } from "~/components/primitives/NamedIcon";
-import {
- NavBar,
- PageInfoGroup,
- PageInfoProperty,
- PageInfoRow,
- PageTitle,
-} from "~/components/primitives/PageHeader";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { RunListSearchSchema } from "~/components/runs/RunStatuses";
-import { RunsTable } from "~/components/runs/RunsTable";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useUser } from "~/hooks/useUser";
-import { redirectWithSuccessMessage } from "~/models/message.server";
-import { TriggerSourcePresenter } from "~/presenters/TriggerSourcePresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { ActivateSourceService } from "~/services/sources/activateSource.server";
-import { cn } from "~/utils/cn";
-import {
- TriggerSourceParamSchema,
- externalTriggerPath,
- externalTriggerRunsParentPath,
- projectTriggersPath,
-} from "~/utils/pathBuilder";
-import { ListPagination } from "../../components/ListPagination";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, projectParam, triggerParam } = TriggerSourceParamSchema.parse(params);
-
- const url = new URL(request.url);
- const s = Object.fromEntries(url.searchParams.entries());
- const searchParams = RunListSearchSchema.parse(s);
-
- const presenter = new TriggerSourcePresenter();
- const { trigger } = await presenter.call({
- userId,
- organizationSlug,
- projectSlug: projectParam,
- triggerSourceId: triggerParam,
- direction: searchParams.direction,
- cursor: searchParams.cursor,
- });
-
- if (!trigger) {
- throw new Response("Trigger not found", {
- status: 404,
- statusText: "Not Found",
- });
- }
-
- return typedjson({ trigger });
-};
-
-const schema = z.object({
- jobId: z.string(),
-});
-
-export const action: ActionFunction = async ({ request, params }) => {
- const userId = await requireUserId(request);
- const { organizationSlug, projectParam, triggerParam } = TriggerSourceParamSchema.parse(params);
-
- const formData = await request.formData();
- const submission = parse(formData, { schema });
-
- if (!submission.value) {
- return json(submission);
- }
-
- try {
- const service = new ActivateSourceService();
-
- const result = await service.call(triggerParam);
-
- return redirectWithSuccessMessage(
- externalTriggerPath({ slug: organizationSlug }, { slug: projectParam }, { id: triggerParam }),
- request,
- `Retrying registration now`
- );
- } catch (error: any) {
- return json({ errors: { body: error.message } }, { status: 400 });
- }
-};
-
-export default function Page() {
- const { trigger } = useTypedLoaderData();
- const organization = useOrganization();
- const project = useProject();
- const user = useUser();
- const navigation = useNavigation();
- const lastSubmission = useActionData();
-
- const [form, { jobId }] = useForm({
- id: "trigger-registration-retry",
- // TODO: type this
- lastSubmission: lastSubmission as any,
- onValidate({ formData }) {
- return parse(formData, { schema });
- },
- });
-
- const isLoading = navigation.state === "submitting" && navigation.formData !== undefined;
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- }
- />
- {trigger.dynamic && (
-
-
- {trigger.dynamic.slug}
-
- }
- />
- )}
- }
- />
-
-
-
-
External Trigger registration runs
-
- External Triggers need to be registered with the external service. You can see the
- list of attempted registrations below.
-
-
- {!trigger.active &&
- (trigger.registrationJob ? (
-
- ) : trigger.dynamic ? null : (
-
- This External Trigger hasn't registered successfully. Contact support for help:{" "}
- {trigger.id}
-
- ))}
-
- {trigger.runList ? (
- <>
-
-
-
- >
- ) : (
-
No registration runs found
- )}
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.completed/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.completed/route.tsx
deleted file mode 100644
index bcd8003990..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.completed/route.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useTypedRouteLoaderData } from "remix-typedjson";
-import { RunCompletedDetail } from "~/components/run/RunCompletedDetail";
-import type { loader as runLoader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route";
-
-function useTriggerRegisterRun() {
- const routeMatch = useTypedRouteLoaderData(
- "routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam"
- );
-
- if (!routeMatch || !routeMatch.run) {
- throw new Error("No run found");
- }
-
- return routeMatch.run;
-}
-
-export default function RunCompletedPage() {
- const run = useTriggerRegisterRun();
- return ;
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.stream/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.stream/route.tsx
deleted file mode 100644
index 287ba86a4c..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.stream/route.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { z } from "zod";
-import { RunStreamPresenter } from "~/presenters/RunStreamPresenter.server";
-import { requireUserId } from "~/services/session.server";
-
-export async function loader({ request, params }: LoaderFunctionArgs) {
- await requireUserId(request);
-
- const { runParam } = z.object({ runParam: z.string() }).parse(params);
-
- const presenter = new RunStreamPresenter();
- return presenter.call({ request, runId: runParam });
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx
deleted file mode 100644
index 532bafddb5..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { Spinner } from "~/components/primitives/Spinner";
-import { TaskDetail } from "~/components/run/TaskDetail";
-import { TaskDetailsPresenter } from "~/presenters/TaskDetailsPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { TriggerSourceRunTaskParamsSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { taskParam } = TriggerSourceRunTaskParamsSchema.parse(params);
-
- const presenter = new TaskDetailsPresenter();
- const task = await presenter.call({
- userId,
- id: taskParam,
- });
-
- return typedjson({
- task,
- });
-};
-
-export default function Page() {
- const { task } = useTypedLoaderData();
-
- if (!task) {
- return (
-
-
-
- );
- }
-
- return ;
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.trigger/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.trigger/route.tsx
deleted file mode 100644
index 4dc25127d1..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.trigger/route.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { TriggerDetail } from "~/components/run/TriggerDetail";
-import { TriggerDetailsPresenter } from "~/presenters/TriggerDetailsPresenter.server";
-import { TriggerSourceRunParamsSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const { runParam } = TriggerSourceRunParamsSchema.parse(params);
-
- const presenter = new TriggerDetailsPresenter();
- const trigger = await presenter.call(runParam);
-
- if (!trigger) {
- throw new Response(null, {
- status: 404,
- });
- }
-
- return typedjson({
- trigger,
- });
-};
-
-export default function Page() {
- const { trigger } = useTypedLoaderData();
-
- return (
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx
deleted file mode 100644
index 59f3e4f10a..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import { useRevalidator } from "@remix-run/react";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { useEffect } from "react";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { RunOverview } from "~/components/run/RunOverview";
-import { prisma } from "~/db.server";
-import { useEventSource } from "~/hooks/useEventSource";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useUser } from "~/hooks/useUser";
-import { RunPresenter } from "~/presenters/RunPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import {
- TriggerSourceRunParamsSchema,
- externalTriggerPath,
- externalTriggerRunPath,
- externalTriggerRunStreamingPath,
- externalTriggerRunsParentPath,
-} from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { runParam, triggerParam } = TriggerSourceRunParamsSchema.parse(params);
-
- const presenter = new RunPresenter();
- const run = await presenter.call({
- userId,
- id: runParam,
- });
-
- const trigger = await prisma.triggerSource.findUnique({
- select: {
- id: true,
- integration: {
- select: {
- id: true,
- title: true,
- slug: true,
- definitionId: true,
- setupStatus: true,
- },
- },
- },
- where: {
- id: triggerParam,
- },
- });
-
- if (!run || !trigger) {
- throw new Response(null, {
- status: 404,
- });
- }
-
- return typedjson({
- run,
- trigger,
- });
-};
-
-export default function Page() {
- const { run, trigger } = useTypedLoaderData();
- const organization = useOrganization();
- const project = useProject();
- const user = useUser();
-
- const revalidator = useRevalidator();
- const events = useEventSource(
- externalTriggerRunStreamingPath(organization, project, trigger, run),
- {
- event: "message",
- }
- );
- useEffect(() => {
- if (events !== null) {
- revalidator.revalidate();
- }
- // WARNING Don't put the revalidator in the useEffect deps array or bad things will happen
- }, [events]); // eslint-disable-line react-hooks/exhaustive-deps
-
- return (
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam._index/route.tsx
deleted file mode 100644
index 6ae57b05c5..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam._index/route.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import { useForm } from "@conform-to/react";
-import { parse } from "@conform-to/zod";
-import { Form, useActionData, useNavigation } from "@remix-run/react";
-import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { z } from "zod";
-import { Callout, variantClasses } from "~/components/primitives/Callout";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { RunListSearchSchema } from "~/components/runs/RunStatuses";
-import { RunsTable } from "~/components/runs/RunsTable";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useUser } from "~/hooks/useUser";
-import { WebhookSourcePresenter } from "~/presenters/WebhookSourcePresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { cn } from "~/utils/cn";
-import { TriggerSourceParamSchema, webhookTriggerRunsParentPath } from "~/utils/pathBuilder";
-import { ListPagination } from "../../components/ListPagination";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, projectParam, triggerParam } = TriggerSourceParamSchema.parse(params);
-
- const url = new URL(request.url);
- const s = Object.fromEntries(url.searchParams.entries());
- const searchParams = RunListSearchSchema.parse(s);
-
- const presenter = new WebhookSourcePresenter();
- const { trigger } = await presenter.call({
- userId,
- organizationSlug,
- projectSlug: projectParam,
- webhookId: triggerParam,
- direction: searchParams.direction,
- cursor: searchParams.cursor,
- });
-
- if (!trigger) {
- throw new Response("Trigger not found", {
- status: 404,
- statusText: "Not Found",
- });
- }
-
- return typedjson({ trigger });
-};
-
-const schema = z.object({
- jobId: z.string(),
-});
-
-export default function Page() {
- const { trigger } = useTypedLoaderData();
- const organization = useOrganization();
- const project = useProject();
- const user = useUser();
- const navigation = useNavigation();
- const lastSubmission = useActionData();
-
- const [form, { jobId }] = useForm({
- id: "trigger-registration-retry",
- // TODO: type this
- lastSubmission: lastSubmission as any,
- onValidate({ formData }) {
- return parse(formData, { schema });
- },
- });
-
- const isLoading = navigation.state === "submitting" && navigation.formData !== undefined;
-
- return (
- <>
-
- Webhook Triggers need to be registered with the external service. You can see the list of
- attempted registrations below.
-
-
- {!trigger.active && (
-
- )}
-
- {trigger.runList ? (
- <>
-
-
-
- >
- ) : (
- No registration runs found
- )}
- >
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam.delivery/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam.delivery/route.tsx
deleted file mode 100644
index 20b658c04c..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam.delivery/route.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { Callout } from "~/components/primitives/Callout";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { RunListSearchSchema } from "~/components/runs/RunStatuses";
-import { WebhookDeliveryRunsTable } from "~/components/runs/WebhookDeliveryRunsTable";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { WebhookDeliveryPresenter } from "~/presenters/WebhookDeliveryPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import {
- TriggerSourceParamSchema,
- webhookTriggerDeliveryRunsParentPath,
-} from "~/utils/pathBuilder";
-import { ListPagination } from "../../components/ListPagination";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, projectParam, triggerParam } = TriggerSourceParamSchema.parse(params);
-
- const url = new URL(request.url);
- const s = Object.fromEntries(url.searchParams.entries());
- const searchParams = RunListSearchSchema.parse(s);
-
- const presenter = new WebhookDeliveryPresenter();
- const { webhook } = await presenter.call({
- userId,
- organizationSlug,
- projectSlug: projectParam,
- webhookId: triggerParam,
- direction: searchParams.direction,
- cursor: searchParams.cursor,
- });
-
- if (!webhook) {
- throw new Response("Trigger not found", {
- status: 404,
- statusText: "Not Found",
- });
- }
-
- return typedjson({ webhook });
-};
-
-export default function Page() {
- const { webhook } = useTypedLoaderData();
- const organization = useOrganization();
- const project = useProject();
-
- return (
- <>
-
- Webhook payloads are delivered to clients for validation and event generation. You can see
- the list of attempted deliveries below.
-
-
- {webhook.requestDeliveries ? (
- <>
-
-
-
- >
- ) : (
- No registration runs found
- )}
- >
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam/route.tsx
deleted file mode 100644
index 19c1ae893c..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam/route.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import { Outlet } from "@remix-run/react";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import {
- NavBar,
- PageInfoGroup,
- PageInfoProperty,
- PageInfoRow,
- PageTabs,
- PageTitle,
-} from "~/components/primitives/PageHeader";
-import { RunListSearchSchema } from "~/components/runs/RunStatuses";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { WebhookSourcePresenter } from "~/presenters/WebhookSourcePresenter.server";
-import { requireUserId } from "~/services/session.server";
-import {
- TriggerSourceParamSchema,
- projectWebhookTriggersPath,
- webhookDeliveryPath,
- webhookTriggerPath,
-} from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { organizationSlug, projectParam, triggerParam } = TriggerSourceParamSchema.parse(params);
-
- const url = new URL(request.url);
- const s = Object.fromEntries(url.searchParams.entries());
- const searchParams = RunListSearchSchema.parse(s);
-
- const presenter = new WebhookSourcePresenter();
- const { trigger } = await presenter.call({
- userId,
- organizationSlug,
- projectSlug: projectParam,
- webhookId: triggerParam,
- direction: searchParams.direction,
- cursor: searchParams.cursor,
- });
-
- if (!trigger) {
- throw new Response("Trigger not found", {
- status: 404,
- statusText: "Not Found",
- });
- }
-
- return typedjson({ trigger });
-};
-
-export default function Page() {
- const { trigger } = useTypedLoaderData();
- const organization = useOrganization();
- const project = useProject();
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam.completed/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam.completed/route.tsx
deleted file mode 100644
index 6a7574a85e..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam.completed/route.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useTypedRouteLoaderData } from "remix-typedjson";
-import { RunCompletedDetail } from "~/components/run/RunCompletedDetail";
-import type { loader as runLoader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam/route";
-
-function useTriggerRegisterRun() {
- const routeMatch = useTypedRouteLoaderData(
- "routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam"
- );
-
- if (!routeMatch || !routeMatch.run) {
- throw new Error("No run found");
- }
-
- return routeMatch.run;
-}
-
-export default function RunCompletedPage() {
- const run = useTriggerRegisterRun();
- return ;
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam.stream/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam.stream/route.tsx
deleted file mode 100644
index 287ba86a4c..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam.stream/route.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { z } from "zod";
-import { RunStreamPresenter } from "~/presenters/RunStreamPresenter.server";
-import { requireUserId } from "~/services/session.server";
-
-export async function loader({ request, params }: LoaderFunctionArgs) {
- await requireUserId(request);
-
- const { runParam } = z.object({ runParam: z.string() }).parse(params);
-
- const presenter = new RunStreamPresenter();
- return presenter.call({ request, runId: runParam });
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx
deleted file mode 100644
index 1e07e6debb..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Await, useLoaderData } from "@remix-run/react";
-import { LoaderFunctionArgs, defer } from "@remix-run/server-runtime";
-import { Suspense } from "react";
-import { Spinner } from "~/components/primitives/Spinner";
-import { TaskDetail } from "~/components/run/TaskDetail";
-import { TaskDetailsPresenter } from "~/presenters/TaskDetailsPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { TriggerSourceRunTaskParamsSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { taskParam } = TriggerSourceRunTaskParamsSchema.parse(params);
-
- const presenter = new TaskDetailsPresenter();
- const taskPromise = presenter.call({
- userId,
- id: taskParam,
- });
-
- return defer({
- taskPromise,
- });
-};
-
-export default function Page() {
- const { taskPromise } = useLoaderData();
-
- return (
- }>
- Error loading task!}>
- {(resolvedTask) => resolvedTask && }
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam.trigger/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam.trigger/route.tsx
deleted file mode 100644
index e186e2ba02..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam.trigger/route.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { TriggerDetail } from "~/components/run/TriggerDetail";
-import { TriggerDetailsPresenter } from "~/presenters/TriggerDetailsPresenter.server";
-import { TriggerSourceRunParamsSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const { runParam } = TriggerSourceRunParamsSchema.parse(params);
-
- const presenter = new TriggerDetailsPresenter();
- const trigger = await presenter.call(runParam);
-
- if (!trigger) {
- throw new Response(null, {
- status: 404,
- });
- }
-
- return typedjson({
- trigger,
- });
-};
-
-export default function Page() {
- const { trigger } = useTypedLoaderData();
-
- return (
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam/route.tsx
deleted file mode 100644
index 37fff33cb7..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam/route.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import { useRevalidator } from "@remix-run/react";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { useEffect } from "react";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { useEventSource } from "remix-utils/sse/react";
-import { RunOverview } from "~/components/run/RunOverview";
-import { prisma } from "~/db.server";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useUser } from "~/hooks/useUser";
-import { RunPresenter } from "~/presenters/RunPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import {
- TriggerSourceRunParamsSchema,
- webhookTriggerPath,
- webhookTriggerRunPath,
- webhookTriggerRunStreamingPath,
- webhookTriggerRunsParentPath,
-} from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { runParam, triggerParam } = TriggerSourceRunParamsSchema.parse(params);
-
- const presenter = new RunPresenter();
- const run = await presenter.call({
- userId,
- id: runParam,
- });
-
- const trigger = await prisma.webhook.findUnique({
- select: {
- id: true,
- key: true,
- integration: {
- select: {
- id: true,
- title: true,
- slug: true,
- definitionId: true,
- setupStatus: true,
- },
- },
- },
- where: {
- id: triggerParam,
- },
- });
-
- if (!run || !trigger) {
- throw new Response(null, {
- status: 404,
- });
- }
-
- return typedjson({
- run,
- trigger,
- });
-};
-
-export default function Page() {
- const { run, trigger } = useTypedLoaderData();
- const organization = useOrganization();
- const project = useProject();
- const user = useUser();
-
- const revalidator = useRevalidator();
- const events = useEventSource(
- webhookTriggerRunStreamingPath(organization, project, trigger, run),
- {
- event: "message",
- }
- );
- useEffect(() => {
- if (events !== null) {
- revalidator.revalidate();
- }
- // WARNING Don't put the revalidator in the useEffect deps array or bad things will happen
- }, [events]); // eslint-disable-line react-hooks/exhaustive-deps
-
- return (
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam.completed/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam.completed/route.tsx
deleted file mode 100644
index dd70b1b88a..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam.completed/route.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useTypedRouteLoaderData } from "remix-typedjson";
-import { RunCompletedDetail } from "~/components/run/RunCompletedDetail";
-import type { loader as runLoader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam/route";
-
-function useTriggerRegisterRun() {
- const routeMatch = useTypedRouteLoaderData(
- "routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam"
- );
-
- if (!routeMatch || !routeMatch.run) {
- throw new Error("No run found");
- }
-
- return routeMatch.run;
-}
-
-export default function RunCompletedPage() {
- const run = useTriggerRegisterRun();
- return ;
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam.stream/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam.stream/route.tsx
deleted file mode 100644
index 287ba86a4c..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam.stream/route.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { z } from "zod";
-import { RunStreamPresenter } from "~/presenters/RunStreamPresenter.server";
-import { requireUserId } from "~/services/session.server";
-
-export async function loader({ request, params }: LoaderFunctionArgs) {
- await requireUserId(request);
-
- const { runParam } = z.object({ runParam: z.string() }).parse(params);
-
- const presenter = new RunStreamPresenter();
- return presenter.call({ request, runId: runParam });
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam.tasks.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam.tasks.$taskParam/route.tsx
deleted file mode 100644
index 1e07e6debb..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam.tasks.$taskParam/route.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Await, useLoaderData } from "@remix-run/react";
-import { LoaderFunctionArgs, defer } from "@remix-run/server-runtime";
-import { Suspense } from "react";
-import { Spinner } from "~/components/primitives/Spinner";
-import { TaskDetail } from "~/components/run/TaskDetail";
-import { TaskDetailsPresenter } from "~/presenters/TaskDetailsPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import { TriggerSourceRunTaskParamsSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { taskParam } = TriggerSourceRunTaskParamsSchema.parse(params);
-
- const presenter = new TaskDetailsPresenter();
- const taskPromise = presenter.call({
- userId,
- id: taskParam,
- });
-
- return defer({
- taskPromise,
- });
-};
-
-export default function Page() {
- const { taskPromise } = useLoaderData();
-
- return (
- }>
- Error loading task!}>
- {(resolvedTask) => resolvedTask && }
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam.trigger/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam.trigger/route.tsx
deleted file mode 100644
index 478dfbe773..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam.trigger/route.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { TriggerDetail } from "~/components/run/TriggerDetail";
-import { TriggerDetailsPresenter } from "~/presenters/TriggerDetailsPresenter.server";
-import { TriggerSourceRunParamsSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const { runParam } = TriggerSourceRunParamsSchema.parse(params);
-
- const presenter = new TriggerDetailsPresenter();
- const trigger = await presenter.call(runParam);
-
- if (!trigger) {
- throw new Response(null, {
- status: 404,
- });
- }
-
- return typedjson({
- trigger,
- });
-};
-
-export default function Page() {
- const { trigger } = useTypedLoaderData();
-
- return (
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam/route.tsx
deleted file mode 100644
index 4c0f7bd2c3..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam/route.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import { useRevalidator } from "@remix-run/react";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { useEffect } from "react";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { useEventSource } from "remix-utils/sse/react";
-import { RunOverview } from "~/components/run/RunOverview";
-import { prisma } from "~/db.server";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { useUser } from "~/hooks/useUser";
-import { RunPresenter } from "~/presenters/RunPresenter.server";
-import { requireUserId } from "~/services/session.server";
-import {
- TriggerSourceRunParamsSchema,
- webhookDeliveryPath,
- webhookTriggerDeliveryRunPath,
- webhookTriggerDeliveryRunsParentPath,
- webhookTriggerRunStreamingPath,
-} from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
- const { runParam, triggerParam } = TriggerSourceRunParamsSchema.parse(params);
-
- const presenter = new RunPresenter();
- const run = await presenter.call({
- userId,
- id: runParam,
- });
-
- const trigger = await prisma.webhook.findUnique({
- select: {
- id: true,
- integration: {
- select: {
- id: true,
- title: true,
- slug: true,
- definitionId: true,
- setupStatus: true,
- },
- },
- },
- where: {
- id: triggerParam,
- },
- });
-
- if (!run || !trigger) {
- throw new Response(null, {
- status: 404,
- });
- }
-
- return typedjson({
- run,
- trigger,
- });
-};
-
-export default function Page() {
- const { run, trigger } = useTypedLoaderData();
- const organization = useOrganization();
- const project = useProject();
- const user = useUser();
-
- const revalidator = useRevalidator();
- const events = useEventSource(
- webhookTriggerRunStreamingPath(organization, project, trigger, run),
- {
- event: "message",
- }
- );
- useEffect(() => {
- if (events !== null) {
- revalidator.revalidate();
- }
- // WARNING Don't put the revalidator in the useEffect deps array or bad things will happen
- }, [events]); // eslint-disable-line react-hooks/exhaustive-deps
-
- return (
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx
deleted file mode 100644
index 1b8e29309e..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Outlet } from "@remix-run/react";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { redirect } from "remix-typedjson";
-import { RouteErrorDisplay } from "~/components/ErrorDisplay";
-import { prisma } from "~/db.server";
-import { useOrganization } from "~/hooks/useOrganizations";
-import { useProject } from "~/hooks/useProject";
-import { Handle } from "~/utils/handle";
-import { ProjectParamSchema, projectPath, v3ProjectPath } from "~/utils/pathBuilder";
-
-export const loader = async ({ params }: LoaderFunctionArgs) => {
- const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
-
- const project = await prisma.project.findUnique({
- select: { version: true },
- where: { slug: projectParam },
- });
-
- if (!project) {
- throw new Response("Project not found", { status: 404, statusText: "Project not found" });
- }
-
- if (project.version === "V3") {
- return redirect(v3ProjectPath({ slug: organizationSlug }, { slug: projectParam }));
- }
-
- return null;
-};
-
-export const handle: Handle = {
- scripts: (match) => [
- {
- src: "https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js",
- crossOrigin: "anonymous",
- },
- ],
-};
-
-export default function Project() {
- return (
- <>
-
- >
- );
-}
-
-export function ErrorBoundary() {
- const org = useOrganization();
- const project = useProject();
- return ;
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam._index/route.tsx
index 96d73f5bc1..3b32688e4e 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam._index/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam._index/route.tsx
@@ -5,6 +5,7 @@ import {
ChevronDownIcon,
ChevronUpIcon,
LightBulbIcon,
+ MagnifyingGlassIcon,
UserPlusIcon,
VideoCameraIcon,
} from "@heroicons/react/20/solid";
@@ -18,6 +19,7 @@ import { Fragment, Suspense, useEffect, useState } from "react";
import { Bar, BarChart, ResponsiveContainer, Tooltip, TooltipProps } from "recharts";
import { TypedAwait, typeddefer, useTypedLoaderData } from "remix-typedjson";
import { ExitIcon } from "~/assets/icons/ExitIcon";
+import { RunsIcon } from "~/assets/icons/RunsIcon";
import { TaskIcon } from "~/assets/icons/TaskIcon";
import { Feedback } from "~/components/Feedback";
import {
@@ -241,7 +243,7 @@ export default function Page() {
setFilterText(e.target.value)}
@@ -378,13 +380,13 @@ export default function Page() {
popoverContent={
<>
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx
index 7336baf0d5..d3ff5fe50c 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx
@@ -6,6 +6,7 @@ import {
ChevronRightIcon,
InformationCircleIcon,
LockOpenIcon,
+ MagnifyingGlassIcon,
MagnifyingGlassMinusIcon,
MagnifyingGlassPlusIcon,
StopCircleIcon,
@@ -1368,7 +1369,7 @@ function SearchField({ onChange }: { onChange: (value: string) => void }) {
updateValue(e.target.value)}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs._index/route.tsx
index a65de41f8c..74638b6034 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs._index/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs._index/route.tsx
@@ -27,7 +27,7 @@ import {
SelectedItemsProvider,
useSelectedItems,
} from "~/components/primitives/SelectedItemsProvider";
-import { Spinner } from "~/components/primitives/Spinner";
+import { Spinner, SpinnerWhite } from "~/components/primitives/Spinner";
import { StepNumber } from "~/components/primitives/StepNumber";
import { TextLink } from "~/components/primitives/TextLink";
import { RunsFilters, TaskRunListSearchFilters } from "~/components/runs/v3/RunFilters";
@@ -352,7 +352,7 @@ function CancelRuns({ onOpen }: { onOpen: (open: boolean) => void }) {
@@ -409,7 +409,7 @@ function ReplayRuns({ onOpen }: { onOpen: (open: boolean) => void }) {
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.settings/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.settings/route.tsx
index 25d3e86ba6..139ed96a22 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.settings/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.settings/route.tsx
@@ -1,5 +1,6 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
+import { FolderIcon } from "@heroicons/react/20/solid";
import { Form, MetaFunction, useActionData, useNavigation } from "@remix-run/react";
import { ActionFunction, json } from "@remix-run/server-runtime";
import { z } from "zod";
@@ -18,6 +19,7 @@ import { Label } from "~/components/primitives/Label";
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
import { Paragraph } from "~/components/primitives/Paragraph";
import * as Property from "~/components/primitives/PropertyTable";
+import { SpinnerWhite } from "~/components/primitives/Spinner";
import { prisma } from "~/db.server";
import { useProject } from "~/hooks/useProject";
import { redirectWithSuccessMessage } from "~/models/message.server";
@@ -164,7 +166,7 @@ export default function Page() {
{...conform.input(projectName, { type: "text" })}
defaultValue={project.name}
placeholder="Your project name"
- icon="folder"
+ icon={FolderIcon}
autoFocus
/>
{projectName.error}
@@ -175,7 +177,7 @@ export default function Page() {
type="submit"
variant={"primary/small"}
disabled={isRenameLoading}
- LeadingIcon={isRenameLoading ? "spinner-white" : undefined}
+ LeadingIcon={isRenameLoading ? SpinnerWhite : undefined}
>
Rename project
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.test/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.test/route.tsx
index d6872e1cff..782afb249d 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.test/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.test/route.tsx
@@ -1,4 +1,4 @@
-import { BookOpenIcon } from "@heroicons/react/20/solid";
+import { BookOpenIcon, MagnifyingGlassIcon } from "@heroicons/react/20/solid";
import {
Link,
MetaFunction,
@@ -209,7 +209,7 @@ function TaskSelector({
{
const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
@@ -20,11 +21,9 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
throw new Response("Project not found", { status: 404, statusText: "Project not found" });
}
- if (project.version === "V2") {
- return redirect(projectPath({ slug: organizationSlug }, { slug: projectParam }));
- }
-
- return null;
+ return typedjson({
+ version: project.version,
+ });
};
export const handle: Handle = {
@@ -37,6 +36,25 @@ export const handle: Handle = {
};
export default function Project() {
+ const { version } = useTypedLoaderData();
+
+ if (version === "V2") {
+ return (
+
+ This project is v2, which was deprecated on Jan 31 2025 after{" "}
+
+ our announcement in August 2024
+
+ .
+ >
+ }
+ />
+ );
+ }
+
return (
<>
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings/route.tsx
index ad5b329888..ca338afac9 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings/route.tsx
@@ -1,5 +1,6 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
+import { ExclamationTriangleIcon, FolderIcon, TrashIcon } from "@heroicons/react/20/solid";
import { Form, MetaFunction, useActionData, useNavigation } from "@remix-run/react";
import { ActionFunction, json } from "@remix-run/server-runtime";
import { redirect } from "remix-typedjson";
@@ -16,6 +17,7 @@ import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
+import { SpinnerWhite } from "~/components/primitives/Spinner";
import { prisma } from "~/db.server";
import { useOrganization } from "~/hooks/useOrganizations";
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
@@ -206,7 +208,7 @@ export default function Page() {
{...conform.input(organizationName, { type: "text" })}
defaultValue={organization.title}
placeholder="Your organization name"
- icon="folder"
+ icon={FolderIcon}
autoFocus
/>
{organizationName.error}
@@ -217,7 +219,7 @@ export default function Page() {
type="submit"
variant={"primary/small"}
disabled={isRenameLoading}
- LeadingIcon={isRenameLoading ? "spinner-white" : undefined}
+ LeadingIcon={isRenameLoading ? SpinnerWhite : undefined}
>
Rename organization
@@ -241,7 +243,7 @@ export default function Page() {
{organizationSlug.error}
@@ -257,7 +259,7 @@ export default function Page() {
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx
index 8452e8ece6..0027aa8308 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx
@@ -1,6 +1,6 @@
import { useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
-import { LockOpenIcon, UserPlusIcon } from "@heroicons/react/20/solid";
+import { EnvelopeIcon, LockOpenIcon, TrashIcon, UserPlusIcon } from "@heroicons/react/20/solid";
import { Form, MetaFunction, useActionData } from "@remix-run/react";
import { ActionFunction, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { useState } from "react";
@@ -24,7 +24,6 @@ import { Button, ButtonContent, LinkButton } from "~/components/primitives/Butto
import { DateTime } from "~/components/primitives/DateTime";
import { Header2, Header3 } from "~/components/primitives/Headers";
import { InfoPanel } from "~/components/primitives/InfoPanel";
-import { NamedIcon } from "~/components/primitives/NamedIcon";
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
import { Paragraph } from "~/components/primitives/Paragraph";
import * as Property from "~/components/primitives/PropertyTable";
@@ -191,7 +190,7 @@ export default function Page() {
{invites.map((invite) => (
-
+
{invite.email}
@@ -361,7 +360,7 @@ function RevokeButton({ invite }: { invite: Invite }) {
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.v3.usage/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.v3.usage/route.tsx
index b54873dd36..28df91d1e1 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.v3.usage/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.v3.usage/route.tsx
@@ -6,7 +6,7 @@ import { Suspense } from "react";
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
import { redirect, typeddefer, useTypedLoaderData } from "remix-typedjson";
import { URL } from "url";
-import { UsageBar } from "~/components/billing/v3/UsageBar";
+import { UsageBar } from "~/components/billing/UsageBar";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
import {
ChartConfig,
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
index a4725a1c21..6793f3417c 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
@@ -1,5 +1,6 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
+import { FolderIcon } from "@heroicons/react/20/solid";
import type { ActionFunction, LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Form, useActionData, useNavigation } from "@remix-run/react";
@@ -27,7 +28,7 @@ import { requireUserId } from "~/services/session.server";
import {
OrganizationParamsSchema,
organizationPath,
- projectPath,
+ v3ProjectPath,
selectPlanPath,
} from "~/utils/pathBuilder";
@@ -110,7 +111,7 @@ export const action: ActionFunction = async ({ request, params }) => {
});
return redirectWithSuccessMessage(
- projectPath(project.organization, project),
+ v3ProjectPath(project.organization, project),
request,
`${submission.value.projectName} created`
);
@@ -143,7 +144,7 @@ export default function Page() {
}
title="Create a new project"
description={`This will create a new project in your "${organization.title}" organization.`}
/>
@@ -159,7 +160,7 @@ export default function Page() {
{projectName.error}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.subscribed/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.subscribed/route.tsx
deleted file mode 100644
index d52b4b1055..0000000000
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.subscribed/route.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import { CheckBadgeIcon } from "@heroicons/react/24/solid";
-import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
-import { RunsVolumeDiscountTable } from "~/components/billing/v2/RunsVolumeDiscountTable";
-import { MainCenteredContainer } from "~/components/layout/AppLayout";
-import { LinkButton } from "~/components/primitives/Buttons";
-import { FormButtons } from "~/components/primitives/FormButtons";
-import { FormTitle } from "~/components/primitives/FormTitle";
-import { Paragraph } from "~/components/primitives/Paragraph";
-import { featuresForRequest } from "~/features.server";
-import { useNewCustomerSubscribed } from "~/hooks/useNewCustomerSubscribed";
-import { OrganizationsPresenter } from "~/presenters/OrganizationsPresenter.server";
-import { BillingService } from "~/services/billing.v2.server";
-import { requireUserId } from "~/services/session.server";
-import { Handle } from "~/utils/handle";
-import { OrganizationParamsSchema } from "~/utils/pathBuilder";
-
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
- const userId = await requireUserId(request);
-
- const { organizationSlug } = OrganizationParamsSchema.parse(params);
-
- const orgsPresenter = new OrganizationsPresenter();
- const { organization } = await orgsPresenter.call({
- userId,
- request,
- organizationSlug,
- projectSlug: undefined,
- });
-
- const { isManagedCloud } = featuresForRequest(request);
- const billingPresenter = new BillingService(isManagedCloud);
- const currentPlan = await billingPresenter.currentPlan(organization.id);
- const plans = await billingPresenter.getPlans();
-
- return typedjson({
- currentPlan,
- plans,
- });
-};
-
-export const handle: Handle = {
- scripts: () => [
- {
- src: "https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js",
- crossOrigin: "anonymous",
- },
- ],
-};
-
-export default function Subscribed() {
- const { currentPlan, plans } = useTypedLoaderData
();
- useNewCustomerSubscribed();
-
- return (
-
- }
- title="You're subscribed!"
- className="mb-0"
- />
-
-
-
-
- Continue
-
- }
- />
-
- );
-}
-
-function PlanItem({ item, value }: { item: string; value: string }) {
- return (
-
- {item}
-
- {value}
-
-
- );
-}
diff --git a/apps/webapp/app/routes/_app.orgs.new/route.tsx b/apps/webapp/app/routes/_app.orgs.new/route.tsx
index 571c6163be..29f8963f7d 100644
--- a/apps/webapp/app/routes/_app.orgs.new/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.new/route.tsx
@@ -1,5 +1,6 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
+import { BuildingOffice2Icon } from "@heroicons/react/20/solid";
import { RadioGroup } from "@radix-ui/react-radio-group";
import type { ActionFunction, LoaderFunctionArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
@@ -117,7 +118,10 @@ export default function NewOrganizationPage() {
return (
-
+ }
+ title="Create an Organization"
+ />
Loading
-
+
Loading Primary…
-
+
Loading Secondary…
-
+
Loading Tertiary…
-
+
Loading Minimal…
-
+
Loading Danger…
@@ -141,8 +146,8 @@ export default function Story() {
Icon only
-
-
+
+
@@ -220,25 +225,25 @@ export default function Story() {
Connect to Slack
-
+
Connect to Slack
Loading
-
+
Loading Primary…
-
+
Loading Secondary…
-
+
Loading Tertiary…
-
+
Loading Minimal…
-
+
Loading Danger…
@@ -264,8 +269,8 @@ export default function Story() {
Icon only
-
-
+
+