Skip to content

Commit

Permalink
SF-02 SF-03 finish implementing subflows (#31)
Browse files Browse the repository at this point in the history
* Adjust title of node-editor when editing subflow

* Use new selectPaletteNodeByFlowNode selector for subflow compatibility

* Update node editing flow with support for editing subflow instances

* Make inputLabels and outputLabels optional

* Update tests for flow/node.logic

* Update tests for node-editor.logic

* Correct error, support no editing node in new node-editor.tsx state call

* Correctly set subflow icon when converting to palette node

* Finish SF-02 and SF-03

* Write new SF-05 ticket for implementing subflow inputs/outputs and place it in ToDo
  • Loading branch information
JoshuaCWebDeveloper authored Jun 5, 2024
1 parent cdc9748 commit 8564e23
Show file tree
Hide file tree
Showing 11 changed files with 463 additions and 51 deletions.
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"cSpell.words": [
"curvyness",
"DEFINS",
"easymde",
"ENDDEFINS",
"jsonata",
"mopt",
"oneditcancel",
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ The backlog is organized by epic, with each task having a unique ID, description
- **SF-04**: Refactor Flow Slice to Manage Entity Collections
- **Objective**: Enhance data management by refactoring the flow slice to handle separate entity collections of flows/subflows, nodes, and directories.
- **Technical Requirements**: Redesign the state management to segregate and manage distinct collections for better modularity and maintainability.
- **SF-05**: Implement Input/Output Functionality for Subflows
- **Objective**: Enable users to define and manage inputs and outputs for subflows.
- **Technical Requirements**:
- Set the number of inputs and outputs in the workspace editing interface.
- Position and wire input/output nodes within the subflow.
- Correctly render subflow instances with the appropriate number of inputs and outputs.
- Correctly apply input and output labels to subflows and their instances.

#### Epic: Backend Integration and Data Management

Expand Down Expand Up @@ -210,8 +217,10 @@ The backlog is organized by epic, with each task having a unique ID, description

| To Do | In Progress | In Review | Done |
| ----- | ----------- | --------- | ----- |
| | SF-02 | | SF-04 |
| | SF-03 | | SF-01 |
| SF-05 | | | SF-04 |
| | | | SF-01 |
| | | | SF-02 |
| | | | SF-03 |

### Progress Tracking

Expand Down
12 changes: 10 additions & 2 deletions packages/flow-client/src/app/components/editor/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,15 @@ const StyledEditor = styled.div`
.editor-header {
display: flex;
justify-content: space-between;
align-items: center;
p {
font-size: 1.4em;
font-size: 1em;
font-weight: 500;
margin: 0.5rem 1rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.actions {
Expand Down Expand Up @@ -146,7 +150,11 @@ export const Editor = () => {
{
FLOW: 'Edit flow',
SUBFLOW: 'Edit subflow',
NODE: `Edit ${editing.data.entityType} node`,
NODE: editing.data.entityType?.startsWith(
'subflow:'
)
? `Edit subflow instance: ${editing.data.name}`
: `Edit ${editing.data.entityType} node`,
}[editing.type]
}
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
FlowNodeEntity,
selectFlowNodeById,
} from '../../redux/modules/flow/flow.slice';
import { selectPaletteNodeById } from '../../redux/modules/palette/node.slice';
import { Description } from './form/description';
import { EditorForm } from './form/editor-form';
import { Icon } from './form/icon';
Expand Down Expand Up @@ -191,7 +190,9 @@ export const NodeEditor = ({}: NodeEditorProps) => {
selectFlowNodeById(state, editing?.id ?? '')
) as FlowNodeEntity;
const editingNodeEntity = useAppSelector(state =>
selectPaletteNodeById(state, editingNode?.type)
editingNode
? flowLogic.node.selectPaletteNodeByFlowNode(state, editingNode)
: null
);
const { propertiesForm } =
useAppSelector(flowLogic.node.editor.selectEditorState) ?? {};
Expand Down Expand Up @@ -228,7 +229,7 @@ export const NodeEditor = ({}: NodeEditorProps) => {
loaded.current = true;
}, [dispatch, flowLogic.node.editor, propertiesForm]);

if (!editingNode) return null;
if (!editingNode || !editingNodeEntity) return null;

return (
<StyledNodeEditor strategy={STRATEGY.Z_INDEX}>
Expand Down
9 changes: 7 additions & 2 deletions packages/flow-client/src/app/red/execute-script.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import environment from '../../environment';
import { FlowNodeEntity } from '../redux/modules/flow/flow.slice';
import {
FlowNodeEntity,
SubflowEntity,
} from '../redux/modules/flow/flow.slice';
import { PaletteNodeEntity } from '../redux/modules/palette/node.slice';
import { JqueryContext } from './mock-jquery';
import { createMockRed } from './mock-red';
Expand Down Expand Up @@ -50,7 +53,9 @@ export const createNodeInstance = (nodeConfig: FlowNodeEntity) => {
}
);

return nodeInstance;
return nodeInstance as typeof nodeInstance & {
subflow?: SubflowEntity;
};
};

// Utility function to deserialize a function from its serialized string representation
Expand Down
144 changes: 144 additions & 0 deletions packages/flow-client/src/app/red/subflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import {
EnvVarType,
EnvironmentVariable,
SubflowEntity,
} from '../redux/modules/flow/flow.slice';

export const createSubflowEditorTemplate = (_subflow: SubflowEntity) => {
return `
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" style="width: calc(100% - 105px)" data-i18n="[placeholder]common.label.name">
</div>
<div id="subflow-input-ui"></div>
`;
};

type JQuery = {
(selector: string | JQuery, context?: unknown): JQuery;
append(...elements: JQuery[]): JQuery;
val(value: string): JQuery;
typedInput(method: string, ...args: unknown[]): string;
typedInput(options: Record<string, unknown>): JQuery;
text(value: string): JQuery;
text(): string;
addClass(value: string): JQuery;
appendTo(element: JQuery): JQuery;
children(selector: string): JQuery;
find(selector: string): JQuery;
each(callback: (index: number, element: JQuery) => void): JQuery;
};

type NodeInstance = {
env?: EnvironmentVariable[];
subflow: SubflowEntity;
};

const subflowDefinition = (
RED: {
nodes: { registerType: (type: string, node: unknown) => void };
},
$: JQuery
) => {
return () =>
RED.nodes.registerType('subflow:__DEFINS__subflow.id__ENDDEFINS__', {
type: 'subflow',
oneditprepare: function (this: NodeInstance) {
const $subflowInputUi = $('#subflow-input-ui');

const initialEnv = Object.fromEntries(
(this.env ?? []).map(env => [env.name, env])
);
const nodeEnv = (this.subflow?.env ?? []).map(
env => initialEnv[env.name] ?? env
);

for (const env of nodeEnv) {
const $formRow = $('<div>').addClass('form-row');
$formRow.append(
$('<label>').addClass('env-name').text(env.name)
);
const $valueInput = $('<input/>', {
type: 'text',
placeholder: 'Value',
class: 'node-input-prop-property-value',
})
.appendTo($formRow)
.typedInput({
types: [
'str',
'num',
'bool',
'json',
're',
'date',
'jsonata',
'bin',
'env',
'node',
'cred',
],
});
$valueInput.typedInput('value', env.value);
$valueInput.typedInput('type', env.type);
$subflowInputUi.append($formRow);
}
},
oneditsave: function (this: NodeInstance) {
const defaultEnv = Object.fromEntries(
(this.subflow?.env ?? []).map(env => [env.name, env])
);
const newEnv: EnvironmentVariable[] = [];
$('#subflow-input-ui')
.children('.form-row')
.each((_index, element) => {
const $formRow = $(element);
const $valueInput = $formRow.find('input');
const name = $formRow.find('.env-name').text();
const value = $valueInput.typedInput('value');
const type = $valueInput.typedInput(
'type'
) as EnvVarType;
if (
defaultEnv[name]?.value !== value ||
defaultEnv[name]?.type !== type
) {
newEnv.push({
name,
value,
type,
});
}
});

this.env = newEnv.length > 0 ? newEnv : undefined;
},
});
};

export const createSubflowDefinitionScript = (subflow: SubflowEntity) => {
const definitionString = subflowDefinition(
...([] as unknown as Parameters<typeof subflowDefinition>)
)
.toString()
.slice(5);

const replaceDefinsVariables = (
definition: string,
context: Record<string, unknown>
) => {
return definition.replace(
/__DEFINS__(.*?)__ENDDEFINS__/g,
(_, path) => {
const keys = path.split('.');
let value = context;
for (const k of keys) {
value = value[k] as Record<string, unknown>;
}
return `${value}`;
}
);
};

return replaceDefinsVariables(definitionString, { subflow });
};
8 changes: 4 additions & 4 deletions packages/flow-client/src/app/redux/modules/flow/flow.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ export interface FlowNodeEntity {
credentials?: Record<string, unknown>;
icon?: string;
info?: string;
inputLabels: string[];
outputLabels: string[];
inputLabels?: string[];
outputLabels?: string[];
[key: string]: unknown; // To allow for other properties dynamically
// React Diagrams
selected?: boolean;
Expand Down Expand Up @@ -124,8 +124,8 @@ export interface SubflowEntity {
icon?: string;
in?: FlowNodeEntity[];
out?: FlowNodeEntity[];
inputLabels: string[];
outputLabels: string[];
inputLabels?: string[];
outputLabels?: string[];
directory?: string;
}

Expand Down
Loading

0 comments on commit 8564e23

Please sign in to comment.