From c1907d4e000f94789d4cb0ce0d7c75270b8949c8 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 30 May 2023 15:35:11 +0100 Subject: [PATCH] Improved the task display with connections and task icon --- .../app/presenters/RunPresenter.server.ts | 30 +++++++++++++- .../DetailView.tsx | 40 +++++++++++++++++++ .../RunCard.tsx | 29 ++++++++++++-- .../TaskStatus.tsx | 19 ++++++--- .../route.tsx | 40 +++++++++++++++++-- integrations/slack/src/tasks.ts | 1 + 6 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/DetailView.tsx diff --git a/apps/webapp/app/presenters/RunPresenter.server.ts b/apps/webapp/app/presenters/RunPresenter.server.ts index fbe8e7ec5..542dc006c 100644 --- a/apps/webapp/app/presenters/RunPresenter.server.ts +++ b/apps/webapp/app/presenters/RunPresenter.server.ts @@ -7,6 +7,13 @@ type RunOptions = { userId: string; }; +export type Task = NonNullable< + Awaited> +>["tasks"][number]; +export type Event = NonNullable< + Awaited> +>["event"]; + export class RunPresenter { #prismaClient: PrismaClient; @@ -40,6 +47,7 @@ export class RunPresenter { select: { id: true, displayKey: true, + connectionKey: true, name: true, icon: true, status: true, @@ -62,7 +70,27 @@ export class RunPresenter { createdAt: "asc", }, }, - runConnections: true, + runConnections: { + select: { + key: true, + apiConnection: { + select: { + metadata: true, + connectionType: true, + client: { + select: { + title: true, + slug: true, + description: true, + scopes: true, + integrationIdentifier: true, + integrationAuthMethod: true, + }, + }, + }, + }, + }, + }, missingConnections: true, }, where: { diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/DetailView.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/DetailView.tsx new file mode 100644 index 000000000..5022881e2 --- /dev/null +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/DetailView.tsx @@ -0,0 +1,40 @@ +import { Event, Task } from "~/presenters/RunPresenter.server"; +import { RunPanel, RunPanelHeader } from "./RunCard"; + +type DetailProps = + | { + type: "task"; + task: Task; + } + | { + type: "event"; + event: Event; + }; + +export function Detail(props: DetailProps) { + switch (props.type) { + case "task": + return ; + case "event": + return ; + } + + return <>; +} + +export function TaskDetail({ name, status, delayUntil }: Task) { + return ( + + {/* //todo what icon to use here? */} + {/* */} + + ); +} + +export function EventDetail({}: Event) { + return ( + + + + ); +} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/RunCard.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/RunCard.tsx index 8b21f8340..cc9d5d248 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/RunCard.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/RunCard.tsx @@ -27,7 +27,7 @@ export function RunPanel({ selected, children, onClick }: RunPanelProps) { type RunPanelHeaderProps = { icon: React.ReactNode; - title: string; + title: React.ReactNode; accessory?: React.ReactNode; }; @@ -44,13 +44,31 @@ export function RunPanelHeader({ ) : ( icon )} - {title} + {typeof title === "string" ? ( + {title} + ) : ( + title + )}
{accessory}
); } +type RunPanelIconTitleProps = { + icon?: string | null; + title: string; +}; + +export function RunPanelIconTitle({ icon, title }: RunPanelIconTitleProps) { + return ( +
+ {icon && } + {title} +
+ ); +} + export function RunPanelBody({ children }: { children: React.ReactNode }) { return
{children}
; } @@ -88,15 +106,20 @@ export function RunPanelIconElement({ export function RunPanelElements({ elements, columns = false, + className, }: { elements: { label: string; value: string }[]; columns?: boolean; + className?: string; }) { return (
{elements.map(({ label, value }, index) => ( diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/TaskStatus.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/TaskStatus.tsx index 9b3f443f0..ad68f442c 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/TaskStatus.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/TaskStatus.tsx @@ -1,22 +1,31 @@ import { TaskStatus } from "@/../../packages/internal/src"; import { CheckCircleIcon, + CheckIcon, ClockIcon, 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, -}: { - status: TaskStatus; - className: string; -}) { + minimal = false, +}: TaskStatusIconProps) { switch (status) { case "COMPLETED": - return ( + return minimal ? ( + + ) : ( 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 index 186b6b625..ab732f087 100644 --- 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 @@ -36,10 +36,12 @@ import { RunPanelHeader, RunPanelIconElement, RunPanelIconSection, + RunPanelIconTitle, TaskSeparator, } from "./RunCard"; import { TaskStatusIcon } from "./TaskStatus"; -import { Fragment, useState } from "react"; +import { Fragment, useCallback, useMemo, useState } from "react"; +import { Detail } from "./DetailView"; export const loader = async ({ request, params }: LoaderArgs) => { const userId = await requireUserId(request); @@ -78,7 +80,13 @@ export default function Page() { const job = useJob(); const [selectedId, setSelectedId] = useState(undefined); - console.log(run); + const selectedItem = useMemo(() => { + if (!selectedId) return undefined; + if (selectedId === run.event.id) + return { type: "event" as const, event: run.event }; + const task = run.tasks.find((task) => task.id === selectedId); + if (task) return { type: "task" as const, task }; + }, [selectedId, run]); return ( @@ -153,6 +161,9 @@ export default function Page() { {run.tasks.map((task, index) => { const isSelected = task.id === selectedId; const isLast = index === run.tasks.length - 1; + const connection = run.runConnections.find( + (c) => c.key === task.connectionKey + ); return ( } - title={task.name} + title={ + + } accessory={ {formatDuration(task.startedAt, task.completedAt, { @@ -187,6 +201,16 @@ export default function Page() { value={task.displayKey} /> )} + {connection && ( + + )} {task.delayUntil && ( )} @@ -213,7 +238,14 @@ export default function Page() { ); })}
-
Detail
+ {/* Detail view */} +
+ {!selectedItem ? ( +
Nothing selected
+ ) : ( + + )} +
diff --git a/integrations/slack/src/tasks.ts b/integrations/slack/src/tasks.ts index 678d68452..dca458a0a 100644 --- a/integrations/slack/src/tasks.ts +++ b/integrations/slack/src/tasks.ts @@ -18,6 +18,7 @@ export const postMessage = authenticatedTask({ return { name: "Post Message", params, + icon: "slack", elements: [ { label: "Channel ID",