Skip to content

Commit

Permalink
refactor(ui): Consolidate and cleanup all axois calls + schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
daryllimyt committed Mar 11, 2024
1 parent 6de4f69 commit 29b5068
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 120 deletions.
3 changes: 0 additions & 3 deletions frontend/src/app/workflows/[id]/cases/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { promises as fs } from "fs"
import path from "path"
import { Metadata } from "next"
import { DefaultQueryClientProvider } from "@/providers/query"
import { WorkflowProvider } from "@/providers/workflow"
import { z } from "zod"

import { columns } from "@/components/cases/columns"
import { DataTable } from "@/components/cases/data-table"
import { caseSchema } from "@/components/cases/data/schema"
import { Navbar } from "@/components/navbar"

export const metadata: Metadata = {
title: "Cases | Tracecat",
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/app/workflows/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@ import { PropsWithChildren } from "react"
import { Metadata } from "next"
import { DefaultQueryClientProvider } from "@/providers/query"
import { WorkflowProvider } from "@/providers/workflow"
import { createClient } from "@/utils/supabase/server"

import { Navbar } from "@/components/navbar"

export const metadata: Metadata = {
title: "Workflows | Tracecat",
}

export default function WorkflowsLayout({
export default async function WorkflowsLayout({
children,
}: PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) {
const supabase = createClient()

const {
data: { session },
} = await supabase.auth.getSession()
return (
<>
<DefaultQueryClientProvider>
<WorkflowProvider>
<WorkflowProvider session={session}>
<div className="no-scrollbar flex h-screen flex-col">
<Navbar />
{children}
Expand Down
45 changes: 6 additions & 39 deletions frontend/src/components/canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ import "reactflow/dist/style.css"

import { useParams } from "next/navigation"
import { useSession } from "@/providers/session"
import { ActionType } from "@/types"

import { ActionMetadata, WorkflowResponse } from "@/types/schemas"
import { updateDndFlow } from "@/lib/flow"
import { WorkflowResponse } from "@/types/schemas"
import { createAction, deleteAction, updateDndFlow } from "@/lib/flow"
import { useToast } from "@/components/ui/use-toast"
import ActionNode, { ActionNodeData } from "@/components/action-node"

Expand Down Expand Up @@ -81,40 +80,6 @@ const WorkflowCanvas: React.FC = () => {
initializeReactFlowInstance()
}, [workflowId, setNodes, setEdges, setViewport])

const createAction = async (type: ActionType, title: string) => {
if (!workflowId || !reactFlowInstance) return
try {
const createActionMetadata = JSON.stringify({
workflow_id: workflowId,
type: type,
title: title,
})
const response = await axios.post<ActionMetadata>(
"http://localhost:8000/actions",
createActionMetadata,
{
headers: {
"Content-Type": "application/json",
},
}
)
console.log("Action created successfully:", response.data)
return response.data.id
} catch (error) {
console.error("Error creating action:", error)
}
}

const deleteAction = async (actionId: string) => {
try {
const url = `http://localhost:8000/actions/${actionId}`
await axios.delete(url)
console.log(`Action with ID ${actionId} deleted successfully.`)
} catch (error) {
console.error(`Error deleting action with ID ${actionId}:`, error)
}
}

// React Flow callbacks
const onConnect = useCallback(
(params: Edge | Connection) => {
Expand Down Expand Up @@ -161,8 +126,10 @@ const WorkflowCanvas: React.FC = () => {

// Create Action in database
const actionId = await createAction(
session,
actionNodeData.type,
actionNodeData.title
actionNodeData.title,
workflowId
)
if (!actionId) return
// Then create Action node in React Flow
Expand All @@ -180,7 +147,7 @@ const WorkflowCanvas: React.FC = () => {

const onNodesDelete = useCallback(
(nodesToDelete: Node[]) => {
Promise.all(nodesToDelete.map((node) => deleteAction(node.id)))
Promise.all(nodesToDelete.map((node) => deleteAction(session, node.id)))
.then(() => {
setNodes((nds) =>
nds.filter((n) => !nodesToDelete.map((nd) => nd.id).includes(n.id))
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/components/forms/action-schemas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,9 @@ export const getActionSchema = (actionType: ActionType) => {
actionFieldSchema: actionFieldSchemas["llm.summarize"],
}
default:
return null // No schema or UI hints available for the given action type
return {
actionSchema: null,
actionFieldSchema: null,
}
}
}
97 changes: 37 additions & 60 deletions frontend/src/components/forms/action.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React, { useCallback, useEffect, useState } from "react"
import { useParams } from "next/navigation"
import { useWorkflowBuilder } from "@/providers/builder"
import { useSession } from "@/providers/session"
import { ActionType } from "@/types"
import { zodResolver } from "@hookform/resolvers/zod"
import { useMutation, useQuery } from "@tanstack/react-query"
import axios from "axios"
import { CircleIcon, Save } from "lucide-react"
import { ControllerRenderProps, useForm } from "react-hook-form"
import { Node } from "reactflow"
import { type Node } from "reactflow"
import { z } from "zod"

import { ActionResponse, actionResponseSchema } from "@/types/schemas"
import { ActionResponse } from "@/types/schemas"
import { getActionById, updateAction } from "@/lib/flow"
import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
Expand Down Expand Up @@ -45,6 +46,14 @@ import {
getActionSchema,
} from "@/components/forms/action-schemas"

type ActionStatus = "online" | "offline"

const baseActionSchema = z.object({
title: z.string(),
description: z.string(),
})
export type BaseActionSchema = z.infer<typeof baseActionSchema>

interface ActionFormProps {
actionId: string
actionType: ActionType
Expand All @@ -54,40 +63,28 @@ export function ActionForm({
actionId,
actionType,
}: ActionFormProps): React.JSX.Element {
const [status, setStatus] = useState("offline")
const [status, setStatus] = useState<ActionStatus>("offline")
const { setNodes } = useWorkflowBuilder()
const params = useParams<{ id: string }>()
const workflowId = params.id

// Fetch Action by ID and Workflow ID
const getActionById = async (): Promise<ActionResponse> => {
try {
const response = await axios.get<ActionResponse>(
`http://localhost:8000/actions/${actionId}?workflow_id=${workflowId}`
)
return actionResponseSchema.parse(response.data)
} catch (error) {
console.error("Error fetching action:", error)
throw error // Rethrow the error to ensure it's caught by useQuery's isError state
}
}
const session = useSession()

const { data: actionResponseData } = useQuery<ActionResponse, Error>({
queryKey: ["selected_action", actionId, workflowId],
queryFn: getActionById,
queryFn: async ({ queryKey }) => {
// Fetch Action by ID and Workflow ID
const [_, actionId, workflowId] = queryKey as [string, string, string]
const result = await getActionById(session, actionId, workflowId)
return result
},
})

const { actionSchema, actionFieldSchema } = getActionSchema(actionType) ?? {}
const { actionSchema, actionFieldSchema } = getActionSchema(actionType)

// Extend the Zod schema dynamically based on the fetched schema
const actionFormSchema = actionSchema
? actionSchema.extend({
title: z.string(),
description: z.string(),
})
: z.object({
title: z.string(),
description: z.string(),
})
? baseActionSchema.merge(actionSchema)
: baseActionSchema
type actionFormSchemaType = z.infer<typeof actionFormSchema>

const form = useForm<actionFormSchemaType>({
Expand Down Expand Up @@ -185,51 +182,35 @@ export function ActionForm({
}
}, [actionResponseData, form.reset])

// Submit form and update Action
async function updateAction(actionId: string, values: actionFormSchemaType) {
const { title, description, ...inputsObject } = values
const inputs = JSON.stringify(inputsObject)
const updateActionParams = {
title: values.title,
description: values.description,
inputs: inputs,
}
console.log("Updating action with:", updateActionParams)
const response = await axios.post(
`http://localhost:8000/actions/${actionId}`,
JSON.stringify(updateActionParams),
{
headers: {
"Content-Type": "application/json",
},
}
)
return response.data // Adjust based on what your API returns
}

function useUpdateAction(actionId: string) {
const mutation = useMutation({
mutationFn: (values: actionFormSchemaType) =>
updateAction(actionId, values),
// Configure your mutation behavior here
updateAction(session, actionId, values),
onSuccess: (data, variables, context) => {
console.log("Action update successful", data)
setNodes((nds: Node[]) =>
nds.map((node: Node) => {
if (node.id === actionId) {
node.data = {
...node.data,
title: data.title,
status: data.status,
isConfigured: data.inputs ? true : false,
...node.data, // Overwrite the existing node data
...data, // Update the node data with the new action data
isConfigured: data.inputs !== null,
}
}
return node
})
)
console.log("Action update successful", data)
toast({
title: "Saved action",
description: "Your action has been updated successfully.",
})
},
onError: (error, variables, context) => {
console.error("Failed to update action:", error)
toast({
title: "Failed to save action",
description: "Could not update your action. Please try again.",
})
},
})

Expand All @@ -239,10 +220,6 @@ export function ActionForm({
const { mutate } = useUpdateAction(actionId)
function onSubmit(values: actionFormSchemaType) {
mutate(values)
toast({
title: "Saved action",
description: "Your action has been updated successfully.",
})
}

// Loading state to defend in a user friendly way
Expand Down
14 changes: 8 additions & 6 deletions frontend/src/components/forms/workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
} from "@/components/ui/tooltip"
import { toast } from "@/components/ui/use-toast"

// Define formSchema for type safety
const workflowFormSchema = z.object({
title: z.string(),
description: z.string(),
Expand All @@ -48,7 +47,6 @@ export function WorkflowForm({
workflowStatus,
}: WorkflowFormProps): React.JSX.Element {
const session = useSession()
console.log("WorkflowForm", session)
const form = useForm<WorkflowForm>({
resolver: zodResolver(workflowFormSchema),
defaultValues: {
Expand All @@ -63,9 +61,17 @@ export function WorkflowForm({
updateWorkflow(session, workflowId, values),
onSuccess: (data, variables, context) => {
console.log("Workflow update successful", data)
toast({
title: "Saved workflow",
description: "Workflow updated successfully.",
})
},
onError: (error, variables, context) => {
console.error("Failed to update workflow:", error)
toast({
title: "Error updating workflow",
description: "Could not update workflow. Please try again.",
})
},
})

Expand All @@ -75,10 +81,6 @@ export function WorkflowForm({
const { mutate } = useUpdateWorkflow(workflowId)
function onSubmit(values: WorkflowForm) {
mutate(values)
toast({
title: "Saved workflow",
description: "Workflow updated successfully.",
})
}

return (
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { redirect } from "next/navigation"
import { Session } from "@supabase/supabase-js"
import { type Session } from "@supabase/supabase-js"
import axios from "axios"

const client = axios.create({
Expand Down
Loading

0 comments on commit 29b5068

Please sign in to comment.