diff --git a/.cspell.json b/.cspell.json
index f7bbaa85f125..a0d2d8f211f6 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -292,7 +292,7 @@
"lastindex",
"Lato",
"Lentczner",
- "lezer"
+ "lezer",
"libarary",
"libauth",
"libspf",
diff --git a/apps/dashboard/src/components/workflow-editor/configure-workflow.tsx b/apps/dashboard/src/components/workflow-editor/configure-workflow.tsx
index e5512d382437..268b55ef18d3 100644
--- a/apps/dashboard/src/components/workflow-editor/configure-workflow.tsx
+++ b/apps/dashboard/src/components/workflow-editor/configure-workflow.tsx
@@ -2,14 +2,14 @@ import { useFormContext } from 'react-hook-form';
import { motion } from 'framer-motion';
import { RouteFill } from '../icons';
import { Input, InputField } from '../primitives/input';
-import { RiArrowRightSLine, RiSettingsLine } from 'react-icons/ri';
+// import { RiArrowRightSLine, RiSettingsLine } from 'react-icons/ri';
import * as z from 'zod';
import { Separator } from '../primitives/separator';
import { TagInput } from '../primitives/tag-input';
import { Textarea } from '../primitives/textarea';
import { workflowSchema } from './schema';
import { useTagsQuery } from '@/hooks/use-tags-query';
-import { Button } from '../primitives/button';
+// import { Button } from '../primitives/button';
import { CopyButton } from '../primitives/copy-button';
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '../primitives/form/form';
import { Switch } from '../primitives/switch';
@@ -128,13 +128,13 @@ export function ConfigureWorkflow() {
/>
-
+ {/*
-
+ */}
);
}
diff --git a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-body.tsx b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-body.tsx
index b43e4e801da9..9eff060ef0f4 100644
--- a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-body.tsx
+++ b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-body.tsx
@@ -1,11 +1,14 @@
-import { useFormContext } from 'react-hook-form';
import { liquid } from '@codemirror/lang-liquid';
import { EditorView } from '@uiw/react-codemirror';
+import { useFormContext } from 'react-hook-form';
+import { Editor } from '@/components/primitives/editor';
import { FormControl, FormField, FormItem, FormMessage } from '@/components/primitives/form/form';
import { InputField } from '@/components/primitives/input';
-import { Editor } from '@/components/primitives/editor';
+import { useFetchStep } from '@/hooks/use-fetch-step';
+import { parseStepVariablesToLiquidVariables } from '@/utils/parseStepVariablesToLiquidVariables';
import { capitalize } from '@/utils/string';
+import { useParams } from 'react-router-dom';
const bodyKey = 'body';
@@ -15,6 +18,10 @@ export const InAppBody = () => {
formState: { errors },
} = useFormContext();
+ const { workflowSlug = '', stepSlug = '' } = useParams<{ workflowSlug: string; stepSlug: string }>();
+
+ const { step } = useFetchStep({ workflowSlug, stepSlug });
+
return (
{
id={field.name}
extensions={[
liquid({
- variables: [{ type: 'variable', label: 'asdf' }],
+ variables: step ? parseStepVariablesToLiquidVariables(step.variables) : [],
}),
EditorView.lineWrapping,
]}
diff --git a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-subject.tsx b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-subject.tsx
index 0daf67acd719..11cb2aae5c4f 100644
--- a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-subject.tsx
+++ b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-subject.tsx
@@ -6,6 +6,9 @@ import { FormControl, FormField, FormItem, FormMessage } from '@/components/prim
import { InputField } from '@/components/primitives/input';
import { Editor } from '@/components/primitives/editor';
import { capitalize } from '@/utils/string';
+import { useParams } from 'react-router-dom';
+import { useFetchStep } from '@/hooks/use-fetch-step';
+import { parseStepVariablesToLiquidVariables } from '@/utils/parseStepVariablesToLiquidVariables';
const subjectKey = 'subject';
@@ -14,6 +17,9 @@ export const InAppSubject = () => {
control,
formState: { errors },
} = useFormContext();
+ const { workflowSlug = '', stepSlug = '' } = useParams<{ workflowSlug: string; stepSlug: string }>();
+
+ const { step } = useFetchStep({ workflowSlug, stepSlug });
return (
{
id={field.name}
extensions={[
liquid({
- variables: [{ type: 'variable', label: 'asdf' }],
+ variables: step ? parseStepVariablesToLiquidVariables(step.variables) : [],
}),
EditorView.lineWrapping,
]}
diff --git a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-tabs.tsx b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-tabs.tsx
index ed7176b7d885..052c7c322a85 100644
--- a/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-tabs.tsx
+++ b/apps/dashboard/src/components/workflow-editor/steps/in-app/in-app-tabs.tsx
@@ -98,6 +98,7 @@ export const InAppTabs = ({ workflow, step }: { workflow: WorkflowResponseDto; s
});
setEditorValue(JSON.stringify(res.previewPayloadExample, null, 2));
};
+
const formValues = useWatch(form);
useDebouncedEffect(
() => {
diff --git a/apps/dashboard/src/utils/parseStepVariablesToLiquidVariables.ts b/apps/dashboard/src/utils/parseStepVariablesToLiquidVariables.ts
new file mode 100644
index 000000000000..a52b0cc67cc0
--- /dev/null
+++ b/apps/dashboard/src/utils/parseStepVariablesToLiquidVariables.ts
@@ -0,0 +1,61 @@
+import { StepDataDto } from '@novu/shared';
+
+interface LiquidVariable {
+ type: 'variable';
+ label: string;
+ detail: string;
+}
+
+type JSONSchema = StepDataDto['variables'];
+
+/**
+ * Parse JSON Schema and extract variables for Liquid autocompletion.
+ * @param schema - The JSON Schema to parse.
+ * @returns An array of variable objects suitable for the Liquid language.
+ */
+export function parseStepVariablesToLiquidVariables(schema: JSONSchema): LiquidVariable[] {
+ const variables: LiquidVariable[] = [];
+
+ function extractProperties(obj: JSONSchema, path = ''): void {
+ if (typeof obj === 'boolean') return; // Handle boolean schema
+
+ if (obj.type === 'object' && obj.properties) {
+ for (const [key, value] of Object.entries(obj.properties)) {
+ const fullPath = path ? `${path}.${key}` : key;
+
+ // Add each property as a variable for autocompletion
+ variables.push({
+ type: 'variable',
+ label: `${fullPath}`,
+ detail: typeof value !== 'boolean' ? value.description || 'JSON Schema variable' : 'JSON Schema variable',
+ });
+
+ // Recursively process nested objects
+ if (typeof value === 'object' && (value.type === 'object' || value.type === 'array')) {
+ extractProperties(value, fullPath);
+ }
+ }
+ } else if (obj.type === 'array' && obj.items) {
+ // For arrays, add a placeholder for array indexing
+ const items = Array.isArray(obj.items) ? obj.items[0] : obj.items;
+ extractProperties(items, `${path}[0]`);
+ }
+
+ // Handle combinators (allOf, anyOf, oneOf)
+ ['allOf', 'anyOf', 'oneOf'].forEach((combiner) => {
+ if (Array.isArray(obj[combiner as keyof typeof obj])) {
+ for (const subSchema of obj[combiner as keyof typeof obj] as JSONSchema[]) {
+ extractProperties(subSchema, path);
+ }
+ }
+ });
+
+ // Handle conditional schemas (if/then/else)
+ if (obj.if) extractProperties(obj.if, path);
+ if (obj.then) extractProperties(obj.then, path);
+ if (obj.else) extractProperties(obj.else, path);
+ }
+
+ extractProperties(schema);
+ return variables;
+}