nocode-js / sequential-workflow-editor

Powerful workflow editor builder for any workflow designer.
https://nocode-js.com/
MIT License
80 stars 7 forks source link

Help with createStepModel #30

Closed NexPlex closed 6 months ago

NexPlex commented 6 months ago

Hi, I'm trying to move your example code from the component to a model.ts file. This code.

public readonly toolboxConfiguration: ToolboxConfiguration = {
        groups: [
            {
                name: 'Step',
                steps: [
                    this.createTaskStep(null, 'save', 'Save file', {"velocity": "0"}),
                    this.createTaskStep(null, 'text', 'Send email', {"velocity": "0"}),
                    this.createTaskStep(null, 'task', 'Create task', {"velocity": "0"}),
                    this.createIfStep(null, [], []),
                    this.createContainerStep(null, [])
                ]
            }
        ]
    };

    private createTaskStep(id: any, type: any, name: any, properties: any) {
        return {
            id,
            componentType: 'task',
            type,
            name,
            properties: properties || {}
        };
    }

    private createIfStep(id: any, _true: any, _false: any) {
        return {
            id,
            componentType: 'switch',
            type: 'if',
            name: 'If',
            branches: {
                'true': _true,
                'false': _false
            },
            properties: {}
        };
    }

    createContainerStep(id: any, steps: any) {
        return {
            id,
            componentType: 'container',
            type: 'loop',
            name: 'Loop',
            properties: {},
            sequence: steps
        };
    }

sequential-workflow-model.ts


// Define a model for an Email Task Step
export const emailTaskModel = createStepModel<Step>('email', 'task', step => {
  // Define the "recipient" property
  step.property('recipient')
    .value(createStringValueModel({
      minLength: 1, // Minimum length of 1 character
    }))
    .label('Recipient');

  // Define the "subject" property
  step.property('subject')
    .value(createStringValueModel({
      minLength: 1, // Minimum length of 1 character
    }))
    .label('Email Subject');

  // Define the "message" property
  step.property('message')
    .value(createStringValueModel({
      minLength: 1, // Minimum length of 1 character
    }))
    .label('Email Message');
});

// Define your custom step types as interfaces if needed
export interface TaskStep extends Step {
  type: string; // Type of task (e.g., 'save', 'text', 'task')
  name: string; // Human-readable name for the task
  velocity: string; // A string representing the "speed" or priority of the task
  // Add any other task-specific properties here
}

export interface IfStep extends Step {
  condition: string; // The condition to evaluate, represented as a string
  branches: {
                'true': true,
                'false': false
            };
  // Any additional properties related to conditional execution
}

export interface ContainerStep extends Step {
  sequence: Step[]; // An array of steps that are part of this container
  // Additional properties that define how the steps in the sequence are executed
}

// Use createStepModel to define your custom steps
export const taskStepModel = createStepModel<TaskStep>('task', 'task', step => {
 step.property('type')
    .value(createStringValueModel({
      // Example configuration: Adjust these properties as needed
      minLength: 1,

    }))
    .label('Task Type');

  step.property('name')
    .value(createStringValueModel({
      minLength: 1, // Minimum length of 1 character
    }))
    .label('Task Name');

  step.property('velocity')
    .value(createStringValueModel({
      // Convert the string pattern to a RegExp object
      pattern: new RegExp('^[0-9]+$'), // Ensures the velocity is a number
    }))
    .label('Velocity');
});

export const ifStepModel = createStepModel<IfStep>('if', 'switch', step => {
  step.property('condition')
    .value(createStringValueModel({
      minLength: 1, // Assuming a simple condition represented as a string
    }))
    .label('Condition');
;
});

export const containerStepModel = createStepModel<ContainerStep>('loop', 'container', step => {
  // Assuming sequence needs to be an array of step IDs or similar identifiers
  step.property('sequence')
    .value(createStringValueModel({
      minLength: 1, // Adjust based on how sequences are represented
    }))
    .label('Sequence');

  // Container-specific properties can be added here
});

export const rootModel = createRootModel<MyDefinition>(root => {
  root.property('inputs')
    .value(
      createVariableDefinitionsValueModel({})
    );
});

export const definitionModel = createDefinitionModel<MyDefinition>(model => {
  model.valueTypes(['string', 'number']);
  model.root(rootModel);
  model.steps([logStepModel, taskStepModel, ifStepModel, containerStepModel, emailTaskModel]);
});

export const editorProvider = EditorProvider.create(definitionModel, {
    uidGenerator: Uid.next,

});
`

calling it like this from the component

this.toolboxConfiguration = { groups: editorProvider.getToolboxGroups() };

the taskStepModel and emailTaskModel seem to work but the ifStepModel and containerStepModel give a lot of errors.

Any suggestions?

b4rtaz commented 6 months ago

Hello @NexPlex!

For branched steps you should use the createBranchedStepModel method. Check this example.

For sequential steps you should use the createSequentialStepModel method. Check this example.

I noticed also your interfaces have properties on the wrong level. All properties should be inside the properties field.

export interface SetStep extends Step {
  type: 'set';
  componentType: 'task';
  properties: {
    result: NullableAnyVariable;
    value: Dynamic<NullableAnyVariable | string | number | boolean>;
  };
}
NexPlex commented 6 months ago

Hi thank you for the feedback,

when implementing the createBranchedStepModel method from your link, I'm getting an error on this line.

const a = context.formatPropertyValue('a', StepNameFormatter.formatDynamic);

Cannot read properties of undefined (reading 'formatDynamic')

b4rtaz commented 6 months ago

You don't need StepNameFormatter to create a branched step model or a sequential step model (btw: the StepNameFormatter is defined at the app level, so it's a custom function).

Here you can find simpler examples:

I would recommend to copy a code from these examples iteratively, not all at once. This will help you to understand what is wrong.

Try to start from a simple branched step like:

export const ifStepModel = createBranchedStepModel<IfStep>('if', 'switch', step => {
    step.branches().value(
        createBranchesValueModel({
            branches: {
                true: [],
                false: []
            }
        })
    );
});

Then add next value models for properties.

b4rtaz commented 6 months ago

This demo is available online here. If you select any if step you can see how it looks:

image

These properties use the dynamic value model, you can read more about it here.

NexPlex commented 6 months ago

Thank you for you help I got the if and loop running without error. I'm having a challenge showing the editor properties window correctly.

in ts

public ngOnInit() {
    const editorProvider = EditorProvider.create(definitionModel, {
        uidGenerator: Uid.next
      });
      this.stepEditorProvider = editorProvider.createStepEditorProvider();
      this.rootEditorProvider = editorProvider.createRootEditorProvider();
      this.validatorConfiguration = {
        root: editorProvider.createRootValidator(),
        step: editorProvider.createStepValidator()
      };
}

in HTML

    [rootEditor]="rootEditorProvider"
    [stepEditor]="stepEditorProvider"

I'm getting this error: Cannot read path: properties/inputs

is this related to this code in my model?

export interface MyDefinition extends Definition {
  properties: {
    inputs: VariableDefinitions;
  };
}

export const rootModel = createRootModel<MyDefinition>(root => {
  root.property('inputs')
    .value(
      createVariableDefinitionsValueModel({})
    );
});
b4rtaz commented 6 months ago

I suppose your start definition doesn't have any value for the inputs field in the root properties.

{
  "properties": { "inputs": { "variables": [] } },
  "sequence": [  ]
}

To create a start definition from the model you can use:

editorProvider.activateDefinition()
NexPlex commented 6 months ago

Sorry, I'm not understanding how to use or where it goes. can you provide an example?

{ "properties": { "inputs": { "variables": [] } }, "sequence": [ ] }

NexPlex commented 6 months ago

If I print to console activatedDefinition seems fine

const editorProvider = EditorProvider.create(definitionModel, {
    uidGenerator: Uid.next
  });
  const activatedDefinition = editorProvider.activateDefinition();
  console.log('activatedDefinition', activatedDefinition)

  this.stepEditorProvider = editorProvider.createStepEditorProvider();
  this.rootEditorProvider = editorProvider.createRootEditorProvider();
  this.validatorConfiguration = {
    root: editorProvider.createRootValidator(),
    step: editorProvider.createStepValidator()
  };

image

b4rtaz commented 6 months ago

It should be something like this (I didn't test it).

public definition: Definition;

public ngOnInit() {
  const editorProvider = EditorProvider.create(definitionModel, {
    uidGenerator: Uid.next
  });
  this.definition = editorProvider.activateDefinition();
  // ...
}
<sqd-designer
  [definition]="definition"
  ...
></sqd-designer>
NexPlex commented 6 months ago

Ok I see what you mean. Resolved the issue. thank you so much!!