Skip to content

Commit

Permalink
feat(dashboard): Autofill payload with example in step preview
Browse files Browse the repository at this point in the history
  • Loading branch information
desiprisg committed Nov 8, 2024
1 parent 5a0efd7 commit 581e7ca
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 17 deletions.
10 changes: 5 additions & 5 deletions apps/dashboard/src/api/workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ export const updateWorkflow = async ({

export const previewStep = async ({
workflowSlug,
payload,
data,
stepSlug,
}: {
workflowSlug: string;
stepSlug: string;
payload?: GeneratePreviewRequestDto;
data?: GeneratePreviewRequestDto;
}): Promise<GeneratePreviewResponseDto> => {
const { data } = await postV2<{ data: GeneratePreviewResponseDto }>(
const res = await postV2<{ data: GeneratePreviewResponseDto }>(
`/workflows/${workflowSlug}/step/${stepSlug}/preview`,
payload
data
);

return data;
return res.data;
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,25 @@ import { Code2 } from '@/components/icons/code-2';
import { Button } from '@/components/primitives/button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/primitives/collapsible';
import { Editor } from '@/components/primitives/editor';
import { usePreviewStep } from '@/hooks/use-preview-step';
import { useParams } from 'react-router-dom';
import { InAppPreview } from '@/components/workflow-editor/in-app-preview';
import { usePreviewStep } from '@/hooks/use-preview-step';
import { loadLanguage } from '@uiw/codemirror-extensions-langs';
import { useFormContext } from 'react-hook-form';
import { useParams } from 'react-router-dom';

export const InAppEditorPreview = () => {
const [editorValue, setEditorValue] = useState('{}');
type InAppEditorPreviewProps = {
value: string;
onChange: (value: string) => void;
};
export const InAppEditorPreview = (props: InAppEditorPreviewProps) => {
const { value, onChange } = props;
const [isEditorOpen, setIsEditorOpen] = useState(true);
const { previewStep, data } = usePreviewStep();
const { workflowSlug = '', stepSlug = '' } = useParams<{
workflowSlug: string;
stepSlug: string;
}>();
const form = useFormContext();
const [payloadError, setPayloadError] = useState('');

return (
Expand Down Expand Up @@ -48,10 +54,9 @@ export const InAppEditorPreview = () => {

<CollapsibleContent className="flex flex-col gap-2 rounded-lg p-1">
<Editor
value={editorValue}
onChange={setEditorValue}
value={value}
onChange={onChange}
lang="json"
basicSetup={{ lineNumbers: true, defaultKeymap: true }}
extensions={[loadLanguage('json')?.extension ?? []]}
className="border-neutral-alpha-200 bg-background text-foreground-600 rounded-lg border border-dashed p-3"
/>
Expand All @@ -63,7 +68,11 @@ export const InAppEditorPreview = () => {
className="self-end"
onClick={() => {
try {
previewStep({ workflowSlug, stepSlug, payload: JSON.parse(editorValue) });
previewStep({
workflowSlug,
stepSlug,
data: { controlValues: form.getValues(), previewPayload: JSON.parse(value) },
});
} catch (e) {
setPayloadError(String(e));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RiEdit2Line, RiPencilRuler2Line } from 'react-icons/ri';
import { Cross2Icon } from '@radix-ui/react-icons';
import { useNavigate, useParams } from 'react-router-dom';
import { useForm } from 'react-hook-form';
import { useForm, useWatch } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { type WorkflowResponseDto, type StepDataDto, type StepUpdateDto } from '@novu/shared';

Expand All @@ -16,11 +16,14 @@ import { buildDynamicZodSchema, buildDefaultValues } from '@/utils/schema';
import { InAppEditor } from '@/components/workflow-editor/steps/in-app/in-app-editor';
import { showToast } from '@/components/primitives/sonner-helpers';
import { ToastIcon } from '@/components/primitives/sonner';
import { useState } from 'react';
import { usePreviewStep } from '@/hooks/use-preview-step';
import useDebouncedEffect from '@/hooks/use-debounced-effect';

const tabsContentClassName = 'h-full w-full px-3 py-3.5';

export const InAppTabs = ({ workflow, step }: { workflow: WorkflowResponseDto; step: StepDataDto }) => {
const { stepSlug = '' } = useParams<{ stepSlug: string }>();
const { stepSlug = '', workflowSlug = '' } = useParams<{ workflowSlug: string; stepSlug: string }>();
const { dataSchema, uiSchema } = step.controls;
const navigate = useNavigate();
const schema = buildDynamicZodSchema(dataSchema ?? {});
Expand All @@ -31,8 +34,10 @@ export const InAppTabs = ({ workflow, step }: { workflow: WorkflowResponseDto; s
defaultValues: buildDefaultValues(uiSchema ?? {}),
values: step.controls.values,
});
const [editorValue, setEditorValue] = useState('{}');
const { reset, formState } = form;

const { previewStep } = usePreviewStep();
const { updateWorkflow } = useUpdateWorkflow({
onSuccess: () => {
showToast({
Expand Down Expand Up @@ -81,6 +86,29 @@ export const InAppTabs = ({ workflow, step }: { workflow: WorkflowResponseDto; s
reset({ ...data });
};

const preview = async (props: {
controlValues: Record<string, unknown>;
previewPayload: Record<string, unknown>;
}) => {
const res = await previewStep({
workflowSlug,
stepSlug,
data: { controlValues: props.controlValues, previewPayload: props.previewPayload },
});
setEditorValue(JSON.stringify(res.previewPayloadExample, null, 2));
};
const formValues = useWatch(form);
useDebouncedEffect(
() => {
preview({
controlValues: form.getValues() as Record<string, unknown>,
previewPayload: JSON.parse(editorValue),
});
},
2000,
[formValues]
);

return (
<Form {...form}>
<form
Expand Down Expand Up @@ -128,7 +156,7 @@ export const InAppTabs = ({ workflow, step }: { workflow: WorkflowResponseDto; s
<InAppEditor uiSchema={uiSchema} />
</TabsContent>
<TabsContent value="preview" className={tabsContentClassName}>
<InAppEditorPreview />
<InAppEditorPreview value={editorValue} onChange={setEditorValue} />
</TabsContent>
<Separator />
<footer className="flex justify-end px-3 py-3.5">
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/hooks/use-debounce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCallback, useEffect } from 'react';
import debounce from 'lodash.debounce';
import { useDataRef } from './use-data-ref';

export const useDebounce = <Arguments = unknown | unknown[]>(callback: (args?: Arguments) => void, ms = 0) => {
export const useDebounce = <Arguments extends any[]>(callback: (...args: Arguments) => void, ms = 0) => {
const callbackRef = useDataRef(callback);

// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
34 changes: 34 additions & 0 deletions apps/dashboard/src/hooks/use-debounced-effect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import debounce from 'lodash.debounce';
import { useEffect, useState, useMemo } from 'react';

type Callback = () => void;
type DependencyList = ReadonlyArray<any>;

/**
* Custom hook that runs the callback immediately on the first render
* and debounced on subsequent renders based on the dependency array.
*
* @param callback - The function to be executed.
* @param deps - Dependency array for the effect.
* @param delay - Delay in milliseconds for the debounced function.
*/
function useDebouncedEffect(callback: Callback, delay: number, deps: DependencyList): void {
const [hasRunOnFirstRender, setHasRunOnFirstRender] = useState(false);

const debouncedCallback = useMemo(() => debounce(callback, delay), [callback, delay]);

useEffect(() => {
if (!hasRunOnFirstRender) {
callback();
setHasRunOnFirstRender(true);
} else {
debouncedCallback();
}

return () => {
debouncedCallback.cancel();
};
}, deps);
}

export default useDebouncedEffect;

0 comments on commit 581e7ca

Please sign in to comment.