Skip to content

Commit

Permalink
job source code in admin UI
Browse files Browse the repository at this point in the history
  • Loading branch information
Sheraff committed Jul 8, 2024
1 parent 770d965 commit 1abe04d
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 6 deletions.
79 changes: 79 additions & 0 deletions admin/client/components/ui/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as React from "react"

import { cn } from "client/utils"

const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"

const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"

const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"

const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"

const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"

const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"

export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
53 changes: 53 additions & 0 deletions admin/client/components/ui/tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"

import { cn } from "client/utils"

const Tabs = TabsPrimitive.Root

const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName

const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName

const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName

export { Tabs, TabsList, TabsTrigger, TabsContent }
45 changes: 41 additions & 4 deletions admin/client/routes/$queueId/$jobId/_job.$taskId/index.lazy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { Graph } from "client/routes/$queueId/$jobId/_job.$taskId/-components/Gr
import { Events } from "client/routes/$queueId/$jobId/_job.$taskId/-components/Events"
import { Button } from "client/components/ui/button"
import { Code } from "client/components/syntax-highlighter"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "client/components/ui/tabs"
import { Braces, CodeXml, Database } from "lucide-react"
import { Card, CardContent } from "client/components/ui/card"

export const Route = createLazyFileRoute('/$queueId/$jobId/_job/$taskId/')({
component: Task
Expand Down Expand Up @@ -37,6 +40,16 @@ function Task() {
refetchInterval: refetch[task.status] ?? false
})

const { data: source } = useQuery({
queryKey: [queueId, 'job-source', task.job],
queryFn: async () => {
const res = await fetch(`/api/source/${task.job}`)
const text = await res.text()
return text
},
staleTime: Infinity,
})

const { data: parent } = useQuery({
queryKey: [queueId, 'task', task.parent_id],
queryFn: async () => {
Expand All @@ -55,10 +68,34 @@ function Task() {
{parent && <Button asChild>
<Link to="/$queueId/$jobId/$taskId" params={{ queueId, jobId: parent.job, taskId: String(parent.id) }}>parent</Link>
</Button>}
<Code language="json">
{JSON.stringify(task, null, 2)}
</Code>
<hr className="my-4" />
<Tabs defaultValue="state" className="w-full my-4">
<TabsList>
<TabsTrigger className="gap-2" value="state"><Database className="h-4 w-4" />Stored state</TabsTrigger>
<TabsTrigger className="gap-2" value="payload"><Braces className="h-4 w-4" />Input payload</TabsTrigger>
{source && <TabsTrigger className="gap-2" value="source"><CodeXml className="h-4 w-4" />Job source</TabsTrigger>}
</TabsList>
<Card className="mt-4">
<CardContent>
<TabsContent value="state">
<Code language="json">
{JSON.stringify(task, null, 2)}
</Code>
</TabsContent>
<TabsContent value="payload">
<Code language="json">
{JSON.stringify(JSON.parse(task.input), null, 2)}
</Code>
</TabsContent>
{source && (
<TabsContent value="source">
<Code language="javascript">
{source}
</Code>
</TabsContent>
)}
</CardContent>
</Card>
</Tabs>
<div className="flex">
<div className="flex-1">
<h3 className="text-lg">Steps</h3>
Expand Down
2 changes: 2 additions & 0 deletions admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@tanstack/react-query": "5.49.2",
"@tanstack/react-query-devtools": "^5.50.1",
"@tanstack/react-router": "^1.43.12",
Expand All @@ -31,6 +32,7 @@
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"lucide-react": "^0.400.0",
"prettier": "^3.3.2",
"queue": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
22 changes: 20 additions & 2 deletions admin/server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import {
} from "queue"
import Database from "better-sqlite3"
import { z } from "zod"
import { format } from "prettier"

type Step = object
type Task = object
type Event = object


const foo = new Job({
id: 'foo',
input: z.object({
Expand Down Expand Up @@ -74,7 +74,6 @@ const getData = (queue: Queue, origin: number) => {
return data
}


// Create an HTTP server
const server = http.createServer((req, res) => {
const url = new URL(req.url || '', `http://${req.headers.host}`)
Expand Down Expand Up @@ -103,6 +102,25 @@ const server = http.createServer((req, res) => {
return
}

const source = url.pathname.match(/^\/api\/source\/(.+)$/)
if (source) {
if (!(source[1] in queue.jobs)) {
res.writeHead(404, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'not found' }, null, '\t'))
return
}
const job = queue.jobs[source[1] as keyof typeof queue.jobs]!
return format(job.string, { parser: "typescript", semi: false })
.then((str) => {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end(str)
})
.catch((error) => {
res.writeHead(500, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: error.message }, null, '\t'))
})
}

const job = url.pathname.match(/^\/api\/jobs\/(.+)$/)
if (job) {
res.writeHead(200, { 'Content-Type': 'application/json' })
Expand Down
8 changes: 8 additions & 0 deletions lib/src/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ export class Job<
readonly triggers: NoInfer<Array<Pipe<string, InputData> | PipeInto<any, InputData>>> | undefined
/** @package */
readonly cron: string | string[] | undefined
/** @package */
readonly string: string

readonly #emitter = new EventEmitter<EventMap<In, Out>>()
readonly type = 'job'
Expand Down Expand Up @@ -187,6 +189,12 @@ export class Job<
this.triggers = opts.triggers as Array<Pipe<string, InputData> | PipeInto<any, InputData>>
this.cron = opts.cron

this.string = `new Job({ ${Object.entries(opts).map(([key, value]) => {
if (typeof value === 'function') return `${key}: ${value.toString()}`
if (key === 'input' || key === 'output') return `${key}: ParserObject`
return `${key}: ${JSON.stringify(value)}`
}).join(', ')} }, ${fn.toString()})`

if (this.cron && this.input) {
try {
this.input.parse({ date: new Date().toISOString() })
Expand Down
35 changes: 35 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1abe04d

Please sign in to comment.