diff --git a/docker-compose.yaml b/docker-compose.yaml index 60f0505..21c27ba 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,7 +7,7 @@ services: - ES_VERSION=7 - INDEXING_ENABLED=true - JAVA_OPTS=-Dpolyglot.engine.WarnInterpreterOnly=false - image: niemen/conductor-server:7.2.0 + image: niemen/conductor-server:7.2.2 networks: - internal ports: @@ -82,13 +82,22 @@ services: max-file: "3" nats: - image: nats-streaming:latest + image: nats:latest networks: - internal + command: "-js --http_port 8222" ports: - 4222:4222 - 8222:8222 + nats-streaming: + image: nats-streaming:latest + networks: + - internal + ports: + - 4223:4222 + - 8223:8222 + schellar: profiles: ["scheduler"] image: flaviostutz/schellar diff --git a/gql-bff/src/features/workflow/resolvers.js b/gql-bff/src/features/workflow/resolvers.js index 3907d00..0d9e494 100644 --- a/gql-bff/src/features/workflow/resolvers.js +++ b/gql-bff/src/features/workflow/resolvers.js @@ -18,11 +18,19 @@ const workflowResolvers = { const { dataSources, tenant } = context; const workflow = await dataSources.workflowApi.getWorkflow(name, version); - if (!isMultiTenant) return workflow; + const conductorHandlers = + await dataSources.eventHandlerApi.getEventHandlerList(); + const startHandlers = conductorHandlers.filter((a) => + a.actions.some( + (x) => + x.action === "start_workflow" && x?.start_workflow?.name === name + ) + ); + if (!isMultiTenant) return { ...workflow, startHandlers }; const wfTenantId = getTenantIdFromDescription(workflow?.description); if (userCanSeeResource(wfTenantId, tenant?.id)) { - return workflow; + return { ...workflow, startHandlers }; } else return new ForbiddenError( "You are not authorized to see this workflow." diff --git a/gql-bff/src/features/workflow/schema.graphql b/gql-bff/src/features/workflow/schema.graphql index d35b05c..5c5d685 100644 --- a/gql-bff/src/features/workflow/schema.graphql +++ b/gql-bff/src/features/workflow/schema.graphql @@ -1,5 +1,6 @@ extend type WorkflowDef { - readOnly: Boolean + readOnly: Boolean, + startHandlers: [EventHandler] } type ExportWorkflows { diff --git a/react-ui/.yarn/install-state.gz b/react-ui/.yarn/install-state.gz index 0c6d958..033d87f 100644 Binary files a/react-ui/.yarn/install-state.gz and b/react-ui/.yarn/install-state.gz differ diff --git a/react-ui/package.json b/react-ui/package.json index b5878d0..daec355 100644 --- a/react-ui/package.json +++ b/react-ui/package.json @@ -12,6 +12,7 @@ "@date-io/moment": "^2.16.1", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@monaco-editor/react": "^4.6.0", "@mui/icons-material": "^5.11.16", "@mui/lab": "^5.0.0-alpha.134", "@mui/material": "^5.13.6", @@ -22,7 +23,6 @@ "@totalsoft/rocket-ui": "^0.1.25", "@totalsoft/rules-algebra-react": "^5.0.22", "@totalsoft/validations": "^1.0.4", - "ace-builds": "1.23.1", "apollo-cache-inmemory": "^1.6.6", "apollo-link-mock": "^1.0.1", "apollo-upload-client": "^17.0.0", @@ -38,7 +38,6 @@ "i18next-browser-languagedetector": "^7.1.0", "i18next-http-backend": "^2.2.1", "js-beautify": "^1.14.0", - "jsonlint-mod": "1.7.6", "lodash": "^4.17.21", "lodash.curry": "^4.1.1", "moment": "2.29.4", @@ -49,7 +48,6 @@ "paths-js": "^0.4.11", "ramda": "^0.29.0", "react": "18.2.0 ", - "react-ace": "10.1.0", "react-country-flag": "3.1.0", "react-diff-viewer": "^3.1.1", "react-dom": "18.2.0 ", @@ -78,7 +76,8 @@ "test:ci": "cross-env CI=true npm test -- --reporters=default --reporters=jest-junit --coverage --coverageReporters=cobertura --coverageReporters=lcov --coverageReporters=html", "eject": "react-scripts eject", "eslint:report": "eslint --fix --ext .js .", - "prettier": "prettier --write **/*.js" + "prettier": "prettier --write **/*.js", + "analyze": "source-map-explorer 'build/static/js/*.js'" }, "lint-staged": { "**/*.+(js|md|css|graphql|json)": "prettier --write" @@ -121,6 +120,7 @@ "react-app-rewired": "^2.1.11", "react-error-overlay": "6.0.11", "react-test-renderer": "^18.2.0", + "source-map-explorer": "2.5.3", "styled-components": "6.0.2" }, "jest": { diff --git a/react-ui/public/static/locales/en/translations.json b/react-ui/public/static/locales/en/translations.json index 0a47b4c..688ae60 100644 --- a/react-ui/public/static/locales/en/translations.json +++ b/react-ui/public/static/locales/en/translations.json @@ -332,7 +332,8 @@ "Export": "Export as JSON", "Delete": "Delete selection", "Execute": "Execute workflow", - "GeneralSettings": "General settings" + "GeneralSettings": "General settings", + "StartHandler": "Start Event Handlers" }, "Tour": { "BadgeContent": "{{curr}} of {{tot}}", diff --git a/react-ui/public/static/locales/fr/translations.json b/react-ui/public/static/locales/fr/translations.json index ee81c77..c4192bf 100644 --- a/react-ui/public/static/locales/fr/translations.json +++ b/react-ui/public/static/locales/fr/translations.json @@ -306,7 +306,8 @@ "Export": "Exporter au format JSON", "Delete": "Supprimer la sélection", "Execute": "Exécuter le workflow", - "GeneralSettings": "Paramètres généraux" + "GeneralSettings": "Paramètres généraux", + "StartHandler": "Start Event Handlers" }, "Tour": { "BadgeContent": "{{curr}} sur {{tot}}", diff --git a/react-ui/public/static/locales/ro/translations.json b/react-ui/public/static/locales/ro/translations.json index 9beb4ba..6c63b10 100644 --- a/react-ui/public/static/locales/ro/translations.json +++ b/react-ui/public/static/locales/ro/translations.json @@ -307,7 +307,8 @@ "Export": "Exporta ca JSON", "Delete": "Sterge selectia", "Execute": "Executa fluxul", - "GeneralSettings": "Setari generale" + "GeneralSettings": "Setari generale", + "StartHandler": "Start Event Handlers" }, "Tour": { "BadgeContent": "{{curr}} din {{tot}}", diff --git a/react-ui/src/__tests__/learnTesting.test.js b/react-ui/src/__tests__/learnTesting.test.js index 1b4e353..84f4561 100644 --- a/react-ui/src/__tests__/learnTesting.test.js +++ b/react-ui/src/__tests__/learnTesting.test.js @@ -53,7 +53,7 @@ describe('Here I learn good testing techniques', () => { it('Should call this function only with numbers', () => { const mock = jest.fn() ;[(1, 2, 3)] |> map(mock) - expect(mock).toBeCalledWith(expect.anything()) + expect(mock).toHaveBeenCalledWith(expect.anything()) }) it('Should filter the the list', () => { diff --git a/react-ui/src/features/common/constants.js b/react-ui/src/features/common/constants.js index c2cd48a..a2e80fb 100644 --- a/react-ui/src/features/common/constants.js +++ b/react-ui/src/features/common/constants.js @@ -13,4 +13,4 @@ export const sortingDirection = { DESC: 'DESC' } -export const fieldsToBeRemoved = ['__typename', 'historyId', 'readOnly'] +export const fieldsToBeRemoved = ['__typename', 'historyId', 'readOnly', 'startHandlers'] diff --git a/react-ui/src/features/designer/builderHandler.js b/react-ui/src/features/designer/builderHandler.js index 6160200..e4f4459 100644 --- a/react-ui/src/features/designer/builderHandler.js +++ b/react-ui/src/features/designer/builderHandler.js @@ -181,10 +181,10 @@ const parseTaskToJSON = (link, parentNode, tasks) => { break case nodeConfig.DECISION.type: { const { decideNode, firstNeutralNode } = handleDecideNode(link.targetPort.getNode()) - tasks.push({...decideNode.inputs, hasDefaultCase: undefined}) + tasks.push({ ...decideNode.inputs, hasDefaultCase: undefined }) if (firstNeutralNode) { if (firstNeutralNode.inputs && firstNeutralNode.inputs.taskReferenceName !== 'END') { - tasks.push({...firstNeutralNode.inputs, hasDefaultCase: undefined}) + tasks.push({ ...firstNeutralNode.inputs, hasDefaultCase: undefined }) } parentNode = firstNeutralNode } @@ -205,10 +205,15 @@ const parseTaskToJSON = (link, parentNode, tasks) => { tasks.push({ ...parentNode.inputs, asyncHandler: undefined, + inputTemplate: undefined, description: JSON.stringify({ description: parentNode.inputs.description, asyncHandler: parentNode.inputs['asyncHandler'] }) }) } else { - tasks.push({ ...parentNode.inputs, asyncHandler: undefined }) + tasks.push({ + ...parentNode.inputs, + asyncHandler: undefined, + inputTemplate: undefined + }) } } @@ -286,3 +291,21 @@ export const getTaskInputsRegex = t => { return inputParameters } + +export const getTaskInputsWithTemplate = (input, template) => { + if (!template) return input + + var newInput = { ...input } + if (!newInput) newInput = {} + + const templateKeys = keys(template) + templateKeys.forEach(k => { + if (template[k] === 'object') { + newInput[k] = '{}' + } else { + newInput[k] = '' + } + }) + + return newInput +} diff --git a/react-ui/src/features/designer/components/BodyWidget.js b/react-ui/src/features/designer/components/BodyWidget.js index 305ce1e..fbd66a4 100644 --- a/react-ui/src/features/designer/components/BodyWidget.js +++ b/react-ui/src/features/designer/components/BodyWidget.js @@ -12,13 +12,13 @@ const S = { ` } -const BodyWidget = ({ canvasClass, workflow, engine, setIsDirty, locked }) => { +const BodyWidget = ({ canvasClass, workflow, engine, setIsDirty, locked, taskDefs }) => { //create diagram from workflow definition useEffect(() => { if (workflow && workflow.tasks) { - drawDiagram(workflow, engine, workflow?.readOnly || locked) + drawDiagram(workflow, engine, workflow?.readOnly || locked, taskDefs) } - }, [engine, locked, workflow]) + }, [engine, locked, workflow, taskDefs]) const handleOnDrop = useCallback( event => { @@ -64,7 +64,8 @@ BodyWidget.propTypes = { engine: PropTypes.object.isRequired, workflow: PropTypes.object.isRequired, setIsDirty: PropTypes.func, - locked: PropTypes.bool.isRequired + locked: PropTypes.bool.isRequired, + taskDefs: PropTypes.array } export default BodyWidget diff --git a/react-ui/src/features/designer/components/UtilitiesBar.js b/react-ui/src/features/designer/components/UtilitiesBar.js index 571b9c3..358510b 100644 --- a/react-ui/src/features/designer/components/UtilitiesBar.js +++ b/react-ui/src/features/designer/components/UtilitiesBar.js @@ -1,6 +1,6 @@ import React, { useCallback, useState } from 'react' import PropTypes from 'prop-types' -import { Paper } from '@mui/material' +import { Paper, ToggleButton, ToggleButtonGroup } from '@mui/material' import { makeStyles } from '@mui/styles' import { IconButton } from '@totalsoft/rocket-ui' import { @@ -11,15 +11,31 @@ import { Redo, DescriptionOutlined, SettingsOutlined -} from '@mui/icons-material' + } from '@mui/icons-material' import PlayCircleIcon from '@mui/icons-material/PlayCircleOutline' +import StartIcon from '@mui/icons-material/Start'; +import PolylineIcon from '@mui/icons-material/Polyline' +import AbcIcon from '@mui/icons-material/Abc' import { Grid } from '@mui/material' import { useTranslation } from 'react-i18next' import styles from '../styles/styles' - const useStyles = makeStyles(styles) -const UtilitiesBar = ({ isNew, isDirty, onExecute, onPreviewJson, onDelete, onShowSettings, onImport, onExport, onUndo, onRedo }) => { +const UtilitiesBar = ({ + isNew, + isDirty, + onExecute, + onPreviewJson, + onDelete, + onShowSettings, + onImport, + onExport, + onUndo, + onRedo, + viewType, + handleViewType, + onViewStartHandler +}) => { const { t } = useTranslation() const classes = useStyles() const [confirmation, setConfirmation] = useState(true) @@ -41,47 +57,69 @@ const UtilitiesBar = ({ isNew, isDirty, onExecute, onPreviewJson, onDelete, onSh return ( - - - - - - - - - - - - - - - - - - - - - + {viewType === 'draw' && ( + <> + + + + + + + + + + + + + + + + + + + + + + + + )} + + + + + + + + + + + ) @@ -97,7 +135,10 @@ UtilitiesBar.propTypes = { onImport: PropTypes.func.isRequired, onExport: PropTypes.func.isRequired, onUndo: PropTypes.func.isRequired, - onRedo: PropTypes.func.isRequired + onRedo: PropTypes.func.isRequired, + viewType: PropTypes.string.isRequired, + handleViewType: PropTypes.func.isRequired, + onViewStartHandler: PropTypes.func.isRequired } export default UtilitiesBar diff --git a/react-ui/src/features/designer/constants/NodeConfig.js b/react-ui/src/features/designer/constants/NodeConfig.js index de44765..042c854 100644 --- a/react-ui/src/features/designer/constants/NodeConfig.js +++ b/react-ui/src/features/designer/constants/NodeConfig.js @@ -87,7 +87,7 @@ const nodeConfigData = { }, TASK: { name: 'TASK', - type: 'TASK', + type: 'SIMPLE', color: 'rgb(123,132,220)', hasParametersTab: true, getInstance: task => new TaskNodeModel(task) diff --git a/react-ui/src/features/designer/diagram/WorkflowDAG.js b/react-ui/src/features/designer/diagram/WorkflowDAG.js index 4adb488..daf4791 100644 --- a/react-ui/src/features/designer/diagram/WorkflowDAG.js +++ b/react-ui/src/features/designer/diagram/WorkflowDAG.js @@ -210,7 +210,7 @@ export default class WorkflowDAG { retval.push( ..._.flatten( - Object.entries(decisionTask.decisionCases).map(([caseValue, tasks]) => { + Object.entries(decisionTask.decisionCases).map(([_caseValue, tasks]) => { return this.processTaskList(tasks, [decisionTask]); }) ) @@ -233,11 +233,11 @@ export default class WorkflowDAG { if (!forkedTasksCount) { const placeholderRef = dfTask.taskReferenceName + "_DF_EMPTY_PLACEHOLDER"; - const placeholderTask = { + /*const placeholderTask = { name: placeholderRef, // will be overwritten if results available taskReferenceName: placeholderRef, // will be overwritten if results available type: "DF_EMPTY_PLACEHOLDER", - }; + };*/ if (_.get(dfTaskResult, "status")) { // Edge case: Backfill placeholder status for 'no tasks spawned'. @@ -325,7 +325,7 @@ export default class WorkflowDAG { lastLoopTask?.type === "DECISION" ) { Object.entries(lastLoopTask.decisionCases).forEach( - ([caseValue, tasks]) => { + ([_caseValue, tasks]) => { const lastTaskInCase = _.last(tasks); this.addVertex(endDoWhileTask, [lastTaskInCase]); } diff --git a/react-ui/src/features/designer/drawingHandler.js b/react-ui/src/features/designer/drawingHandler.js index e4e14cf..7e5b935 100644 --- a/react-ui/src/features/designer/drawingHandler.js +++ b/react-ui/src/features/designer/drawingHandler.js @@ -1,20 +1,18 @@ import { DiagramModel } from '@projectstorm/react-diagrams-core' -import Workflow2Graph from './workflowToGraph' -import defaultTo from 'lodash/fp/defaultTo' import { isEmpty, toArray } from 'lodash' import StartNodeModel from './nodeModels/startNode/StartNodeModel' import EndNodeModel from './nodeModels/endNode/EndNodeModel' -import { last, values } from 'ramda' -import { isDefault, nodeConfig } from './constants/NodeConfig' +import { last, values, keys } from 'ramda' +import { nodeConfig } from './constants/NodeConfig' import WorkflowDAG from './diagram/WorkflowDAG' export const clearDiagram = engine => { engine.model = new DiagramModel() } -export const drawDiagram = (workflow, engine, locked) => { +export const drawDiagram = (workflow, engine, locked, tasks) => { clearDiagram(engine) - workflow.tasks.map(x => createNode(engine, x)) + workflow.tasks.map(x => createNode(engine, x, tasks)) appendStartAnd(engine, workflow) linkAllNodes(engine, workflow) @@ -26,10 +24,7 @@ const getMatchingTaskRefNode = (engine, taskRefName) => { } const getGraphState = definition => { - const wfe2graph = new Workflow2Graph() const dag = new WorkflowDAG(null, definition) - const wfe = defaultTo({ tasks: [] })(null) - const { edges, vertices } = wfe2graph.convert(wfe, definition) return { edges: dag.graph.edges(), @@ -38,7 +33,7 @@ const getGraphState = definition => { } // Links two nodes together ( out -> in ) -const linkNodes = (node1, node2, whichPort) => { +const linkNodes = (node1, node2) => { if (node1.type === 'DECISION') { const decisionCase = Object.keys(node1.inputs.decisionCases).filter(a => node1.inputs.decisionCases[a].some(a => a.taskReferenceName === node2.inputs.taskReferenceName) @@ -224,7 +219,7 @@ const calculateNestedPosition = (engine, branchTask, parentNode, k, branchSpread return { branchPosX, branchPosY } } //Creates new node based on the task definition -export const createNode = (engine, task, branchX = null, branchY = null, forkDepth = 1) => { +export const createNode = (engine, task, taskList, branchX = null, branchY = null, forkDepth = 1) => { switch (task.type) { case nodeConfig.DECISION.type: { const { x, y } = calculatePosition(engine, branchX, branchY) @@ -257,7 +252,7 @@ export const createNode = (engine, task, branchX = null, branchY = null, forkDep caseNum, forkDepth ) - createNode(engine, branchTask, branchPosX, branchPosY, forkDepth + 1) + createNode(engine, branchTask, taskList, branchPosX, branchPosY, forkDepth + 1) }) }) break @@ -287,7 +282,7 @@ export const createNode = (engine, task, branchX = null, branchY = null, forkDep branchNum, forkDepth ) - createNode(engine, branchTask, branchPosX, branchPosY, forkDepth + 1) + createNode(engine, branchTask, taskList, branchPosX, branchPosY, forkDepth + 1) }) }) break @@ -296,6 +291,17 @@ export const createNode = (engine, task, branchX = null, branchY = null, forkDep const { x, y } = calculatePosition(engine, branchX, branchY) const node = nodeConfig[task.type]?.getInstance(task) node.setPosition(x, y) + var taskDef = taskList?.find(a => a.name === node.inputs.name) + if (taskDef && taskDef.inputTemplate) { + node.inputs.inputTemplate = taskDef.inputTemplate + const templateKeys = keys(taskDef.inputTemplate) + const inputKeys = keys(node.inputs.inputParameters) + inputKeys.forEach(k => { + if (templateKeys.includes(k) && taskDef.inputTemplate[k] === 'object' && typeof node.inputs.inputParameters[k] === 'object') { + node.inputs.inputParameters = { ...node.inputs.inputParameters, [k]: JSON.stringify(node.inputs.inputParameters[k], null, '\t') } + } + }) + } engine.model.addNode(node) break } diff --git a/react-ui/src/features/designer/nodeModels/decisionNode/DecisionLogicForm.js b/react-ui/src/features/designer/nodeModels/decisionNode/DecisionLogicForm.js index 35cef2f..19e4d8f 100644 --- a/react-ui/src/features/designer/nodeModels/decisionNode/DecisionLogicForm.js +++ b/react-ui/src/features/designer/nodeModels/decisionNode/DecisionLogicForm.js @@ -1,6 +1,4 @@ -import 'ace-builds/src-noconflict/mode-javascript' -import 'ace-builds/src-noconflict/theme-tomorrow' -import AceEditor from 'react-ace' +import Editor from '@monaco-editor/react' import { get, set } from '@totalsoft/rules-algebra-react' import React from 'react' import PropTypes from 'prop-types' @@ -37,16 +35,22 @@ const DecisionLogicForm = ({ inputsLens, toggle }) => { )} {caseExpression && ( - get) || emptyString} onChange={inputsLens.caseExpression |> set} - wrapEnabled={true} + autoIndent={true} + options={{ + scrollBeyondLastLine: false, + smoothScrolling: true, + selectOnLineNumbers: true, + minimap: { + enabled: false + } + }} /> )} diff --git a/react-ui/src/features/designer/nodeModels/eventNode/EventNodeInputParameters.js b/react-ui/src/features/designer/nodeModels/eventNode/EventNodeInputParameters.js index bf498f9..d143e43 100644 --- a/react-ui/src/features/designer/nodeModels/eventNode/EventNodeInputParameters.js +++ b/react-ui/src/features/designer/nodeModels/eventNode/EventNodeInputParameters.js @@ -1,11 +1,5 @@ -import React, { useCallback, useEffect, useState } from 'react' -import AceEditor from 'react-ace' -import 'ace-builds/src-noconflict/mode-json' -import 'ace-builds/src-noconflict/theme-tomorrow' -//this is required for dev -//import 'ace-builds/webpack-resolver' -import JsonLint from 'jsonlint-mod' - +import React, { useCallback, useState } from 'react' +import Editor from '@monaco-editor/react' import { get } from '@totalsoft/rules-algebra-react' import PropTypes from 'prop-types' import { Divider, Paper, Grid } from '@mui/material' @@ -21,7 +15,6 @@ const EventNodeInputParameters = ({ inputParametersLens, onPayloadChange }) => { const payloadLens = process.env.REACT_APP_USE_NBB_MESSAGE === 'true' ? inputParametersLens?.Payload : inputParametersLens const payload = (payloadLens |> get) || emptyObject const [localState, setLocalState] = useState(JSON.stringify(payload, null, 4)) - const [annotations, setAnnotations] = useState() const handleChange = useCallback( value => { @@ -31,17 +24,6 @@ const EventNodeInputParameters = ({ inputParametersLens, onPayloadChange }) => { [onPayloadChange] ) - useEffect(() => { - try { - JsonLint.parse(localState) - setAnnotations(null) - } catch (e) { - const row = parseInt(e.message.match(/Parse error on line (\d+)/)[1]) - 1 - const annotation = { row, text: e.message, type: 'error' } - setAnnotations([annotation]) - } - }, [localState]) - return ( <> @@ -55,18 +37,23 @@ const EventNodeInputParameters = ({ inputParametersLens, onPayloadChange }) => { - ) diff --git a/react-ui/src/features/designer/nodeModels/httpNode/HttpNodeBody.js b/react-ui/src/features/designer/nodeModels/httpNode/HttpNodeBody.js index 01ca44e..afad886 100644 --- a/react-ui/src/features/designer/nodeModels/httpNode/HttpNodeBody.js +++ b/react-ui/src/features/designer/nodeModels/httpNode/HttpNodeBody.js @@ -1,6 +1,4 @@ -import AceEditor from 'react-ace' -import 'ace-builds/src-noconflict/mode-javascript' -import 'ace-builds/src-noconflict/theme-tomorrow' +import Editor from '@monaco-editor/react' import React, { useCallback } from 'react' import styles from '../../styles/styles' import PropTypes from 'prop-types' @@ -43,16 +41,23 @@ const HttpNodeBody = ({ httpRequestLens }) => { - set} - wrapEnabled={true} /> diff --git a/react-ui/src/features/designer/nodeModels/lambdaNode/LambdaNodeInputParameters.js b/react-ui/src/features/designer/nodeModels/lambdaNode/LambdaNodeInputParameters.js index ed80787..2048cab 100644 --- a/react-ui/src/features/designer/nodeModels/lambdaNode/LambdaNodeInputParameters.js +++ b/react-ui/src/features/designer/nodeModels/lambdaNode/LambdaNodeInputParameters.js @@ -1,6 +1,4 @@ -import AceEditor from 'react-ace' -import 'ace-builds/src-noconflict/mode-javascript' -import 'ace-builds/src-noconflict/theme-tomorrow' +import Editor from '@monaco-editor/react' import React, { useCallback } from 'react' import PropTypes from 'prop-types' import { Divider, Grid } from '@mui/material' @@ -52,16 +50,22 @@ const LambdaNodeInputParameters = ({ inputParametersLens }) => { - set} - wrapEnabled={true} /> diff --git a/react-ui/src/features/designer/nodeModels/taskNode/TaskNodeModel.js b/react-ui/src/features/designer/nodeModels/taskNode/TaskNodeModel.js index 7dd434e..c0157ec 100644 --- a/react-ui/src/features/designer/nodeModels/taskNode/TaskNodeModel.js +++ b/react-ui/src/features/designer/nodeModels/taskNode/TaskNodeModel.js @@ -1,6 +1,6 @@ import { DefaultNodeModel, DefaultPortModel } from '@projectstorm/react-diagrams' import { nodeConfig } from 'features/designer/constants/NodeConfig' -import { getTaskInputsRegex } from 'features/designer/builderHandler' +import { getTaskInputsRegex, getTaskInputsWithTemplate } from 'features/designer/builderHandler' import { hash } from 'utils/functions' import { anyInOneOut } from '../validations' @@ -12,7 +12,8 @@ export default class TaskNodeModel extends DefaultNodeModel { this.inputs = { name: task?.name ?? name, - inputParameters: task?.inputParameters ?? getTaskInputsRegex(task), + inputParameters: getTaskInputsWithTemplate(task?.inputParameters, task?.inputTemplate) ?? getTaskInputsRegex(task), + inputTemplate: task?.inputTemplate, taskReferenceName: task?.taskReferenceName ?? task?.name?.toLowerCase()?.trim() + '_ref_' + hash(), type: task?.type ?? type, description: task?.description, diff --git a/react-ui/src/features/schedule/edit/components/ScheduleData.js b/react-ui/src/features/schedule/edit/components/ScheduleData.js index 9f9e148..6323779 100644 --- a/react-ui/src/features/schedule/edit/components/ScheduleData.js +++ b/react-ui/src/features/schedule/edit/components/ScheduleData.js @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react' +import React, { useCallback } from 'react' import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import { Grid } from '@mui/material' @@ -11,17 +11,12 @@ import { Card, FakeText, Autocomplete } from '@totalsoft/rocket-ui' import { Info } from '@mui/icons-material' import { Typography } from '@totalsoft/rocket-ui' import { get, set } from '@totalsoft/react-state-lens' -import JsonLint from 'jsonlint-mod' -import AceEditor from 'react-ace' -import 'ace-builds/src-noconflict/mode-json' -import 'ace-builds/src-noconflict/theme-tomorrow' +import Editor from '@monaco-editor/react' const ScheduleData = ({ scheduleLens, validation, loading, workflows }) => { const { t } = useTranslation() const schedule = scheduleLens |> get - const [annotations, setAnnotations] = useState() - const handleActiveChanged = useCallback(() => { set(scheduleLens.enabled, !schedule?.enabled) }, [schedule?.enabled, scheduleLens]) @@ -44,14 +39,6 @@ const ScheduleData = ({ scheduleLens, validation, loading, workflows }) => { const onContextChange = useCallback( value => { set(scheduleLens.workflowContext, value) - try { - JsonLint.parse(value) - setAnnotations(null) - } catch (e) { - const row = parseInt(e.message.match(/Parse error on line (\d+)/)[1]) - 1 - const annotation = { row, text: e.message, type: 'error' } - setAnnotations([annotation]) - } }, [scheduleLens.workflowContext] ) @@ -122,18 +109,22 @@ const ScheduleData = ({ scheduleLens, validation, loading, workflows }) => { {t('Schedule.WorkflowContext')} - get) || emptyString} onChange={onContextChange} - wrapEnabled={true} /> diff --git a/react-ui/src/features/task/common/defaultConfiguration.js b/react-ui/src/features/task/common/defaultConfiguration.js index 3977a5c..40834d0 100644 --- a/react-ui/src/features/task/common/defaultConfiguration.js +++ b/react-ui/src/features/task/common/defaultConfiguration.js @@ -6,7 +6,8 @@ const defaultConfiguration = { retryDelaySeconds: 1, responseTimeoutSeconds: 10, rateLimitPerFrequency: 1, - rateLimitFrequencyInSeconds: 1 + rateLimitFrequencyInSeconds: 1, + inputTemplate: "{}" } export default defaultConfiguration diff --git a/react-ui/src/features/task/edit/components/Task.js b/react-ui/src/features/task/edit/components/Task.js index 0e14c4c..328b82f 100644 --- a/react-ui/src/features/task/edit/components/Task.js +++ b/react-ui/src/features/task/edit/components/Task.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React, { useEffect, useCallback } from 'react' import PropTypes from 'prop-types' import { Grid } from '@mui/material' import { useTranslation } from 'react-i18next' @@ -10,6 +10,7 @@ import { useHeader } from 'providers/AreasProvider' import { onTextBoxChange } from 'utils/propertyChangeAdapters' import { Autocomplete, TextField, Card, FakeText } from '@totalsoft/rocket-ui' import { getErrors, isValid } from '@totalsoft/pure-validations-react' +import { Editor } from '@monaco-editor/react' const Task = ({ taskLens, validation, loading, saving, onSave, isDirty, timeoutPolicyList, retryLogicList }) => { const { t } = useTranslation() @@ -28,7 +29,8 @@ const Task = ({ taskLens, validation, loading, saving, onSave, isDirty, timeoutP timeoutSeconds, responseTimeoutSeconds, inputKeys, - outputKeys + outputKeys, + inputTemplate } = task const createTime = t('DATE_FORMAT', { date: { value: taskLens?.createTime |> get, format: 'DD-MM-YYYY HH:mm:ss' } }) @@ -37,6 +39,13 @@ const Task = ({ taskLens, validation, loading, saving, onSave, isDirty, timeoutP setHeader() }, [isDirty, name, onSave, readOnly, saving, setHeader]) + const onInputTemplateChange = useCallback( + value => { + set(taskLens.inputTemplate, value) + }, + [taskLens.inputTemplate] + ) + if (loading) { return } @@ -144,6 +153,25 @@ const Task = ({ taskLens, validation, loading, saving, onSave, isDirty, timeoutP onChange={taskLens.outputKeys |> set |> onTextBoxChange} /> + + + diff --git a/react-ui/src/features/task/edit/components/TaskContainer.js b/react-ui/src/features/task/edit/components/TaskContainer.js index a7cdbf5..f81bb61 100644 --- a/react-ui/src/features/task/edit/components/TaskContainer.js +++ b/react-ui/src/features/task/edit/components/TaskContainer.js @@ -4,15 +4,12 @@ import { useError, useQueryWithErrorHandling } from 'hooks/errorHandling' import { SAVE_TASK_MUTATION } from '../mutations/SaveTaskMutation' import { useToast } from '@totalsoft/rocket-ui' import { TASK_QUERY } from '../queries/TaskQuery.js' -import { TIMEOUT_POLICY_OPTIONS } from '../queries/TimeoutPolicyListQuery' -import { RETRY_LOGIC_OPTIONS } from '../queries/RetryLogicListQuery' import { isDirty, isPropertyDirty } from '@totalsoft/change-tracking' import { useParams, useNavigate } from 'react-router' import { useMutation } from '@apollo/client' import { useTranslation } from 'react-i18next' import Task from './Task' import { queryLimit as limit } from '../../../common/constants' -import { emptyArray } from 'utils/constants' import defaultConfiguration from '../../common/defaultConfiguration' import { useReactOidc } from '@axa-fr/react-oidc-context' import { buildValidator } from '../validator' @@ -39,17 +36,14 @@ const TaskContainer = () => { variables: { name }, skip: isNew, onCompleted: taskData => { - resetTask(taskData?.getTaskDefinition) + resetTask({ ...taskData?.getTaskDefinition, inputTemplate: JSON.stringify(taskData?.getTaskDefinition.inputTemplate, null, '\t') }) } }) const { data } = useQueryWithErrorHandling(TASK_NAMES_QUERY, { variables: { limit } }) - const { data: timeoutPoliciesData } = useQueryWithErrorHandling(TIMEOUT_POLICY_OPTIONS) - const timeoutPolicyList = timeoutPoliciesData?.__type?.enumValues || emptyArray - - const { data: retryLogicData } = useQueryWithErrorHandling(RETRY_LOGIC_OPTIONS) - const retryLogicList = retryLogicData?.__type?.enumValues || emptyArray + const timeoutPolicyList = ['RETRY', 'TIME_OUT_WF', 'ALERT_ONLY'] + const retryLogicList = ['FIXED', 'EXPONENTIAL_BACKOFF', 'LINEAR_BACKOFF'] const taskValidator = useMemo(() => buildValidator(data?.getTaskDefs, name), [data?.getTaskDefs, name]) const [validation, validate, resetValidation] = useDirtyFieldValidation(taskValidator) @@ -71,10 +65,16 @@ const TaskContainer = () => { if (isNew) saveTask({ variables: { - input: [{ ...omit(['readOnly'], task), createTime: new Date().getTime(), ownerEmail: oidcUser?.profile.preferred_username }] + input: [ + { + ...omit(['readOnly'], { ...task, inputTemplate: JSON.parse(task.inputTemplate) }), + createTime: new Date().getTime(), + ownerEmail: oidcUser?.profile.preferred_username + } + ] } }) - else saveTask({ variables: { input: omit(['readOnly'], task) } }) + else saveTask({ variables: { input: omit(['readOnly'], { ...task, inputTemplate: JSON.parse(task.inputTemplate) }) } }) }, [isNew, oidcUser?.profile.preferred_username, saveTask, task, validate]) useEffect(() => { diff --git a/react-ui/src/features/task/edit/queries/RetryLogicListQuery.js b/react-ui/src/features/task/edit/queries/RetryLogicListQuery.js deleted file mode 100644 index 5fd0370..0000000 --- a/react-ui/src/features/task/edit/queries/RetryLogicListQuery.js +++ /dev/null @@ -1,11 +0,0 @@ -import { gql } from '@apollo/client' - -export const RETRY_LOGIC_OPTIONS = gql` - query retryLogicListQuery { - __type(name: "RetryLogic") { - enumValues { - name - } - } - } -` diff --git a/react-ui/src/features/task/edit/queries/TaskQuery.js b/react-ui/src/features/task/edit/queries/TaskQuery.js index 8e12955..7b42b7e 100644 --- a/react-ui/src/features/task/edit/queries/TaskQuery.js +++ b/react-ui/src/features/task/edit/queries/TaskQuery.js @@ -18,6 +18,7 @@ export const TASK_QUERY = gql` rateLimitFrequencyInSeconds inputKeys outputKeys + inputTemplate } } ` diff --git a/react-ui/src/features/task/edit/queries/TimeoutPolicyListQuery.js b/react-ui/src/features/task/edit/queries/TimeoutPolicyListQuery.js deleted file mode 100644 index efbc620..0000000 --- a/react-ui/src/features/task/edit/queries/TimeoutPolicyListQuery.js +++ /dev/null @@ -1,11 +0,0 @@ -import { gql } from '@apollo/client' - -export const TIMEOUT_POLICY_OPTIONS = gql` - query timeoutPolicyListQuery { - __type(name: "TimeoutPolicy") { - enumValues { - name - } - } - } -` diff --git a/react-ui/src/features/task/list/queries/TaskListQuery.js b/react-ui/src/features/task/list/queries/TaskListQuery.js index da8cb55..fac3955 100644 --- a/react-ui/src/features/task/list/queries/TaskListQuery.js +++ b/react-ui/src/features/task/list/queries/TaskListQuery.js @@ -7,6 +7,7 @@ export const TASK_LIST_QUERY = gql` description createTime inputKeys + inputTemplate } } ` diff --git a/react-ui/src/features/workflow/edit/components/Workflow.js b/react-ui/src/features/workflow/edit/components/Workflow.js index 9825601..0ffe03b 100644 --- a/react-ui/src/features/workflow/edit/components/Workflow.js +++ b/react-ui/src/features/workflow/edit/components/Workflow.js @@ -27,13 +27,16 @@ import SideMenu from './sideMenu/SideMenu' import PreviewJsonDialog from './modals/PreviewJsonDialog' import { defaultFileName } from 'features/workflow/common/constants' import workflowConfig from 'features/designer/constants/WorkflowConfig' -import { useToast } from '@totalsoft/rocket-ui' +import { useToast, Dialog } from '@totalsoft/rocket-ui' +import WorkflowJson from './WorkflowJson' +import JsonViewer from 'features/common/components/JsonViewer' -const Workflow = ({ loading, isNew, resetWorkflow, isDirty, workflowLens, diagram, setIsDirty }) => { +const Workflow = ({ loading, isNew, resetWorkflow, isDirty, workflowLens, diagram, setIsDirty, taskDefs }) => { const { t } = useTranslation() const showError = useError() const addToast = useToast() + const [viewType, setViewType] = useState('draw') const { oidcUser } = useReactOidc() const clientQuery = useClientQueryWithErrorHandling() @@ -45,10 +48,14 @@ const Workflow = ({ loading, isNew, resetWorkflow, isDirty, workflowLens, diagra const [settingsDialog, setSettingsDialog] = useState(false) const [previewDialog, setPreviewDialog] = useState(false) const [currentWorkflow, setCurrentWorkflow] = useState(null) + const [startHandlersDialog, showStartHandlersDialog] = useState(false) + const toggleStartHandlersDialog = useCallback(() => showStartHandlersDialog(current => !current), []) const workflow = workflowLens |> get const { engine } = diagram + const handleChangeViewType = useCallback((event, value) => setViewType(value), [setViewType]) + const [runWfQuery, { called: wfCalled, loading: loadingWf, data: wfData }] = useLazyQuery(WORKFLOW_LIST_QUERY) const [runTskQuery, { called: tskCalled, loading: loadingTsk, data: tskData }] = useLazyQuery(TASK_LIST_QUERY) @@ -84,7 +91,8 @@ const Workflow = ({ loading, isNew, resetWorkflow, isDirty, workflowLens, diagra color: nodeConfig.TASK.color, isSystemTask: false, workflow: wfl, - inputKeys: wfl.inputKeys + inputKeys: wfl.inputKeys, + inputTemplate: wfl.inputTemplate })) setTrayItems(trayList) break @@ -104,6 +112,27 @@ const Workflow = ({ loading, isNew, resetWorkflow, isDirty, workflowLens, diagra wfData?.getWorkflowList ]) + useEffect(() => { + if (workflow.tasks) { + setCurrentWorkflow(workflow) + } else { + if (isNew) { + setCurrentWorkflow({ + name: '', + description: '', + version: 0, + tasks: [], + schemaVersion: 2, + restartable: true, + workflowStatusListenerEnabled: true, + ownerEmail: '', + timeoutPolicy: 'ALERT_ONLY', + timeoutSeconds: 0 + }) + } + } + }, [workflow, isNew]) + const toggleExecDialog = useCallback(() => { setExecDialog(current => !current) }, []) @@ -216,6 +245,13 @@ const Workflow = ({ loading, isNew, resetWorkflow, isDirty, workflowLens, diagra const handleUndo = useCallback(() => diagram.undo(), [diagram]) const handleRedo = useCallback(() => diagram.redo(), [diagram]) + const onChangeJson = useCallback( + v => { + resetWorkflow(JSON.parse(v)) + }, + [resetWorkflow] + ) + if (loading) { return } @@ -233,10 +269,29 @@ const Workflow = ({ loading, isNew, resetWorkflow, isDirty, workflowLens, diagra onDelete={handleDelete} onUndo={handleUndo} onRedo={handleRedo} + viewType={viewType} + handleViewType={handleChangeViewType} + onViewStartHandler={toggleStartHandlersDialog} /> - - - + {viewType === 'draw' && ( + + + + + + )} + {viewType === 'json' && currentWorkflow && ( + + + + )} {execDialog && ( + + } + /> ) } @@ -263,7 +326,8 @@ Workflow.propTypes = { isNew: PropTypes.bool.isRequired, diagram: PropTypes.object.isRequired, workflowLens: PropTypes.object.isRequired, - setIsDirty: PropTypes.func.isRequired + setIsDirty: PropTypes.func.isRequired, + taskDefs: PropTypes.array } export default Workflow diff --git a/react-ui/src/features/workflow/edit/components/WorkflowContainer.js b/react-ui/src/features/workflow/edit/components/WorkflowContainer.js index 7c040f6..586766a 100644 --- a/react-ui/src/features/workflow/edit/components/WorkflowContainer.js +++ b/react-ui/src/features/workflow/edit/components/WorkflowContainer.js @@ -33,6 +33,7 @@ import { NotFound } from '@totalsoft/rocket-ui' import workflowConfig from 'features/designer/constants/WorkflowConfig' import { skipParametersByParsing } from 'features/workflow/common/constants' import { CREATE_UPDATE_WORKFLOW_MUTATION } from '../mutations/CreateOrUpdateWorkflowMutation' +import { TASK_LIST_QUERY } from 'features/task/list/queries/TaskListQuery' const WorkflowContainer = () => { const { t } = useTranslation() @@ -73,7 +74,7 @@ const WorkflowContainer = () => { const [nameDialog, showNameDialog] = useState(false) const [tourDialog, showTourDialog] = useState(false) const [startTourDialog, showStartTourDialog] = useState(false) - + const toggleNameDialog = useCallback(() => showNameDialog(current => !current), []) const toggleTourDialog = useCallback(() => showTourDialog(current => !current), []) const toggleStartTourDialog = useCallback(() => showStartTourDialog(current => !current), []) @@ -83,6 +84,8 @@ const WorkflowContainer = () => { variables: { name, version, skip: isNew } }) + const { data: tasks } = useQueryWithErrorHandling(TASK_LIST_QUERY, {}) + const errorStatus = error?.graphQLErrors[0]?.extensions?.response?.status useEffect(() => { @@ -144,6 +147,10 @@ const WorkflowContainer = () => { }, [isValid]) const handleSave = useCallback(() => { + if (workflow?.tasks) { + drawDiagram(workflow, engine, workflow?.readOnly, tasks?.getTaskDefinitionList) + } + if (isValid()) { try { const jsonObject = parseDiagramToJSON(engine) @@ -168,6 +175,7 @@ const WorkflowContainer = () => { showError(err) } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ createOrUpdateWorkflow, engine, @@ -185,7 +193,9 @@ const WorkflowContainer = () => { workflow?.ownerEmail, workflow?.timeoutSeconds, workflow?.workflowStatusListenerEnabled, - workflowLens + workflowLens, + workflow?.tasks, + tasks?.getTaskDefinitionList ]) const handleTaskDialogCancel = useCallback(() => { @@ -227,16 +237,13 @@ const WorkflowContainer = () => { parseObjectParameters(node.inputs, skipParametersByParsing) node.options.name = inputs?.inputs?.name - if ( - inputs?.inputs.type === nodeConfig.DECISION.type && - (isPropertyDirty('inputs.decisionCases', inputsDirtyInfo)) - ) { + if (inputs?.inputs.type === nodeConfig.DECISION.type && isPropertyDirty('inputs.decisionCases', inputsDirtyInfo)) { const cases = keys(inputs?.inputs?.decisionCases) decisionCasesToPorts(node, cases) } if (inputs?.inputs?.type === nodeConfig.EVENT.type) { try { - node.options.name = inputs?.inputs?.taskReferenceName; + node.options.name = inputs?.inputs?.taskReferenceName if (localPayload) { const payload = JSON.parse(localPayload) node.inputs.inputParameters = @@ -335,6 +342,7 @@ const WorkflowContainer = () => { diagram={diagram} loading={loading} isDirty={isDirty} + taskDefs={tasks?.getTaskDefinitionList} setIsDirty={setIsDirty} /> { title={t('Workflow.AddName')} disableBackdropClick maxWidth='sm' - showAc actions={[ - , - ]} diff --git a/react-ui/src/features/workflow/edit/components/WorkflowJson.js b/react-ui/src/features/workflow/edit/components/WorkflowJson.js new file mode 100644 index 0000000..64a62f8 --- /dev/null +++ b/react-ui/src/features/workflow/edit/components/WorkflowJson.js @@ -0,0 +1,103 @@ +import React, { useCallback, useState, useRef } from 'react' +import PropTypes from 'prop-types' +import { FakeText } from '@totalsoft/rocket-ui' +import { useTranslation } from 'react-i18next' +import { Grid } from '@mui/material' +import WorkflowDAG from 'features/designer/diagram/WorkflowDAG' +import WorkflowGraph from 'features/designer/diagram/WorkflowGraph' +import Editor from '@monaco-editor/react' + +const WorkflowJson = ({ loading, workflow, onChangeJson }) => { + const { t } = useTranslation() + const editorRef = useRef() + const [decorations, setDecorations] = useState([]) + + const handleOnClick = useCallback( + node => { + let editor = editorRef.current.getModel() + let searchResult = editor.findMatches(`"taskReferenceName": "${node.ref.ref}"`) + if (searchResult.length) { + editorRef.current.revealLineInCenter(searchResult[0]?.range?.startLineNumber, 0) + setDecorations( + editorRef.current.deltaDecorations(decorations, [ + { + range: searchResult[0]?.range, + options: { + isWholeLine: true + //inlineClassName: classes.editorLineDecorator + } + } + ]) + ) + } + }, + [decorations] + ) + + const onchange = useCallback( + v => { + //debugger + try { + JSON.parse(v) + onChangeJson(v) + } catch { + console.log('json not valid') + } + }, + [onChangeJson] + ) + const dag = new WorkflowDAG(null, workflow) + const formatJson = useCallback((editor, _monaco) => { + editorRef.current = editor + //setTimeout(() => editor.getAction('editor.action.formatDocument').run(), 100) + }, []) + + if (loading) { + return + } + + return ( + <> +
+ + + { + if (key === '__typename' || key === 'startHandlers') return + return value + }, + '\t' + )} + onMount={formatJson} + onChange={onchange} + /> + + + + + + + ) +} + +WorkflowJson.propTypes = { + loading: PropTypes.bool.isRequired, + workflow: PropTypes.object, + onChangeJson: PropTypes.func.isRequired +} + +export default WorkflowJson diff --git a/react-ui/src/features/workflow/edit/components/modals/GeneralSettingsDialog.js b/react-ui/src/features/workflow/edit/components/modals/GeneralSettingsDialog.js index 12b053c..7523a3b 100644 --- a/react-ui/src/features/workflow/edit/components/modals/GeneralSettingsDialog.js +++ b/react-ui/src/features/workflow/edit/components/modals/GeneralSettingsDialog.js @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next' import PropTypes from 'prop-types' import { Dialog } from '@totalsoft/rocket-ui' import GeneralSettingsModal from './GeneralSettingsModal' +import { Button } from '@totalsoft/rocket-ui' const GeneralSettingsDialog = ({ open, onClose, onYes, workflowLens }) => { const { t } = useTranslation() @@ -11,10 +12,15 @@ const GeneralSettingsDialog = ({ open, onClose, onYes, workflowLens }) => { + {t('General.Buttons.Save')} + , + + ]} title={t('Designer.UtilitiesBar.GeneralSettings')} maxWidth='md' disableBackdropClick diff --git a/react-ui/src/features/workflow/edit/components/modals/GeneralSettingsModal.js b/react-ui/src/features/workflow/edit/components/modals/GeneralSettingsModal.js index 7f15d7e..a80cdb1 100644 --- a/react-ui/src/features/workflow/edit/components/modals/GeneralSettingsModal.js +++ b/react-ui/src/features/workflow/edit/components/modals/GeneralSettingsModal.js @@ -13,8 +13,8 @@ const GeneralSettingsModal = ({ workflowLens }) => { const workflow = workflowLens |> get const handleChange = useCallback( - propPath => event => { - set(workflowLens[propPath], event.target.value) + propPath => value => { + set(workflowLens[propPath], value) }, [workflowLens] ) diff --git a/react-ui/src/features/workflow/edit/components/workflowTask/EditTaskModal.js b/react-ui/src/features/workflow/edit/components/workflowTask/EditTaskModal.js index 04123b2..ee17b5c 100644 --- a/react-ui/src/features/workflow/edit/components/workflowTask/EditTaskModal.js +++ b/react-ui/src/features/workflow/edit/components/workflowTask/EditTaskModal.js @@ -61,6 +61,7 @@ const EditTaskModal = ({ onCancel, onSave, inputsLens, dirtyInfo, onPayloadChang {tabIndex == 1 && ( diff --git a/react-ui/src/features/workflow/edit/components/workflowTask/InputParameterItem.js b/react-ui/src/features/workflow/edit/components/workflowTask/InputParameterItem.js index 224be0b..4af31ff 100644 --- a/react-ui/src/features/workflow/edit/components/workflowTask/InputParameterItem.js +++ b/react-ui/src/features/workflow/edit/components/workflowTask/InputParameterItem.js @@ -1,30 +1,75 @@ -import React from 'react' +import React, { useCallback } from 'react' import PropTypes from 'prop-types' -import { Grid } from '@mui/material' +import { Grid, Typography } from '@mui/material' import { TextField, IconButton } from '@totalsoft/rocket-ui' import { get, set } from '@totalsoft/rules-algebra-react' import { onTextBoxChange } from 'utils/propertyChangeAdapters' import { useTranslation } from 'react-i18next' -function InputParameterItem({ valueLens, param, onRemove }) { +import Editor from '@monaco-editor/react' + +function InputParameterItem({ valueLens, param, onRemove, isFromTemplate, typeFromTemplate }) { const { t } = useTranslation() const value = valueLens |> get + const onInputTemplateChange = useCallback( + value => { + set(valueLens, value) + }, + [valueLens] + ) + + const isJson = () => { + return typeFromTemplate === 'object' + } + return ( - - set |> onTextBoxChange} - debounceBy={100} - variant='outlined' - multiline - maxRows={5} - /> - + {isJson() ? ( + + {param} + + + ) : ( + + set |> onTextBoxChange} + debounceBy={100} + variant='outlined' + multiline + maxRows={5} + /> + + )} - + {!isFromTemplate && ( + + )} ) @@ -33,7 +78,9 @@ function InputParameterItem({ valueLens, param, onRemove }) { InputParameterItem.propTypes = { valueLens: PropTypes.object.isRequired, param: PropTypes.string.isRequired, - onRemove: PropTypes.func.isRequired + onRemove: PropTypes.func.isRequired, + isFromTemplate: PropTypes.bool, + typeFromTemplate: PropTypes.string } export default InputParameterItem diff --git a/react-ui/src/features/workflow/edit/components/workflowTask/InputParameters.js b/react-ui/src/features/workflow/edit/components/workflowTask/InputParameters.js index 3b4d893..ec69db3 100644 --- a/react-ui/src/features/workflow/edit/components/workflowTask/InputParameters.js +++ b/react-ui/src/features/workflow/edit/components/workflowTask/InputParameters.js @@ -9,7 +9,7 @@ import InputParametersHeader from './InputParametersHeader' import { Divider } from '@mui/material' import EventNodeInputParameters from 'features/designer/nodeModels/eventNode/EventNodeInputParameters' -const InputParameters = ({ inputParametersLens, nodeType, onPayloadChange }) => { +const InputParameters = ({ inputParametersLens, inputTemplate, nodeType, onPayloadChange }) => { const renderInputParameters = type => { switch (type) { case nodeConfig.HTTP.type: @@ -36,7 +36,7 @@ const InputParameters = ({ inputParametersLens, nodeType, onPayloadChange }) => <> - + ) } @@ -46,6 +46,7 @@ const InputParameters = ({ inputParametersLens, nodeType, onPayloadChange }) => InputParameters.propTypes = { inputParametersLens: PropTypes.object.isRequired, + inputTemplate: PropTypes.object, nodeType: PropTypes.string.isRequired, onPayloadChange: PropTypes.func.isRequired } diff --git a/react-ui/src/features/workflow/edit/components/workflowTask/InputParametersList.js b/react-ui/src/features/workflow/edit/components/workflowTask/InputParametersList.js index de6db05..54c92d2 100644 --- a/react-ui/src/features/workflow/edit/components/workflowTask/InputParametersList.js +++ b/react-ui/src/features/workflow/edit/components/workflowTask/InputParametersList.js @@ -4,9 +4,11 @@ import { Grid } from '@mui/material' import InputParameterItem from './InputParameterItem' import { get, over } from '@totalsoft/rules-algebra-react' import { dissoc, keys } from 'ramda' -function InputParametersList({ inputParametersLens }) { + +function InputParametersList({ inputParametersLens, inputTemplate }) { const inputParameters = inputParametersLens |> get const properties = keys(inputParameters) + const template = inputTemplate ? keys(inputTemplate |> get) : [] const handleRemoveItem = useCallback( key => () => { @@ -20,7 +22,14 @@ function InputParametersList({ inputParametersLens }) { {properties?.map( (param, index) => param !== 'scriptExpression' && ( - + get) : ''} + onRemove={handleRemoveItem(param)} + /> ) )} @@ -28,7 +37,8 @@ function InputParametersList({ inputParametersLens }) { } InputParametersList.propTypes = { - inputParametersLens: PropTypes.object.isRequired + inputParametersLens: PropTypes.object.isRequired, + inputTemplate: PropTypes.object } export default InputParametersList diff --git a/react-ui/src/features/workflow/edit/queries/WorkflowQuery.js b/react-ui/src/features/workflow/edit/queries/WorkflowQuery.js index ff99fcf..95f920a 100644 --- a/react-ui/src/features/workflow/edit/queries/WorkflowQuery.js +++ b/react-ui/src/features/workflow/edit/queries/WorkflowQuery.js @@ -5,6 +5,31 @@ export const WORKFLOW_QUERY = gql` query getWorkflow($name: String!, $version: Int!, $skip: Boolean!) { getWorkflow(name: $name, version: $version) @skip(if: $skip) { ...partialWorkflowDef + startHandlers { + name + active + event + condition + actions { + action + completeTask { + output + workflowId + taskRefName + } + failTask { + output + workflowId + taskRefName + } + startWorkflow { + name + version + input + } + expandInlineJSON + } + } tasks { ...workflowTask forkTasks { diff --git a/react-ui/src/utils/propertyChangeAdapters/__tests__/index.tests.js b/react-ui/src/utils/propertyChangeAdapters/__tests__/index.tests.js index 7de9bd3..8afe178 100644 --- a/react-ui/src/utils/propertyChangeAdapters/__tests__/index.tests.js +++ b/react-ui/src/utils/propertyChangeAdapters/__tests__/index.tests.js @@ -1,15 +1,6 @@ import { addPropertyPrefix } from '../index' describe('propertyChangeAdapters tests suite:', () => { - // it("addPropertyPrefix should be memoized: ", () => { - // const prefix = "prefix"; - // const fn = str => str; - - // const firstResult = addPropertyPrefix(prefix, fn); - // const secondResult = addPropertyPrefix(prefix, fn); - // expect(Object.is(firstResult, secondResult)).toBe(true); - // }) - it('addPropertyPrefix should prefix first param:', () => { const prefix = 'prefix' const fn = str => str diff --git a/react-ui/yarn.lock b/react-ui/yarn.lock index fcd4ce6..4548000 100644 --- a/react-ui/yarn.lock +++ b/react-ui/yarn.lock @@ -2909,6 +2909,30 @@ __metadata: languageName: node linkType: hard +"@monaco-editor/loader@npm:^1.4.0": + version: 1.4.0 + resolution: "@monaco-editor/loader@npm:1.4.0" + dependencies: + state-local: ^1.0.6 + peerDependencies: + monaco-editor: ">= 0.21.0 < 1" + checksum: 374ec0ea872ee15b33310e105a43217148161480d3955c5cece87d0f801754cd2c45a3f6c539a75da18a066c1615756fb87eaf1003f1df6a64a0cbce5d2c3749 + languageName: node + linkType: hard + +"@monaco-editor/react@npm:^4.6.0": + version: 4.6.0 + resolution: "@monaco-editor/react@npm:4.6.0" + dependencies: + "@monaco-editor/loader": ^1.4.0 + peerDependencies: + monaco-editor: ">= 0.25.0 < 1" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 9d44e76c5baad6db5f84c90a5540fbd3c9af691b97d76cf2a99b3c8273004d0efe44c2572d80e9d975c9af10022c21e4a66923924950a5201e82017c8b20428c + languageName: node + linkType: hard + "@mui/base@npm:5.0.0-beta.6": version: 5.0.0-beta.6 resolution: "@mui/base@npm:5.0.0-beta.6" @@ -4703,13 +4727,6 @@ __metadata: languageName: node linkType: hard -"JSV@npm:^4.0.2": - version: 4.0.2 - resolution: "JSV@npm:4.0.2" - checksum: 9e2e070b2149d4ffed6c135dedb5f8cebb870dc63c0ca6d3cb647067248c7ca15d1a934ee46e9d2ac0e468436d0f70bc1b63cd5f93e705ee9aa0669430e6e66f - languageName: node - linkType: hard - "abab@npm:^2.0.3, abab@npm:^2.0.5, abab@npm:^2.0.6": version: 2.0.6 resolution: "abab@npm:2.0.6" @@ -4734,20 +4751,6 @@ __metadata: languageName: node linkType: hard -"ace-builds@npm:1.23.1": - version: 1.23.1 - resolution: "ace-builds@npm:1.23.1" - checksum: e0172764279dea7c01e8b6c40ec9c0ac8874931e5fba8ef9d9e513a1e670ea60683d153be25af22c2edee7a5f9712b5f22a502acf3a7699b536d3e8e196fa715 - languageName: node - linkType: hard - -"ace-builds@npm:^1.4.14": - version: 1.23.3 - resolution: "ace-builds@npm:1.23.3" - checksum: b703efa5353e29fff1e91484ebe36c8ebe401c3dfc32aaa3f3d225809d3b4a892e7ae5a1612f82b5060da14ccfd2d9b7ae2201f0037b75a193ca3da4ff6941c9 - languageName: node - linkType: hard - "acorn-globals@npm:^6.0.0": version: 6.0.0 resolution: "acorn-globals@npm:6.0.0" @@ -5295,6 +5298,7 @@ __metadata: "@date-io/moment": ^2.16.1 "@emotion/react": ^11.11.1 "@emotion/styled": ^11.11.0 + "@monaco-editor/react": ^4.6.0 "@mui/icons-material": ^5.11.16 "@mui/lab": ^5.0.0-alpha.134 "@mui/material": ^5.13.6 @@ -5307,7 +5311,6 @@ __metadata: "@totalsoft/rocket-ui": ^0.1.25 "@totalsoft/rules-algebra-react": ^5.0.22 "@totalsoft/validations": ^1.0.4 - ace-builds: 1.23.1 apollo-cache-inmemory: ^1.6.6 apollo-link-mock: ^1.0.1 apollo-upload-client: ^17.0.0 @@ -5334,7 +5337,6 @@ __metadata: jest-environment-jsdom: ^29.6.1 jest-junit: ^16.0.0 js-beautify: ^1.14.0 - jsonlint-mod: 1.7.6 lodash: ^4.17.21 lodash.curry: ^4.1.1 mini-css-extract-plugin: 2.7.6 @@ -5347,7 +5349,6 @@ __metadata: prettier: ^3.0.0 ramda: ^0.29.0 react: "18.2.0 " - react-ace: 10.1.0 react-app-rewired: ^2.1.11 react-country-flag: 3.1.0 react-diff-viewer: ^3.1.1 @@ -5363,6 +5364,7 @@ __metadata: reactour: ^1.18.6 resize-observer-polyfill: ^1.5.1 simplebar-react: ^3.2.4 + source-map-explorer: 2.5.3 styled-components: 6.0.2 subscriptions-transport-ws: ^0.11.0 languageName: unknown @@ -5837,6 +5839,15 @@ __metadata: languageName: node linkType: hard +"btoa@npm:^1.2.1": + version: 1.2.1 + resolution: "btoa@npm:1.2.1" + bin: + btoa: bin/btoa.js + checksum: afbf004fb1b1d530e053ffa66ef5bd3878b101c59d808ac947fcff96810b4452abba2b54be687adadea2ba9efc7af48b04228742789bf824ef93f103767e690c + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -5973,7 +5984,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.0.0, chalk@npm:^2.4.1, chalk@npm:^2.4.2": +"chalk@npm:^2.0.0, chalk@npm:^2.4.1": version: 2.4.2 resolution: "chalk@npm:2.4.2" dependencies: @@ -6436,13 +6447,20 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^3.19.2, core-js@npm:^3.8.3": +"core-js@npm:^3.19.2": version: 3.31.1 resolution: "core-js@npm:3.31.1" checksum: 14519213a63c55cf188bdd2f4dece54583feaf6b90e75d6c65e07f509cd487055bf64898aeda7c97c36029ac1ea2f2ed8e4b02281553f6a257e7143a32a14015 languageName: node linkType: hard +"core-js@npm:^3.8.3": + version: 3.33.2 + resolution: "core-js@npm:3.33.2" + checksum: 71de081acbd060ff985afdcdf2552de4a00ab3ac4695c77f3535b72ddf4526920dcd0cb73e72e57c2ae16e384838a6d55790e138f0a19d60afcf851f89d0064d + languageName: node + linkType: hard + "core-util-is@npm:~1.0.0": version: 1.0.3 resolution: "core-util-is@npm:1.0.3" @@ -6530,9 +6548,9 @@ __metadata: linkType: hard "crypto-js@npm:^4.0.0": - version: 4.1.1 - resolution: "crypto-js@npm:4.1.1" - checksum: b3747c12ee3a7632fab3b3e171ea50f78b182545f0714f6d3e7e2858385f0f4101a15f2517e033802ce9d12ba50a391575ff4638c9de3dd9b2c4bc47768d5425 + version: 4.2.0 + resolution: "crypto-js@npm:4.2.0" + checksum: f051666dbc077c8324777f44fbd3aaea2986f198fe85092535130d17026c7c2ccf2d23ee5b29b36f7a4a07312db2fae23c9094b644cc35f7858b1b4fcaf27774 languageName: node linkType: hard @@ -7806,13 +7824,6 @@ __metadata: languageName: node linkType: hard -"diff-match-patch@npm:^1.0.5": - version: 1.0.5 - resolution: "diff-match-patch@npm:1.0.5" - checksum: 841522d01b09cccbc4e4402cf61514a81b906349a7d97b67222390f2d35cf5df277cb23959eeed212d5e46afb5629cebab41b87918672c5a05c11c73688630e3 - languageName: node - linkType: hard - "diff-sequences@npm:^27.5.1": version: 27.5.1 resolution: "diff-sequences@npm:27.5.1" @@ -8090,7 +8101,7 @@ __metadata: languageName: node linkType: hard -"ejs@npm:^3.1.6": +"ejs@npm:^3.1.5, ejs@npm:^3.1.6": version: 3.1.9 resolution: "ejs@npm:3.1.9" dependencies: @@ -8383,7 +8394,7 @@ __metadata: languageName: node linkType: hard -"escape-html@npm:~1.0.3": +"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" checksum: 6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24 @@ -10564,7 +10575,7 @@ __metadata: languageName: node linkType: hard -"is-wsl@npm:^2.2.0": +"is-wsl@npm:^2.1.1, is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" dependencies: @@ -11950,19 +11961,6 @@ __metadata: languageName: node linkType: hard -"jsonlint-mod@npm:1.7.6": - version: 1.7.6 - resolution: "jsonlint-mod@npm:1.7.6" - dependencies: - JSV: ^4.0.2 - chalk: ^2.4.2 - underscore: ^1.9.1 - bin: - jsonlint: lib/cli.js - checksum: 5d2bca547fa009f25c15dff448dde730b660fa8239ceea43cf86cce0dfc402448d7bf200c68c26a18b60e8d994e4f7cbdaecd41ff5bcce1441badd6e60eeedb7 - languageName: node - linkType: hard - "jsonpointer@npm:^5.0.0": version: 5.0.1 resolution: "jsonpointer@npm:5.0.1" @@ -12685,16 +12683,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": - version: 1.0.4 - resolution: "mkdirp@npm:1.0.4" - bin: - mkdirp: bin/cmd.js - checksum: a96865108c6c3b1b8e1d5e9f11843de1e077e57737602de1b82030815f311be11f96f09cce59bd5b903d0b29834733e5313f9301e3ed6d6f6fba2eae0df4298f - languageName: node - linkType: hard - -"mkdirp@npm:~0.5.1": +"mkdirp@npm:^0.5.1, mkdirp@npm:~0.5.1": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -12705,6 +12694,15 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: a96865108c6c3b1b8e1d5e9f11843de1e077e57737602de1b82030815f311be11f96f09cce59bd5b903d0b29834733e5313f9301e3ed6d6f6fba2eae0df4298f + languageName: node + linkType: hard + "moment@npm:2.29.4, moment@npm:^2.24.0": version: 2.29.4 resolution: "moment@npm:2.29.4" @@ -13130,6 +13128,16 @@ __metadata: languageName: node linkType: hard +"open@npm:^7.3.1": + version: 7.4.2 + resolution: "open@npm:7.4.2" + dependencies: + is-docker: ^2.0.0 + is-wsl: ^2.1.1 + checksum: 3333900ec0e420d64c23b831bc3467e57031461d843c801f569b2204a1acc3cd7b3ec3c7897afc9dde86491dfa289708eb92bba164093d8bd88fb2c231843c91 + languageName: node + linkType: hard + "open@npm:^8.0.9, open@npm:^8.4.0": version: 8.4.2 resolution: "open@npm:8.4.2" @@ -14593,22 +14601,6 @@ __metadata: languageName: node linkType: hard -"react-ace@npm:10.1.0": - version: 10.1.0 - resolution: "react-ace@npm:10.1.0" - dependencies: - ace-builds: ^1.4.14 - diff-match-patch: ^1.0.5 - lodash.get: ^4.4.2 - lodash.isequal: ^4.5.0 - prop-types: ^15.7.2 - peerDependencies: - react: ^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 - checksum: 8fc67e02f911dcc31cf474df97c06f2ba5b5f402d9ed425895e479e2daddf4894a2cd0e81744b37b7f2b07109cfe4470fc5ebeed305d648c596e3442142e4823 - languageName: node - linkType: hard - "react-app-polyfill@npm:^3.0.0": version: 3.0.0 resolution: "react-app-polyfill@npm:3.0.0" @@ -15400,6 +15392,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:~2.6.2": + version: 2.6.3 + resolution: "rimraf@npm:2.6.3" + dependencies: + glob: ^7.1.3 + bin: + rimraf: ./bin.js + checksum: 3ea587b981a19016297edb96d1ffe48af7e6af69660e3b371dbfc73722a73a0b0e9be5c88089fbeeb866c389c1098e07f64929c7414290504b855f54f901ab10 + languageName: node + linkType: hard + "robust-predicates@npm:^3.0.0": version: 3.0.2 resolution: "robust-predicates@npm:3.0.2" @@ -15942,6 +15945,29 @@ __metadata: languageName: node linkType: hard +"source-map-explorer@npm:2.5.3": + version: 2.5.3 + resolution: "source-map-explorer@npm:2.5.3" + dependencies: + btoa: ^1.2.1 + chalk: ^4.1.0 + convert-source-map: ^1.7.0 + ejs: ^3.1.5 + escape-html: ^1.0.3 + glob: ^7.1.6 + gzip-size: ^6.0.0 + lodash: ^4.17.20 + open: ^7.3.1 + source-map: ^0.7.4 + temp: ^0.9.4 + yargs: ^16.2.0 + bin: + sme: bin/cli.js + source-map-explorer: bin/cli.js + checksum: 1d4e619d7eb224f38a3dadfb20eb34a56cfc29bd237b4815b60257e7fe5ee9f791fda3e0bba91318e0f2beffec5cca573abb8b5030a95f305ce4abee93296065 + languageName: node + linkType: hard + "source-map-js@npm:^1.0.1, source-map-js@npm:^1.0.2": version: 1.0.2 resolution: "source-map-js@npm:1.0.2" @@ -15996,7 +16022,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.7.3": +"source-map@npm:^0.7.3, source-map@npm:^0.7.4": version: 0.7.4 resolution: "source-map@npm:0.7.4" checksum: 01cc5a74b1f0e1d626a58d36ad6898ea820567e87f18dfc9d24a9843a351aaa2ec09b87422589906d6ff1deed29693e176194dc88bcae7c9a852dc74b311dbf5 @@ -16085,6 +16111,13 @@ __metadata: languageName: node linkType: hard +"state-local@npm:^1.0.6": + version: 1.0.7 + resolution: "state-local@npm:1.0.7" + checksum: d1afcf1429e7e6eb08685b3a94be8797db847369316d4776fd51f3962b15b984dacc7f8e401ad20968e5798c9565b4b377afedf4e4c4d60fe7495e1cbe14a251 + languageName: node + linkType: hard + "statuses@npm:2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" @@ -16580,6 +16613,16 @@ __metadata: languageName: node linkType: hard +"temp@npm:^0.9.4": + version: 0.9.4 + resolution: "temp@npm:0.9.4" + dependencies: + mkdirp: ^0.5.1 + rimraf: ~2.6.2 + checksum: 8709d4d63278bd309ca0e49e80a268308dea543a949e71acd427b3314cd9417da9a2cc73425dd9c21c6780334dbffd67e05e7be5aaa73e9affe8479afc6f20e3 + languageName: node + linkType: hard + "tempy@npm:^0.6.0": version: 0.6.0 resolution: "tempy@npm:0.6.0" @@ -16933,13 +16976,6 @@ __metadata: languageName: node linkType: hard -"underscore@npm:^1.9.1": - version: 1.13.6 - resolution: "underscore@npm:1.13.6" - checksum: d5cedd14a9d0d91dd38c1ce6169e4455bb931f0aaf354108e47bd46d3f2da7464d49b2171a5cf786d61963204a42d01ea1332a903b7342ad428deaafaf70ec36 - languageName: node - linkType: hard - "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0"