Improved the task display with connections and task icon
This commit is contained in:
@@ -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: {
|
||||
|
||||
+40
@@ -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>
|
||||
);
|
||||
}
|
||||
+26
-3
@@ -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) => (
|
||||
|
||||
+14
-5
@@ -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
-4
@@ -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>
|
||||
|
||||
@@ -18,6 +18,7 @@ export const postMessage = authenticatedTask({
|
||||
return {
|
||||
name: "Post Message",
|
||||
params,
|
||||
icon: "slack",
|
||||
elements: [
|
||||
{
|
||||
label: "Channel ID",
|
||||
|
||||
Reference in New Issue
Block a user