diff --git a/.gitignore b/.gitignore index 80b3fd8..9aaeea0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ /.pnp .pnp.js +/Seer-Core +/Seer-Core/aql-processor/run-aql-result/.gitignore +/Seer-Core/aql-processor/user-data-in/.gitignore # testing /coverage diff --git a/Seer-Core/aql-processor/run-aql-result/.gitignore b/Seer-Core/aql-processor/run-aql-result/.gitignore deleted file mode 100644 index 86d0cb2..0000000 --- a/Seer-Core/aql-processor/run-aql-result/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore everything in this directory -* -# Except this file -!.gitignore \ No newline at end of file diff --git a/Seer-Core/aql-processor/user-data-in/.gitignore b/Seer-Core/aql-processor/user-data-in/.gitignore deleted file mode 100644 index 5e7d273..0000000 --- a/Seer-Core/aql-processor/user-data-in/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore everything in this directory -* -# Except this file -!.gitignore diff --git a/sample-flows/tutorial-flow.json b/sample-flows/tutorial-flow.json index fbb7c01..dac728d 100644 --- a/sample-flows/tutorial-flow.json +++ b/sample-flows/tutorial-flow.json @@ -444,6 +444,41 @@ "schemas": [] }, "nodes": [ + { + "label": "RevenueByDivision", + "nodeId": "295c7f55-9008-46d2-9f00-e5fcfa8cdbf6", + "type": "sequence", + "description": "Connect your extractors for execution", + "isValid": true, + "renamed": "RevenueByDivision", + "pattern": "(){0,35}()", + "upstreamNodes": [ + { + "label": "RevenueOfDivision", + "nodeId": "3f122664-92b1-4d36-ac49-4341d3281294", + "type": "union", + "visible": false, + "renamed": "RevenueOfDivision" + }, + { + "label": "Amount", + "nodeId": "7ac4050a-15a3-4c8b-8eb3-ece334de9bd2", + "type": "regex", + "visible": false, + "renamed": "Amount" + } + ], + "tokens": [{ "min": "0", "max": "35" }] + }, + { + "label": "RevenueConsolidated", + "nodeId": "9de33a19-783f-4a93-83dd-3467710dd430", + "type": "consolidate", + "description": "Remove overlapping annotations", + "isValid": true, + "consolidateTarget": "RevenueByDivision", + "consolidatePolicy": "NotContainedWithin" + }, { "label": "Division", "nodeId": "bba756a6-5a45-426c-a8d9-4a1cd6a19881", @@ -459,31 +494,83 @@ "caseSensitivity": "match", "lemmaMatch": false, "externalResourceChecked": false, - "mapTerms": false + "mapTerms": false, + "attributes": [ + { + "nodeId": "bba756a6-5a45-426c-a8d9-4a1cd6a19881", + "label": "Division", + "value": "Division", + "visible": true, + "disabled": true + } + ] }, { - "label": "Preposition", - "nodeId": "df2832a2-957f-43aa-88e4-a09fdf4c3b46", + "label": "Metric", + "nodeId": "8494ae16-4f5e-4e48-87d0-988b98c2cefd", "type": "dictionary", "description": "Phrases to be matched.", "isValid": true, - "items": ["from", "for"], + "items": ["revenue"], "caseSensitivity": "ignore", - "lemmaMatch": false, + "lemmaMatch": true, "externalResourceChecked": false, - "mapTerms": false + "mapTerms": false, + "attributes": [ + { + "nodeId": "8494ae16-4f5e-4e48-87d0-988b98c2cefd", + "label": "Metric", + "value": "Metric", + "visible": true, + "disabled": true + } + ] }, { - "label": "Metric", - "nodeId": "8494ae16-4f5e-4e48-87d0-988b98c2cefd", + "label": "Preposition", + "nodeId": "df2832a2-957f-43aa-88e4-a09fdf4c3b46", "type": "dictionary", "description": "Phrases to be matched.", "isValid": true, - "items": ["revenue"], + "items": ["from", "for"], "caseSensitivity": "ignore", - "lemmaMatch": true, + "lemmaMatch": false, "externalResourceChecked": false, - "mapTerms": false + "mapTerms": false, + "attributes": [ + { + "nodeId": "df2832a2-957f-43aa-88e4-a09fdf4c3b46", + "label": "Preposition", + "value": "Preposition", + "visible": true, + "disabled": true + } + ] + }, + { + "label": "Amount", + "nodeId": "7ac4050a-15a3-4c8b-8eb3-ece334de9bd2", + "type": "regex", + "description": "Build a regular expression extractor.", + "isValid": true, + "regexInput": "\\$\\d+(\\.\\d+)?\\s+billion", + "expressionType": "regular", + "caseSensitivity": "match", + "tokenRange": { "checked": false, "range": [0, 0] }, + "canonEq": false, + "dotAll": true, + "multiline": false, + "unixLines": false, + "attributes": [ + { + "nodeId": "7ac4050a-15a3-4c8b-8eb3-ece334de9bd2", + "label": "Amount", + "visible": true, + "disabled": true + } + ], + "editLabel": "", + "editId": null }, { "label": "RevenueOfDivision1", @@ -509,7 +596,30 @@ "renamed": "Metric" } ], - "tokens": [{ "min": "1", "max": "2" }] + "tokens": [{ "min": "1", "max": "2" }], + "attributes": [ + { + "nodeId": "863f5c4c-6c1e-427a-9482-5592732aa81e", + "label": "RevenueOfDivision1", + "visible": true, + "value": "RevenueOfDivision", + "disabled": true + }, + { + "label": "Division", + "nodeId": "bba756a6-5a45-426c-a8d9-4a1cd6a19881", + "value": "Division", + "disabled": false, + "visible": true + }, + { + "label": "Metric", + "nodeId": "8494ae16-4f5e-4e48-87d0-988b98c2cefd", + "value": "Metric", + "disabled": false, + "visible": true + } + ] }, { "label": "RevenueOfDivision2", @@ -545,23 +655,38 @@ "tokens": [ { "min": "0", "max": "1" }, { "min": "0", "max": "2" } + ], + "attributes": [ + { + "nodeId": "27fc9430-cb52-4dff-965b-f5f2261c43eb", + "label": "RevenueOfDivision2", + "visible": true, + "value": "RevenueOfDivision", + "disabled": true + }, + { + "label": "Metric", + "nodeId": "8494ae16-4f5e-4e48-87d0-988b98c2cefd", + "value": "Metric", + "disabled": false, + "visible": true + }, + { + "label": "Preposition", + "nodeId": "df2832a2-957f-43aa-88e4-a09fdf4c3b46", + "value": "Preposition", + "disabled": false, + "visible": false + }, + { + "label": "Division", + "nodeId": "bba756a6-5a45-426c-a8d9-4a1cd6a19881", + "value": "Division", + "disabled": false, + "visible": true + } ] }, - { - "label": "Amount", - "nodeId": "7ac4050a-15a3-4c8b-8eb3-ece334de9bd2", - "type": "regex", - "description": "Build a regular expression extractor.", - "isValid": true, - "regexInput": "\\$\\d+(\\.\\d+)?\\s+billion", - "expressionType": "regular", - "caseSensitivity": "match", - "tokenRange": { "checked": false, "range": [0, 0] }, - "canonEq": false, - "dotAll": true, - "multiline": false, - "unixLines": false - }, { "label": "RevenueOfDivision", "nodeId": "3f122664-92b1-4d36-ac49-4341d3281294", @@ -577,42 +702,31 @@ "label": "RevenueOfDivision2", "nodeId": "27fc9430-cb52-4dff-965b-f5f2261c43eb" } - ] - }, - { - "label": "RevenueByDivision", - "nodeId": "295c7f55-9008-46d2-9f00-e5fcfa8cdbf6", - "type": "sequence", - "description": "Connect your extractors for execution", - "isValid": true, - "renamed": "RevenueByDivision", - "pattern": "(){0,35}()", - "upstreamNodes": [ + ], + "attributes": [ { - "label": "RevenueOfDivision", - "nodeId": "3f122664-92b1-4d36-ac49-4341d3281294", - "type": "union", - "visible": false, - "renamed": "RevenueOfDivision" + "nodeId": "863f5c4c-6c1e-427a-9482-5592732aa81e", + "label": "RevenueOfDivision1", + "visible": true, + "value": "RevenueOfDivision", + "disabled": true }, { - "label": "Amount", - "nodeId": "7ac4050a-15a3-4c8b-8eb3-ece334de9bd2", - "type": "regex", - "visible": false, - "renamed": "Amount" + "label": "Division", + "nodeId": "bba756a6-5a45-426c-a8d9-4a1cd6a19881", + "value": "Division", + "disabled": false, + "visible": true + }, + { + "label": "Metric", + "nodeId": "8494ae16-4f5e-4e48-87d0-988b98c2cefd", + "value": "Metric", + "disabled": false, + "visible": true } ], - "tokens": [{ "min": "0", "max": "35" }] - }, - { - "label": "RevenueConsolidated", - "nodeId": "9de33a19-783f-4a93-83dd-3467710dd430", - "type": "consolidate", - "description": "Remove overlapping annotations", - "isValid": true, - "consolidateTarget": "RevenueByDivision", - "consolidatePolicy": "NotContainedWithin" + "mismatchedAttributes": false }, { "label": "Input Documents_1", diff --git a/server.js b/server.js index 4ca5738..3fe6e8c 100644 --- a/server.js +++ b/server.js @@ -87,10 +87,9 @@ const createZipArchive = async (tmpFolder, fileName) => { const moveZipFile = (tmpFolder, fileName) => { const currentPath = `${tmpFolder}/${fileName}`; const destinationPath = `${systemTdataFolder}/user-data-in/${fileName}`; - fs.rename(currentPath, destinationPath, function (err) { + fs.copyFile(currentPath, destinationPath, function (err) { if (err) { console.log(`error moving zipfile ${fileName}`); - throw err; } else { console.log(`moved zipfile ${fileName} to be processed.`); } @@ -201,7 +200,7 @@ app.post( //read document to render in UI const docPath = `${workingFolder}/payload.txt`; const document = fs.readFileSync(docPath, 'utf8'); - fs.rmSync(workingFolder, { recursive: true, force: true }); + // fs.rmSync(workingFolder, { recursive: true, force: true }); res.status(200).send({ message: 'Execution submitted successfully.', @@ -227,6 +226,9 @@ const formatResults = ({ annotations, instrumentationInfo }) => { annotation.forEach((elem) => { const attributes = {}; Object.keys(elem).forEach((key) => { + if (elem[key] === null) { + return; + } const { location, text } = elem[key]; attributes[key] = { start: location?.begin, @@ -305,7 +307,7 @@ app.get( if (errorMessage) { console.log('found error message', errorMessage); deleteFile(file, resultFileName); - return res.status(200).send({ status: 'error', message: errorMessage }); + return res.status(409).send({ status: 'error', message: errorMessage }); } const results = diff --git a/src/config/nlpPalette.json b/src/config/nlpPalette.json index dbf224d..322d671 100644 --- a/src/config/nlpPalette.json +++ b/src/config/nlpPalette.json @@ -60,7 +60,7 @@ "ui_data": { "cardinality": { "min": 1, - "max": 1 + "max": -1 }, "label": "Output Port" } @@ -119,48 +119,48 @@ } } }, - { - "id": "literal-node", - "op": "literal", - "parameters": { "type": "literal" }, - "inputs": [ - { - "id": "inPort", - "app_data": { - "ui_data": { - "cardinality": { - "min": 1, - "max": 1 - }, - "label": "Input Port" - } - } - } - ], - "outputs": [ - { - "id": "outPort", - "app_data": { - "ui_data": { - "cardinality": { - "min": 1, - "max": -1 - }, - "label": "Output Port" - } - } - } - ], - "app_data": { - "ui_data": { - "label": "Literal", - "description": "Literal string to be matched.", - "image": "/images/palette/nodes/row.svg", - "x_pos": 0, - "y_pos": 0 - } - } - } + { + "id": "literal-node", + "op": "literal", + "parameters": { "type": "literal" }, + "inputs": [ + { + "id": "inPort", + "app_data": { + "ui_data": { + "cardinality": { + "min": 1, + "max": 1 + }, + "label": "Input Port" + } + } + } + ], + "outputs": [ + { + "id": "outPort", + "app_data": { + "ui_data": { + "cardinality": { + "min": 1, + "max": -1 + }, + "label": "Output Port" + } + } + } + ], + "app_data": { + "ui_data": { + "label": "Literal", + "description": "Literal string to be matched.", + "image": "/images/palette/nodes/row.svg", + "x_pos": 0, + "y_pos": 0 + } + } + } ] }, { diff --git a/src/nlp-visual-editor/components/attributes-list.jsx b/src/nlp-visual-editor/components/attributes-list.jsx new file mode 100644 index 0000000..fb35b5a --- /dev/null +++ b/src/nlp-visual-editor/components/attributes-list.jsx @@ -0,0 +1,144 @@ +/* + +Copyright 2022 Elyra Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { Button, TextInput, Checkbox } from 'carbon-components-react'; +import { Edit16 } from '@carbon/icons-react'; + +export class AttributesList extends React.Component { + constructor(props) { + super(props); + this.state = { + editIndex: null, + }; + } + + onSaveAttributeLabel(newLabel, index) { + let hasError = false; + if ( + this.props.attributes.find((attribute) => attribute.value === newLabel) + ) { + hasError = true; + this.setState({ + errorMessage: 'Two attributes cannot have the same value.', + }); + } else { + this.setState({ errorMessage: undefined }); + } + const newAttributes = this.props.attributes.map((v, i) => { + if (index === i) { + return { + ...v, + value: newLabel, + }; + } else { + return v; + } + }); + this.props.onChange(newAttributes, hasError); + } + + onSaveAttributeVisible(visible, index) { + const newAttributes = this.props.attributes.map((v, i) => { + if (index === i) { + return { + ...v, + visible, + }; + } else { + return v; + } + }); + this.props.onChange(newAttributes); + } + + render() { + const { attributes } = this.props; + const { errorMessage } = this.state; + return ( +
+

Attributes

+ {!attributes || attributes.length === 0 ? ( + No attributes available. + ) : ( + attributes.map((attribute, index) => { + const { value, visible, label, disabled } = attribute; + // This returns the editable input + if (index === this.state.editIndex) { + return ( + { + const keyPressed = e.key || e.keyCode; + if (this.state.editLabel === '') { + return; + } + if ( + !errorMessage && + (keyPressed === 'Enter' || + keyPressed === 13 || + keyPressed === 'Escape' || + keyPressed === 27) + ) { + this.setState({ editIndex: null }); + } + }} + onChange={(e) => { + this.onSaveAttributeLabel(e.target.value, index); + }} + defaultValue={value ?? label} + /> + ); + } else { + return ( +
+ + this.onSaveAttributeVisible(value, index) + } + disabled={disabled} + defaultChecked={visible} + /> + {value ?? label} +
+ ); + } + }) + )} +
+ ); + } +} diff --git a/src/nlp-visual-editor/components/index.js b/src/nlp-visual-editor/components/index.js new file mode 100644 index 0000000..f73894c --- /dev/null +++ b/src/nlp-visual-editor/components/index.js @@ -0,0 +1,19 @@ +/* + +Copyright 2022 Elyra Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export { RHSPanelButtons } from './rhs-panel-buttons'; +export { AttributesList } from './attributes-list'; diff --git a/src/nlp-visual-editor/components/rhs-panel-buttons.jsx b/src/nlp-visual-editor/components/rhs-panel-buttons.jsx index 0d17ee3..34639d4 100644 --- a/src/nlp-visual-editor/components/rhs-panel-buttons.jsx +++ b/src/nlp-visual-editor/components/rhs-panel-buttons.jsx @@ -17,7 +17,11 @@ limitations under the License. import React from 'react'; import { Button, ButtonSet } from 'carbon-components-react'; -function RHSPanelButtons({ onClosePanel, onSavePanel, showSaveButton = true }) { +export function RHSPanelButtons({ + onClosePanel, + onSavePanel, + showSaveButton = true, +}) { return ( <> {showSaveButton ? ( @@ -41,5 +45,3 @@ function RHSPanelButtons({ onClosePanel, onSavePanel, showSaveButton = true }) { ); } - -export default RHSPanelButtons; diff --git a/src/nlp-visual-editor/components/rhs-panel.jsx b/src/nlp-visual-editor/components/rhs-panel.jsx index ed0e77a..874144b 100644 --- a/src/nlp-visual-editor/components/rhs-panel.jsx +++ b/src/nlp-visual-editor/components/rhs-panel.jsx @@ -84,7 +84,7 @@ class RHSPanel extends React.Component { ); case 'union': - return ; + return ; case 'sequence': return ; default: diff --git a/src/nlp-visual-editor/components/rhs-panel.scss b/src/nlp-visual-editor/components/rhs-panel.scss index 369a696..7231123 100644 --- a/src/nlp-visual-editor/components/rhs-panel.scss +++ b/src/nlp-visual-editor/components/rhs-panel.scss @@ -45,6 +45,14 @@ limitations under the License. } } + .attributes { + line-height: 30px; + .bx--checkbox-wrapper { + float: left; + padding: 0; + } + } + .rhs-buttons { position: fixed; bottom: 0; diff --git a/src/nlp-visual-editor/nlp-visual-editor.jsx b/src/nlp-visual-editor/nlp-visual-editor.jsx index 2e8a060..028c8f3 100644 --- a/src/nlp-visual-editor/nlp-visual-editor.jsx +++ b/src/nlp-visual-editor/nlp-visual-editor.jsx @@ -301,21 +301,19 @@ class VisualEditor extends React.Component { const { selectedNodeId, editorSettings } = this.state; const payload = []; - let upstreamNodeIds = this.canvasController + let upstreamNodes = this.canvasController .getUpstreamNodes([selectedNodeId], pipelineId) - .nodes[pipelineId].reverse(); - - const objNodes = nodes.reduce((sum, curr) => { - sum[curr.nodeId] = { ...{}, ...curr }; - return sum; - }, {}); + .nodes[pipelineId].reverse() + .map((id) => { + return nodes.find((n) => n.nodeId === id); + }); - upstreamNodeIds.forEach((id) => { - let node = objNodes[id]; + upstreamNodes.forEach((node) => { if (node.type !== 'input') { const results = this.jsonToXML.transform( node, editorSettings.moduleName, + nodes, ); if (!Array.isArray(results)) { //dictionaries return a list @@ -401,6 +399,14 @@ class VisualEditor extends React.Component { }); this.props.setTabularResults(undefined); } + }) + .catch((err) => { + clearInterval(this.timer); + this.setState({ + isLoading: false, + errorMessage: err.message, + }); + this.props.setTabularResults(undefined); }); }; @@ -703,16 +709,28 @@ class VisualEditor extends React.Component { node, }); } - if (['linkNodes', 'deleteLink'].includes(editType)) { + if (editType === 'linkNodes') { // Automatically update union node properties when links are created / deleted. - const linkId = data.id ?? data.linkIds?.[0]; - const link = this.canvasController.getLink(linkId); - const linkedNodes = [link.srcNodeId, link.trgNodeId]; - for (const nodeId of linkedNodes) { - const node = nodes.find((n) => n.nodeId === nodeId); - if (node.type === 'union') { - this.updateUnionProperties(node); - } + const link = this.canvasController.getLink(data.linkIds?.[0]); + const trgNode = nodes.find((n) => n.nodeId === link?.trgNodeId); + if (trgNode.type === 'union') { + this.updateUnionProperties(trgNode); + } + if ( + trgNode.type === 'consolidate' && + !this.canvasController + .getLinks() + .find((l) => l.trgNodeId === trgNode.nodeId) + ) { + this.canvasController.deleteLink(link.id); + } + } else if (editType === 'deleteLink') { + // Automatically update union node properties when links are created / deleted. + const trgNode = nodes.find( + (n) => n.nodeId === data.targetObject?.trgNodeId, + ); + if (trgNode.type === 'union') { + this.updateUnionProperties(trgNode); } } }; diff --git a/src/nlp-visual-editor/nodes/components/consolidate-panel.jsx b/src/nlp-visual-editor/nodes/components/consolidate-panel.jsx index 0529bb2..0a23cfc 100644 --- a/src/nlp-visual-editor/nodes/components/consolidate-panel.jsx +++ b/src/nlp-visual-editor/nodes/components/consolidate-panel.jsx @@ -14,15 +14,37 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -import React, { Children, isValidElement, cloneElement } from 'react'; -import PropTypes from 'prop-types'; +import React from 'react'; import { connect } from 'react-redux'; -import { Checkbox, Dropdown, TextArea } from 'carbon-components-react'; -import RHSPanelButtons from '../../components/rhs-panel-buttons'; +import { Dropdown } from 'carbon-components-react'; +import { AttributesList, RHSPanelButtons } from '../../components'; import { getImmediateUpstreamNodes } from '../../../utils'; import { saveNlpNode, setShowRightPanel } from '../../../redux/slice'; +const consolidateMethod = [ + { + id: 'ContainedWithin', + text: 'Contained Within', + }, + { + id: 'NotContainedWithin', + text: 'Not Contained Within', + }, + { + id: 'ContainsButNotEqual', + text: 'Contains But Not Equal', + }, + { + id: 'ExactMatch', + text: 'Exact match', + }, + { + id: 'LeftToRight', + text: 'Left To Right', + }, +]; + class ConsolidatePanel extends React.Component { constructor(props) { super(props); @@ -43,49 +65,64 @@ class ConsolidatePanel extends React.Component { this.state = { upstreamNodes: upstreamNodes, - consolidateTarget: this.props.consolidateTarget, - consolidatePolicy: this.props.consolidatePolicy, - consolidateMethod: [ - { - id: 'ContainedWithin', - text: 'Contained Within', - }, - { - id: 'NotContainedWithin', - text: 'Not Contained Within', - }, - { - id: 'ContainsButNotEqual', - text: 'Contains But Not Equal', - }, - { - id: 'ExactMatch', - text: 'Exact match', - }, - { - id: 'LeftToRight', - text: 'Left To Right', - }, - ], + attributes: this.getAttributes(props.consolidateTarget), + consolidateTarget: props.consolidateTarget, + consolidatePolicy: props.consolidatePolicy, }; } + getAttributes(consolidateTarget) { + const { nodes } = this.props; + const primaryNodeInfo = this.state?.consolidateTarget ?? consolidateTarget; + if (!primaryNodeInfo) { + return []; + } + const primaryNode = nodes.find((n) => n.nodeId === primaryNodeInfo.nodeId); + return ( + primaryNode?.attributes?.map((attr) => { + return { + ...attr, + disabled: false, + }; + }) ?? [] + ); + } + validateParameters = () => { - const { consolidatePolicy, consolidateTarget } = this.state; + const { consolidatePolicy, consolidateTarget, hasAttributesError } = + this.state; const { nodeId } = this.props; - const node = { - nodeId, - consolidateTarget, - consolidatePolicy, - isValid: true, - }; - this.props.saveNlpNode({ node }); - this.props.setShowRightPanel({ showPanel: false }); + if (!hasAttributesError) { + const node = { + nodeId, + consolidateTarget, + consolidatePolicy, + isValid: true, + }; + this.props.saveNlpNode({ node }); + this.props.setShowRightPanel({ showPanel: false }); + } }; render() { - const { pattern } = this.state; + const { attributes, upstreamNodes, consolidateTarget, consolidatePolicy } = + this.state; + const nodeOptions = []; + upstreamNodes.forEach((upstreamNode) => { + const node = this.props.nodes.find( + (n) => n.nodeId === upstreamNode.nodeId, + ); + const { attributes, label, nodeId } = node; + attributes?.forEach((attribute) => { + nodeOptions.push({ + nodeId, + attribute: attribute.value ?? label, + label, + text: `<${label}.${attribute.value ?? label}>`, + }); + }); + }); return (
Manage overlapping matches @@ -94,14 +131,15 @@ class ConsolidatePanel extends React.Component { size="sm" light label="Output Column" - initialSelectedItem={this.state.upstreamNodes.find( - (item) => this.state.consolidateTarget == item.label, + initialSelectedItem={nodeOptions.find( + (item) => consolidateTarget?.text == item.text, )} - items={this.state.upstreamNodes} - itemToString={(item) => (item ? item.label : '')} + items={nodeOptions} + itemToString={(item) => (item ? item.text : '')} onChange={(e) => { this.setState({ - consolidateTarget: e.selectedItem.label, + consolidateTarget: e.selectedItem, + attributes: this.getAttributes(e.selectedItem), }); }} /> @@ -110,10 +148,10 @@ class ConsolidatePanel extends React.Component { size="sm" light label="Method" - initialSelectedItem={this.state.consolidateMethod.find( - (item) => this.state.consolidatePolicy == item.id, + initialSelectedItem={consolidateMethod.find( + (item) => consolidatePolicy == item.id, )} - items={this.state.consolidateMethod} + items={consolidateMethod} itemToString={(item) => (item ? item.text : '')} onChange={(e) => { this.setState({ @@ -121,6 +159,16 @@ class ConsolidatePanel extends React.Component { }); }} /> + { + this.setState({ + attributes: newAttributes, + hasAttributesError: hasError, + }); + }} + label={this.props.label} + /> { this.props.setShowRightPanel({ showPanel: false }); diff --git a/src/nlp-visual-editor/nodes/components/dictionary-panel.jsx b/src/nlp-visual-editor/nodes/components/dictionary-panel.jsx index f9bc15b..ddb9194 100644 --- a/src/nlp-visual-editor/nodes/components/dictionary-panel.jsx +++ b/src/nlp-visual-editor/nodes/components/dictionary-panel.jsx @@ -39,8 +39,8 @@ import { TableToolbarContent, DataTable, } from 'carbon-components-react'; -import RHSPanelButtons from '../../components/rhs-panel-buttons'; -import { Delete16 } from '@carbon/icons-react'; +import { AttributesList, RHSPanelButtons } from '../../components'; +import { Delete16, Edit16 } from '@carbon/icons-react'; import classNames from 'classnames'; import { connect } from 'react-redux'; import { parse } from 'csv-parse/browser/esm'; @@ -62,6 +62,7 @@ class DictionaryPanel extends React.Component { ? props.items?.filter(filterItems) ?? [] : Object.keys(props.items).filter(filterItems); this.state = { + label: props.label, inputText: '', items: filteredItems, caseSensitivity: props.caseSensitivity, @@ -73,6 +74,17 @@ class DictionaryPanel extends React.Component { mappedItems: Array.isArray(props.items ?? []) ? {} : props.items, page: 1, pageSize: 10, + attributes: props.attributes ?? [ + { + nodeId: this.props.nodeId, + label: this.props.label, + value: this.props.label, + visible: true, + disabled: true, + }, + ], + editId: null, + editLabel: null, }; this.reader.onload = (event) => { const { items, mapTerms, mappedItems } = this.state; @@ -164,6 +176,7 @@ class DictionaryPanel extends React.Component { }; onSavePane = () => { + const { hasAttributesError } = this.state; const errorMessage = this.validateParameters(); const { items, @@ -172,10 +185,11 @@ class DictionaryPanel extends React.Component { externalResourceChecked, mapTerms, mappedItems, + attributes, } = this.state; const { nodeId } = this.props; - if (!errorMessage) { + if (!errorMessage && !hasAttributesError) { const node = { nodeId, items: mapTerms ? mappedItems : items, @@ -184,6 +198,7 @@ class DictionaryPanel extends React.Component { externalResourceChecked, isValid: true, mapTerms, + attributes, }; this.props.saveNlpNode({ node }); this.props.setShowRightPanel({ showPanel: false }); @@ -216,15 +231,15 @@ class DictionaryPanel extends React.Component { render() { const { - inputText, caseSensitivity, externalResourceChecked, lemmaMatch, errorMessage, - items, mapTerms, mappedItems, + attributes, } = this.state; + const { nodeId } = this.props; return (
{ - this.setState({ mapTerms: !mapTerms }); + this.setState({ + mapTerms: !mapTerms, + attributes: !mapTerms + ? [ + attributes[0], + { + label: 'Mapped Term', + value: 'Mapped Term', + nodeId, + visible: true, + disabled: true, + }, + ] + : [attributes[0]], + }); }} + id={`toggle-${nodeId}`} labelText="Map Terms" /> + +
+ { + this.setState({ + attributes: newAttributes, + hasAttributesError: hasError, + }); + }} + label={this.props.label} + /> { this.props.setShowRightPanel({ showPanel: false }); diff --git a/src/nlp-visual-editor/nodes/components/dictionary-panel.scss b/src/nlp-visual-editor/nodes/components/dictionary-panel.scss index ffaefba..36097f3 100644 --- a/src/nlp-visual-editor/nodes/components/dictionary-panel.scss +++ b/src/nlp-visual-editor/nodes/components/dictionary-panel.scss @@ -18,6 +18,13 @@ limitations under the License. display: flex; flex-direction: column; + .attributes { + line-height: 30px; + .bx--checkbox-wrapper { + float: left; + padding: 0; + } + } .input-controls { display: inline-flex; flex-direction: row; diff --git a/src/nlp-visual-editor/nodes/components/filter-panel.jsx b/src/nlp-visual-editor/nodes/components/filter-panel.jsx index 1b09180..3cb9558 100644 --- a/src/nlp-visual-editor/nodes/components/filter-panel.jsx +++ b/src/nlp-visual-editor/nodes/components/filter-panel.jsx @@ -16,9 +16,8 @@ limitations under the License. */ import React from 'react'; import PropTypes from 'prop-types'; -import { Checkbox, TextInput, Dropdown } from 'carbon-components-react'; -import RHSPanelButtons from '../../components/rhs-panel-buttons'; -import classNames from 'classnames'; +import { Dropdown } from 'carbon-components-react'; +import { RHSPanelButtons, AttributesList } from '../../components'; import { connect } from 'react-redux'; import { getImmediateUpstreamNodes } from '../../../utils'; @@ -26,10 +25,67 @@ import { getImmediateUpstreamNodes } from '../../../utils'; import { saveNlpNode, setShowRightPanel } from '../../../redux/slice'; +const filterTypeItems = [ + { + id: 'exclusive-predicates', + text: 'Exclude', + }, + { + id: 'inclusive-predicates', + text: 'Include', + }, +]; + +const funcNameItems = [ + { + id: '', + text: 'equals', + }, + { + id: 'Contains', + text: 'contains', + }, + { + id: '', + text: 'starts with', + }, + { + id: '', + text: 'ends with', + }, + { + id: 'Overlaps', + text: 'overlaps with', + }, + { + id: '', + text: 'occurs before', + }, + { + id: '', + text: 'occurs after', + }, +]; + +const scopeItems = [ + { + id: 'length', + text: 'length', + }, + { + id: 'text', + text: 'text', + }, + { + id: 'span', + text: 'span', + }, +]; + class FilterPanel extends React.Component { constructor(props) { super(props); - let upstream = getImmediateUpstreamNodes( + let upstreamNodes = getImmediateUpstreamNodes( this.props.nodeId, this.props.canvasController.getLinks(this.props.pipelineId), ); @@ -37,92 +93,89 @@ class FilterPanel extends React.Component { sum[curr.nodeId] = curr; return sum; }, {}); - upstream = upstream.map((u) => ({ id: u, text: nodes[u].label })); this.state = { filterType: props.filterType, - filterTypeItems: [ - { - id: 'exclusive-predicates', - text: 'Exclude', - }, - { - id: 'inclusive-predicates', - text: 'Include', - }, - ], + attributes: props.attributes ?? this.getAttributes(props.primary), primary: props.primary, funcName: props.funcName, - funcNameItems: [ - { - id: '', - text: 'equals', - }, - { - id: 'Contains', - text: 'contains', - }, - { - id: '', - text: 'starts with', - }, - { - id: '', - text: 'ends with', - }, - { - id: 'Overlaps', - text: 'overlaps with', - }, - { - id: '', - text: 'occurs before', - }, - { - id: '', - text: 'occurs after', - }, - ], secondary: props.secondary, - upstream: upstream, + upstreamNodes, scope: props.scope, - scopeItems: [ - { - id: 'length', - text: 'length', - }, - { - id: 'text', - text: 'text', - }, - { - id: 'span', - text: 'span', - }, - ], }; } - componentDidUpdate(prevProps) {} + getAttributes(primaryNodeId) { + const pipelineLinks = this.props.canvasController.getLinks( + this.props.pipelineId, + ); + const upstreamNodes = getImmediateUpstreamNodes( + this.props.nodeId, + pipelineLinks, + ); + const primaryNode = this.props.nodes.find( + (n) => n.nodeId === primaryNodeId ?? upstreamNodes?.[0], + ); + return ( + primaryNode?.attributes?.map((attr) => { + return { + ...attr, + disabled: false, + }; + }) ?? [] + ); + } onSavePane = () => { - const { primary, filterType, funcName, secondary, scope } = this.state; - const { nodeId } = this.props; - - const node = { - nodeId, + const { primary, filterType, funcName, secondary, scope, - isValid: true, - }; - this.props.saveNlpNode({ node }); - this.props.setShowRightPanel({ showPanel: false }); + hasAttributesError, + attributes, + } = this.state; + const { nodeId } = this.props; + + if (!hasAttributesError) { + const node = { + nodeId, + primary, + filterType, + funcName, + secondary, + attributes, + scope, + isValid: true, + }; + this.props.saveNlpNode({ node }); + this.props.setShowRightPanel({ showPanel: false }); + } }; render() { - const { inputText, lemmaMatch, errorMessage, attributes } = this.state; + const { + attributes, + upstreamNodes, + filterType, + primary, + secondary, + scope, + funcName, + } = this.state; + const nodeOptions = []; + upstreamNodes.forEach((nodeId) => { + const node = this.props.nodes.find((n) => n.nodeId === nodeId); + const { attributes, label } = node; + attributes?.forEach((attribute) => { + nodeOptions.push({ + nodeId, + attribute: attribute.value ?? label, + label, + text: `<${label}.${attribute.value ?? label}>`, + }); + }); + }); return (
@@ -130,11 +183,11 @@ class FilterPanel extends React.Component { id="filterType" size="sm" light - initialSelectedItem={this.state.filterTypeItems.find( - (item) => this.state.filterType == item.id, + initialSelectedItem={filterTypeItems.find( + (item) => filterType == item.id, )} label="Filter" - items={this.state.filterTypeItems} + items={filterTypeItems} itemToString={(item) => (item ? item.text : '')} onChange={(e) => { this.setState({ @@ -147,15 +200,16 @@ class FilterPanel extends React.Component { id="primary" size="sm" light - initialSelectedItem={this.state.upstream.find( - (item) => this.state.primary == item.id, + initialSelectedItem={nodeOptions.find( + (item) => primary.text == item.text, )} label="Primary" - items={this.state.upstream} + items={nodeOptions} itemToString={(item) => (item ? item.text : '')} onChange={(e) => { this.setState({ - primary: e.selectedItem.id, + primary: e.selectedItem, + attributes: this.getAttributes(e.selectedItem.nodeId), }); }} /> @@ -164,11 +218,9 @@ class FilterPanel extends React.Component { id="scope" size="sm" light - initialSelectedItem={this.state.scopeItems.find( - (item) => this.state.scope == item.id, - )} + initialSelectedItem={scopeItems.find((item) => scope == item.id)} label="Scope" - items={this.state.scopeItems} + items={scopeItems} itemToString={(item) => (item ? item.text : '')} onChange={(e) => { this.setState({ @@ -181,11 +233,11 @@ class FilterPanel extends React.Component { id="funcName" size="sm" light - initialSelectedItem={this.state.funcNameItems.find( - (item) => this.state.funcName == item.id, + initialSelectedItem={funcNameItems.find( + (item) => funcName == item.id, )} label="Filter" - items={this.state.funcNameItems} + items={funcNameItems} itemToString={(item) => (item ? item.text : '')} onChange={(e) => { this.setState({ @@ -198,19 +250,29 @@ class FilterPanel extends React.Component { id="secondary" size="sm" light - initialSelectedItem={this.state.upstream.find( - (item) => this.state.secondary == item.id, + initialSelectedItem={nodeOptions.find( + (item) => secondary.text == item.text, )} label="Secondary" - items={this.state.upstream} + items={nodeOptions} itemToString={(item) => (item ? item.text : '')} onChange={(e) => { this.setState({ - secondary: e.selectedItem.id, + secondary: e.selectedItem, }); }} /> + { + this.setState({ + attributes: newAttributes, + hasAttributesError: hasError, + }); + }} + label={this.props.label} + /> { this.props.setShowRightPanel({ showPanel: false }); diff --git a/src/nlp-visual-editor/nodes/components/input-panel.jsx b/src/nlp-visual-editor/nodes/components/input-panel.jsx index 5270498..3cf5275 100644 --- a/src/nlp-visual-editor/nodes/components/input-panel.jsx +++ b/src/nlp-visual-editor/nodes/components/input-panel.jsx @@ -20,7 +20,7 @@ import { FileUploader, FileUploaderItem, } from 'carbon-components-react'; -import RHSPanelButtons from '../../components/rhs-panel-buttons'; +import { RHSPanelButtons } from '../../components'; import { connect } from 'react-redux'; import './input-panel.scss'; diff --git a/src/nlp-visual-editor/nodes/components/literal-panel.jsx b/src/nlp-visual-editor/nodes/components/literal-panel.jsx index b8ebb40..e6b0aa8 100644 --- a/src/nlp-visual-editor/nodes/components/literal-panel.jsx +++ b/src/nlp-visual-editor/nodes/components/literal-panel.jsx @@ -16,12 +16,11 @@ limitations under the License. */ import React from 'react'; import PropTypes from 'prop-types'; -import { Checkbox, TextInput } from 'carbon-components-react'; -import RHSPanelButtons from '../../components/rhs-panel-buttons'; +import { TextInput, Checkbox } from 'carbon-components-react'; +import { RHSPanelButtons, AttributesList } from '../../components'; import classNames from 'classnames'; import { connect } from 'react-redux'; - -// import './dictionary-panel.scss'; +import './literal-panel.scss'; import { saveNlpNode, setShowRightPanel } from '../../../redux/slice'; @@ -29,9 +28,20 @@ class LiteralPanel extends React.Component { constructor(props) { super(props); this.state = { + label: props.label, inputText: props.inputText, lemmaMatch: props.lemmaMatch, errorMessage: undefined, + attributes: props.attributes ?? [ + { + label: props.label, + visible: true, + disabled: true, + nodeId: props.nodeId, + }, + ], + editId: null, + editLabel: null, }; } @@ -50,14 +60,16 @@ class LiteralPanel extends React.Component { onSavePane = () => { const errorMessage = this.validateParameters(); - const { lemmaMatch, inputText } = this.state; + const { lemmaMatch, inputText, attributes, hasAttributesError } = + this.state; const { nodeId } = this.props; - if (!errorMessage) { + if (!errorMessage && !hasAttributesError) { const node = { nodeId, inputText, lemmaMatch, + attributes, isValid: true, }; this.props.saveNlpNode({ node }); @@ -76,7 +88,7 @@ class LiteralPanel extends React.Component { }; render() { - const { inputText, lemmaMatch, errorMessage } = this.state; + const { inputText, lemmaMatch, errorMessage, attributes } = this.state; return (
+ { + this.setState({ + attributes: newAttributes, + hasAttributesError: hasError, + }); + }} + label={this.props.label} + /> { this.props.setShowRightPanel({ showPanel: false }); diff --git a/src/nlp-visual-editor/nodes/components/literal-panel.scss b/src/nlp-visual-editor/nodes/components/literal-panel.scss new file mode 100644 index 0000000..7484530 --- /dev/null +++ b/src/nlp-visual-editor/nodes/components/literal-panel.scss @@ -0,0 +1,25 @@ +/* + +Copyright 2022 Elyra Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +.literal-panel { + .attributes { + line-height: 30px; + .bx--checkbox-wrapper { + float: left; + padding: 0; + } + } +} diff --git a/src/nlp-visual-editor/nodes/components/regex-panel.jsx b/src/nlp-visual-editor/nodes/components/regex-panel.jsx index d7572e8..0d66341 100644 --- a/src/nlp-visual-editor/nodes/components/regex-panel.jsx +++ b/src/nlp-visual-editor/nodes/components/regex-panel.jsx @@ -26,7 +26,7 @@ import { RadioButtonGroup, TextArea, } from 'carbon-components-react'; -import RHSPanelButtons from '../../components/rhs-panel-buttons'; +import { RHSPanelButtons, AttributesList } from '../../components'; import './regex-panel.scss'; import { saveNlpNode, setShowRightPanel } from '../../../redux/slice'; @@ -34,9 +34,19 @@ import { saveNlpNode, setShowRightPanel } from '../../../redux/slice'; class RegexPanel extends React.Component { constructor(props) { super(props); - const { saveNlpNode, setShowRightPanel, label, ...rest } = props; + const { saveNlpNode, setShowRightPanel, label, nodeId, ...rest } = props; this.state = { ...rest, + attributes: props.attributes ?? [ + { + nodeId: nodeId, + label: label, + visible: true, + disabled: true, + }, + ], + editLabel: '', + editId: null, }; } @@ -66,7 +76,13 @@ class RegexPanel extends React.Component { }; validateParameters = () => { - const { errorMessage, regexInput, ...rest } = this.state; + const { + errorMessage, + regexInput, + attributes, + hasAttributesError, + ...rest + } = this.state; const { nodeId } = this.props; let err = undefined; try { @@ -80,9 +96,10 @@ class RegexPanel extends React.Component { this.setState({ errorMessage: err }); - if (!err) { + if (!err && !hasAttributesError) { const node = { nodeId, + attributes, regexInput, ...rest, isValid: true, @@ -104,7 +121,7 @@ class RegexPanel extends React.Component { this.setState({ ...props }); }; - onExpresionTypeChange = (type) => { + onExpressionTypeChange = (type) => { const { caseSensitivity } = this.state; let props = { expressionType: type }; if (type === 'literal') { @@ -138,10 +155,11 @@ class RegexPanel extends React.Component { multiline, unixLines, errorMessage, + attributes, } = this.state; + const { nodeId, label } = this.props; const disableCheckboxes = expressionType === 'literal'; - return (
@@ -153,8 +171,27 @@ class RegexPanel extends React.Component { invalid={errorMessage !== undefined} invalidText={errorMessage} onChange={(e) => { + let attributes = [this.state.attributes[0]]; + try { + var num_groups = new RegExp( + e.target.value.toString() + '|', + ).exec('').length; + for (let i = 1; i < num_groups; i++) { + attributes.push({ + nodeId, + label, + value: `group${i}`, + visible: true, + disabled: false, + }); + } + } catch (e) { + attributes = this.state.attributes; + } + this.setState({ regexInput: e.target.value, + attributes, errorMessage: undefined, }); }} @@ -164,7 +201,7 @@ class RegexPanel extends React.Component { name="rdExpression" defaultSelected={expressionType} onChange={(value) => { - this.onExpresionTypeChange(value); + this.onExpressionTypeChange(value); }} >
+ +
+ { + this.setState({ + attributes: newAttributes, + hasAttributesError: hasError, + }); + }} + label={this.props.label} + /> { this.props.setShowRightPanel({ showPanel: false }); diff --git a/src/nlp-visual-editor/nodes/components/regex-panel.scss b/src/nlp-visual-editor/nodes/components/regex-panel.scss index ec25118..def6290 100644 --- a/src/nlp-visual-editor/nodes/components/regex-panel.scss +++ b/src/nlp-visual-editor/nodes/components/regex-panel.scss @@ -15,8 +15,13 @@ See the License for the specific language governing permissions and limitations under the License. */ .regex-panel { - height: 100%; - + .attributes { + line-height: 30px; + .bx--checkbox-wrapper { + float: left; + padding: 0; + } + } .bx--tab-content { height: 100%; } diff --git a/src/nlp-visual-editor/nodes/components/sequence-panel.jsx b/src/nlp-visual-editor/nodes/components/sequence-panel.jsx index 31449a4..92e4ae4 100644 --- a/src/nlp-visual-editor/nodes/components/sequence-panel.jsx +++ b/src/nlp-visual-editor/nodes/components/sequence-panel.jsx @@ -14,18 +14,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -import React, { Children, isValidElement, cloneElement } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { - Button, - TextInput, - Checkbox, - Dropdown, - TextArea, -} from 'carbon-components-react'; -import RHSPanelButtons from '../../components/rhs-panel-buttons'; -import { Edit16 } from '@carbon/icons-react'; +import { TextArea } from 'carbon-components-react'; +import { RHSPanelButtons, AttributesList } from '../../components'; import './sequence-panel.scss'; import { getImmediateUpstreamNodes } from '../../../utils'; @@ -34,83 +27,103 @@ class SequencePanel extends React.Component { constructor(props) { super(props); this.state = { - nodeId: this.props.nodeId, - label: this.props.label, - renamed: this.props.renamed, - pattern: this.props.pattern, - upstreamNodes: JSON.parse(JSON.stringify(this.props.upstreamNodes)), + nodeId: props.nodeId, + label: props.label, + renamed: props.renamed, + pattern: props.pattern, editId: null, editLabel: '', + hasAttributesError: false, + attributes: props.attributes ?? [], }; } componentDidMount() { - let { pattern, upstreamNodes } = this.props; - if (pattern === '') { - ({ pattern, upstreamNodes } = this.constructPattern()); - this.setState({ pattern, upstreamNodes }); - } - if (!this.props.renamed) { - this.setState({ renamed: this.state.label }); - } + const { label } = this.props; + const { pattern, attributes } = this.constructPattern(this.props.pattern); + this.setState({ + label, + pattern, + attributes, + }); } componentDidUpdate(prevProps) { if (this.props.nodeId !== prevProps.nodeId) { - let renamed = this.props.renamed; - if (!this.props.renamed || this.props.renamed === '') { - renamed = this.props.label; - } const { label } = this.props; - if (this.props.pattern === '') { - const { pattern, upstreamNodes } = this.constructPattern(); - this.setState({ label, renamed, pattern, upstreamNodes }); - } else { - this.setState({ - label, - renamed, - pattern: this.props.pattern, - upstreamNodes: this.props.upstreamNodes, - }); - } + const { pattern, attributes } = this.constructPattern(this.props.pattern); + this.setState({ + label, + pattern, + attributes, + }); } } - constructPattern = () => { - const { canvasController, nodeId, pipelineId, nodes } = this.props; + constructPattern = (currentPattern) => { + const { canvasController, nodeId, pipelineId, nodes, label } = this.props; const pipelineLinks = canvasController.getLinks(pipelineId); const immediateNodes = getImmediateUpstreamNodes(nodeId, pipelineLinks); - let pattern = ''; - const upstreamNodes = []; + let pattern = currentPattern ?? ''; + const newAttributes = [ + { + nodeId, + label, + visible: true, + value: this.state.attributes?.[0]?.value ?? label, + disabled: true, + }, + ]; immediateNodes.forEach((id, index) => { const node = nodes.find((n) => n.nodeId === id); - const { label, nodeId, type, visible } = node; - pattern += `(<${label}.${label}>)`; - if (index < immediateNodes.length - 1) { - pattern += `{1,2}`; + const { label, nodeId, type, visible, attributes } = node; + if (currentPattern !== undefined) { + pattern = pattern.replace( + new RegExp(`<${label}.(.*?)>`), + `<${label}.${attributes?.[0]?.value ?? label}>`, + ); + } else { + pattern += `(<${label}.${attributes?.[0]?.value ?? label}>)`; + if (index < immediateNodes.length - 1) { + pattern += `{1,2}`; + } } - upstreamNodes.push({ + // Add all attributes from each node (but filter out other node's attributes) + newAttributes.push({ label, nodeId, - type, - visible: visible || false, - renamed: label, + value: attributes?.[0]?.value ?? label, + disabled: false, + visible: true, }); }); - return { pattern, upstreamNodes }; + return { pattern, attributes: newAttributes }; }; parsePattern = () => { - const { pattern, upstreamNodes } = this.state; - const newList = []; + const { pattern, attributes, label, nodeId } = this.state; + const newList = [ + attributes?.[0] ?? { + nodeId, + label, + visible: true, + disabled: true, + }, + ]; const nodeList = pattern.match(/\(<.+?(?=\.)/g); if (nodeList) { nodeList.forEach((n) => { const nodeName = n.substring(2, n.length); - const { nodeId, type, visible, renamed } = upstreamNodes.find( - (n) => n.label === nodeName, - ); - newList.push({ label: nodeName, nodeId, type, visible, renamed }); + const { nodeId, type, visible, renamed, attributes } = + this.props.nodes.find((n) => n.label === nodeName); + newList.push({ + label: nodeName, + nodeId, + type, + visible, + renamed, + attributes, + }); }); } return newList; @@ -133,7 +146,7 @@ class SequencePanel extends React.Component { }; validateParameters = () => { - const { pattern, renamed } = this.state; + const { pattern, attributes, hasAttributesError } = this.state; const { nodeId } = this.props; let errorMessage = @@ -141,14 +154,12 @@ class SequencePanel extends React.Component { this.setState({ errorMessage }); - if (!errorMessage) { + if (!errorMessage && !hasAttributesError) { const tokens = this.getTokens(); - const upstreamNodes = this.parsePattern(); const node = { nodeId, - renamed, + attributes, pattern, - upstreamNodes, tokens, isValid: true, }; @@ -157,21 +168,8 @@ class SequencePanel extends React.Component { } }; - onSaveAttributeLabel(node) { - const localNodes = JSON.parse(JSON.stringify(this.state.upstreamNodes)); - const targetNode = localNodes.find((n) => n.nodeId === node.nodeId); - targetNode.renamed = this.state.editLabel; - this.setState({ upstreamNodes: localNodes, editId: false }); - } - onSaveAttributeVisible(node, value) { - const localNodes = JSON.parse(JSON.stringify(this.state.upstreamNodes)); - const targetNode = localNodes.find((n) => n.nodeId === node.nodeId); - targetNode.visible = value; - this.setState({ upstreamNodes: localNodes }); - } - render() { - const { pattern } = this.state; + const { pattern, attributes } = this.state; return (