Finished HTTP node creation and extensive node creation documentation in project

This commit is contained in:
Richard Osborne
2025-12-29 08:56:46 +01:00
parent fad9f1006d
commit 6fd59e83e6
13 changed files with 1008 additions and 247 deletions

View File

@@ -116,10 +116,14 @@ export function ComponentItem({
// If this item is a valid drop target, execute the drop
if (isDropTarget && onDrop) {
e.stopPropagation(); // Prevent bubble to Tree (for root drop fallback)
// End drag IMMEDIATELY at event handler level (before action chain)
// This matches the working root drop pattern
PopupLayer.instance.dragCompleted();
const node: TreeNode = { type: 'component', data: component };
onDrop(node);
setIsDropTarget(false);
// Note: dragCompleted() is called by handleDropOn - don't call it here
}
},
[isDropTarget, component, onDrop]

View File

@@ -111,10 +111,14 @@ export function FolderItem({
// If this folder is a valid drop target, execute the drop
if (isDropTarget && onDrop) {
e.stopPropagation(); // Prevent bubble to Tree (for root drop fallback)
// End drag IMMEDIATELY at event handler level (before action chain)
// This matches the working root drop pattern
PopupLayer.instance.dragCompleted();
const node: TreeNode = { type: 'folder', data: folder };
onDrop(node);
setIsDropTarget(false);
// Note: dragCompleted() is called by handleDropOn - don't call it here
}
},
[isDropTarget, folder, onDrop]

View File

@@ -523,15 +523,12 @@ export function useComponentActions() {
// Check for naming conflicts
if (ProjectModel.instance?.getComponentWithName(newName)) {
alert(`Component "${newName}" already exists in that folder`);
PopupLayer.instance.dragCompleted();
return;
}
const oldName = component.name;
// End drag operation FIRST - before the rename triggers a re-render
// This prevents the drag state from persisting across the tree rebuild
PopupLayer.instance.dragCompleted();
// Note: dragCompleted() now called by ComponentItem.handleMouseUp before this action
UndoQueue.instance.pushAndDo(
new UndoActionGroup({
@@ -554,7 +551,6 @@ export function useComponentActions() {
// Prevent moving folder into itself
if (targetPath.startsWith(sourcePath + '/') || targetPath === sourcePath) {
alert('Cannot move folder into itself');
PopupLayer.instance.dragCompleted();
return;
}
@@ -565,7 +561,6 @@ export function useComponentActions() {
if (!componentsToMove || componentsToMove.length === 0) {
console.log('Folder is empty, nothing to move');
PopupLayer.instance.dragCompleted();
return;
}
@@ -584,8 +579,7 @@ export function useComponentActions() {
renames.push({ component: comp, oldName: comp.name, newName });
});
// End drag operation FIRST - before the rename triggers a re-render
PopupLayer.instance.dragCompleted();
// Note: dragCompleted() now called by FolderItem.handleMouseUp before this action
UndoQueue.instance.pushAndDo(
new UndoActionGroup({
@@ -612,14 +606,12 @@ export function useComponentActions() {
// Check for naming conflicts
if (ProjectModel.instance?.getComponentWithName(newName)) {
alert(`Component "${newName}" already exists`);
PopupLayer.instance.dragCompleted();
return;
}
const oldName = component.name;
// End drag operation FIRST - before the rename triggers a re-render
PopupLayer.instance.dragCompleted();
// Note: dragCompleted() now called by ComponentItem.handleMouseUp before this action
UndoQueue.instance.pushAndDo(
new UndoActionGroup({
@@ -646,7 +638,6 @@ export function useComponentActions() {
if (!componentsToMove || componentsToMove.length === 0) {
console.log('Folder is empty, nothing to move');
PopupLayer.instance.dragCompleted();
return;
}
@@ -670,12 +661,10 @@ export function useComponentActions() {
if (hasConflict) {
alert(`Some components would conflict with existing names`);
PopupLayer.instance.dragCompleted();
return;
}
// End drag operation FIRST - before the rename triggers a re-render
PopupLayer.instance.dragCompleted();
// Note: dragCompleted() now called by ComponentItem.handleMouseUp before this action
UndoQueue.instance.pushAndDo(
new UndoActionGroup({

View File

@@ -3,11 +3,26 @@ import StringListTemplate from '../../../../../templates/propertyeditor/stringli
import PopupLayer from '../../../../popuplayer';
import { ToastLayer } from '../../../../ToastLayer/ToastLayer';
// Helper to normalize list input - handles both string and array formats
function normalizeList(value) {
if (value === undefined || value === null || value === '') {
return [];
}
if (Array.isArray(value)) {
// Already an array (legacy proplist data) - extract labels if objects
return value.map((item) => (typeof item === 'object' && item.label ? item.label : String(item)));
}
if (typeof value === 'string') {
return value.split(',').filter(Boolean);
}
return [];
}
var StringList = function (args) {
View.call(this);
this.parent = args.parent;
this.list = args.list !== undefined && args.list !== '' ? args.list.split(',') : [];
this.list = normalizeList(args.list);
this.type = args.type;
this.plug = args.plug;
this.title = args.title;
@@ -32,7 +47,7 @@ StringList.prototype.listUpdated = function () {
if (this.onUpdate) {
var updated = this.onUpdate(this.list.join(','));
if (updated) {
this.list = updated.value ? updated.value.split(',') : [];
this.list = normalizeList(updated.value);
this.isDefault = updated.isDefault;
}
}

View File

@@ -23,7 +23,7 @@ function registerNodes(noodlRuntime) {
// Data
require('./src/nodes/std-library/data/restnode'),
require('./src/nodes/std-library/data/httpnode'),
// require('./src/nodes/std-library/data/httpnode'), // moved to viewer for debugging
// Custom code
require('./src/nodes/std-library/expression'),

View File

@@ -1,19 +1,22 @@
"use strict";
'use strict';
function createSetter(args) {
console.log('[EdgeTriggeredInput] 🔧 createSetter called for signal input');
var currentValue = false;
var currentValue = false;
return function(value) {
value = value ? true : false;
//value changed from false to true
if(value && currentValue === false) {
args.valueChangedToTrue.call(this);
}
currentValue = value;
};
return function (value) {
console.log('[EdgeTriggeredInput] ⚡ setter called with value:', value, 'currentValue:', currentValue);
value = value ? true : false;
//value changed from false to true
if (value && currentValue === false) {
console.log('[EdgeTriggeredInput] ✅ Triggering valueChangedToTrue!');
args.valueChangedToTrue.call(this);
}
currentValue = value;
};
}
module.exports = {
createSetter: createSetter
};
createSetter: createSetter
};

View File

@@ -84,12 +84,22 @@ Node.prototype.registerInputIfNeeded = function () {
};
Node.prototype.setInputValue = function (name, value) {
// DEBUG: Track input value setting for HTTP node
if (this.name === 'net.noodl.HTTP') {
console.log('[Node.setInputValue] 🎯 HTTP node setting input:', name, 'value:', value);
}
const input = this.getInput(name);
if (!input) {
console.log("node doesn't have input", name);
console.log("node doesn't have input", name, 'for node:', this.name);
return;
}
// DEBUG: Track if setter exists
if (this.name === 'net.noodl.HTTP' && name === 'fetch') {
console.log('[Node.setInputValue] 🎯 HTTP fetch input found, calling setter');
}
//inputs with units always expect objects in the shape of {value, unit, ...}
//these inputs might sometimes get raw numbers without units, and in those cases
//Noodl should just update the value and not the other parameters

View File

@@ -200,6 +200,7 @@ function defineNode(opts) {
Object.keys(opts.inputs).forEach(function (name) {
var input = opts.inputs[name];
if (input.valueChangedToTrue) {
console.log('[NodeDefinition] 📌 Registering signal input:', name, 'for node:', opts.name);
node._inputs[name] = {
set: EdgeTriggeredInput.createSetter({
valueChangedToTrue: input.valueChangedToTrue

View File

@@ -19,6 +19,9 @@
// Note: This file uses CommonJS module format to match the noodl-runtime pattern
// DEBUG: Confirm module is loaded
console.log('[HTTP Node Module] 📦 httpnode.js MODULE LOADED');
/**
* Extract value from object using JSONPath-like syntax
* Supports: $.data.users, $.items[0].name, $.meta.pagination.total
@@ -88,12 +91,13 @@ var HttpNode = {
searchTags: ['http', 'request', 'fetch', 'api', 'rest', 'curl'],
initialize: function () {
console.log('[HTTP Node] 🚀 INITIALIZE called - node instance created');
this._internal.inputValues = {};
this._internal.outputValues = {};
this._internal.headers = [];
this._internal.queryParams = [];
this._internal.bodyFields = [];
this._internal.responseMapping = [];
this._internal.headers = '';
this._internal.queryParams = '';
this._internal.bodyFields = '';
this._internal.responseMapping = '';
this._internal.inspectData = null;
},
@@ -105,6 +109,7 @@ var HttpNode = {
},
inputs: {
// Static inputs - these don't need to trigger port regeneration
url: {
type: 'string',
displayName: 'URL',
@@ -114,40 +119,12 @@ var HttpNode = {
this._internal.url = value;
}
},
method: {
type: {
name: 'enum',
enums: [
{ label: 'GET', value: 'GET' },
{ label: 'POST', value: 'POST' },
{ label: 'PUT', value: 'PUT' },
{ label: 'PATCH', value: 'PATCH' },
{ label: 'DELETE', value: 'DELETE' },
{ label: 'HEAD', value: 'HEAD' },
{ label: 'OPTIONS', value: 'OPTIONS' }
]
},
displayName: 'Method',
group: 'Request',
default: 'GET',
set: function (value) {
this._internal.method = value;
}
},
timeout: {
type: 'number',
displayName: 'Timeout (ms)',
group: 'Request',
default: 30000,
set: function (value) {
this._internal.timeout = value;
}
},
fetch: {
type: 'signal',
displayName: 'Fetch',
group: 'Actions',
valueChangedToTrue: function () {
console.log('[HTTP Node] ⚡ FETCH SIGNAL RECEIVED - valueChangedToTrue triggered!');
this.scheduleFetch();
}
},
@@ -159,6 +136,7 @@ var HttpNode = {
this.cancelFetch();
}
}
// Note: method, timeout, and config ports are now dynamic (in updatePorts)
},
outputs: {
@@ -212,7 +190,9 @@ var HttpNode = {
},
prototypeExtensions: {
setInputValue: function (name, value) {
// Store values for dynamic inputs only - static inputs (including signals)
// use the base Node.prototype.setInputValue which calls input.set()
_storeInputValue: function (name, value) {
this._internal.inputValues[name] = value;
},
@@ -233,25 +213,45 @@ var HttpNode = {
registerInputIfNeeded: function (name) {
if (this.hasInput(name)) return;
// Dynamic inputs for path params, headers, query params, body fields, auth
const inputSetters = {
path: this.setInputValue.bind(this),
header: this.setInputValue.bind(this),
query: this.setInputValue.bind(this),
body: this.setInputValue.bind(this),
auth: this.setInputValue.bind(this)
// Configuration inputs - these set internal state
const configSetters = {
method: this.setMethod.bind(this),
timeout: this.setTimeout.bind(this),
headers: this.setHeaders.bind(this),
queryParams: this.setQueryParams.bind(this),
bodyType: this.setBodyType.bind(this),
bodyFields: this.setBodyFields.bind(this),
authType: this.setAuthType.bind(this),
responseMapping: this.setResponseMapping.bind(this)
};
for (const [prefix, setter] of Object.entries(inputSetters)) {
if (name.startsWith(prefix + '-')) {
return this.registerInput(name, { set: setter.bind(this, name) });
if (configSetters[name]) {
return this.registerInput(name, {
set: configSetters[name]
});
}
// Dynamic inputs for path params, headers, query params, body fields, auth, mapping paths
// These use _storeInputValue to just store values (no signal trigger needed)
const dynamicPrefixes = ['path-', 'header-', 'query-', 'body-', 'auth-', 'mapping-path-'];
for (const prefix of dynamicPrefixes) {
if (name.startsWith(prefix)) {
return this.registerInput(name, {
set: this._storeInputValue.bind(this, name)
});
}
}
},
scheduleFetch: function () {
if (this._internal.hasScheduledFetch) return;
console.log('[HTTP Node] scheduleFetch called');
if (this._internal.hasScheduledFetch) {
console.log('[HTTP Node] Already scheduled, skipping');
return;
}
this._internal.hasScheduledFetch = true;
console.log('[HTTP Node] Scheduling doFetch after inputs update');
this.scheduleAfterInputsHaveUpdated(this.doFetch.bind(this));
},
@@ -278,12 +278,16 @@ var HttpNode = {
// Add query parameters
const queryParams = {};
// From visual config
// From visual config (stringlist format)
if (this._internal.queryParams) {
for (const qp of this._internal.queryParams) {
const value = this._internal.inputValues['query-' + qp.key];
const queryList = this._internal.queryParams
.split(',')
.map((q) => q.trim())
.filter(Boolean);
for (const qp of queryList) {
const value = this._internal.inputValues['query-' + qp];
if (value !== undefined && value !== null && value !== '') {
queryParams[qp.key] = value;
queryParams[qp] = value;
}
}
}
@@ -312,12 +316,16 @@ var HttpNode = {
buildHeaders: function () {
const headers = {};
// From visual config
// From visual config (stringlist format)
if (this._internal.headers) {
for (const h of this._internal.headers) {
const value = this._internal.inputValues['header-' + h.key];
const headerList = this._internal.headers
.split(',')
.map((h) => h.trim())
.filter(Boolean);
for (const h of headerList) {
const value = this._internal.inputValues['header-' + h];
if (value !== undefined && value !== null) {
headers[h.key] = String(value);
headers[h] = String(value);
}
}
}
@@ -341,32 +349,38 @@ var HttpNode = {
}
const bodyType = this._internal.bodyType || 'json';
const bodyFields = this._internal.bodyFields || [];
const bodyFieldsStr = this._internal.bodyFields || '';
// Parse stringlist format
const bodyFields = bodyFieldsStr
.split(',')
.map((f) => f.trim())
.filter(Boolean);
if (bodyType === 'json') {
const body = {};
for (const field of bodyFields) {
const value = this._internal.inputValues['body-' + field.key];
const value = this._internal.inputValues['body-' + field];
if (value !== undefined) {
body[field.key] = value;
body[field] = value;
}
}
return Object.keys(body).length > 0 ? JSON.stringify(body) : undefined;
} else if (bodyType === 'form') {
const formData = new FormData();
for (const field of bodyFields) {
const value = this._internal.inputValues['body-' + field.key];
const value = this._internal.inputValues['body-' + field];
if (value !== undefined && value !== null) {
formData.append(field.key, value);
formData.append(field, value);
}
}
return formData;
} else if (bodyType === 'urlencoded') {
const params = new URLSearchParams();
for (const field of bodyFields) {
const value = this._internal.inputValues['body-' + field.key];
const value = this._internal.inputValues['body-' + field];
if (value !== undefined && value !== null) {
params.append(field.key, String(value));
params.append(field, String(value));
}
}
return params.toString();
@@ -390,10 +404,20 @@ var HttpNode = {
this._internal.responseHeaders = responseHeaders;
// Process response mappings
const mappings = this._internal.responseMapping || [];
for (const mapping of mappings) {
const outputName = 'out-' + mapping.name;
const value = extractByPath(responseBody, mapping.path);
// Output names are in responseMapping (comma-separated)
// Path for each output is in inputValues['mapping-path-{name}']
const mappingStr = this._internal.responseMapping || '';
const outputNames = mappingStr
.split(',')
.map((m) => m.trim())
.filter(Boolean);
for (const name of outputNames) {
// Get the path from the corresponding input port
const path = this._internal.inputValues['mapping-path-' + name] || '$';
const outputName = 'out-' + name;
const value = extractByPath(responseBody, path);
this.registerOutputIfNeeded(outputName);
this._internal.outputValues[outputName] = value;
@@ -415,6 +439,7 @@ var HttpNode = {
},
doFetch: function () {
console.log('[HTTP Node] doFetch executing');
this._internal.hasScheduledFetch = false;
const url = this.buildUrl();
@@ -423,11 +448,20 @@ var HttpNode = {
const body = this.buildBody();
const timeout = this._internal.timeout || 30000;
console.log('[HTTP Node] Request details:', {
url,
method,
headers,
body: body ? '(has body)' : '(no body)',
timeout
});
// Store for inspect
this._internal.lastRequestUrl = url;
// Validate URL
if (!url) {
console.log('[HTTP Node] No URL provided, sending failure');
this._internal.error = 'URL is required';
this.flagOutputDirty('error');
this.sendSignalOnOutput('failure');
@@ -503,11 +537,11 @@ var HttpNode = {
// Configuration setters called from setup function
setHeaders: function (value) {
this._internal.headers = value || [];
this._internal.headers = value || '';
},
setQueryParams: function (value) {
this._internal.queryParams = value || [];
this._internal.queryParams = value || '';
},
setBodyType: function (value) {
@@ -515,15 +549,23 @@ var HttpNode = {
},
setBodyFields: function (value) {
this._internal.bodyFields = value || [];
this._internal.bodyFields = value || '';
},
setResponseMapping: function (value) {
this._internal.responseMapping = value || [];
this._internal.responseMapping = value || '';
},
setAuthType: function (value) {
this._internal.authType = value;
},
setMethod: function (value) {
this._internal.method = value || 'GET';
},
setTimeout: function (value) {
this._internal.timeout = value || 30000;
}
}
};
@@ -534,36 +576,6 @@ var HttpNode = {
function updatePorts(nodeId, parameters, editorConnection) {
const ports = [];
// URL input (already defined in static inputs, but we want it first)
ports.push({
name: 'url',
displayName: 'URL',
type: 'string',
plug: 'input',
group: 'Request'
});
// Method input
ports.push({
name: 'method',
displayName: 'Method',
type: {
name: 'enum',
enums: [
{ label: 'GET', value: 'GET' },
{ label: 'POST', value: 'POST' },
{ label: 'PUT', value: 'PUT' },
{ label: 'PATCH', value: 'PATCH' },
{ label: 'DELETE', value: 'DELETE' },
{ label: 'HEAD', value: 'HEAD' },
{ label: 'OPTIONS', value: 'OPTIONS' }
]
},
default: 'GET',
plug: 'input',
group: 'Request'
});
// Parse URL for path parameters: /users/{userId} → userId port
if (parameters.url) {
const pathParams = parameters.url.match(/\{([A-Za-z0-9_]+)\}/g) || [];
@@ -580,7 +592,7 @@ function updatePorts(nodeId, parameters, editorConnection) {
}
}
// Headers configuration
// Headers configuration - comma-separated list
ports.push({
name: 'headers',
displayName: 'Headers',
@@ -590,21 +602,23 @@ function updatePorts(nodeId, parameters, editorConnection) {
});
// Generate input ports for each header
if (parameters.headers && Array.isArray(parameters.headers)) {
for (const header of parameters.headers) {
if (header.key) {
ports.push({
name: 'header-' + header.key,
displayName: header.key,
type: 'string',
plug: 'input',
group: 'Headers'
});
}
if (parameters.headers) {
const headerList = parameters.headers
.split(',')
.map((h) => h.trim())
.filter(Boolean);
for (const header of headerList) {
ports.push({
name: 'header-' + header,
displayName: header,
type: 'string',
plug: 'input',
group: 'Headers'
});
}
}
// Query parameters configuration
// Query parameters configuration - comma-separated list
ports.push({
name: 'queryParams',
displayName: 'Query Parameters',
@@ -614,20 +628,44 @@ function updatePorts(nodeId, parameters, editorConnection) {
});
// Generate input ports for each query param
if (parameters.queryParams && Array.isArray(parameters.queryParams)) {
for (const param of parameters.queryParams) {
if (param.key) {
ports.push({
name: 'query-' + param.key,
displayName: param.key,
type: '*',
plug: 'input',
group: 'Query Parameters'
});
}
if (parameters.queryParams) {
const queryList = parameters.queryParams
.split(',')
.map((q) => q.trim())
.filter(Boolean);
for (const param of queryList) {
ports.push({
name: 'query-' + param,
displayName: param,
type: 'string',
plug: 'input',
group: 'Query Parameters'
});
}
}
// Method selector as dynamic port (so we can track parameter changes properly)
ports.push({
name: 'method',
displayName: 'Method',
type: {
name: 'enum',
enums: [
{ label: 'GET', value: 'GET' },
{ label: 'POST', value: 'POST' },
{ label: 'PUT', value: 'PUT' },
{ label: 'PATCH', value: 'PATCH' },
{ label: 'DELETE', value: 'DELETE' },
{ label: 'HEAD', value: 'HEAD' },
{ label: 'OPTIONS', value: 'OPTIONS' }
],
allowEditOnly: true
},
default: 'GET',
plug: 'input',
group: 'Request'
});
// Body type selector (only shown for POST/PUT/PATCH)
const method = parameters.method || 'GET';
if (['POST', 'PUT', 'PATCH'].includes(method)) {
@@ -649,7 +687,7 @@ function updatePorts(nodeId, parameters, editorConnection) {
group: 'Body'
});
// Body fields configuration
// Body fields configuration - comma-separated list
const bodyType = parameters.bodyType || 'json';
if (bodyType === 'json' || bodyType === 'form' || bodyType === 'urlencoded') {
ports.push({
@@ -660,25 +698,56 @@ function updatePorts(nodeId, parameters, editorConnection) {
group: 'Body'
});
// Generate input ports for each body field
if (parameters.bodyFields && Array.isArray(parameters.bodyFields)) {
for (const field of parameters.bodyFields) {
if (field.key) {
ports.push({
name: 'body-' + field.key,
displayName: field.key,
type: '*',
plug: 'input',
group: 'Body'
});
}
// Type options for body fields
const _types = [
{ label: 'String', value: 'string' },
{ label: 'Number', value: 'number' },
{ label: 'Boolean', value: 'boolean' },
{ label: 'Array', value: 'array' },
{ label: 'Object', value: 'object' },
{ label: 'Any', value: '*' }
];
// Generate type selector and value input ports for each body field
if (parameters.bodyFields) {
const fieldList = parameters.bodyFields
.split(',')
.map((f) => f.trim())
.filter(Boolean);
for (const field of fieldList) {
// Get the selected type for this field (default to string)
const fieldType = parameters['body-type-' + field] || 'string';
// Type selector for this field
ports.push({
name: 'body-type-' + field,
displayName: field + ' Type',
type: {
name: 'enum',
enums: _types,
allowEditOnly: true
},
default: 'string',
plug: 'input',
group: 'Body'
});
// Value input for this field (type matches selected type)
ports.push({
name: 'body-' + field,
displayName: field,
type: fieldType,
plug: 'input',
group: 'Body'
});
}
}
} else if (bodyType === 'raw') {
// Raw body - use code editor with JSON syntax
ports.push({
name: 'body-raw',
displayName: 'Body',
type: 'string',
type: { name: 'string', allowEditOnly: true, codeeditor: 'json' },
plug: 'input',
group: 'Body'
});
@@ -760,41 +829,28 @@ function updatePorts(nodeId, parameters, editorConnection) {
});
}
// Response mapping
ports.push({
name: 'responseMapping',
displayName: 'Response Mapping',
type: { name: 'stringlist', allowEditOnly: true },
plug: 'input',
group: 'Response Mapping'
});
// Generate output ports for each response mapping
if (parameters.responseMapping && Array.isArray(parameters.responseMapping)) {
for (const mapping of parameters.responseMapping) {
if (mapping.name) {
ports.push({
name: 'out-' + mapping.name,
displayName: mapping.name,
type: '*',
plug: 'output',
group: 'Response'
});
}
}
}
// Timeout
// Timeout setting
ports.push({
name: 'timeout',
displayName: 'Timeout (ms)',
type: 'number',
default: 30000,
plug: 'input',
group: 'Settings'
group: 'Request'
});
// Actions
// Response mapping - add output names, then specify JSONPath for each
// User adds output names like: userId, userName, totalCount
// For each name, we generate a "Path" input and an output port
ports.push({
name: 'responseMapping',
displayName: 'Output Fields',
type: { name: 'stringlist', allowEditOnly: true },
plug: 'input',
group: 'Response Mapping'
});
// Signal inputs - MUST be in dynamic ports for editor to show them
ports.push({
name: 'fetch',
displayName: 'Fetch',
@@ -811,6 +867,44 @@ function updatePorts(nodeId, parameters, editorConnection) {
group: 'Actions'
});
// URL input - also needs to be in dynamic ports
ports.push({
name: 'url',
displayName: 'URL',
type: 'string',
plug: 'input',
group: 'Request'
});
// Generate path input ports and output ports for each response mapping
if (parameters.responseMapping && typeof parameters.responseMapping === 'string') {
const outputNames = parameters.responseMapping
.split(',')
.map((m) => m.trim())
.filter(Boolean);
for (const name of outputNames) {
// Path input port for specifying JSONPath (e.g., $.data.id)
ports.push({
name: 'mapping-path-' + name,
displayName: name + ' Path',
type: 'string',
default: '$',
plug: 'input',
group: 'Response Mapping'
});
// Output port for the extracted value
ports.push({
name: 'out-' + name,
displayName: name,
type: '*',
plug: 'output',
group: 'Response'
});
}
}
// Standard outputs
ports.push({
name: 'response',
@@ -891,7 +985,8 @@ module.exports = {
event.name === 'bodyType' ||
event.name === 'bodyFields' ||
event.name === 'authType' ||
event.name === 'responseMapping'
event.name === 'responseMapping' ||
event.name.startsWith('body-type-') // Body field type changes
) {
updatePorts(node.id, node.parameters, context.editorConnection);
}

View File

@@ -60,6 +60,9 @@ export default function registerNodes(noodlRuntime) {
require('./nodes/std-library/colorblend'),
require('./nodes/std-library/animate-to-value'),
// HTTP node - temporarily here for debugging (normally in noodl-runtime)
require('@noodl/runtime/src/nodes/std-library/data/httpnode'),
//require('./nodes/std-library/variables/number'), // moved to runtime
//require('./nodes/std-library/variables/string'),
//require('./nodes/std-library/variables/boolean'),