diff --git a/apps/dashboard-app/app/(dashboard)/automations/_components/PublicWorkflows.tsx b/apps/dashboard-app/app/(dashboard)/automations/_components/PublicWorkflows.tsx
index 50e64cf..6b564a2 100644
--- a/apps/dashboard-app/app/(dashboard)/automations/_components/PublicWorkflows.tsx
+++ b/apps/dashboard-app/app/(dashboard)/automations/_components/PublicWorkflows.tsx
@@ -11,11 +11,11 @@ const PublicWorkflows = () => {
useEffect(()=>{
async function fetchWorkflows(){
const flows = await getPublicWorkflowsAction();
- console.log(flows)
setWorkflows(flows);
}
fetchWorkflows()
},[])
+
return (
{workflows?.map((workflow:any) => (
diff --git a/apps/dashboard-app/app/(dashboard)/automations/_components/Template.tsx b/apps/dashboard-app/app/(dashboard)/automations/_components/Template.tsx
new file mode 100644
index 0000000..2a72dcf
--- /dev/null
+++ b/apps/dashboard-app/app/(dashboard)/automations/_components/Template.tsx
@@ -0,0 +1,117 @@
+'use client'
+
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@repo/ui/molecules/shadcn/Card'
+import { ArrowRightIcon, CopyIcon } from 'lucide-react'
+import DynamicIcon from '../../../../components/DynamicIcon'
+import { useRouter } from 'next/navigation'
+import { useSession } from 'next-auth/react'
+import { useToast } from '../../../../hooks/useToast'
+import { duplicateWorkflow, editFlow, makeFlowPublic } from '../../../actions/workflows/workflow'
+import { Label } from '@repo/ui/atoms/shadcn/Label'
+import { Switch } from '@repo/ui/molecules/shadcn/Switch'
+import { useState } from 'react'
+import { Input } from '@repo/ui/atoms/shadcn/Input'
+import { Textarea } from '@repo/ui/atoms/shadcn/Textarea'
+
+
+const Template = ({workflow}:any) => {
+ const router = useRouter();
+ const session = useSession();
+ const userId = session.data?.user?.id;
+ const [toggle,setToggle] = useState(workflow.shared || false)
+ const [name, setName] = useState(workflow.name);
+ const [description, setDescription] = useState(workflow.description);
+ const [showNameEdit,setShowNameEdit] = useState(false);
+ const [showDescriptionEdit,setShowDescriptionEdit] = useState(false);
+
+ const onToggle = async () =>{
+ setToggle(!toggle)
+ await makeFlowPublic(workflow.id,!toggle)
+ }
+
+ const handleEditName = async () =>{
+ if (showNameEdit) {
+ setName(name)
+ await editFlow(workflow.id,name,description);
+ }
+ setShowNameEdit(!showNameEdit);
+ // router.refresh();
+ }
+
+ const handleEditDescription = async () =>{
+ if (showDescriptionEdit){
+ setDescription(description)
+ await editFlow(workflow.id,name,description);
+ }
+ setShowDescriptionEdit(!showDescriptionEdit);
+ }
+
+ return (
+
+
+
+
+ {showNameEdit ? (
+
setName(e.target.value)}
+ onBlur={handleEditName} // Exit edit mode when clicking outside the input
+ autoFocus // Automatically focus the input when entering edit mode
+ />
+ ) : (
+ // Add onDoubleClick to enable editing on double click
+
+ {name}
+
+ )}
+
+
+
+
+
+
+
+
+
+ {showDescriptionEdit ? (
+
+
+
+
+
+
+
+
+ {workflow.trigger && <>
+
+
+ >
+ }
+ {workflow.actions.map((action:any) => (
+ <>
+ >
+ ))}
+
+
+
+
+ )
+}
+
+export default Template
\ No newline at end of file
diff --git a/apps/dashboard-app/app/(dashboard)/automations/_components/Templates.tsx b/apps/dashboard-app/app/(dashboard)/automations/_components/Templates.tsx
new file mode 100644
index 0000000..7964dba
--- /dev/null
+++ b/apps/dashboard-app/app/(dashboard)/automations/_components/Templates.tsx
@@ -0,0 +1,32 @@
+"use client"
+/* eslint-disable */
+
+import React, { useEffect, useState } from 'react'
+import { getTemplatesAction } from '../../../actions/workflows/workflow'
+import PublicWorkflow from './PublicWorkflow'
+import { useSession } from 'next-auth/react'
+import Template from './Template'
+
+const Templates = () => {
+ const [workflows,setWorkflows] = useState
([])
+ const session = useSession();
+ const userId = session.data?.user?.id;
+
+ useEffect(()=>{
+ async function fetchWorkflows(){
+ const flows = await getTemplatesAction(userId || '');
+ setWorkflows(flows);
+ }
+ fetchWorkflows()
+ },[userId])
+
+ return (
+
+ {workflows?.map((workflow:any) => (
+
+ ))}
+
+ )
+}
+
+export default Templates
\ No newline at end of file
diff --git a/apps/dashboard-app/app/(dashboard)/automations/_components/WorkflowDropdown.tsx b/apps/dashboard-app/app/(dashboard)/automations/_components/WorkflowDropdown.tsx
index 5076370..0a18ca5 100644
--- a/apps/dashboard-app/app/(dashboard)/automations/_components/WorkflowDropdown.tsx
+++ b/apps/dashboard-app/app/(dashboard)/automations/_components/WorkflowDropdown.tsx
@@ -4,7 +4,7 @@ import { CopyIcon, Edit2Icon, TrashIcon } from 'lucide-react'
import { useRouter } from 'next/navigation'
import React, { useState } from 'react'
import { useToast } from '../../../../hooks/useToast'
-import { deleteFlow, duplicateWorkflow, makeFlowPublic, publishFlow } from '../../../actions/workflows/workflow'
+import { createTemplate, deleteFlow, duplicateWorkflow, makeFlowPublic, publishFlow } from '../../../actions/workflows/workflow'
import { Checkbox } from '@repo/ui/atoms/shadcn/Checkbox'
import { useSession } from 'next-auth/react'
@@ -49,6 +49,17 @@ const WorkflowDropdown = ({workflow}:any) => {
toast({title: "Error", description: res?.error, variant: 'destructive'})
}
}
+
+ const onTemplateCreation = async () => {
+ const res = await createTemplate(workflow.id,userId)
+ if (res.success){
+ toast({title: "Success", description: res?.success, variant: 'default'})
+ router.refresh()
+ }
+ else if (res.error){
+ toast({title: "Error", description: res?.error, variant: 'destructive'})
+ }
+ }
return (
router.push(`/automations/editor/${workflow.id}`)}>
@@ -63,16 +74,22 @@ const WorkflowDropdown = ({workflow}:any) => {
Duplicate
+
handleDelete(workflow.id)}>
diff --git a/apps/dashboard-app/app/(dashboard)/automations/editor/[editorId]/_components/action-forms/code/PythonCode.tsx b/apps/dashboard-app/app/(dashboard)/automations/editor/[editorId]/_components/action-forms/code/PythonCode.tsx
index decf169..de90969 100644
--- a/apps/dashboard-app/app/(dashboard)/automations/editor/[editorId]/_components/action-forms/code/PythonCode.tsx
+++ b/apps/dashboard-app/app/(dashboard)/automations/editor/[editorId]/_components/action-forms/code/PythonCode.tsx
@@ -198,6 +198,17 @@ export const PythonCode = ({funcType,nodeType,type,subType,node}: any) => {
setCodeBlocks(updatedCodeBlocks);
}
+ const modifyVariable = (blockId: number,index: number,key: string,value: string) => {
+ const updatedCodeBlocks = codeBlocks.map((block:any) => {
+ if (block.id === blockId) {
+ block.variables[index] = { key, value };
+ }
+ return block;
+ });
+ setCodeBlocks(updatedCodeBlocks);
+
+ }
+
return (
@@ -222,7 +233,8 @@ export const PythonCode = ({funcType,nodeType,type,subType,node}: any) => {
{codeBlocks.map((block: any,index: number) => (
+ removeCodeBlock={removeCodeBlock} runTillCurrentCode={runTillCurrentCode}
+ modifyVariable={modifyVariable} index={index}/>
))}
{output && (
diff --git a/apps/dashboard-app/app/(dashboard)/automations/editor/[editorId]/_components/action-forms/code/PythonCodeBlock.tsx b/apps/dashboard-app/app/(dashboard)/automations/editor/[editorId]/_components/action-forms/code/PythonCodeBlock.tsx
index ee01e37..34ffaa8 100644
--- a/apps/dashboard-app/app/(dashboard)/automations/editor/[editorId]/_components/action-forms/code/PythonCodeBlock.tsx
+++ b/apps/dashboard-app/app/(dashboard)/automations/editor/[editorId]/_components/action-forms/code/PythonCodeBlock.tsx
@@ -15,6 +15,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@repo/
import { Input } from '@repo/ui/atoms/shadcn/Input';
import { Label } from '@repo/ui/atoms/shadcn/Label';
import { Alert, AlertDescription } from '@repo/ui/atoms/shadcn/Alert';
+import PythonCodeBlockVariables from './PythonCodeBlockVariables';
interface VariablesProps {
key: string;
@@ -29,7 +30,7 @@ interface CodeBlockProps {
}
const PythonCodeBlock = ({block,modifyTitle,removeVariable,addVariable,modifyCode,addCodeBlock,removeCodeBlock,
- runTillCurrentCode,index}:any) => {
+ runTillCurrentCode,modifyVariable,index}:any) => {
const [output, setOutput] = useState('');
const [error, setError] = useState('');
@@ -46,6 +47,9 @@ const PythonCodeBlock = ({block,modifyTitle,removeVariable,addVariable,modifyCod
setValue('')
}
+
+
+
const runCode = async (block:any) => {
setError('');
setOutput('');
@@ -120,22 +124,8 @@ const PythonCodeBlock = ({block,modifyTitle,removeVariable,addVariable,modifyCod
{block.variables?.map((variable: any,index:any) => (
-
-
- {variable.key}
-
-
- {variable.value}
-
-
-
removeVariable(block.id, index)}
- alertTitle='Delete Property'
- alertDescription='Are you sure you want to delete this property?'
- buttonDiv={}
- alertActionText='Delete'
- />
-
+
))}
{
+ const [key, setKey] = useState(variable.key);
+ const [value, setValue] = useState(variable.value);
+ const [showKeyEdit, setShowKeyEdit] = useState(false);
+ const [showValueEdit, setShowValueEdit] = useState(false);
+
+ const modifyKey = async () =>{
+ if (showKeyEdit){
+ setKey(key)
+ await modifyVariable(block.id,index,key,value)
+ }
+ setShowKeyEdit(!showKeyEdit)
+ }
+
+ const modifyValue = async () =>{
+ if (showValueEdit){
+ setValue(value)
+ await modifyVariable(block.id,index,key,value)
+ }
+ setShowValueEdit(!showValueEdit)
+ }
+
+ return (
+
+ {showKeyEdit ? (
+
setKey(e.target.value)}
+ onBlur={modifyKey} // Exit edit mode when clicking outside the input
+ autoFocus // Automatically focus the input when entering edit mode
+ />):(
+
+ {key}
+
+ )}
+ {showValueEdit ? (
+
setValue(e.target.value)}
+ onBlur={modifyValue} // Exit edit mode when clicking outside the input
+ autoFocus // Automatically focus the input when entering edit mode
+ />):(
+
+ {value}
+
+ )}
+
+
removeVariable(block.id, index)}
+ alertTitle='Delete Property'
+ alertDescription='Are you sure you want to delete this property?'
+ buttonDiv={}
+ alertActionText='Delete'
+ />
+
+ )
+}
+
+export default PythonCodeBlockVariables
\ No newline at end of file
diff --git a/apps/dashboard-app/app/(dashboard)/automations/page.tsx b/apps/dashboard-app/app/(dashboard)/automations/page.tsx
index 73c5315..230ec76 100644
--- a/apps/dashboard-app/app/(dashboard)/automations/page.tsx
+++ b/apps/dashboard-app/app/(dashboard)/automations/page.tsx
@@ -9,6 +9,7 @@ import Workflows from './_components/Workflows'
import Event from './editor/[editorId]/_components/Event'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@repo/ui/molecules/shadcn/Tabs'
import PublicWorkflows from './_components/PublicWorkflows'
+import Templates from './_components/Templates'
const Page = () => {
const session = useSession();
@@ -22,8 +23,11 @@ const Page = () => {
My Automations
-
- Public Automations
+
+ My Automation Templates
+
+
+ Public Automation Templates
@@ -45,7 +49,10 @@ const Page = () => {
-
+
+
+
+
diff --git a/apps/dashboard-app/app/actions/workflows/workflow.ts b/apps/dashboard-app/app/actions/workflows/workflow.ts
index 833ec33..1ff26b4 100644
--- a/apps/dashboard-app/app/actions/workflows/workflow.ts
+++ b/apps/dashboard-app/app/actions/workflows/workflow.ts
@@ -12,7 +12,8 @@ import {createWorkflow, editWorkflow, getWorkflowsByUserId, publishWorkflow, de
updateAction,
updateTrigger,
makeWorkflowPublic,
- getPublicWorkflows
+ getPublicWorkflows,
+ getTemplatesByUserId
} from '@repo/prisma-db/repo/workflow';
import {logger} from '@repo/winston-logger/index';
@@ -37,6 +38,12 @@ export const getPublicWorkflowsAction = async () => {
return workflows;
}
+export const getTemplatesAction = async (userId:string) => {
+ const workflows = await getTemplatesByUserId(userId)
+ return workflows;
+}
+
+
export const editFlow = async (workflowId:string, name:string, description: string) => {
logger.info('Editing workflow',workflowId, name, description);
const workflow = await editWorkflow(workflowId,name,description);
@@ -148,24 +155,88 @@ export const createActionAction = async({workflowId, actionId, metadata,sortingO
export const duplicateWorkflow = async (workflowId:string,userId:any) => {
try{
const workflow = await getWorkflowById(workflowId);
+ if (!workflow){
+ return {error: "Workflow not found"}
+ }
const newWorkflow = await createWorkflow({
- name: workflow?.name +" Duplicated", description: workflow?.description, userId:userId, publish: false,share:false});
+ name: workflow?.name +" Duplicated", description: workflow?.description, userId:userId, publish: false,
+ shared:false, template: false});
+ if (!newWorkflow){
+ return {error: "Workflow creation failed"}
+ }
const trigger:any = workflow?.trigger;
-
const actions:any = workflow?.actions;
- await createTrigger({workflowId: newWorkflow.id, triggerId: trigger.triggerId, metadata: trigger.metadata});
- for (let i=0; i {
+ try{
+ const workflow = await getWorkflowById(workflowId);
+ if (!workflow){
+ return {error: "Workflow not found"}
+ }
+ const newWorkflow = await createWorkflow({
+ name: workflow?.name +" Template", description: workflow?.description, userId:userId, publish: false,
+ shared:false, template:true});
+ const trigger:any = workflow?.trigger;
+
+ const actions:any = workflow?.actions;
+ try {
+ await createTrigger({workflowId: newWorkflow.id, triggerId: trigger.triggerId, metadata: trigger.metadata});
+ }
+ catch (error) {
+ return {error: "Trigger creation for this template failed"}
+ }
+ try{
+ for (let i=0; i 0 ){
+ metadata.codeBlocks = metadata.codeBlocks.map((block:any) => {
+ if (block.hasOwnProperty('variables') && block.variables.length > 0){
+ block.variables = block.variables.map((variable:any) => {
+ variable.value = "";
+ return variable;
+ })
+ }
+ return block;
+ })
+ }
+ await createAction({
+ workflowId: newWorkflow.id, actionId: actions[i].actionId, metadata: metadata,
+ sortingOrder: actions[i].sortingOrder});
+ }
+ }
+ catch (error) {
+ return {error: "Actions Creation for this template failed"}
+ }
+ return {success: "Template created successfully", result: workflow}
+ }
+ catch (error) {
+ return {error: "Template creation failed"}
+ }
+}
+
export const updateTriggerAction = async({id,triggerId, metadata}:any) => {
try{
const trigger = await updateTrigger(id, triggerId, metadata);
diff --git a/packages/prisma-db/prisma/migrations/20241119025648_template_boolean_added/migration.sql b/packages/prisma-db/prisma/migrations/20241119025648_template_boolean_added/migration.sql
new file mode 100644
index 0000000..44da3af
--- /dev/null
+++ b/packages/prisma-db/prisma/migrations/20241119025648_template_boolean_added/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "workflow_schema"."Workflow" ADD COLUMN "template" BOOLEAN DEFAULT false;
diff --git a/packages/prisma-db/prisma/schema/workflow.prisma b/packages/prisma-db/prisma/schema/workflow.prisma
index d03c8a7..a08bb54 100644
--- a/packages/prisma-db/prisma/schema/workflow.prisma
+++ b/packages/prisma-db/prisma/schema/workflow.prisma
@@ -4,6 +4,7 @@ model Workflow {
description String
publish Boolean? @default(false)
shared Boolean? @default(false)
+ template Boolean? @default(false)
lastRun String?
userId String
events Event[]
diff --git a/packages/prisma-db/src/workflow.ts b/packages/prisma-db/src/workflow.ts
index a0eff61..94d36cf 100644
--- a/packages/prisma-db/src/workflow.ts
+++ b/packages/prisma-db/src/workflow.ts
@@ -2,12 +2,15 @@ import { WorkflowAction, WorkflowTrigger } from '@prisma/client'
import db from './index'
//Create functions
-export const createWorkflow = async ({name,description,userId}:any) => {
+export const createWorkflow = async ({name,description,userId,publish,shared,template}:any) => {
const workflow = await db.workflow.create({
data:{
name,
description,
userId,
+ publish,
+ shared,
+ template
}
})
return workflow;
@@ -59,7 +62,8 @@ export const getWorkflowsByUserId = async (userId: string) => {
if (userId){
const workflows = await db.workflow.findMany({
where:{
- userId
+ userId,
+ template: false
},
include:{
actions: {
@@ -86,6 +90,37 @@ export const getWorkflowsByUserId = async (userId: string) => {
}
}
+export const getTemplatesByUserId = async (userId: string) => {
+ if (userId){
+ const workflows = await db.workflow.findMany({
+ where:{
+ userId,
+ template: true
+ },
+ include:{
+ actions: {
+ include:{
+ type: {
+ include:{
+ actionType: true
+ }
+ }
+ }
+ },
+ trigger: {
+ include:{
+ type: {
+ include:{
+ triggerType: true
+ }
+ }
+ }
+ }
+ },
+ })
+ return workflows;
+ }
+}
//Get Functions
export const getWorkflowsById = async (id: string) => {
const workflow = await db.workflow.findUnique({
@@ -121,7 +156,8 @@ export const getWorkflowsById = async (id: string) => {
export const getPublicWorkflows = async () => {
const workflows = await db.workflow.findMany({
where:{
- shared: true
+ shared: true,
+ template: true
},
include:{
actions: {
@@ -333,6 +369,7 @@ export const makeWorkflowPublic = async (workflowId: string, state:boolean) => {
return 'Workflow made private'
}
+
export const updateWorkflowLastRun = async (workflowId: string, lastRun: string) => {
const workflow = await db.workflow.update({
where:{