Untitled
unknown
plain_text
a year ago
22 kB
5
Indexable
import { StoreApi } from 'zustand';
import {
AllowedDataType,
ExecutedInOut,
Input,
InputSlug,
NodeSystemType,
SelectedDataType,
WorkFlowNode,
} from '@/app/workflows/[slug]/components/workflow.data';
import { executeFunction } from '@/actions/workflow/functionActions';
import { FieldData } from '@/lib/form/schema';
import {
GetFieldKey,
prepareSubmitData,
} from '@/app/workflows/[slug]/components/WorkflowMain/nodes/Utils/formUtils';
import { FormEvents, FormStore } from './formTypes';
import { AutomationExecutionStore, NodesOutput } from './formAutomation.store';
import BE_API_V1 from '@/lib/api/api';
import { formEventSystem } from '@/app/forms/[formId]/components/preview/context/formEventSystem';
// Helper functions for node execution
function resolveVariableInput(
input: Input | undefined,
executionContext: { [key: string]: any },
): any {
if (!input || !input.selected_data) {
return undefined;
}
if (input.selected_data.selected_data_type === SelectedDataType.Expr) {
const expr = input.selected_data.data as string;
return expr.replace(/\{\{(.*?)\}\}/g, (match, variable) => {
return executionContext[variable.trim()] || match;
});
}
return input.selected_data.data;
}
// Helper functions for node execution
function resolveVariableInputv2(
input: Input | undefined,
nodesOutput: NodesOutput,
): any {
if (!input || !input.selected_data) {
return undefined;
}
if (input.selected_data.selected_data_type === SelectedDataType.Expr) {
const expr = input.selected_data.data as string;
return expr.replace(/\{@([\w._-]+)\}/g, (match, variable) => {
const keys = variable.split('.');
return nodesOutput[`${keys[0]}.${keys[1]}`].data || match;
});
}
return input.selected_data.data;
}
function findFieldLocation(fieldId: string, formStore: any) {
const state = formStore.getState();
for (const tab of state.tabs) {
for (const section of tab.sections) {
for (const row of section.rows) {
for (const column of row.columns) {
for (const component of column.component) {
if (component.id === fieldId) {
return {
tabId: tab.id,
sectionId: section.id,
rowId: row.id,
columnId: column.id,
component,
};
}
}
}
}
}
}
return null;
}
function findSecionLocation(sectionId: string, formStore: any) {
const state = formStore.getState();
for (const tab of state.tabs) {
for (const section of tab.sections) {
if (section.id === sectionId) {
return {
tabId: tab.id,
section,
};
}
}
}
return null;
}
function findTabLocation(tabId: string, formStore: any) {
const state = formStore.getState();
for (const tab of state.tabs) {
if (tab.id === tabId) {
return {
tab,
};
}
}
return null;
}
export function getFrontendAutomationEventsFromType(
systemType?: NodeSystemType,
): FormEvents {
switch (systemType) {
case NodeSystemType.FromOnLoad:
return FormEvents.FORM_ON_LOAD;
case NodeSystemType.ButtonOnClick:
return FormEvents.BUTTON_ON_CLICK;
case NodeSystemType.FieldOnChange:
return FormEvents.FIELD_CHANGE;
default:
console.log('Unknown Type');
}
return FormEvents.FORM_ON_LOAD;
}
// Trigger Handlers
export function handleFormOnLoad(): ExecutedInOut {
return {
inputs: [],
outputs: [
{
data_type: AllowedDataType.DataTypeForm,
slug: 'form_slug',
data: '',
has_many: false,
},
],
run_status: 1,
};
}
export async function handleButtonOnClick(
node: WorkFlowNode,
nodesOutput: NodesOutput,
): Promise<ExecutedInOut> {
const buttonId = resolveVariableInputv2(
node?.inputs?.find((input) => input.slug === InputSlug.ButtonOnClick),
nodesOutput,
);
const inOut = {
inputs: [
{
data_type: AllowedDataType.DataTypeText,
slug: InputSlug.ButtonOnClick,
data: buttonId,
has_many: false,
},
],
outputs: [],
run_status: 1,
};
return inOut;
}
export async function handleTabOnSubmit(
node: WorkFlowNode,
formStore: StoreApi<AutomationExecutionStore>,
executionContext: { [key: string]: any },
): Promise<ExecutedInOut> {
const tabId = resolveVariableInput(
node?.inputs?.find((input) => input.slug === InputSlug.FormTabs),
executionContext,
);
// Implement tab submit logic using formStore
return {
inputs: [
{
data_type: AllowedDataType.DataTypeText,
slug: InputSlug.FormTabs,
data: tabId,
has_many: false,
},
],
outputs: [],
run_status: 1,
};
}
// Implement other handler functions similarly, using formStore to interact with form data
export async function handleSetFields(
node: WorkFlowNode,
formStore: StoreApi<FormStore>,
nodesOutput: NodesOutput,
): ExecutedInOut {
const executedInOut: ExecutedInOut = {
inputs: [],
outputs: [],
run_status: 1,
};
try {
const fieldsInput = node.inputs?.find(
(input) => input.slug === InputSlug.FormFields,
);
const fieldIds = resolveVariableInputv2(
fieldsInput,
nodesOutput,
) as string[];
if (!fieldIds || !Array.isArray(fieldIds)) {
throw new Error('Invalid or missing field IDs');
}
const { setFormValue } = formStore.getState();
const getField = formStore.getState().getComponentFieldData;
fieldIds.forEach((fieldId) => {
const attributeInput = node.inputs?.find(
(input) => input.slug === `${InputSlug.AttributeSlugPrefix}${fieldId}`,
);
if (attributeInput) {
const value = resolveVariableInputv2(attributeInput, nodesOutput);
const field = getField(fieldId);
if (field) {
const fieldData = field as FieldData;
const key = GetFieldKey(fieldData);
setFormValue(key, value);
executedInOut?.inputs?.push({
data_type: attributeInput.allowed_data_types.type,
slug: attributeInput.slug,
data: value,
has_many: attributeInput.allow_many ?? false,
});
}
}
});
return executedInOut;
} catch (error: any) {
executedInOut.run_status = -1;
executedInOut.error = error.message;
return executedInOut;
}
}
export async function handleTabSubmit(
formStore: StoreApi<FormStore>,
): Promise<ExecutedInOut> {
const {} = formStore.getState();
const inOut: ExecutedInOut = {
outputs: [],
run_status: 1,
};
return inOut;
}
export async function handleFormSubmit(
formStore: StoreApi<FormStore>,
): Promise<ExecutedInOut> {
const {} = formStore.getState();
const inOut: ExecutedInOut = {
outputs: [],
run_status: 1,
};
formEventSystem.notify(FormEvents.ON_FORM_SUBMIT, {
action: FormEvents.ON_FORM_SUBMIT,
});
return new Promise((resolve) => {
const listener = (result: any) => {
formEventSystem.unsubscribe(id);
inOut.outputs?.push({
data_type: AllowedDataType.DataTypeObject,
slug: InputSlug.SubmitForm,
data: result,
has_many: false,
});
resolve(inOut);
};
const id = formEventSystem.subscribe(
FormEvents.ON_FORM_SUBMITTED,
listener,
);
});
}
export function handleFieldOnChange(
node: WorkFlowNode,
formStore: any,
nodesOutput: NodesOutput,
): ExecutedInOut {
const fields = resolveVariableInputv2(
node?.inputs?.find((input) => input.slug === InputSlug.FormFields),
nodesOutput,
) as string[];
const inputs: any[] = [];
fields.forEach((field) => {
const value = resolveVariableInput(
node?.inputs?.find(
(input) => input.slug === `${InputSlug.AttributeSlugPrefix}${field}`,
),
formStore.getState().executionContext,
);
inputs.push({
data_type: AllowedDataType.DataTypeText,
slug: `${InputSlug.AttributeSlugPrefix}${field}`,
data: value,
has_many: false,
});
formStore.setFieldValue(field, value);
});
return {
inputs: [
{
data_type: AllowedDataType.DataTypeText,
slug: InputSlug.FormFields,
data: fields,
has_many: true,
},
...inputs,
],
outputs: [],
run_status: 1,
};
}
export function handleFieldShowHide(
node: WorkFlowNode,
formStore: any,
): ExecutedInOut {
const executedInOut: ExecutedInOut = {
inputs: [],
outputs: [],
run_status: 1,
};
try {
// Extract inputs from node
const targetFieldsInput = node?.inputs?.find(
(input) => input.slug === InputSlug.FormFields,
);
const showHideInput = node?.inputs?.find(
(input) => input.slug === InputSlug.FieldShowHide,
);
if (!targetFieldsInput || !showHideInput) {
return executedInOut;
}
const fieldIds = targetFieldsInput.selected_data?.data as string[]; // Assuming it's an array of field IDs
const action = showHideInput?.selected_data?.data as 'show' | 'hide';
const input = {
data_type: targetFieldsInput.allowed_data_types.type,
slug: targetFieldsInput.slug,
has_many: targetFieldsInput.allow_many ?? false,
data: targetFieldsInput.selected_data,
};
executedInOut?.inputs?.push(input);
fieldIds.forEach((fieldId: string) => {
// Get the field's location in the form
const fieldLocation = findFieldLocation(fieldId, formStore);
if (!fieldLocation) {
return;
}
// Update the field's visibility
formStore
.getState()
.updateComponent(
fieldLocation.tabId,
fieldLocation.sectionId,
fieldLocation.rowId,
fieldLocation.columnId,
fieldId,
{
data: {
...fieldLocation.component.data,
hide_by_default: action === 'hide',
},
},
);
});
return executedInOut;
} catch (error: any) {
executedInOut.run_status = -1;
executedInOut.error = error.message;
return executedInOut;
}
}
export function handleTabShowHide(
node: WorkFlowNode,
formStore: any,
): ExecutedInOut {
const executedInOut: ExecutedInOut = {
inputs: [],
outputs: [],
run_status: 1,
};
try {
const targetTab = node?.inputs?.find(
(input) => input.slug === InputSlug.FormTabs,
);
const showHideInput = node?.inputs?.find(
(input) => input.slug === InputSlug.FieldShowHide,
);
if (!targetTab || !showHideInput) {
return executedInOut;
}
const tabId = targetTab.selected_data?.data as string;
const action = showHideInput?.selected_data?.data as 'show' | 'hide';
const input = {
data_type: targetTab.allowed_data_types.type,
slug: targetTab.slug,
has_many: targetTab.allow_many ?? false,
data: targetTab.selected_data,
};
executedInOut?.inputs?.push(input);
// Now, perform the show/hide action on the formStore
// Get the tab's location in the form
const tabLocation = findTabLocation(tabId, formStore);
if (!tabLocation) {
return {
...executedInOut,
run_status: -1,
};
}
const updatedTabData = {
...tabLocation.tab,
hide_by_default: action === 'hide',
};
// Update the tab's visibility
formStore.getState().updateTab(tabLocation.tab.id, updatedTabData);
return executedInOut;
} catch (error: any) {
executedInOut.run_status = -1;
executedInOut.error = error.message;
return executedInOut;
}
}
export function handleSectionShowHide(
node: WorkFlowNode,
formStore: any,
): ExecutedInOut {
const executedInOut: ExecutedInOut = {
inputs: [],
outputs: [],
run_status: 1,
};
try {
// Extract inputs from node
const targetSection = node?.inputs?.find(
(input) => input.slug === InputSlug.FormSections,
);
const showHideInput = node?.inputs?.find(
(input) => input.slug === InputSlug.FieldShowHide,
);
if (!targetSection || !showHideInput) {
return executedInOut;
}
const sectionId = targetSection.selected_data?.data as string;
const action = showHideInput?.selected_data?.data as 'show' | 'hide';
const input = {
data_type: targetSection.allowed_data_types.type,
slug: targetSection.slug,
has_many: targetSection.allow_many ?? false,
data: targetSection.selected_data,
};
executedInOut?.inputs?.push(input);
// Now, perform the show/hide action on the formStore
// Get the section's location in the form
const sectionLocation = findSecionLocation(sectionId, formStore);
if (!sectionLocation) {
return {
...executedInOut,
run_status: -1,
};
}
const updatedSectionData = {
...sectionLocation.section,
hide_by_default: action === 'hide',
};
// Update the section's visibility
formStore
.getState()
.updateSection(
sectionLocation.tabId,
sectionLocation.section.id,
updatedSectionData,
);
return executedInOut;
} catch (error: any) {
executedInOut.run_status = -1;
executedInOut.error = error.message;
return executedInOut;
}
}
export function handleReadOrWrite(
node: WorkFlowNode,
formStore: any,
): ExecutedInOut {
const executedInOut: ExecutedInOut = {
inputs: [],
outputs: [],
run_status: 1,
};
try {
// Extract inputs from node
const targetFieldsInput = node?.inputs?.find(
(input) => input.slug === InputSlug.FormFields,
);
const readAndWrite = node?.inputs?.find(
(input) => input.slug === InputSlug.FieldReadWrite,
);
if (!targetFieldsInput || !readAndWrite) {
return executedInOut;
}
const fieldIds = targetFieldsInput.selected_data?.data as string[]; // Assuming it's an array of field IDs
const action = readAndWrite?.selected_data?.data as 'read' | 'write';
const input = {
data_type: targetFieldsInput.allowed_data_types.type,
slug: targetFieldsInput.slug,
has_many: targetFieldsInput.allow_many ?? false,
data: targetFieldsInput.selected_data,
};
executedInOut?.inputs?.push(input);
// Now, perform the show/hide action on the formStore
fieldIds.forEach((fieldId: string) => {
// Get the field's location in the form
const fieldLocation = findFieldLocation(fieldId, formStore);
if (!fieldLocation) {
return;
}
// Update the field's visibility
formStore
.getState()
.updateComponent(
fieldLocation.tabId,
fieldLocation.sectionId,
fieldLocation.rowId,
fieldLocation.columnId,
fieldId,
{
data: {
...fieldLocation.component.data,
disabled: action === 'read',
},
},
);
});
return executedInOut;
} catch (error: any) {
executedInOut.run_status = -1;
executedInOut.error = error.message;
return executedInOut;
}
}
export function handleMandatory(
node: WorkFlowNode,
formStore: any,
): ExecutedInOut {
const executedInOut: ExecutedInOut = {
inputs: [],
outputs: [],
run_status: 1,
};
try {
// Extract inputs from node
const targetFieldsInput = node?.inputs?.find(
(input) => input.slug === InputSlug.FormFields,
);
const mandatory = node?.inputs?.find(
(input) => input.slug === InputSlug.FieldMandatory,
);
if (!targetFieldsInput || !mandatory) {
return executedInOut;
}
const fieldIds = targetFieldsInput.selected_data?.data as string[]; // Assuming it's an array of field IDs
const action = mandatory?.selected_data?.data as
| 'mandatory'
| 'non_mandatory';
const input = {
data_type: targetFieldsInput.allowed_data_types.type,
slug: targetFieldsInput.slug,
has_many: targetFieldsInput.allow_many ?? false,
data: targetFieldsInput.selected_data,
};
executedInOut?.inputs?.push(input);
// Now, perform the show/hide action on the formStore
fieldIds.forEach((fieldId: string) => {
// Get the field's location in the form
const fieldLocation = findFieldLocation(fieldId, formStore);
if (!fieldLocation) {
return;
}
// Update the field's visibility
formStore
.getState()
.updateComponent(
fieldLocation.tabId,
fieldLocation.sectionId,
fieldLocation.rowId,
fieldLocation.columnId,
fieldId,
{
data: {
...fieldLocation.component.data,
validation: {
...fieldLocation.component.data.validation,
required: action === 'mandatory',
},
},
},
);
});
return executedInOut;
} catch (error: any) {
executedInOut.run_status = -1;
executedInOut.error = error.message;
return executedInOut;
}
}
export function handleShowError(
node: WorkFlowNode,
formStore: any,
nodesOutput: NodesOutput,
): ExecutedInOut {
const fields = resolveVariableInputv2(
node?.inputs?.find((input) => input.slug === InputSlug.FormFields),
nodesOutput,
) as string[];
const inputs: any[] = [];
fields.forEach((field) => {
const value = resolveVariableInputv2(
node?.inputs?.find(
(input) => input.slug === `${InputSlug.AttributeSlugPrefix}${field}`,
),
nodesOutput,
);
inputs.push({
data_type: AllowedDataType.DataTypeText,
slug: `${InputSlug.AttributeSlugPrefix}${field}`,
data: value,
has_many: false,
});
formStore.setFieldValue(field, value);
});
return {
inputs: [
{
data_type: AllowedDataType.DataTypeText,
slug: InputSlug.FormFields,
data: fields,
has_many: true,
},
...inputs,
],
outputs: [],
run_status: 1,
};
}
export async function handleExecuteFunction(
node: WorkFlowNode,
formStore: any,
nodesOutput: NodesOutput,
): Promise<ExecutedInOut> {
const functionInput = node?.inputs?.find(
(input) => input.slug === InputSlug.Function,
);
const bodyInput = node?.inputs?.find(
(input) => input.slug === InputSlug.Body,
);
const functionData = resolveVariableInput(functionInput, nodesOutput);
const fId = functionData.function_id;
const bodyData = resolveVariableInput(bodyInput, nodesOutput);
try {
const data = await executeFunction(fId, JSON.parse(bodyData));
if (data.status === 'completed') {
return {
inputs: [],
outputs: [
{
data_type: AllowedDataType.DataTypeInt,
slug: 'status_code',
data: 200, // TODO
has_many: false,
},
{
data_type: AllowedDataType.DataTypeJson,
slug: 'response_body',
data: data.result,
has_many: false,
},
],
run_status: 1,
};
}
return {
inputs: [],
outputs: [],
run_status: -1,
error: data.error,
};
} catch (error) {
return {
inputs: [],
outputs: [],
run_status: -1,
// error: error,
};
}
}
export async function handleSendHttpRequest(
node: WorkFlowNode,
formStore: any,
nodesOutput: NodesOutput,
): Promise<ExecutedInOut> {
const urlInput = node?.inputs?.find((input) => input.slug === InputSlug.Url);
const methodInput = node?.inputs?.find(
(input) => input.slug === InputSlug.RequestType,
);
const headersInput = node?.inputs?.find(
(input) => input.slug === InputSlug.Headers,
);
const bodyInput = node?.inputs?.find(
(input) => input.slug === InputSlug.Body,
);
const url = resolveVariableInputv2(urlInput, nodesOutput);
const method = resolveVariableInputv2(methodInput, nodesOutput);
const headers = resolveVariableInputv2(headersInput, nodesOutput);
const body = resolveVariableInputv2(bodyInput, nodesOutput);
// TODO add headers in better manner and set content type in headers
try {
const response = await fetch(url, {
method,
headers,
body,
});
const responseData = await response.text();
return {
inputs: [],
outputs: [
{
data_type: AllowedDataType.DataTypeInt,
slug: 'status_code',
data: response.status,
has_many: false,
},
{
data_type: AllowedDataType.DataTypeJson,
slug: 'response_body',
data: responseData,
has_many: false,
},
],
run_status: 1,
};
} catch (error) {
return {
inputs: [],
outputs: [],
run_status: -1,
// error: error.message,
};
}
}
export function handleParseJson(
node: WorkFlowNode,
formStore: any,
nodesOutput: NodesOutput,
): ExecutedInOut {
const jsonInput = node?.inputs?.find((input) => input.slug === 'json');
const jsonAttributesInput = node?.inputs?.find(
(input) => input.slug === InputSlug.JsonAttributes,
);
const jsonString = resolveVariableInputv2(jsonInput, nodesOutput);
const jsonAttributes = resolveVariableInput(jsonAttributesInput, nodesOutput);
let parsedJson;
try {
parsedJson = JSON.parse(jsonString);
} catch (error) {
return {
inputs: [
{
data_type: AllowedDataType.DataTypeText,
slug: 'json',
data: jsonString,
has_many: false,
},
],
outputs: [],
run_status: -1,
error: 'Invalid JSON string',
};
}
const outputs: any[] = [];
jsonAttributes.fields.forEach((field: any) => {
const value = field.path
.split('$:')
.reduce((obj: any, key: string) => obj && obj[key], parsedJson);
// const convertedValue = convertToDataType(
// value,
// field.outputType as AllowedDataType,
// field.has_many,
// );
outputs.push({
data_type: field.outputType,
slug: field.path,
data: value,
has_many: field.has_many,
});
});
return {
inputs: [],
outputs,
run_status: 1,
};
}
Editor is loading...
Leave a Comment