Untitled

 avatar
unknown
plain_text
5 months ago
22 kB
1
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