diff --git a/apps/webapp/app/components/primitives/NamedIcon.tsx b/apps/webapp/app/components/primitives/NamedIcon.tsx index 09071bac7..de3f33ac1 100644 --- a/apps/webapp/app/components/primitives/NamedIcon.tsx +++ b/apps/webapp/app/components/primitives/NamedIcon.tsx @@ -221,6 +221,9 @@ const icons = { "custom-event": (className: string) => ( ), + "register-source": (className: string) => ( + + ), "schedule-interval": (className: string) => ( ), diff --git a/apps/webapp/app/components/run/RunCompletedDetail.tsx b/apps/webapp/app/components/run/RunCompletedDetail.tsx index 839b99bf5..ed85bb515 100644 --- a/apps/webapp/app/components/run/RunCompletedDetail.tsx +++ b/apps/webapp/app/components/run/RunCompletedDetail.tsx @@ -2,7 +2,7 @@ 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 { useRun } from "~/hooks/useRun"; +import { MatchedRun, useRun } from "~/hooks/useRun"; import { formatDuration } from "~/utils"; import { RunPanel, @@ -14,9 +14,7 @@ import { RunPanelIconSection, } from "./RunCard"; -export function RunCompletedDetail() { - const run = useRun(); - +export function RunCompletedDetail({ run }: { run: MatchedRun }) { return ( ; + const run = useRun(); + + return ; } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.triggersources.$triggerParam_.runs.$runParam.completed/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.triggersources.$triggerParam_.runs.$runParam.completed/route.tsx new file mode 100644 index 000000000..bd2788457 --- /dev/null +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.triggersources.$triggerParam_.runs.$runParam.completed/route.tsx @@ -0,0 +1,20 @@ +import { useTypedRouteLoaderData } from "remix-typedjson"; +import { RunCompletedDetail } from "~/components/run/RunCompletedDetail"; +import type { loader as runLoader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.triggersources.$triggerParam_.runs.$runParam/route"; + +function useTriggerRegisterRun() { + const routeMatch = useTypedRouteLoaderData( + "routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.triggersources.$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_.triggersources.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.triggersources.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx new file mode 100644 index 000000000..d2a118146 --- /dev/null +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.triggersources.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx @@ -0,0 +1,32 @@ +import { LoaderArgs } from "@remix-run/server-runtime"; +import { typedjson, useTypedLoaderData } from "remix-typedjson"; +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 }: LoaderArgs) => { + const userId = await requireUserId(request); + const { taskParam } = TriggerSourceRunTaskParamsSchema.parse(params); + + const presenter = new TaskDetailsPresenter(); + const task = await presenter.call({ + userId, + id: taskParam, + }); + + if (!task) { + throw new Response(null, { + status: 404, + }); + } + + return typedjson({ + task, + }); +}; + +export default function Page() { + const { task } = useTypedLoaderData(); + return ; +} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.triggersources.$triggerParam_.runs.$runParam.trigger/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.triggersources.$triggerParam_.runs.$runParam.trigger/route.tsx new file mode 100644 index 000000000..89298f9f5 --- /dev/null +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.triggersources.$triggerParam_.runs.$runParam.trigger/route.tsx @@ -0,0 +1,34 @@ +import { LoaderArgs } 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 }: LoaderArgs) => { + 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_.triggersources.$triggerParam_.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.triggersources.$triggerParam_.runs.$runParam/route.tsx new file mode 100644 index 000000000..abd65c7ad --- /dev/null +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.triggersources.$triggerParam_.runs.$runParam/route.tsx @@ -0,0 +1,80 @@ +import { useRevalidator } from "@remix-run/react"; +import { LoaderArgs } from "@remix-run/server-runtime"; +import { useEffect } from "react"; +import { typedjson, useTypedLoaderData } from "remix-typedjson"; +import { useEventSource } from "remix-utils"; +import { RunOverview } from "~/components/run/RunOverview"; +import { useOrganization } from "~/hooks/useOrganizations"; +import { useProject } from "~/hooks/useProject"; +import { RunPresenter } from "~/presenters/RunPresenter.server"; +import { requireUserId } from "~/services/session.server"; +import { + TriggerSourceRunParamsSchema, + triggerSourcePath, + triggerSourceRunPath, + triggerSourceRunStreamingPath, +} from "~/utils/pathBuilder"; + +export const loader = async ({ request, params }: LoaderArgs) => { + const userId = await requireUserId(request); + const { runParam, triggerParam } = TriggerSourceRunParamsSchema.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, + triggerParam, + }); +}; + +export default function Page() { + const { run, triggerParam } = useTypedLoaderData(); + const organization = useOrganization(); + const project = useProject(); + + const revalidator = useRevalidator(); + const events = useEventSource( + triggerSourceRunStreamingPath( + organization, + project, + { id: triggerParam }, + 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/utils/pathBuilder.ts b/apps/webapp/app/utils/pathBuilder.ts index fac248a05..bd8e1935f 100644 --- a/apps/webapp/app/utils/pathBuilder.ts +++ b/apps/webapp/app/utils/pathBuilder.ts @@ -39,6 +39,15 @@ export const TriggerSourceParamSchema = ProjectParamSchema.extend({ triggerParam: z.string(), }); +export const TriggerSourceRunParamsSchema = TriggerSourceParamSchema.extend({ + runParam: z.string(), +}); + +export const TriggerSourceRunTaskParamsSchema = + TriggerSourceRunParamsSchema.extend({ + taskParam: z.string(), + }); + export function organizationsPath() { return `/`; } @@ -194,6 +203,24 @@ export function triggerSourceRunsPath( return `${triggerSourcePath(organization, project, trigger)}/runs`; } +export function triggerSourceRunPath( + organization: OrgForPath, + project: ProjectForPath, + trigger: TriggerForPath, + run: RunForPath +) { + return `${triggerSourceRunsPath(organization, project, trigger)}/${run.id}`; +} + +export function triggerSourceRunStreamingPath( + organization: OrgForPath, + project: ProjectForPath, + trigger: TriggerForPath, + run: RunForPath +) { + return `${triggerSourceRunPath(organization, project, trigger, run)}/stream`; +} + function triggerSourceParam(trigger: TriggerForPath) { return trigger.id; }