Skip to content

Commit

Permalink
Merge branch 'master' into fix/poll-task-worker
Browse files Browse the repository at this point in the history
  • Loading branch information
KarlaCarvajal authored Dec 11, 2024
2 parents 5009c00 + 8f5be05 commit 40182cb
Show file tree
Hide file tree
Showing 17 changed files with 353 additions and 23 deletions.
2 changes: 1 addition & 1 deletion dashboard/package-lock.json

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

2 changes: 1 addition & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1",
"@tanstack/react-query": "^5.37.1",
"class-variance-authority": "^0.7.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Button } from '@/components/ui/button'
import LinkWithTenant from '@/app/[tenantId]/components/LinkWithTenant'
import { ExternalLinkIcon } from 'lucide-react'
import { ComponentProps } from 'react'
import { cn } from '@/components/utils'

type ExternalLinkButtonProps = {
href: string
label: string
target?: string
} & ComponentProps<typeof Button>

export const ExternalLinkButton = ({ href, label, target, className, ...props }: ExternalLinkButtonProps) => (
<Button variant="link" className={cn('h-5 w-fit p-0 text-xs', className)} {...props}>
<LinkWithTenant href={href} target={target} className="flex gap-1">
{label} <ExternalLinkIcon className="h-4 w-4" />
</LinkWithTenant>
</Button>
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NodeRun, TaskNode, UserTaskNode } from 'littlehorse-client/proto'
import { EyeIcon } from 'lucide-react'
import { FC, useCallback } from 'react'
import { useModal } from '../hooks/useModal'
import { Button } from '@/components/ui/button'

type Prop = {
nodeRuns: [NodeRun]
Expand All @@ -20,10 +21,14 @@ export const NodeRunsList: FC<Prop> = ({ nodeRuns, taskNode, userTaskNode, nodeR

return (
<div className="mt-2 flex justify-center">
<button className="flex items-center gap-1 p-1 text-blue-500 hover:bg-gray-200" onClick={showNodeRuns}>
<Button
variant="ghost"
className="flex items-center gap-1 p-1 text-blue-500 hover:bg-gray-200"
onClick={showNodeRuns}
>
<EyeIcon className="h-4 w-4" />
View NodeRuns
</button>
</Button>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,36 @@
import { UserTaskDefDetails } from '@/app/[tenantId]/(diagram)/components/NodeTypes/UserTask/UserTaskDefDetails'
import LinkWithTenant from '@/app/[tenantId]/components/LinkWithTenant'
import { ExternalLinkIcon, UserIcon } from 'lucide-react'
import { ExternalLinkIcon, EyeIcon, UserIcon } from 'lucide-react'
import { FC, memo } from 'react'
import { Handle, Position } from 'reactflow'
import { NodeRunsList } from '../../NodeRunsList'
import { Fade } from '../Fade'
import { NodeProps } from '../index'
import { NodeDetails } from '../NodeDetails'
import { useParams, useRouter } from 'next/navigation'
import { Button } from '@/components/ui/button'
import { ExternalLinkButton } from '../../ExternalLinkButton'

const Node: FC<NodeProps> = ({ data, selected }) => {
const router = useRouter()
const tenantId = useParams().tenantId as string

if (!data.userTask) return null
const { fade, userTask, nodeRun, nodeNeedsToBeHighlighted, nodeRunsList } = data

return (
<>
<NodeDetails>
<div className="">
<div className="flex items-center gap-1 text-nowrap">
<h3 className="font-bold">UserTask</h3>
<LinkWithTenant
className="flex items-center justify-center gap-1 text-blue-500 hover:underline"
target="_blank"
href={`/userTaskDef/${userTask.userTaskDefName}`}
>
{userTask.userTaskDefName} <ExternalLinkIcon className="h-4 w-4" />
</LinkWithTenant>
</div>
<div className="flex flex-col">
<h3 className="font-bold">UserTask</h3>
<ExternalLinkButton href={`/userTaskDef/${userTask.userTaskDefName}`} label={userTask.userTaskDefName} />
{nodeRun && (
<ExternalLinkButton
href={`/userTaskDef/audit/${nodeRun.id?.wfRunId?.id}/${nodeRun.userTask?.userTaskRunId?.userTaskGuid}`}
label="Audit Log"
/>
)}

{nodeRun ? (
<NodeRunsList nodeRuns={nodeRunsList} userTaskNode={userTask} nodeRun={nodeRun} />
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export const WfRunsHeader: FC<Props> = ({ spec, currentStatus, currentWindow, se
<SelectTrigger className="w-[150px] min-w-fit">
<div className="flex items-center gap-2">
<ClockIcon className="h-5 w-5 fill-none stroke-black" />
<SelectValue>{currentWindow !== -1 ? `Last ${TIME_RANGES_NAMES[currentWindow]}` : TIME_RANGES_NAMES[currentWindow]}</SelectValue>
<SelectValue>
{currentWindow !== -1 ? `Last ${TIME_RANGES_NAMES[currentWindow]}` : TIME_RANGES_NAMES[currentWindow]}
</SelectValue>
</div>
</SelectTrigger>
<SelectContent>
Expand Down
37 changes: 37 additions & 0 deletions dashboard/src/app/[tenantId]/components/Details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { TagIcon } from 'lucide-react'
import { FC } from 'react'
import { Versions } from '../userTaskDef/[...props]/components/Versions'

export type DetailsProps = {
itemHeader: string
header: string
version?: number[]
status?: string
description?: Record<string, string | React.ReactNode>
}

export const Details: FC<DetailsProps> = ({ itemHeader, header, version, status, description }) => {
return (
<div>
<div className="mb-4">
<span className="italic">{itemHeader}</span>
<h1 className="block text-2xl font-bold">{header}</h1>
<div className="flex flex-row gap-2 text-sm text-gray-500">
{version && version.length > 1 ? (
<div className="flex items-center gap-2">
<TagIcon className="h-5 w-5" />
{version[0]}
</div>
) : null}
</div>
{description &&
Object.entries(description).map(([key, value]) => (
<div key={key}>
<b>{key}</b>: {value}
</div>
))}
{status && <div className="italic text-gray-400">{status}</div>}
</div>
</div>
)
}
11 changes: 9 additions & 2 deletions dashboard/src/app/[tenantId]/components/LinkWithTenant.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
'use client'
import { cn } from '@/components/utils'
import NextLink from 'next/link'
import { useParams } from 'next/navigation'
import { ComponentProps } from 'react'

const LinkWithTenant = ({ ...props }: ComponentProps<typeof NextLink>) => {
const LinkWithTenant = ({ linkStyle, ...props }: ComponentProps<typeof NextLink> & { linkStyle?: boolean }) => {
const { tenantId } = useParams()
return <NextLink {...props} href={`/${tenantId}${props.href}`} />
return (
<NextLink
{...props}
href={`/${tenantId}${props.href}`}
className={cn(props.className, { 'text-blue-500 underline': linkStyle })}
/>
)
}

export default LinkWithTenant
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@
import { UserTaskDef } from 'littlehorse-client/proto'
import { FC } from 'react'
import { Versions } from './Versions'
import { TagIcon } from 'lucide-react'

type Props = {
id: Pick<UserTaskDef, 'name' | 'version' | 'description'>
staticVersion?: boolean
}
export const Details: FC<Props> = ({ id }) => {
export const Details: FC<Props> = ({ id, staticVersion = false }) => {
return (
<div className="mb-4">
<span className="italic">UserTaskDef</span>
<h1 className="block text-2xl font-bold">{id.name}</h1>
{id.description && <div className="italic text-gray-400">{id.description}</div>}
<div className="flex flex-row gap-2 text-sm text-gray-500">
<Versions id={id} />
{staticVersion ? (
<div className="flex items-center gap-2">
<TagIcon className="h-5 w-5" />
{id.version}
</div>
) : (
<Versions id={id} />
)}
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import React, { FC, Fragment, useState } from 'react'
import { useDebounce } from 'use-debounce'
import { Details } from './Details'
import { Fields } from './Fields'
import { useParams } from 'next/navigation'
import { useParams, useRouter } from 'next/navigation'

type Props = {
spec: UserTaskDefProto
Expand All @@ -43,6 +43,7 @@ export const UserTaskDef: FC<Props> = ({ spec }) => {
const [limit, setLimit] = useState<number>(SEARCH_DEFAULT_LIMIT)
const [userIdToSearchFor] = useDebounce(userId, DEBOUNCE_DELAY)
const [userGroupToSearchFor] = useDebounce(userGroup, DEBOUNCE_DELAY)
const router = useRouter()

const { isPending, data, hasNextPage, fetchNextPage } = useInfiniteQuery({
queryKey: [
Expand Down Expand Up @@ -178,6 +179,15 @@ export const UserTaskDef: FC<Props> = ({ spec }) => {
? utcToLocalDateTime(userTaskRun.scheduledTime)
: NOT_APPLICABLE_LABEL}
</TableCell>
<TableCell>
<Button asChild>
<LinkWithTenant
href={`/userTaskDef/audit/${userTaskRun.id?.wfRunId?.id}/${userTaskRun.id?.userTaskGuid}`}
>
View Audit Log
</LinkWithTenant>
</Button>
</TableCell>
</TableRow>
)
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import { VersionSelector } from '@/app/[tenantId]/components/VersionSelector'
import { UserTaskDefId } from 'littlehorse-client/proto'
import { useParams } from 'next/navigation'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use client'
import { useState } from 'react'
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from '@/components/ui/pagination'
import { UserTaskEvent } from 'littlehorse-client/proto'

type AuditTableProps = {
events: UserTaskEvent[]
}

export function AuditTable({ events }: AuditTableProps) {
const [currentPage, setCurrentPage] = useState(1)
const itemsPerPage = 10

const allEvents = events.filter(event => event.saved !== undefined).reverse()

const totalPages = Math.ceil(allEvents.length / itemsPerPage)
const startIndex = (currentPage - 1) * itemsPerPage
const paginatedEvents = allEvents.slice(startIndex, startIndex + itemsPerPage)

return (
<div className="mx-auto w-full max-w-2xl border-blue-300">
<Table>
<TableCaption className="mb-2">Audit Log</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Saved At</TableHead>
<TableHead>Saved By</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{paginatedEvents.map(event => (
<TableRow key={`${event.time}`}>
<TableCell>{new Date(event.time ?? 'N/A').toLocaleString()}</TableCell>
<TableCell> {event.saved?.userId}</TableCell>
</TableRow>
))}
</TableBody>
</Table>

{totalPages > 1 && (
<Pagination className="mt-4">
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={e => {
e.preventDefault()
if (currentPage > 1) setCurrentPage(currentPage - 1)
}}
/>
</PaginationItem>
{[...Array(totalPages)].map((_, index) => (
<PaginationItem key={index + 1}>
<PaginationLink
href="#"
isActive={currentPage === index + 1}
onClick={e => {
e.preventDefault()
setCurrentPage(index + 1)
}}
>
{index + 1}
</PaginationLink>
</PaginationItem>
))}
<PaginationItem>
<PaginationNext
href="#"
onClick={e => {
e.preventDefault()
if (currentPage < totalPages) setCurrentPage(currentPage + 1)
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
)}
</div>
)
}
54 changes: 54 additions & 0 deletions dashboard/src/app/[tenantId]/userTaskDef/audit/[...ids]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { getUserTaskRun } from '@/app/actions/getUserTaskRun'
import { Metadata } from 'next'
import { notFound } from 'next/navigation'
import { ClientError, Status } from 'nice-grpc-common'
import { Details } from '@/app/[tenantId]/components/Details'
import LinkWithTenant from '@/app/[tenantId]/components/LinkWithTenant'
import { AuditTable } from './AuditTable'
import Link from 'next/link'
import { Button } from '@/components/ui/button'

type Props = { params: { ids: string[]; tenantId: string } }

export default async function Page({ params: { ids, tenantId } }: Props) {
const [wfRunId, userTaskGuid] = ids

try {
const userTaskRun = await getUserTaskRun(tenantId, wfRunId, userTaskGuid)

return (
<div>
<Details
itemHeader={'UserTaskRun'}
header={userTaskRun.userTaskDefId?.name ?? 'N/A'}
version={[userTaskRun.userTaskDefId?.version ?? -1]}
description={{
wfRunId: (
<Button variant="link" className="p-0" asChild>
<LinkWithTenant href={`/wfRun/${wfRunId}`}>{wfRunId}</LinkWithTenant>
</Button>
),
userTaskRunId: userTaskGuid,
}}
/>
{userTaskRun.events.some(event => event.saved !== undefined) ? (
<AuditTable events={userTaskRun.events} />
) : (
<div className="mt-20 w-full text-center">
<p className="text-center text-xl font-bold">No save history found.</p>
<p className="text-center text-primary/50">This UserTaskRun has not been saved since it was created.</p>
</div>
)}
</div>
)
} catch (error) {
if (error instanceof ClientError && error.code === Status.NOT_FOUND) return notFound()
throw error
}
}

export async function generateMetadata({ params: { ids } }: Props): Promise<Metadata> {
return {
title: `UserTask Audit ${ids[1]} | Littlehorse`,
}
}
Loading

0 comments on commit 40182cb

Please sign in to comment.