Improved the task display with connections and task icon

This commit is contained in:
Matt Aitken
2023-05-30 15:35:11 +01:00
parent 0a898900bf
commit c1907d4e00
6 changed files with 146 additions and 13 deletions
@@ -7,6 +7,13 @@ type RunOptions = {
userId: string;
};
export type Task = NonNullable<
Awaited<ReturnType<RunPresenter["call"]>>
>["tasks"][number];
export type Event = NonNullable<
Awaited<ReturnType<RunPresenter["call"]>>
>["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: {
@@ -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 <TaskDetail {...props.task} />;
case "event":
return <EventDetail {...props.event} />;
}
return <></>;
}
export function TaskDetail({ name, status, delayUntil }: Task) {
return (
<RunPanel selected={false}>
{/* //todo what icon to use here? */}
{/* <RunPanelHeader icon={undefined} title={name} accessory={status === "WAITING" && delayUntil ? <button} /> */}
</RunPanel>
);
}
export function EventDetail({}: Event) {
return (
<RunPanel selected={false}>
<RunPanelHeader icon={undefined} title={""} />
</RunPanel>
);
}
@@ -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
)}
<Paragraph variant="small/bright">{title}</Paragraph>
{typeof title === "string" ? (
<Paragraph variant="small/bright">{title}</Paragraph>
) : (
title
)}
</div>
<div className="flex items-center gap-2">{accessory}</div>
</div>
);
}
type RunPanelIconTitleProps = {
icon?: string | null;
title: string;
};
export function RunPanelIconTitle({ icon, title }: RunPanelIconTitleProps) {
return (
<div className="flex items-center gap-1">
{icon && <NamedIcon name={icon} className="h-5 w-5" />}
{title}
</div>
);
}
export function RunPanelBody({ children }: { children: React.ReactNode }) {
return <div className="p-4">{children}</div>;
}
@@ -88,15 +106,20 @@ export function RunPanelIconElement({
export function RunPanelElements({
elements,
columns = false,
className,
}: {
elements: { label: string; value: string }[];
columns?: boolean;
className?: string;
}) {
return (
<div
className={cn(
"grid items-baseline gap-x-4 gap-y-1",
columns ? "auto-cols-max grid-flow-col auto-rows-max" : "grid-cols-2"
columns
? "grid-cols-[repeat(auto-fit,minmax(120px,1fr))]"
: "grid-cols-2",
className
)}
>
{elements.map(({ label, value }, index) => (
@@ -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 ? (
<CheckIcon
className={cn(taskStatusClassNameColor(status), className)}
/>
) : (
<CheckCircleIcon
className={cn(taskStatusClassNameColor(status), className)}
/>
@@ -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<string | undefined>(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 (
<PageContainer>
@@ -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 (
<Fragment key={task.id}>
<RunPanel
@@ -163,13 +174,16 @@ export default function Page() {
icon={
<TaskStatusIcon
status={task.status}
minimal={true}
className={cn(
"h-5 w-5",
!isSelected && "text-slate-400"
)}
/>
}
title={task.name}
title={
<RunPanelIconTitle icon={task.icon} title={task.name} />
}
accessory={
<Paragraph variant="extra-small">
{formatDuration(task.startedAt, task.completedAt, {
@@ -187,6 +201,16 @@ export default function Page() {
value={task.displayKey}
/>
)}
{connection && (
<RunPanelIconElement
icon={
connection.apiConnection.client
.integrationIdentifier
}
label="Connection"
value={connection.apiConnection.client.title}
/>
)}
{task.delayUntil && (
<RunPanelIconElement
icon="clock"
@@ -204,6 +228,7 @@ export default function Page() {
label: element.label,
value: element.text,
}))}
className="mt-4"
/>
)}
</RunPanelBody>
@@ -213,7 +238,14 @@ export default function Page() {
);
})}
</div>
<div>Detail</div>
{/* Detail view */}
<div>
{!selectedItem ? (
<div>Nothing selected</div>
) : (
<Detail {...selectedItem} />
)}
</div>
</div>
</PageBody>
</PageContainer>
+1
View File
@@ -18,6 +18,7 @@ export const postMessage = authenticatedTask({
return {
name: "Post Message",
params,
icon: "slack",
elements: [
{
label: "Channel ID",