aboutsummaryrefslogtreecommitdiffstats
path: root/sdc-workflow-designer-ui/src/main/frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'sdc-workflow-designer-ui/src/main/frontend/src')
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/App.js106
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/appConstants.js37
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/appSelectors.js16
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/config/Configuration.js63
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/config/config.json6
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesActions.js25
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesApi.js35
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesConstants.js24
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesReducer.js26
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesSaga.js41
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesSelectors.js22
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/Catalog.js53
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/CatalogView.jsx182
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogActions-test.js65
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogReducer-test.js112
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSagas-test.js109
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogActions.js48
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogApi.js45
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogConstants.js31
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogReducer.js60
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSagas.js56
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/AddWorkflow.jsx45
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/Header.jsx46
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/Main.jsx79
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/StatusSelector.js24
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/Workflows.jsx47
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/Version.js19
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/VersionView.jsx127
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/Composition.js62
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionUpdate.js131
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js284
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/bpmnUtils.js93
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButton.js36
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButtonsPanel.js56
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionActions.js34
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionConstants.js38
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionReducer.js44
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionSelectors.js32
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomContextPadProvider.js43
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomElementFactory.js101
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomPalette.js151
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRenderer.js176
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRules.js136
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomUpdater.js136
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/index.js22
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/index.js99
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/descriptors/camunda.json1025
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/WorkflowPropertiesProvider.js300
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/index.js10
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/InputOutputParameterProps.js21
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/InputOutputProps.js13
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/WorkflowServiceTaskDelegateProps.js123
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/createInputOutputTabGroups.js58
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/Delegate.js78
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutput.js289
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutputHelper.js173
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutputParameter.js424
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutputUpdater.js74
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/ResultVariable.js52
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/WorkflowActivity.js89
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/WorkflowImplementationType.js226
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/implementationConstants.js34
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/newDiagram.bpmn9
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/readOnly.js134
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/CreateVersion.js53
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/CreateVersionView.jsx95
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/__tests__/CreateVersionView_snapshot-test.js28
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/__tests__/__snapshots__/CreateVersionView_snapshot-test.js.snap81
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/createVersionConstants.js30
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/views/NewVersionContainer.jsx45
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/general/General.js39
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/general/GeneralView.js61
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/general/generalSelectors.js33
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/InputOutput.js83
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/InputOutputView.jsx234
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/__tests__/inputOutputActions-test.js150
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/__tests__/inputOutputReducer-test.js191
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/__tests__/inputOutputSelectors-test.js149
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputActions.js53
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputConstants.js39
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputReducer.js130
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputSelectors.js106
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputValidations.js64
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/DataRow.jsx87
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/NoDataRow.jsx32
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/Tab.js43
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/TableBody.jsx50
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/TableHead.jsx41
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js82
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionConstants.js59
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionController.js73
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionControllerView.jsx171
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/__tests__/VersionControllerView_snapshot-test.js59
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/__tests__/__snapshots__/VersionControllerView_snapshot-test.js.snap39
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerConstants.js29
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js36
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/ActionButtons.js92
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/OperationModeButtons.js70
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/SvgButton.js62
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/VersionButton.js38
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/VersionSelect.js69
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/VersionsContainer.js60
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/WorkflowTitle.js34
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionModeReducer.js10
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionReducer.js51
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js167
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/CreateWorkflow.js61
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/CreateWorkflowView.jsx101
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/CreateWorkflowView_snapshot-test.js36
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/__snapshots__/CreateWorkflowView_snapshot-test.js.snap83
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/createWorkflowSaga-test.js76
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/createWorkflowSelector-test.js51
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowApi.js30
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowConstants.js44
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowSaga.js79
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowSelector.js31
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/Overview.js86
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/OverviewView.jsx142
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/OverviewView_snapshot-test.js39
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/__snapshots__/OverviewView_snapshot-test.js.snap217
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/overviewReducer-test.js96
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewApi.js46
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewConstansts.js55
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewReducer.js26
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewSagas.js104
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewSelectors.js62
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/VersionList/VersionListItem.jsx74
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/VersionList/index.js51
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/VersionTree.jsx43
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/WorkflowDetails.jsx75
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/WorkflowHeader.jsx106
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/WorkflowVersions.jsx67
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/workflowConstants.js27
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/workflowReducer.js60
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/workflowSelectors.js34
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/i18n/I18n.js31
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/i18n/i18next-scanner.config.js44
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/i18n/languages.json97
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/i18n/translationSaga.js24
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/index.js40
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextActions.js21
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextConstants.js22
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextReducer.js25
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextSelector.js16
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/rootReducers.js51
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/rootSaga.js38
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/routes.js59
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/services/restAPIUtil.js150
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/archiveLabel/ArchiveLabel.js8
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/components/Description/index.js50
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/components/Select/index.js54
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/components/VersionInfo/index.js43
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/errorResponseHandler/errorResponseHandler.js31
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/Loader.jsx50
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderConstants.js38
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderReducer.js49
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/__tests__/loaderReducer-test.js56
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapper.js35
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapperView.jsx99
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperActions-test.js85
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperReducer-test.js61
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperActions.js46
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperComponents.js27
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperReducer.js32
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationLink.jsx48
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenu.jsx39
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItem.jsx44
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItems.jsx47
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/index.js73
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/Notifications.js36
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/NotificationsView.jsx56
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/NotificationView_snapshot-test.js30
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/__snapshots__/NotificationView_snapshot-test.js.snap9
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/notificationsReducer-test.js61
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsActions.js60
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsConstants.js21
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsReducer.js30
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsSagas.js33
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/pubsub/base-pubsub.ts127
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/pubsub/plugin-pubsub.ts30
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/scroll/InfiniteScroll.js161
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/scroll/Scrollbars.js58
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/searchInput/SearchInput.jsx131
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/shared/tree/Tree.jsx231
-rw-r--r--sdc-workflow-designer-ui/src/main/frontend/src/store.js42
185 files changed, 14224 insertions, 0 deletions
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/App.js b/sdc-workflow-designer-ui/src/main/frontend/src/App.js
new file mode 100644
index 00000000..5b7c154d
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/App.js
@@ -0,0 +1,106 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { hot } from 'react-hot-loader';
+import React, { Component } from 'react';
+import { Route, withRouter } from 'react-router-dom';
+import qs from 'qs';
+import { connect } from 'react-redux';
+import { PluginPubSub } from 'shared/pubsub/plugin-pubsub';
+import 'resources/scss/style.scss';
+import 'bpmn-js-properties-panel/styles/properties.less';
+import { routes } from 'wfapp/routes';
+import { USER_ID } from 'wfapp/appConstants';
+import { getVersionsAction } from 'features/workflow/overview/overviewConstansts';
+import { setOperationModeAction } from 'features/version/versionConstants';
+import { setPluginContext } from './pluginContext/pluginContextActions';
+import { notificationType } from 'wfapp/pluginContext/pluginContextConstants';
+const RouteWithSubRoutes = route => (
+ <Route
+ path={route.path}
+ exact={route.exact}
+ render={props => <route.component {...props} routes={route.routes} />}
+ />
+);
+
+function mapActionsToProps(dispatch) {
+ return {
+ getOverview: workflowId => {
+ dispatch(getVersionsAction(workflowId));
+ dispatch(setOperationModeAction());
+ },
+ setPluginContext: payload => dispatch(setPluginContext(payload))
+ };
+}
+
+class App extends Component {
+ constructor(props) {
+ super(props);
+
+ this.searchParams = qs.parse(location.search, {
+ ignoreQueryPrefix: true
+ });
+
+ if (this.searchParams && this.searchParams.userId) {
+ localStorage.setItem(USER_ID, this.searchParams.userId);
+ }
+ }
+
+ componentDidMount() {
+ if (this.searchParams) {
+ const {
+ eventsClientId,
+ parentUrl,
+ workflowId,
+ versionId
+ } = this.searchParams;
+
+ if (eventsClientId && parentUrl) {
+ this.props.setPluginContext({
+ eventsClientId,
+ parentUrl
+ });
+ const client = new PluginPubSub(eventsClientId, parentUrl);
+ client.notify(notificationType.READY);
+ }
+ if (workflowId && versionId) {
+ this.props.getOverview(workflowId);
+ this.props.history.push(
+ `/workflow/${workflowId}/version/${versionId}/composition`
+ );
+ }
+ }
+ }
+
+ render() {
+ return (
+ <div className="workflow-app">
+ {routes.map((route, i) => (
+ <RouteWithSubRoutes key={`App.route.${i}`} {...route} />
+ ))}
+ </div>
+ );
+ }
+}
+
+export default hot(module)(
+ withRouter(
+ connect(
+ null,
+ mapActionsToProps
+ )(App)
+ )
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/appConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/appConstants.js
new file mode 100644
index 00000000..710c4d8c
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/appConstants.js
@@ -0,0 +1,37 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createAction } from 'redux-actions';
+export const LANG = 'en';
+export const USER_ID = 'USER_ID';
+export const VERSION_LEVEL_LIST = [
+ {
+ id: '2',
+ name: 'Major',
+ value: '2'
+ },
+ {
+ id: '1',
+ name: 'Minor',
+ value: '1'
+ }
+];
+
+export const NETWORK_GENERIC_ERROR = 'NETWORK_GENERIC_ERROR';
+export const genericNetworkErrorAction = createAction(
+ NETWORK_GENERIC_ERROR,
+ error => error
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/appSelectors.js b/sdc-workflow-designer-ui/src/main/frontend/src/appSelectors.js
new file mode 100644
index 00000000..657429e4
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/appSelectors.js
@@ -0,0 +1,16 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const i18nSelector = state => state && state.i18n.locale;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/config/Configuration.js b/sdc-workflow-designer-ui/src/main/frontend/src/config/Configuration.js
new file mode 100644
index 00000000..36ad33d7
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/config/Configuration.js
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2018 European Support Limited
+ *
+ * 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 configData from './config.json';
+
+class Configuration {
+ get(key) {
+ return configData[key];
+ }
+
+ set(key, value) {
+ var prev = configData[key];
+ configData[key] = value;
+ return prev;
+ }
+
+ setCatalogApiRoot(CatalogApiRoot) {
+ let restCatalogPrefix = CatalogApiRoot,
+ restPrefix = CatalogApiRoot.replace(
+ /\/feProxy\b[^:]*$/,
+ '/feProxy/onboarding-api'
+ );
+
+ this.set('restPrefix', restPrefix);
+ this.set('restCatalogPrefix', restCatalogPrefix);
+ }
+
+ setCatalogApiHeaders(CatalogApiHeaders) {
+ this.set('CatalogApiHeaders', CatalogApiHeaders);
+
+ let { userId: { value: UserID } = {} } = CatalogApiHeaders;
+ this.set('UserID', UserID);
+ }
+}
+
+const configuration = new Configuration();
+
+(function setDefaultRestPrefixes(configuration) {
+ configuration.set('restPrefix', configuration.get('defaultRestPrefix'));
+ configuration.set(
+ 'restCatalogPrefix',
+ configuration.get('defaultRestCatalogPrefix')
+ );
+ configuration.set(
+ 'activitiesRestPrefix',
+ configuration.get('activitiesRestPrefix')
+ );
+ configuration.set('appContextPath', configuration.get('appContextPath'));
+})(configuration);
+
+export default configuration;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/config/config.json b/sdc-workflow-designer-ui/src/main/frontend/src/config/config.json
new file mode 100644
index 00000000..96a7ca5b
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/config/config.json
@@ -0,0 +1,6 @@
+{
+ "version": "0.1",
+ "appContextPath" : "/",
+ "defaultRestPrefix": "/workflows/wf",
+ "activitiesRestPrefix": "/workflows/v1.0"
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesActions.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesActions.js
new file mode 100644
index 00000000..833f6e5f
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesActions.js
@@ -0,0 +1,25 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { SET_ACTIVITIES_LIST, GET_ACTIVITIES } from './activitiesConstants';
+
+export const setActivitiesList = payload => ({
+ type: SET_ACTIVITIES_LIST,
+ payload
+});
+
+export const getActivitiesList = () => ({
+ type: GET_ACTIVITIES
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesApi.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesApi.js
new file mode 100644
index 00000000..10141e09
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesApi.js
@@ -0,0 +1,35 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 RestfulAPIUtil from 'services/restAPIUtil';
+import Configuration from 'config/Configuration.js';
+import { activityStatus } from './activitiesConstants';
+
+function baseUrl() {
+ const restPrefix = Configuration.get('activitiesRestPrefix');
+ return `${restPrefix}/activity-spec`;
+}
+
+export default {
+ fetchActivities: () => {
+ return RestfulAPIUtil.fetch(
+ `${baseUrl()}?status=${activityStatus.CERTIFIED}`
+ );
+ },
+
+ fetchActivity: id => {
+ return RestfulAPIUtil.fetch(`${baseUrl()}/${id}/versions/latest`);
+ }
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesConstants.js
new file mode 100644
index 00000000..f99789c2
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesConstants.js
@@ -0,0 +1,24 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const SET_ACTIVITIES_LIST = 'activites/SET_ACTIVITIES_LIST';
+export const GET_ACTIVITIES = 'activities/GET_ACTIVITIES';
+
+export const activityStatus = {
+ CERTIFIED: 'Certified',
+ DRAFT: 'Draft',
+ DEPRICATED: 'Depricated',
+ DELETED: 'Deleted'
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesReducer.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesReducer.js
new file mode 100644
index 00000000..5c361401
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesReducer.js
@@ -0,0 +1,26 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { SET_ACTIVITIES_LIST } from './activitiesConstants';
+
+export default (state = [], action) => {
+ switch (action.type) {
+ case SET_ACTIVITIES_LIST: {
+ return action.payload;
+ }
+ default:
+ return state;
+ }
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesSaga.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesSaga.js
new file mode 100644
index 00000000..2350b9c3
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesSaga.js
@@ -0,0 +1,41 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { call, put, takeEvery, all } from 'redux-saga/effects';
+import { genericNetworkErrorAction } from 'src/appConstants';
+import { GET_ACTIVITIES } from './activitiesConstants';
+import activitiesApi from './activitiesApi';
+import { setActivitiesList } from './activitiesActions';
+
+function* fetchActivities() {
+ try {
+ const activitiesList = yield call(activitiesApi.fetchActivities);
+ const updatedActivitiesList = yield all(
+ activitiesList.items.map(item =>
+ call(activitiesApi.fetchActivity, item.id)
+ )
+ );
+
+ yield put(setActivitiesList(updatedActivitiesList));
+ } catch (error) {
+ yield put(genericNetworkErrorAction(error));
+ }
+}
+
+function* activitiesSaga() {
+ yield takeEvery(GET_ACTIVITIES, fetchActivities);
+}
+
+export default activitiesSaga;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesSelectors.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesSelectors.js
new file mode 100644
index 00000000..fc0c55ab
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/activities/activitiesSelectors.js
@@ -0,0 +1,22 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const activitiesSelector = state =>
+ state &&
+ state.activities.map(item => ({
+ ...item,
+ value: item.name
+ }));
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/Catalog.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/Catalog.js
new file mode 100644
index 00000000..b675b220
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/Catalog.js
@@ -0,0 +1,53 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { connect } from 'react-redux';
+
+import CatalogView from 'features/catalog/CatalogView';
+import {
+ fetchWorkflow,
+ searchChangedAction
+} from 'features/catalog/catalogActions';
+
+import { showCustomModalAction } from 'shared/modal/modalWrapperActions';
+import { NEW_WORKFLOW_MODAL } from 'shared/modal/modalWrapperComponents';
+import { clearWorkflowAction } from 'features/workflow/workflowConstants';
+
+const mapStateToProps = state => ({
+ catalog: state.catalog
+});
+
+const mapDispatchToProps = dispatch => ({
+ handleFetchWorkflow: ({ sort, offset, searchNameFilter, status }) =>
+ dispatch(fetchWorkflow({ sort, offset, searchNameFilter, status })),
+ clearWorkflow: () => dispatch(clearWorkflowAction),
+ showNewWorkflowModal: () =>
+ dispatch(
+ showCustomModalAction({
+ customComponentName: NEW_WORKFLOW_MODAL,
+ title: 'New Workflow'
+ })
+ ),
+ searchInputChanged: searchValue =>
+ dispatch(searchChangedAction(searchValue))
+});
+
+const Catalog = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(CatalogView);
+
+export default Catalog;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/CatalogView.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/CatalogView.jsx
new file mode 100644
index 00000000..edaa64f3
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/CatalogView.jsx
@@ -0,0 +1,182 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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, { Component } from 'react';
+import PropTypes from 'prop-types';
+import InfiniteScroll from 'shared/scroll/InfiniteScroll';
+import Workflows from 'features/catalog/views/Workflows';
+import AddWorkflow from 'features/catalog/views/AddWorkflow';
+import { WORKFLOW_STATUS } from 'features/workflow/workflowConstants';
+
+import Header from 'features/catalog/views/Header';
+import Main from 'features/catalog/views/Main';
+import { NAME, ASC, DESC } from 'features/catalog/catalogConstants';
+
+class CatalogView extends Component {
+ constructor(props) {
+ super(props);
+ }
+
+ componentDidMount() {
+ const { clearWorkflow } = this.props;
+ clearWorkflow();
+ this.fetchWorkflows();
+ }
+
+ fetchWorkflows = () => {
+ const {
+ catalog: { sort, status, searchNameFilter },
+ handleFetchWorkflow
+ } = this.props;
+ handleFetchWorkflow({
+ sort,
+ searchNameFilter,
+ status
+ });
+ };
+
+ handleAlphabeticalOrderByClick = e => {
+ e.preventDefault();
+
+ const {
+ handleFetchWorkflow,
+ catalog: { sort, status, searchNameFilter }
+ } = this.props;
+
+ const payload = {
+ ...sort
+ };
+
+ payload[NAME] = payload[NAME] === ASC ? DESC : ASC;
+ handleFetchWorkflow({
+ sort: payload,
+ searchNameFilter,
+ status
+ });
+ };
+ handleStatusChange = value => {
+ const {
+ handleFetchWorkflow,
+ catalog: { sort, searchNameFilter }
+ } = this.props;
+
+ handleFetchWorkflow({
+ sort,
+ searchNameFilter,
+ status: value
+ });
+ };
+
+ handleScroll = () => {
+ const {
+ catalog: {
+ paging: { offset },
+ sort,
+ status,
+ searchNameFilter
+ },
+ handleFetchWorkflow
+ } = this.props;
+ handleFetchWorkflow({
+ sort,
+ offset,
+ searchNameFilter,
+ status
+ });
+ };
+
+ goToOverviewPage = id => {
+ const { history } = this.props;
+ const { location } = history;
+ history.push(`${location.pathname}workflow/${id}/overview`);
+ };
+
+ searchChange = searchValue => {
+ this.setState({ searchValue: searchValue });
+ this.dispatchChange(searchValue);
+ };
+
+ dispatchChange = searchValue => {
+ const { searchInputChanged, catalog } = this.props;
+ searchInputChanged({
+ ...catalog,
+ searchNameFilter: searchValue
+ });
+ sessionStorage.setItem('searchNameFilter', searchValue);
+ };
+
+ render() {
+ const { catalog, showNewWorkflowModal } = this.props;
+ const {
+ sort,
+ paging: { hasMore, total },
+ items,
+ status,
+ searchNameFilter
+ } = catalog;
+ const alphabeticalOrder = sort[NAME];
+
+ return (
+ <div className="wf-catalog">
+ <Header
+ status={status}
+ statusChange={this.handleStatusChange}
+ searchChange={this.searchChange}
+ searchValue={searchNameFilter}
+ />
+ <InfiniteScroll
+ useWindow={false}
+ loadMore={this.handleScroll}
+ hasMore={hasMore}>
+ <Main
+ total={total}
+ alphabeticalOrder={alphabeticalOrder}
+ onAlphabeticalOrderByClick={
+ this.handleAlphabeticalOrderByClick
+ }>
+ <div className="main__content">
+ {status === WORKFLOW_STATUS.ACTIVE && (
+ <AddWorkflow onClick={showNewWorkflowModal} />
+ )}
+ <Workflows
+ items={items}
+ onWorkflowClick={this.goToOverviewPage}
+ />
+ </div>
+ </Main>
+ </InfiniteScroll>
+ </div>
+ );
+ }
+}
+
+CatalogView.propTypes = {
+ history: PropTypes.object,
+ catalog: PropTypes.object,
+ handleResetWorkflow: PropTypes.func,
+ handleFetchWorkflow: PropTypes.func,
+ showNewWorkflowModal: PropTypes.func,
+ clearWorkflow: PropTypes.func,
+ searchInputChanged: PropTypes.func,
+ searchNameFilter: PropTypes.string
+};
+
+CatalogView.defaultProps = {
+ showNewWorkflowModal: () => {},
+ clearWorkflow: () => {}
+};
+
+export default CatalogView;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogActions-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogActions-test.js
new file mode 100644
index 00000000..bd2b0006
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogActions-test.js
@@ -0,0 +1,65 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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.
+*/
+
+'use strict';
+
+import {
+ FETCH_WORKFLOW,
+ UPDATE_WORKFLOW,
+ LIMIT,
+ NAME,
+ ASC
+} from 'features/catalog/catalogConstants';
+import { WORKFLOW_STATUS } from 'features/workflow/workflowConstants';
+import { fetchWorkflow, updateWorkflow } from 'features/catalog/catalogActions';
+
+describe('Catalog Actions', () => {
+ it('should have `fetchWorkflow` action', () => {
+ const sort = { [NAME]: ASC };
+ const offset = 0;
+ const status = WORKFLOW_STATUS.ACTIVE;
+ expect(fetchWorkflow({ sort, offset, status })).toEqual({
+ type: FETCH_WORKFLOW,
+ payload: {
+ sort,
+ limit: LIMIT,
+ status,
+ offset
+ }
+ });
+ });
+
+ it('should have `updateWorkflow` action', () => {
+ const payload = {
+ paging: {
+ offset: 1,
+ limit: 1,
+ count: 1,
+ hasMore: false,
+ total: 2
+ },
+ items: [],
+ sort: {
+ name: 'asc'
+ }
+ };
+
+ expect(updateWorkflow(payload)).toEqual({
+ type: UPDATE_WORKFLOW,
+ payload
+ });
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogReducer-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogReducer-test.js
new file mode 100644
index 00000000..264e8112
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogReducer-test.js
@@ -0,0 +1,112 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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.
+*/
+
+'use strict';
+
+import { NAME, ASC, DESC } from 'features/catalog/catalogConstants';
+import catalogReducer, { initialState } from 'features/catalog/catalogReducer';
+import { updateWorkflow } from 'features/catalog/catalogActions';
+import { WORKFLOW_STATUS } from 'features/workflow/workflowConstants';
+
+describe('Catalog Reducer', () => {
+ const state = {
+ paging: {
+ offset: 1,
+ limit: 1,
+ count: 1,
+ hasMore: false,
+ total: 2
+ },
+ status: WORKFLOW_STATUS.ACTIVE,
+ sort: {
+ [NAME]: ASC
+ },
+ searchNameFilter: '',
+ items: [
+ {
+ id: 'c5b7ca1a0f7944bfa948b85b32c5f314',
+ name: 'Workflow_2',
+ description: null,
+ versionStates: ['DRAFT'],
+ versions: null
+ },
+ {
+ id: '221336ef3f1645c686bc81899368ac27',
+ name: 'Workflow_1',
+ description: null,
+ versionStates: ['DRAFT'],
+ versions: null
+ }
+ ]
+ };
+
+ const sort = {
+ [NAME]: DESC
+ };
+
+ const offset = 0;
+
+ const dataPayload = {
+ paging: {
+ offset,
+ limit: 10,
+ count: 2,
+ hasMore: false,
+ total: 2
+ },
+ items: [
+ {
+ id: 'c5b7ca1a0f7944bfa948b85b32c5f314',
+ name: 'Workflow_2',
+ description: null,
+ versionStates: ['DRAFT'],
+ versions: null
+ },
+ {
+ id: '221336ef3f1645c686bc81899368ac27',
+ name: 'Workflow_1',
+ description: null,
+ versionStates: ['DRAFT'],
+ versions: null
+ }
+ ]
+ };
+
+ it('returns the initial state', () => {
+ expect(catalogReducer(undefined, {})).toEqual(initialState);
+ });
+
+ it('should replace results when page is first', () => {
+ expect(
+ catalogReducer(state, updateWorkflow({ sort, ...dataPayload }))
+ ).toEqual({
+ ...initialState,
+ sort,
+ ...dataPayload
+ });
+ });
+
+ it('should add results when page is not first', () => {
+ expect(
+ catalogReducer(
+ state,
+ updateWorkflow({ sort, ...{ ...dataPayload, offset: 2 } })
+ ).items
+ ).toEqual(
+ expect.arrayContaining([...dataPayload.items, ...state.items])
+ );
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSagas-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSagas-test.js
new file mode 100644
index 00000000..d3e9bda2
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSagas-test.js
@@ -0,0 +1,109 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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.
+*/
+
+'use strict';
+
+import { runSaga } from 'redux-saga';
+import { takeLatest } from 'redux-saga/effects';
+
+import {
+ NAME,
+ DESC,
+ LIMIT,
+ SEARCH_CHANGED
+} from 'features/catalog/catalogConstants';
+import { WORKFLOW_STATUS } from 'features/workflow/workflowConstants';
+import catalogApi from '../catalogApi';
+import { fetchWorkflow, updateWorkflow } from 'features/catalog/catalogActions';
+import catalogSaga, {
+ fetchWorkflowSaga,
+ debounceSearchChanged
+} from 'features/catalog/catalogSagas';
+
+jest.mock('../catalogApi');
+
+describe('Catalog Sagas', () => {
+ it('should watch for `fetchWorkflow` action', () => {
+ const gen = catalogSaga();
+
+ expect(gen.next().value).toEqual(
+ takeLatest(fetchWorkflow, fetchWorkflowSaga)
+ );
+ expect(gen.next().value).toEqual(
+ takeLatest(SEARCH_CHANGED, debounceSearchChanged)
+ );
+ expect(gen.next().done).toBe(true);
+ });
+
+ it('should get workflows and put `updateWorkflow` action', async () => {
+ const sort = {
+ [NAME]: DESC
+ };
+ const status = WORKFLOW_STATUS.ACTIVE;
+ const offset = 0;
+ const searchNameFilter = undefined;
+ const data = {
+ paging: {
+ offset,
+ limit: 10,
+ count: 2,
+ hasMore: false,
+ total: 2
+ },
+ status: WORKFLOW_STATUS.ACTIVE,
+ searchNameFilter: 'w',
+ items: [
+ {
+ id: 'c5b7ca1a0f7944bfa948b85b32c5f314',
+ name: 'Workflow_2',
+ description: null,
+ versionStates: ['DRAFT'],
+ versions: null
+ },
+ {
+ id: '221336ef3f1645c686bc81899368ac27',
+ name: 'Workflow_1',
+ description: null,
+ versionStates: ['DRAFT'],
+ versions: null
+ }
+ ]
+ };
+ const dispatched = [];
+
+ catalogApi.getWorkflows.mockReturnValue(data);
+
+ await runSaga(
+ {
+ dispatch: action => dispatched.push(action)
+ },
+ fetchWorkflowSaga,
+ fetchWorkflow({ sort, offset, status })
+ ).done;
+
+ expect(dispatched).toEqual(
+ expect.arrayContaining([updateWorkflow({ sort, ...data })])
+ );
+
+ expect(catalogApi.getWorkflows).toBeCalledWith({
+ sort,
+ status,
+ limit: LIMIT,
+ offset: offset + LIMIT,
+ searchNameFilter
+ });
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogActions.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogActions.js
new file mode 100644
index 00000000..81c22848
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogActions.js
@@ -0,0 +1,48 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createActions, createAction } from 'redux-actions';
+
+import {
+ NAMESPACE,
+ LIMIT,
+ SEARCH_CHANGED,
+ FETCH_WORKFLOW
+} from 'features/catalog/catalogConstants';
+
+export const {
+ [NAMESPACE]: { updateWorkflow }
+} = createActions({
+ [NAMESPACE]: {
+ UPDATE_WORKFLOW: undefined
+ }
+});
+
+export const fetchWorkflow = createAction(
+ FETCH_WORKFLOW,
+ ({ sort, offset, searchNameFilter, status }) => ({
+ sort,
+ limit: LIMIT,
+ offset,
+ searchNameFilter,
+ status
+ })
+);
+
+export const searchChangedAction = createAction(
+ SEARCH_CHANGED,
+ payload => payload
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogApi.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogApi.js
new file mode 100644
index 00000000..b15f3d8e
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogApi.js
@@ -0,0 +1,45 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 qs from 'qs';
+
+import RestfulAPIUtil from 'services/restAPIUtil';
+import Configuration from 'config/Configuration.js';
+
+function baseUrl() {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/workflows`;
+}
+
+const Api = {
+ getWorkflows: ({ sort, limit, offset, searchNameFilter, status }) => {
+ const queryParams = {
+ sort: Object.keys(sort).map(key => `${key}:${sort[key]}`),
+ limit,
+ offset,
+ archiving: status
+ };
+ if (searchNameFilter) queryParams.searchNameFilter = searchNameFilter;
+ const queryString = qs.stringify(queryParams, {
+ indices: false,
+ addQueryPrefix: true
+ });
+
+ return RestfulAPIUtil.fetch(`${baseUrl()}${queryString}`);
+ }
+};
+
+export default Api;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogConstants.js
new file mode 100644
index 00000000..44bdc6bb
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogConstants.js
@@ -0,0 +1,31 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const NAMESPACE = 'catalog';
+
+export const NAME = 'name';
+export const ASC = 'asc';
+export const DESC = 'desc';
+
+//Limit = max tiles in a standard screen
+export const LIMIT = 31;
+
+export const SEARCH_BUFFER = 1000;
+export const SEARCH_CHANGED = `catalog/SEARCH_CHANGED`;
+
+export const FETCH_WORKFLOW = `${NAMESPACE}/FETCH_WORKFLOW`;
+export const UPDATE_WORKFLOW = `${NAMESPACE}/UPDATE_WORKFLOW`;
+export const RESET_WORKFLOW = `${NAMESPACE}/RESET_WORKFLOW`;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogReducer.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogReducer.js
new file mode 100644
index 00000000..db7bc43b
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogReducer.js
@@ -0,0 +1,60 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 {
+ NAME,
+ ASC,
+ UPDATE_WORKFLOW,
+ SEARCH_CHANGED
+} from 'features/catalog/catalogConstants';
+import { WORKFLOW_STATUS } from 'features/workflow/workflowConstants';
+export const initialState = {
+ paging: {
+ hasMore: true,
+ total: 0
+ },
+ sort: {
+ [NAME]: ASC
+ },
+ status: WORKFLOW_STATUS.ACTIVE,
+ //In order to save state inside iframe session
+ searchNameFilter: sessionStorage.getItem('searchNameFilter') || ''
+};
+
+const catalogReducer = (state = initialState, action) => {
+ const { type, payload } = action;
+
+ switch (type) {
+ case UPDATE_WORKFLOW:
+ return {
+ ...state,
+ ...payload,
+ items:
+ payload.paging.offset === 0
+ ? [...payload.items]
+ : [...state.items, ...payload.items]
+ };
+ case SEARCH_CHANGED:
+ return {
+ ...state,
+ searchNameFilter: action.payload.searchNameFilter
+ };
+ default:
+ return state;
+ }
+};
+
+export default catalogReducer;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSagas.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSagas.js
new file mode 100644
index 00000000..dd77bc97
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSagas.js
@@ -0,0 +1,56 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { call, put, takeLatest } from 'redux-saga/effects';
+import { delay } from 'redux-saga';
+
+import catalogApi from 'features/catalog/catalogApi';
+import { fetchWorkflow, updateWorkflow } from 'features/catalog/catalogActions';
+import {
+ SEARCH_CHANGED,
+ LIMIT,
+ SEARCH_BUFFER
+} from 'features/catalog/catalogConstants';
+
+const noOp = () => {};
+
+export function* fetchWorkflowSaga({ payload }) {
+ const { sort, limit, offset, searchNameFilter, status } = payload;
+ try {
+ const data = yield call(catalogApi.getWorkflows, {
+ sort,
+ limit: LIMIT,
+ offset: offset === undefined ? 0 : offset + limit,
+ searchNameFilter,
+ status
+ });
+ yield put(updateWorkflow({ sort, status, ...data }));
+ } catch (e) {
+ noOp();
+ }
+}
+
+export function* debounceSearchChanged({ payload }) {
+ yield call(delay, SEARCH_BUFFER);
+ yield call(fetchWorkflowSaga, { payload });
+}
+
+function* catalogSaga() {
+ yield takeLatest(fetchWorkflow, fetchWorkflowSaga);
+ yield takeLatest(SEARCH_CHANGED, debounceSearchChanged);
+}
+
+export default catalogSaga;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/AddWorkflow.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/AddWorkflow.jsx
new file mode 100644
index 00000000..1700d92e
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/AddWorkflow.jsx
@@ -0,0 +1,45 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import { Translate } from 'react-redux-i18n';
+import { SVGIcon } from 'onap-ui-react';
+
+class AddWorkflow extends React.Component {
+ render() {
+ const { onClick } = this.props;
+ return (
+ <div
+ className="add-workflow"
+ data-test-id="wf-catalog-add-workflow"
+ onClick={onClick}>
+ <div className="add-workflow__icon">
+ <SVGIcon name="plusCircleThick" />
+ </div>
+ <div className="add-workflow__label">
+ <Translate value="catalog.addWorkflow" />
+ </div>
+ </div>
+ );
+ }
+}
+
+AddWorkflow.propTypes = {
+ onClick: PropTypes.func
+};
+
+export default AddWorkflow;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/Header.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/Header.jsx
new file mode 100644
index 00000000..b70c0a50
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/Header.jsx
@@ -0,0 +1,46 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import SearchInput from 'shared/searchInput/SearchInput';
+import StatusSelect from './StatusSelector';
+
+const Header = ({ searchChange, searchValue, statusChange, status }) => (
+ <div className="header">
+ <StatusSelect status={status} onChange={statusChange} />
+ <div className="header__search">
+ <SearchInput
+ dataTestId="wf-catalog-search"
+ onChange={searchChange}
+ value={searchValue}
+ />
+ </div>
+ </div>
+);
+
+Header.propTypes = {
+ searchChange: PropTypes.func,
+ searchValue: PropTypes.string,
+ statusChange: PropTypes.func,
+ status: PropTypes.string
+};
+
+Header.defaultProps = {
+ searchChange: () => {},
+ searchValue: ''
+};
+export default Header;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/Main.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/Main.jsx
new file mode 100644
index 00000000..b4288f3b
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/Main.jsx
@@ -0,0 +1,79 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Translate } from 'react-redux-i18n';
+
+import { ASC } from 'features/catalog/catalogConstants';
+import { SVGIcon } from 'onap-ui-react';
+
+class Main extends Component {
+ render() {
+ const {
+ total,
+ alphabeticalOrder,
+ onAlphabeticalOrderByClick,
+ children
+ } = this.props;
+
+ return (
+ <div className="main">
+ <div className="main__header">
+ <div className="main__header__total">
+ <Translate value="catalog.elementFound" count={total} />
+ </div>
+ <div className="main__header__order">
+ <div className="main__header__order__label">
+ <Translate value="catalog.orderBy" />:
+ </div>
+ <div
+ className="main__header__order__alphabetical"
+ data-test-id="wf-catalog-alphabetical-order"
+ onClick={onAlphabeticalOrderByClick}>
+ <div className="main__header__order__alphabetical__label">
+ <Translate value="catalog.alphabeticalOrder" />
+ </div>
+ <div
+ className={
+ (alphabeticalOrder === ASC &&
+ 'main__header__order__alphabetical__icon') ||
+ 'main__header__order__alphabetical__icon main__header__order__alphabetical__icon--flip'
+ }>
+ <SVGIcon name="caretDown" />
+ </div>
+ </div>
+ </div>
+ </div>
+ {children}
+ </div>
+ );
+ }
+}
+
+Main.propTypes = {
+ total: PropTypes.number,
+ alphabeticalOrder: PropTypes.string,
+ onAlphabeticalOrderByClick: PropTypes.func,
+ handleSort: PropTypes.func,
+ children: PropTypes.node
+};
+
+Main.defaultProps = {
+ total: 0
+};
+
+export default Main;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/StatusSelector.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/StatusSelector.js
new file mode 100644
index 00000000..5b77bd5d
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/StatusSelector.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { WORKFLOW_STATUS } from 'features/workflow/workflowConstants';
+
+const StatusSelect = ({ status, onChange }) => (
+ <select
+ className="wf-status-select"
+ value={status}
+ data-test-id="status-select"
+ onChange={e => onChange(e.target.value)}>
+ {Object.keys(WORKFLOW_STATUS).map((type, i) => (
+ <option key={`type.${i}`} value={WORKFLOW_STATUS[type]}>
+ {type}
+ </option>
+ ))}
+ </select>
+);
+
+StatusSelect.propTypes = {
+ status: PropTypes.string,
+ onChange: PropTypes.func
+};
+
+export default StatusSelect;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/Workflows.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/Workflows.jsx
new file mode 100644
index 00000000..d69ec47e
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/catalog/views/Workflows.jsx
@@ -0,0 +1,47 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+
+import { Tile, TileInfo, TileInfoLine } from 'onap-ui-react';
+
+const Workflows = ({ items, onWorkflowClick }) =>
+ items.map((workflow, index) => (
+ <Tile
+ key={`workflow.${index}`}
+ dataTestId="wf-catalog-workflow-item"
+ headerText="WF"
+ headerColor="blue"
+ iconName="workflow"
+ iconColor="blue"
+ onClick={() => onWorkflowClick(workflow.id)}>
+ <TileInfo>
+ <TileInfoLine type="title">{workflow.name}</TileInfoLine>
+ </TileInfo>
+ </Tile>
+ ));
+
+Workflows.propTypes = {
+ items: PropTypes.array,
+ onWorkflowClick: PropTypes.func
+};
+
+Workflows.defaultProps = {
+ items: []
+};
+
+export default Workflows;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/Version.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/Version.js
new file mode 100644
index 00000000..5df68759
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/Version.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux';
+import VersionView from 'features/version/VersionView';
+import { workflowVersionFetchRequestedAction } from 'features/version/versionConstants';
+
+const mapStateToProps = ({ currentVersion: { operationMode } }) => {
+ return {
+ operationMode
+ };
+};
+
+const mapDispatchToProps = dispatch => ({
+ loadSelectedVersion: payload =>
+ dispatch(workflowVersionFetchRequestedAction(payload))
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(VersionView);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/VersionView.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/VersionView.jsx
new file mode 100644
index 00000000..48c671e2
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/VersionView.jsx
@@ -0,0 +1,127 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import { Route, matchPath } from 'react-router-dom';
+import { I18n } from 'react-redux-i18n';
+
+import NavigationSideBar from 'shared/navigationSideBar/index';
+import VersionController from 'features/version/versionController/VersionController';
+
+class VersionView extends React.Component {
+ componentDidMount() {
+ const { loadSelectedVersion, match } = this.props;
+ const workflowId = match.params.workflowId;
+ const versionId = match.params.versionId;
+ loadSelectedVersion({ workflowId, versionId });
+ }
+
+ onSelect = item => {
+ const { history, match } = this.props;
+
+ if (!item.disabled) {
+ history.push(
+ item.path === '/' ? match.url : `${match.url}${item.path}`
+ );
+ }
+ };
+
+ getGroups = () => {
+ const { routes } = this.props;
+
+ const items = routes.map(route => {
+ return route.i18nName
+ ? {
+ ...route,
+ name: I18n.t(route.i18nName)
+ }
+ : route;
+ });
+
+ return [
+ {
+ id: 'WORKFLOW',
+ items
+ }
+ ];
+ };
+
+ getActiveItemIdProps = () => {
+ const { location, routes, match } = this.props;
+
+ const activeItem = routes.find(route =>
+ matchPath(location.pathname, {
+ path: `${match.path}${route.path}`,
+ exact: true,
+ strict: false
+ })
+ );
+
+ return activeItem && activeItem.id;
+ };
+
+ render() {
+ const { match, routes, history, operationMode } = this.props;
+
+ const groups = this.getGroups();
+ const activeItemId = this.getActiveItemIdProps();
+
+ return (
+ <div className="version-wrapper">
+ <VersionController
+ operationMode={operationMode}
+ history={history}
+ match={match}
+ key="versionControllerView"
+ />
+
+ <div
+ className={`${operationMode ? '' : 'workflow-view'}`}
+ key="workflowView">
+ {!operationMode && (
+ <div className="workflow-navigation-side-bar">
+ <NavigationSideBar
+ groups={groups}
+ activeItemId={activeItemId}
+ onSelect={this.onSelect}
+ />
+ </div>
+ )}
+ {routes.map((route, i) => (
+ <Route
+ key={`Version.route.${i}`}
+ exact={route.exact}
+ path={`${match.url}${route.path}`}
+ component={route.component}
+ />
+ ))}
+ </div>
+ </div>
+ );
+ }
+}
+
+VersionView.propTypes = {
+ history: PropTypes.object,
+ location: PropTypes.object,
+ match: PropTypes.object,
+ routes: PropTypes.array,
+ loadSelectedVersion: PropTypes.func,
+ operationMode: PropTypes.bool
+};
+
+export default VersionView;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/Composition.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/Composition.js
new file mode 100644
index 00000000..97697c70
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/Composition.js
@@ -0,0 +1,62 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { connect } from 'react-redux';
+import { I18n } from 'react-redux-i18n';
+import { updateComposition, updateValidation } from './compositionActions';
+import CompositionView from './CompositionView';
+import { showErrorModalAction } from '../../../shared/modal/modalWrapperActions';
+import { getComposition, getErrors } from './compositionSelectors';
+import { getWorkflowName } from '../../workflow/workflowSelectors';
+import { activitiesSelector } from 'features/activities/activitiesSelectors';
+import { getInputOutputForComposition } from 'features/version/inputOutput/inputOutputSelectors';
+import { getVersionInfo } from 'features/version/general/generalSelectors';
+import { getIsCertified } from 'features/version/general/generalSelectors';
+import { isWorkflowArchive } from 'features/workflow/workflowSelectors';
+
+function mapStateToProps(state) {
+ return {
+ composition: getComposition(state),
+ name: getWorkflowName(state),
+ versionName: getVersionInfo(state).name,
+ activities: activitiesSelector(state),
+ inputOutput: getInputOutputForComposition(state),
+ errors: getErrors(state),
+ isReadOnly: getIsCertified(state) || isWorkflowArchive(state)
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ compositionUpdate: composition =>
+ dispatch(updateComposition(composition)),
+ showErrorModal: msg =>
+ dispatch(
+ showErrorModalAction({
+ title: I18n.t('workflow.composition.bpmnError'),
+ body: msg,
+ withButtons: true,
+ closeButtonText: I18n.t('buttons.okBtn')
+ })
+ ),
+ validationUpdate: (element, isValid) =>
+ dispatch(updateValidation({ element, isValid }))
+ };
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(CompositionView);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionUpdate.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionUpdate.js
new file mode 100644
index 00000000..e5756eb5
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionUpdate.js
@@ -0,0 +1,131 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { I18n } from 'react-redux-i18n';
+
+import CustomModeler from 'features/version/composition/custom-modeler';
+import camundaModuleDescriptor from 'features/version/composition/custom-properties-provider/descriptors/camunda';
+import { setElementInputsOutputs } from 'features/version/composition/bpmnUtils.js';
+
+import { connect } from 'react-redux';
+import { updateComposition } from 'features/version/composition/compositionActions';
+import { showErrorModalAction } from 'shared/modal/modalWrapperActions';
+import { getComposition } from 'features/version/composition/compositionSelectors';
+import { getWorkflowName } from 'features/workflow/workflowSelectors';
+import { activitiesSelector } from 'features/activities/activitiesSelectors';
+import { getInputOutputForComposition } from 'features/version/inputOutput/inputOutputSelectors';
+
+class CompositionUpdate extends Component {
+ static propTypes = {
+ compositionUpdate: PropTypes.func,
+ showErrorModal: PropTypes.func,
+ composition: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
+ inputOutput: PropTypes.object,
+ activities: PropTypes.object,
+ certifyBack: PropTypes.func
+ };
+
+ constructor(props) {
+ super(props);
+ this.generatedId = 'bpmn-container' + Date.now();
+ this.fileInput = React.createRef();
+ this.bpmnContainer = React.createRef();
+ }
+
+ componentDidMount() {
+ const { composition, activities, inputOutput } = this.props;
+
+ this.modeler = new CustomModeler({
+ moddleExtensions: {
+ camunda: camundaModuleDescriptor
+ },
+ workflow: {
+ activities: activities,
+ workflowInputOutput: inputOutput
+ }
+ });
+
+ this.setDiagramToBPMN(composition);
+ }
+
+ setDiagramToBPMN = diagram => {
+ let modeler = this.modeler;
+ this.modeler.importXML(diagram, err => {
+ if (err) {
+ return this.props.showErrorModal(
+ I18n.t('workflow.composition.importErrorMsg')
+ );
+ }
+ const canvas = modeler.get('canvas');
+ const { businessObject } = canvas._rootElement;
+
+ setElementInputsOutputs(
+ businessObject,
+ this.props.inputOutput,
+ this.modeler.get('moddle')
+ );
+
+ this.exportDiagramToStore();
+ });
+ };
+
+ exportDiagramToStore = () => {
+ this.modeler.saveXML({ format: true }, (err, xml) => {
+ if (err) {
+ return this.props.showErrorModal(
+ I18n.t('workflow.composition.saveErrorMsg')
+ );
+ }
+ this.props.compositionUpdate(xml);
+ this.props.certifyBack();
+ });
+ };
+
+ render() {
+ return <div />;
+ }
+}
+
+function mapStateToProps(state) {
+ return {
+ composition: getComposition(state),
+ name: getWorkflowName(state),
+ activities: activitiesSelector(state),
+ inputOutput: getInputOutputForComposition(state)
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ compositionUpdate: composition =>
+ dispatch(updateComposition(composition)),
+ showErrorModal: msg =>
+ dispatch(
+ showErrorModalAction({
+ title: I18n.t('workflow.composition.bpmnError'),
+ body: msg,
+ withButtons: true,
+ closeButtonText: I18n.t('buttons.okBtn')
+ })
+ )
+ };
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(CompositionUpdate);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js
new file mode 100644
index 00000000..e444d98c
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js
@@ -0,0 +1,284 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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, { Component } from 'react';
+import fileSaver from 'file-saver';
+import isEqual from 'lodash.isequal';
+import PropTypes from 'prop-types';
+import propertiesPanelModule from 'bpmn-js-properties-panel';
+import { I18n } from 'react-redux-i18n';
+
+import CustomModeler from './custom-modeler';
+import propertiesProviderModule from './custom-properties-provider/provider/camunda';
+import camundaModuleDescriptor from './custom-properties-provider/descriptors/camunda';
+import newDiagramXML from './newDiagram.bpmn';
+import CompositionButtons from './components/CompositionButtonsPanel';
+import { setElementInputsOutputs } from './bpmnUtils.js';
+import {
+ PROCESS_DEFAULT_ID,
+ COMPOSITION_ERROR_COLOR,
+ COMPOSITION_VALID_COLOR,
+ CAMUNDA_PANEL_INPUTS_NAMES
+} from './compositionConstants';
+import readOnly from './readOnly';
+
+function setStatusToElement(type, status, parent) {
+ let elements = parent.getElementsByTagName(type);
+ for (let item of elements) {
+ if (item.name !== 'selectedExtensionElement') {
+ item.readOnly = status;
+ item.disabled = status;
+ }
+ }
+}
+
+function disablePanelInputs(status) {
+ let panel = document.getElementById('js-properties-panel');
+
+ if (panel) {
+ setStatusToElement('input', status, panel);
+ setStatusToElement('button', status, panel);
+ setStatusToElement('select', status, panel);
+
+ //distinguish editable and clickable fields using attr and style
+ CAMUNDA_PANEL_INPUTS_NAMES.map(name => {
+ const div = document.getElementById(name);
+ if (div) {
+ div.setAttribute('editable-readonly', !status);
+ }
+ });
+ }
+}
+class CompositionView extends Component {
+ static propTypes = {
+ compositionUpdate: PropTypes.func,
+ showErrorModal: PropTypes.func,
+ composition: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
+ name: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
+ versionName: PropTypes.string,
+ inputOutput: PropTypes.object,
+ activities: PropTypes.array,
+ validationUpdate: PropTypes.func,
+ errors: PropTypes.array,
+ isReadOnly: PropTypes.bool
+ };
+
+ constructor(props) {
+ super(props);
+ this.generatedId = 'bpmn-container' + Date.now();
+ this.fileInput = React.createRef();
+ this.bpmnContainer = React.createRef();
+ this.versionChanged = false;
+ }
+ componentDidUpdate(prevProps) {
+ const { errors, isReadOnly, versionName, composition } = this.props;
+ if (!isEqual(prevProps.errors, errors)) {
+ errors.map(item => {
+ this.modeling.setColor([item.element], {
+ fill: item.isValid
+ ? COMPOSITION_VALID_COLOR
+ : COMPOSITION_ERROR_COLOR
+ });
+ });
+ }
+ if (prevProps.isReadOnly !== isReadOnly) {
+ this.modeler.get('readOnly').readOnly(isReadOnly);
+ disablePanelInputs(isReadOnly);
+ }
+
+ if (prevProps.versionName !== versionName) {
+ this.versionChanged = true;
+ }
+ if (
+ !isEqual(prevProps.composition, composition) &&
+ this.versionChanged
+ ) {
+ this.setDiagramToBPMN(composition);
+ this.versionChanged = false;
+ }
+ }
+ componentDidMount() {
+ const {
+ composition,
+ activities,
+ inputOutput,
+ validationUpdate,
+ isReadOnly
+ } = this.props;
+
+ const readOnlyModule = {
+ __init__: ['readOnly'],
+ readOnly: ['type', readOnly]
+ };
+ this.modeler = new CustomModeler({
+ propertiesPanel: {
+ parent: '#js-properties-panel'
+ },
+ additionalModules: [
+ propertiesPanelModule,
+ propertiesProviderModule,
+ readOnlyModule
+ ],
+ moddleExtensions: {
+ camunda: camundaModuleDescriptor
+ },
+ workflow: {
+ activities: activities,
+ getActivityInputsOutputs: this.getActivityInputsOutputs,
+ workflowInputOutput: inputOutput,
+ validationUpdate: validationUpdate
+ }
+ });
+
+ this.modeler.attachTo('#' + this.generatedId);
+ this.setDiagramToBPMN(composition);
+ this.modeler.on('element.out', () => this.exportDiagramToStore());
+ this.modeler.on('element.click', this.handleCompositionStatus);
+ this.modeler.on(
+ 'propertiesPanel.changed',
+ this.handleCompositionStatus
+ );
+ this.modeling = this.modeler.get('modeling');
+ this.modeler.get('readOnly').readOnly(isReadOnly);
+ }
+ handleCompositionStatus = () => {
+ disablePanelInputs(this.props.isReadOnly);
+ };
+ getActivityInputsOutputs = selectedValue => {
+ const selectedActivity = this.props.activities.find(
+ el => el.name === selectedValue
+ );
+
+ if (selectedActivity) {
+ const inputsOutputs = {
+ inputs: selectedActivity.inputs,
+ outputs: selectedActivity.outputs
+ };
+ return inputsOutputs;
+ } else return { inputs: [], outputs: [] };
+ };
+
+ setDiagramToBPMN = diagram => {
+ let modeler = this.modeler;
+ this.modeler.importXML(diagram, err => {
+ if (err) {
+ return this.props.showErrorModal(
+ I18n.t('workflow.composition.importErrorMsg')
+ );
+ }
+ const canvas = modeler.get('canvas');
+ canvas.zoom('fit-viewport');
+ const { businessObject } = canvas._rootElement;
+
+ this.setDefaultIdAndName(businessObject);
+ setElementInputsOutputs(
+ businessObject,
+ this.props.inputOutput,
+ this.modeler.get('moddle')
+ );
+ disablePanelInputs(this.props.isReadOnly);
+ });
+ };
+ setDefaultIdAndName = businessObject => {
+ const { name = '' } = this.props;
+ if (!businessObject.name) {
+ businessObject.name = name;
+ }
+
+ if (businessObject.id === PROCESS_DEFAULT_ID || !businessObject.id) {
+ businessObject.id = name.toLowerCase().replace(/\s/g, '_');
+ }
+ };
+ exportDiagramToStore = () => {
+ this.modeler.saveXML({ format: true }, (err, xml) => {
+ if (err) {
+ return this.props.showErrorModal(
+ I18n.t('workflow.composition.saveErrorMsg')
+ );
+ }
+ return this.props.compositionUpdate(xml);
+ });
+ };
+
+ exportDiagram = () => {
+ const { name, showErrorModal, versionName } = this.props;
+ this.modeler.saveXML({ format: true }, (err, xml) => {
+ if (err) {
+ return showErrorModal(
+ I18n.t('workflow.composition.exportErrorMsg')
+ );
+ }
+ const blob = new Blob([xml], { type: 'text/html;charset=utf-8' });
+ fileSaver.saveAs(
+ blob,
+ `${name.replace(/\s/g, '').toLowerCase()}-${versionName}.bpmn`
+ );
+ });
+ };
+
+ loadNewDiagram = () => {
+ this.setDiagramToBPMN(newDiagramXML);
+ };
+
+ uploadDiagram = () => {
+ this.fileInput.current.click();
+ };
+
+ handleFileInputChange = filesList => {
+ const file = filesList[0];
+ const reader = new FileReader();
+ reader.onloadend = event => {
+ var xml = event.target.result;
+ this.setDiagramToBPMN(xml);
+ this.fileInput.value = '';
+ };
+ reader.readAsText(file);
+ };
+
+ render() {
+ return (
+ <div className="composition-view content">
+ <input
+ ref={this.fileInput}
+ onChange={e => this.handleFileInputChange(e.target.files)}
+ id="file-input"
+ accept=".bpmn, .xml"
+ type="file"
+ name="file-input"
+ style={{ display: 'none' }}
+ />
+ <div
+ ref={this.bpmnContainer}
+ className="bpmn-container"
+ id={this.generatedId}
+ />
+ <div className="bpmn-sidebar">
+ <div
+ className="properties-panel"
+ id="js-properties-panel"
+ />
+ <CompositionButtons
+ isReadOnly={this.props.isReadOnly}
+ onClean={this.loadNewDiagram}
+ onDownload={this.exportDiagram}
+ onUpload={this.uploadDiagram}
+ />
+ </div>
+ </div>
+ );
+ }
+}
+
+export default CompositionView;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/bpmnUtils.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/bpmnUtils.js
new file mode 100644
index 00000000..ada2bdc4
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/bpmnUtils.js
@@ -0,0 +1,93 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { bpmnElementsTypes } from './compositionConstants';
+function getExtension(element, type) {
+ if (!element.extensionElements || !element.extensionElements.values) {
+ return null;
+ }
+
+ return element.extensionElements.values.filter(function(e) {
+ return e.$instanceOf(type);
+ })[0];
+}
+
+export function updatedData(moddle, inputData, existingArray, type, parent) {
+ return inputData.map(item => {
+ const existingInput = existingArray.find(el => el.name === item.name);
+ const updatedElement = moddle.create(
+ type,
+ existingInput ? { ...item, value: existingInput.value } : item
+ );
+ updatedElement.$parent = parent;
+ return updatedElement;
+ });
+}
+
+export function setElementInputsOutputs(
+ businessObject,
+ inputOutput,
+ moddle,
+ cleanInputsOutpus
+) {
+ const { inputs = [], outputs = [] } = inputOutput;
+
+ if (!businessObject.extensionElements) {
+ businessObject.extensionElements = moddle.create(
+ bpmnElementsTypes.EXTENSION_ElEMENTS
+ );
+ businessObject.extensionElements.$parent = businessObject.id;
+ }
+
+ const existingInputOutput = getExtension(
+ businessObject,
+ bpmnElementsTypes.INPUT_OUTPUT
+ );
+
+ const processInputs = updatedData(
+ moddle,
+ inputs,
+ cleanInputsOutpus
+ ? []
+ : (existingInputOutput && existingInputOutput.inputParameters) ||
+ [],
+ bpmnElementsTypes.INPUT_PARAMETER,
+ businessObject.id
+ );
+
+ const processOutputs = updatedData(
+ moddle,
+ outputs,
+ cleanInputsOutpus
+ ? []
+ : (existingInputOutput && existingInputOutput.outputParameters) ||
+ [],
+ bpmnElementsTypes.OUTPUT_PARAMETER,
+ businessObject.id
+ );
+
+ const processInputOutput = moddle.create(bpmnElementsTypes.INPUT_OUTPUT);
+ processInputOutput.$parent = businessObject.id;
+ processInputOutput.inputParameters = [...processInputs];
+ processInputOutput.outputParameters = [...processOutputs];
+
+ const extensionElements = businessObject.extensionElements.get('values');
+ businessObject.extensionElements.set(
+ 'values',
+ extensionElements
+ .filter(item => item.$type !== bpmnElementsTypes.INPUT_OUTPUT)
+ .concat(processInputOutput)
+ );
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButton.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButton.js
new file mode 100644
index 00000000..62500d25
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButton.js
@@ -0,0 +1,36 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import { SVGIcon } from 'onap-ui-react';
+
+const CompositionButton = ({ onClick, name, title, disabled }) => (
+ <div
+ onClick={disabled ? () => {} : onClick}
+ className={`diagram-btn ${disabled ? 'disabled' : ''}`}>
+ <SVGIcon title={title} name={name} />
+ </div>
+);
+
+CompositionButton.propTypes = {
+ onClick: PropTypes.func,
+ className: PropTypes.string,
+ name: PropTypes.string,
+ title: PropTypes.string,
+ disabled: PropTypes.bool
+};
+
+export default CompositionButton;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButtonsPanel.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButtonsPanel.js
new file mode 100644
index 00000000..0292fd4e
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButtonsPanel.js
@@ -0,0 +1,56 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import CompositionButton from './CompositionButton';
+
+const Divider = () => <div className="divider" />;
+
+const CompositionButtons = ({ onClean, onUpload, onDownload, isReadOnly }) => (
+ <div className="composition-buttons">
+ <CompositionButton
+ disabled={isReadOnly}
+ data-test-id="composition-clear-btn"
+ onClick={onClean}
+ name="trashO"
+ title="clear"
+ />
+
+ <Divider />
+ <CompositionButton
+ data-test-id="composition-download-btn"
+ onClick={onDownload}
+ name="download"
+ title="download"
+ />
+ <Divider />
+ <CompositionButton
+ disabled={isReadOnly}
+ data-test-id="composition-download-upload"
+ onClick={onUpload}
+ name="upload"
+ title="upload"
+ />
+ </div>
+);
+
+CompositionButtons.propTypes = {
+ onClean: PropTypes.func,
+ onUpload: PropTypes.func,
+ onDownload: PropTypes.func,
+ isReadOnly: PropTypes.bool
+};
+export default CompositionButtons;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionActions.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionActions.js
new file mode 100644
index 00000000..fe74ba0d
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionActions.js
@@ -0,0 +1,34 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 {
+ SET_COMPOSITION,
+ UPDATE_ERRORS,
+ DELETE_COMPOSITION
+} from './compositionConstants';
+
+export const updateComposition = payload => ({
+ type: SET_COMPOSITION,
+ payload
+});
+
+export const deleteCompositionArtifact = () => ({
+ type: DELETE_COMPOSITION
+});
+
+export const updateValidation = payload => ({
+ type: UPDATE_ERRORS,
+ payload
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionConstants.js
new file mode 100644
index 00000000..1db40210
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionConstants.js
@@ -0,0 +1,38 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const SET_COMPOSITION = 'composition/SET_COMPOSITION';
+export const DELETE_COMPOSITION = 'composition/DELETE_COMPOSITION';
+export const UPDATE_ERRORS = 'composition/UPDATE_ERRORS';
+
+export const bpmnElementsTypes = {
+ EXTENSION_ElEMENTS: 'bpmn:ExtensionElements',
+ INPUT_OUTPUT: 'camunda:InputOutput',
+ INPUT_PARAMETER: 'camunda:InputParameter',
+ OUTPUT_PARAMETER: 'camunda:OutputParameter'
+};
+
+export const PROCESS_DEFAULT_ID = 'Process_1';
+
+export const COMPOSITION_ERROR_COLOR = '#f0c2c2';
+export const COMPOSITION_VALID_COLOR = 'white';
+
+//list of field ids (contenteditable) that has separate treatment
+export const CAMUNDA_PANEL_INPUTS_NAMES = [
+ 'camunda-parameterType-text',
+ 'camunda-documentation',
+ 'camunda-name',
+ 'camunda-listener-field-value'
+];
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionReducer.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionReducer.js
new file mode 100644
index 00000000..9deb9cbd
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionReducer.js
@@ -0,0 +1,44 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { SET_COMPOSITION, DELETE_COMPOSITION } from './compositionConstants';
+import { UPDATE_ERRORS } from './compositionConstants';
+import newDiagramXML from './newDiagram.bpmn';
+
+export default (state = { diagram: newDiagramXML, errors: [] }, action) => {
+ switch (action.type) {
+ case SET_COMPOSITION:
+ return {
+ ...state,
+ diagram: action.payload
+ };
+ case DELETE_COMPOSITION:
+ return {
+ ...state,
+ diagram: newDiagramXML
+ };
+ case UPDATE_ERRORS: {
+ const filteredErrors = state.errors.filter(
+ item => item.element.id !== action.payload.element.id
+ );
+ return {
+ ...state,
+ errors: [...filteredErrors, action.payload]
+ };
+ }
+ default:
+ return state;
+ }
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionSelectors.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionSelectors.js
new file mode 100644
index 00000000..e6b51f1a
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionSelectors.js
@@ -0,0 +1,32 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const getComposition = state =>
+ state && state.currentVersion && state.currentVersion.composition.diagram;
+
+export const getCompositionHasErrors = state =>
+ state &&
+ state.currentVersion &&
+ state.currentVersion.composition &&
+ state.currentVersion.composition.errors &&
+ Boolean(
+ state.currentVersion.composition.errors.find(item => !item.isValid)
+ );
+
+export const getErrors = state =>
+ state &&
+ state.currentVersion &&
+ state.currentVersion.composition &&
+ state.currentVersion.composition.errors;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomContextPadProvider.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomContextPadProvider.js
new file mode 100644
index 00000000..0f2ba528
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomContextPadProvider.js
@@ -0,0 +1,43 @@
+import inherits from 'inherits';
+
+import ContextPadProvider from 'bpmn-js/lib/features/context-pad/ContextPadProvider';
+
+import { isAny } from 'bpmn-js/lib/features/modeling/util/ModelingUtil';
+
+import { assign, bind } from 'min-dash';
+
+export default function CustomContextPadProvider(injector, connect, translate) {
+ injector.invoke(ContextPadProvider, this);
+
+ var cached = bind(this.getContextPadEntries, this);
+
+ this.getContextPadEntries = function(element) {
+ var actions = cached(element);
+
+ var businessObject = element.businessObject;
+
+ function startConnect(event, element, autoActivate) {
+ connect.start(event, element, autoActivate);
+ }
+
+ if (isAny(businessObject, ['custom:triangle', 'custom:circle'])) {
+ assign(actions, {
+ connect: {
+ group: 'connect',
+ className: 'bpmn-icon-connection-multi',
+ title: translate('Connect using custom connection'),
+ action: {
+ click: startConnect,
+ dragstart: startConnect
+ }
+ }
+ });
+ }
+
+ return actions;
+ };
+}
+
+inherits(CustomContextPadProvider, ContextPadProvider);
+
+CustomContextPadProvider.$inject = ['injector', 'connect', 'translate'];
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomElementFactory.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomElementFactory.js
new file mode 100644
index 00000000..01d4d278
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomElementFactory.js
@@ -0,0 +1,101 @@
+import { assign } from 'min-dash';
+
+import inherits from 'inherits';
+
+import BpmnElementFactory from 'bpmn-js/lib/features/modeling/ElementFactory';
+import { DEFAULT_LABEL_SIZE } from 'bpmn-js/lib/util/LabelUtil';
+
+/**
+ * A custom factory that knows how to create BPMN _and_ custom elements.
+ */
+export default function CustomElementFactory(bpmnFactory, moddle) {
+ BpmnElementFactory.call(this, bpmnFactory, moddle);
+
+ var self = this;
+
+ /**
+ * Create a diagram-js element with the given type (any of shape, connection, label).
+ *
+ * @param {String} elementType
+ * @param {Object} attrs
+ *
+ * @return {djs.model.Base}
+ */
+ this.create = function(elementType, attrs) {
+ var type = attrs.type;
+
+ if (elementType === 'label') {
+ return self.baseCreate(
+ elementType,
+ assign({ type: 'label' }, DEFAULT_LABEL_SIZE, attrs)
+ );
+ }
+
+ // add type to businessObject if custom
+ if (/^custom:/.test(type)) {
+ if (!attrs.businessObject) {
+ attrs.businessObject = {
+ type: type
+ };
+
+ if (attrs.id) {
+ assign(attrs.businessObject, {
+ id: attrs.id
+ });
+ }
+ }
+
+ // add width and height if shape
+ if (!/:connection$/.test(type)) {
+ assign(attrs, self._getCustomElementSize(type));
+ }
+
+ if (!('$instanceOf' in attrs.businessObject)) {
+ // ensure we can use ModelUtil#is for type checks
+ Object.defineProperty(attrs.businessObject, '$instanceOf', {
+ value: function(type) {
+ return this.type === type;
+ }
+ });
+ }
+
+ return self.baseCreate(elementType, attrs);
+ }
+
+ return self.createBpmnElement(elementType, attrs);
+ };
+}
+
+inherits(CustomElementFactory, BpmnElementFactory);
+
+CustomElementFactory.$inject = ['bpmnFactory', 'moddle'];
+
+/**
+ * Returns the default size of custom shapes.
+ *
+ * The following example shows an interface on how
+ * to setup the custom shapes's dimensions.
+ *
+ * @example
+ *
+ * var shapes = {
+ * triangle: { width: 40, height: 40 },
+ * rectangle: { width: 100, height: 20 }
+ * };
+ *
+ * return shapes[type];
+ *
+ *
+ * @param {String} type
+ *
+ * @return {Dimensions} a {width, height} object representing the size of the element
+ */
+CustomElementFactory.prototype._getCustomElementSize = function(type) {
+ var shapes = {
+ __default: { width: 100, height: 80 },
+ 'custom:triangle': { width: 40, height: 40 },
+ 'custom:circle': { width: 140, height: 140 }
+ };
+
+ return shapes[type] || shapes.__default;
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomPalette.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomPalette.js
new file mode 100644
index 00000000..a8adb2fd
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomPalette.js
@@ -0,0 +1,151 @@
+import { assign } from 'min-dash';
+
+/**
+ * A palette that allows you to create BPMN _and_ custom elements.
+ */
+export default function PaletteProvider(
+ palette,
+ create,
+ elementFactory,
+ spaceTool,
+ lassoTool,
+ handTool,
+ globalConnect,
+ translate
+) {
+ this._create = create;
+ this._elementFactory = elementFactory;
+ this._spaceTool = spaceTool;
+ this._lassoTool = lassoTool;
+ this._handTool = handTool;
+ this._globalConnect = globalConnect;
+ this._translate = translate;
+
+ palette.registerProvider(this);
+}
+
+PaletteProvider.$inject = [
+ 'palette',
+ 'create',
+ 'elementFactory',
+ 'spaceTool',
+ 'lassoTool',
+ 'handTool',
+ 'globalConnect',
+ 'translate'
+];
+
+PaletteProvider.prototype.getPaletteEntries = function() {
+ var actions = {},
+ create = this._create,
+ elementFactory = this._elementFactory,
+ spaceTool = this._spaceTool,
+ lassoTool = this._lassoTool,
+ handTool = this._handTool,
+ globalConnect = this._globalConnect,
+ translate = this._translate;
+
+ function createAction(type, group, className, title, options) {
+ function createListener(event) {
+ var shape = elementFactory.createShape(
+ assign({ type: type }, options)
+ );
+
+ if (options) {
+ shape.businessObject.di.isExpanded = options.isExpanded;
+ }
+
+ create.start(event, shape);
+ }
+
+ var shortType = type.replace(/^bpmn:/, '');
+
+ return {
+ group: group,
+ className: className,
+ title: title || 'Create ' + shortType,
+ action: {
+ dragstart: createListener,
+ click: createListener
+ }
+ };
+ }
+
+ assign(actions, {
+ 'hand-tool': {
+ group: 'tools',
+ className: 'bpmn-icon-hand-tool',
+ title: translate('Activate the hand tool'),
+ action: {
+ click: function(event) {
+ handTool.activateHand(event);
+ }
+ }
+ },
+ 'lasso-tool': {
+ group: 'tools',
+ className: 'bpmn-icon-lasso-tool',
+ title: translate('Activate the lasso tool'),
+ action: {
+ click: function(event) {
+ lassoTool.activateSelection(event);
+ }
+ }
+ },
+ 'space-tool': {
+ group: 'tools',
+ className: 'bpmn-icon-space-tool',
+ title: translate('Activate the create/remove space tool'),
+ action: {
+ click: function(event) {
+ spaceTool.activateSelection(event);
+ }
+ }
+ },
+ 'global-connect-tool': {
+ group: 'tools',
+ className: 'bpmn-icon-connection-multi',
+ title: translate('Activate the global connect tool'),
+ action: {
+ click: function(event) {
+ globalConnect.toggle(event);
+ }
+ }
+ },
+ 'tool-separator': {
+ group: 'tools',
+ separator: true
+ },
+ 'create.start-event': createAction(
+ 'bpmn:StartEvent',
+ 'event',
+ 'bpmn-icon-start-event-none'
+ ),
+ 'create.intermediate-event': createAction(
+ 'bpmn:IntermediateThrowEvent',
+ 'event',
+ 'bpmn-icon-intermediate-event-none',
+ translate('Create Intermediate/Boundary Event')
+ ),
+ 'create.end-event': createAction(
+ 'bpmn:EndEvent',
+ 'event',
+ 'bpmn-icon-end-event-none'
+ ),
+ 'create.exclusive-gateway': createAction(
+ 'bpmn:ExclusiveGateway',
+ 'gateway',
+ 'bpmn-icon-gateway-none',
+ translate('Create Gateway')
+ ),
+ 'create.task': createAction('bpmn:Task', 'activity', 'bpmn-icon-task'),
+ 'create.subprocess-expanded': createAction(
+ 'bpmn:SubProcess',
+ 'activity',
+ 'bpmn-icon-subprocess-expanded',
+ translate('Create expanded SubProcess'),
+ { isExpanded: true }
+ )
+ });
+ return actions;
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRenderer.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRenderer.js
new file mode 100644
index 00000000..f397fed9
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRenderer.js
@@ -0,0 +1,176 @@
+import inherits from 'inherits';
+
+import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';
+
+import { componentsToPath, createLine } from 'diagram-js/lib/util/RenderUtil';
+
+import {
+ append as svgAppend,
+ attr as svgAttr,
+ create as svgCreate
+} from 'tiny-svg';
+
+/**
+ * A renderer that knows how to render custom elements.
+ */
+export default function CustomRenderer(eventBus, styles) {
+ BaseRenderer.call(this, eventBus, 2000);
+
+ var computeStyle = styles.computeStyle;
+
+ this.drawTriangle = function(p, side) {
+ var halfSide = side / 2,
+ points,
+ attrs;
+
+ points = [halfSide, 0, side, side, 0, side];
+
+ attrs = computeStyle(attrs, {
+ stroke: '#3CAA82',
+ strokeWidth: 2,
+ fill: '#3CAA82'
+ });
+
+ var polygon = svgCreate('polygon');
+
+ svgAttr(polygon, {
+ points: points
+ });
+
+ svgAttr(polygon, attrs);
+
+ svgAppend(p, polygon);
+
+ return polygon;
+ };
+
+ this.getTrianglePath = function(element) {
+ var x = element.x,
+ y = element.y,
+ width = element.width,
+ height = element.height;
+
+ var trianglePath = [
+ ['M', x + width / 2, y],
+ ['l', width / 2, height],
+ ['l', -width, 0],
+ ['z']
+ ];
+
+ return componentsToPath(trianglePath);
+ };
+
+ this.drawCircle = function(p, width, height) {
+ var cx = width / 2,
+ cy = height / 2;
+
+ var attrs = computeStyle(attrs, {
+ stroke: '#4488aa',
+ strokeWidth: 4,
+ fill: 'white'
+ });
+
+ var circle = svgCreate('circle');
+
+ svgAttr(circle, {
+ cx: cx,
+ cy: cy,
+ r: Math.round((width + height) / 4)
+ });
+
+ svgAttr(circle, attrs);
+
+ svgAppend(p, circle);
+
+ return circle;
+ };
+
+ this.getCirclePath = function(shape) {
+ var cx = shape.x + shape.width / 2,
+ cy = shape.y + shape.height / 2,
+ radius = shape.width / 2;
+
+ var circlePath = [
+ ['M', cx, cy],
+ ['m', 0, -radius],
+ ['a', radius, radius, 0, 1, 1, 0, 2 * radius],
+ ['a', radius, radius, 0, 1, 1, 0, -2 * radius],
+ ['z']
+ ];
+
+ return componentsToPath(circlePath);
+ };
+
+ this.drawCustomConnection = function(p, element) {
+ var attrs = computeStyle(attrs, {
+ stroke: '#ff471a',
+ strokeWidth: 2
+ });
+
+ return svgAppend(p, createLine(element.waypoints, attrs));
+ };
+
+ this.getCustomConnectionPath = function(connection) {
+ var waypoints = connection.waypoints.map(function(p) {
+ return p.original || p;
+ });
+
+ var connectionPath = [['M', waypoints[0].x, waypoints[0].y]];
+
+ waypoints.forEach(function(waypoint, index) {
+ if (index !== 0) {
+ connectionPath.push(['L', waypoint.x, waypoint.y]);
+ }
+ });
+
+ return componentsToPath(connectionPath);
+ };
+}
+
+inherits(CustomRenderer, BaseRenderer);
+
+CustomRenderer.$inject = ['eventBus', 'styles'];
+
+CustomRenderer.prototype.canRender = function(element) {
+ return /^custom:/.test(element.type);
+};
+
+CustomRenderer.prototype.drawShape = function(p, element) {
+ var type = element.type;
+
+ if (type === 'custom:triangle') {
+ return this.drawTriangle(p, element.width);
+ }
+
+ if (type === 'custom:circle') {
+ return this.drawCircle(p, element.width, element.height);
+ }
+};
+
+CustomRenderer.prototype.getShapePath = function(shape) {
+ var type = shape.type;
+
+ if (type === 'custom:triangle') {
+ return this.getTrianglePath(shape);
+ }
+
+ if (type === 'custom:circle') {
+ return this.getCirclePath(shape);
+ }
+};
+
+CustomRenderer.prototype.drawConnection = function(p, element) {
+ var type = element.type;
+
+ if (type === 'custom:connection') {
+ return this.drawCustomConnection(p, element);
+ }
+};
+
+CustomRenderer.prototype.getConnectionPath = function(connection) {
+ var type = connection.type;
+
+ if (type === 'custom:connection') {
+ return this.getCustomConnectionPath(connection);
+ }
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRules.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRules.js
new file mode 100644
index 00000000..1dce143d
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRules.js
@@ -0,0 +1,136 @@
+import { reduce } from 'min-dash';
+
+import inherits from 'inherits';
+
+import { is } from 'bpmn-js/lib/util/ModelUtil';
+
+import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
+
+var HIGH_PRIORITY = 1500;
+
+function isCustom(element) {
+ return element && /^custom:/.test(element.type);
+}
+
+/**
+ * Specific rules for custom elements
+ */
+export default function CustomRules(eventBus) {
+ RuleProvider.call(this, eventBus);
+}
+
+inherits(CustomRules, RuleProvider);
+
+CustomRules.$inject = ['eventBus'];
+
+CustomRules.prototype.init = function() {
+ /**
+ * Can shape be created on target container?
+ */
+ function canCreate(shape, target) {
+ // only judge about custom elements
+ if (!isCustom(shape)) {
+ return;
+ }
+
+ // allow creation on processes
+ return (
+ is(target, 'bpmn:Process') ||
+ is(target, 'bpmn:Participant') ||
+ is(target, 'bpmn:Collaboration')
+ );
+ }
+
+ /**
+ * Can source and target be connected?
+ */
+ function canConnect(source, target) {
+ // only judge about custom elements
+ if (!isCustom(source) && !isCustom(target)) {
+ return;
+ }
+
+ // allow connection between custom shape and task
+ if (isCustom(source)) {
+ if (is(target, 'bpmn:Task')) {
+ return { type: 'custom:connection' };
+ } else {
+ return false;
+ }
+ } else if (isCustom(target)) {
+ if (is(source, 'bpmn:Task')) {
+ return { type: 'custom:connection' };
+ } else {
+ return false;
+ }
+ }
+ }
+
+ this.addRule('elements.move', HIGH_PRIORITY, function(context) {
+ var target = context.target,
+ shapes = context.shapes;
+
+ var type;
+
+ // do not allow mixed movements of custom / BPMN shapes
+ // if any shape cannot be moved, the group cannot be moved, too
+ var allowed = reduce(
+ shapes,
+ function(result, s) {
+ if (type === undefined) {
+ type = isCustom(s);
+ }
+
+ if (type !== isCustom(s) || result === false) {
+ return false;
+ }
+
+ return canCreate(s, target);
+ },
+ undefined
+ );
+
+ // reject, if we have at least one
+ // custom element that cannot be moved
+ return allowed;
+ });
+
+ this.addRule('shape.create', HIGH_PRIORITY, function(context) {
+ var target = context.target,
+ shape = context.shape;
+
+ return canCreate(shape, target);
+ });
+
+ this.addRule('shape.resize', HIGH_PRIORITY, function(context) {
+ var shape = context.shape;
+
+ if (isCustom(shape)) {
+ // cannot resize custom elements
+ return false;
+ }
+ });
+
+ this.addRule('connection.create', HIGH_PRIORITY, function(context) {
+ var source = context.source,
+ target = context.target;
+
+ return canConnect(source, target);
+ });
+
+ this.addRule('connection.reconnectStart', HIGH_PRIORITY, function(context) {
+ var connection = context.connection,
+ source = context.hover || context.source,
+ target = connection.target;
+
+ return canConnect(source, target, connection);
+ });
+
+ this.addRule('connection.reconnectEnd', HIGH_PRIORITY, function(context) {
+ var connection = context.connection,
+ source = connection.source,
+ target = context.hover || context.target;
+
+ return canConnect(source, target, connection);
+ });
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomUpdater.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomUpdater.js
new file mode 100644
index 00000000..532c24f3
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomUpdater.js
@@ -0,0 +1,136 @@
+import inherits from 'inherits';
+
+import { pick, assign } from 'min-dash';
+
+import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
+
+import {
+ add as collectionAdd,
+ remove as collectionRemove
+} from 'diagram-js/lib/util/Collections';
+
+/**
+ * A handler responsible for updating the custom element's businessObject
+ * once changes on the diagram happen.
+ */
+export default function CustomUpdater(eventBus, bpmnjs) {
+ CommandInterceptor.call(this, eventBus);
+
+ function updateCustomElement(e) {
+ var context = e.context,
+ shape = context.shape,
+ businessObject = shape.businessObject;
+
+ if (!isCustom(shape)) {
+ return;
+ }
+
+ var parent = shape.parent;
+
+ var customElements = bpmnjs._customElements;
+
+ // make sure element is added / removed from bpmnjs.customElements
+ if (!parent) {
+ collectionRemove(customElements, businessObject);
+ } else {
+ collectionAdd(customElements, businessObject);
+ }
+
+ // save custom element position
+ assign(businessObject, pick(shape, ['x', 'y']));
+ }
+
+ function updateCustomConnection(e) {
+ var context = e.context,
+ connection = context.connection,
+ source = connection.source,
+ target = connection.target,
+ businessObject = connection.businessObject;
+
+ var parent = connection.parent;
+
+ var customElements = bpmnjs._customElements;
+
+ // make sure element is added / removed from bpmnjs.customElements
+ if (!parent) {
+ collectionRemove(customElements, businessObject);
+ } else {
+ collectionAdd(customElements, businessObject);
+ }
+
+ // update waypoints
+ assign(businessObject, {
+ waypoints: copyWaypoints(connection)
+ });
+
+ if (source && target) {
+ assign(businessObject, {
+ source: source.id,
+ target: target.id
+ });
+ }
+ }
+
+ this.executed(
+ ['shape.create', 'shape.move', 'shape.delete'],
+ ifCustomElement(updateCustomElement)
+ );
+
+ this.reverted(
+ ['shape.create', 'shape.move', 'shape.delete'],
+ ifCustomElement(updateCustomElement)
+ );
+
+ this.executed(
+ [
+ 'connection.create',
+ 'connection.reconnectStart',
+ 'connection.reconnectEnd',
+ 'connection.updateWaypoints',
+ 'connection.delete',
+ 'connection.layout',
+ 'connection.move'
+ ],
+ ifCustomElement(updateCustomConnection)
+ );
+
+ this.reverted(
+ [
+ 'connection.create',
+ 'connection.reconnectStart',
+ 'connection.reconnectEnd',
+ 'connection.updateWaypoints',
+ 'connection.delete',
+ 'connection.layout',
+ 'connection.move'
+ ],
+ ifCustomElement(updateCustomConnection)
+ );
+}
+
+inherits(CustomUpdater, CommandInterceptor);
+
+CustomUpdater.$inject = ['eventBus', 'bpmnjs'];
+
+/////// helpers ///////////////////////////////////
+
+function copyWaypoints(connection) {
+ return connection.waypoints.map(function(p) {
+ return { x: p.x, y: p.y };
+ });
+}
+
+function isCustom(element) {
+ return element && /custom:/.test(element.type);
+}
+
+function ifCustomElement(fn) {
+ return function(event) {
+ var context = event.context,
+ element = context.shape || context.connection;
+
+ if (isCustom(element)) {
+ fn(event);
+ }
+ };
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/index.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/index.js
new file mode 100644
index 00000000..f1085390
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/index.js
@@ -0,0 +1,22 @@
+import CustomElementFactory from './CustomElementFactory';
+import CustomRenderer from './CustomRenderer';
+import CustomPalette from './CustomPalette';
+import CustomRules from './CustomRules';
+import CustomUpdater from './CustomUpdater';
+import CustomContextPadProvider from './CustomContextPadProvider';
+
+export default {
+ __init__: [
+ 'customRenderer',
+ 'paletteProvider',
+ 'customRules',
+ 'customUpdater',
+ 'contextPadProvider'
+ ],
+ elementFactory: ['type', CustomElementFactory],
+ customRenderer: ['type', CustomRenderer],
+ paletteProvider: ['type', CustomPalette],
+ customRules: ['type', CustomRules],
+ customUpdater: ['type', CustomUpdater],
+ contextPadProvider: ['type', CustomContextPadProvider]
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/index.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/index.js
new file mode 100644
index 00000000..86fbff6a
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/index.js
@@ -0,0 +1,99 @@
+import Modeler from 'bpmn-js/lib/Modeler';
+
+import { assign, isArray } from 'min-dash';
+
+import inherits from 'inherits';
+
+import CustomModule from './custom';
+
+export default function CustomModeler(options) {
+ Modeler.call(this, options);
+
+ this._customElements = [];
+}
+
+inherits(CustomModeler, Modeler);
+
+CustomModeler.prototype._modules = [].concat(CustomModeler.prototype._modules, [
+ CustomModule
+]);
+
+/**
+ * Add a single custom element to the underlying diagram
+ *
+ * @param {Object} customElement
+ */
+CustomModeler.prototype._addCustomShape = function(customElement) {
+ this._customElements.push(customElement);
+
+ var canvas = this.get('canvas'),
+ elementFactory = this.get('elementFactory');
+
+ var customAttrs = assign({ businessObject: customElement }, customElement);
+
+ var customShape = elementFactory.create('shape', customAttrs);
+
+ return canvas.addShape(customShape);
+};
+
+CustomModeler.prototype._addCustomConnection = function(customElement) {
+ this._customElements.push(customElement);
+
+ var canvas = this.get('canvas'),
+ elementFactory = this.get('elementFactory'),
+ elementRegistry = this.get('elementRegistry');
+
+ var customAttrs = assign({ businessObject: customElement }, customElement);
+
+ var connection = elementFactory.create(
+ 'connection',
+ assign(customAttrs, {
+ source: elementRegistry.get(customElement.source),
+ target: elementRegistry.get(customElement.target)
+ }),
+ elementRegistry.get(customElement.source).parent
+ );
+
+ return canvas.addConnection(connection);
+};
+
+/**
+ * Add a number of custom elements and connections to the underlying diagram.
+ *
+ * @param {Array<Object>} customElements
+ */
+CustomModeler.prototype.addCustomElements = function(customElements) {
+ if (!isArray(customElements)) {
+ throw new Error('argument must be an array');
+ }
+
+ var shapes = [],
+ connections = [];
+
+ customElements.forEach(function(customElement) {
+ if (isCustomConnection(customElement)) {
+ connections.push(customElement);
+ } else {
+ shapes.push(customElement);
+ }
+ });
+
+ // add shapes before connections so that connections
+ // can already rely on the shapes being part of the diagram
+ shapes.forEach(this._addCustomShape, this);
+
+ connections.forEach(this._addCustomConnection, this);
+};
+
+/**
+ * Get custom elements with their current status.
+ *
+ * @return {Array<Object>} custom elements on the diagram
+ */
+CustomModeler.prototype.getCustomElements = function() {
+ return this._customElements;
+};
+
+function isCustomConnection(element) {
+ return element.type === 'custom:connection';
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/descriptors/camunda.json b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/descriptors/camunda.json
new file mode 100644
index 00000000..6613c4bf
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/descriptors/camunda.json
@@ -0,0 +1,1025 @@
+{
+ "name": "Camunda",
+ "uri": "http://camunda.org/schema/1.0/bpmn",
+ "prefix": "camunda",
+ "xml": {
+ "tagAlias": "lowerCase"
+ },
+ "associations": [],
+ "types": [
+ {
+ "name": "InOutBinding",
+ "superClass": ["Element"],
+ "isAbstract": true,
+ "properties": [
+ {
+ "name": "source",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "sourceExpression",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "target",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "businessKey",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "local",
+ "isAttr": true,
+ "type": "Boolean",
+ "default": false
+ },
+ {
+ "name": "variables",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "In",
+ "superClass": ["InOutBinding"],
+ "meta": {
+ "allowedIn": ["bpmn:CallActivity"]
+ }
+ },
+ {
+ "name": "Out",
+ "superClass": ["InOutBinding"],
+ "meta": {
+ "allowedIn": ["bpmn:CallActivity"]
+ }
+ },
+ {
+ "name": "AsyncCapable",
+ "isAbstract": true,
+ "extends": ["bpmn:Activity", "bpmn:Gateway", "bpmn:Event"],
+ "properties": [
+ {
+ "name": "async",
+ "isAttr": true,
+ "type": "Boolean",
+ "default": false
+ },
+ {
+ "name": "asyncBefore",
+ "isAttr": true,
+ "type": "Boolean",
+ "default": false
+ },
+ {
+ "name": "asyncAfter",
+ "isAttr": true,
+ "type": "Boolean",
+ "default": false
+ },
+ {
+ "name": "exclusive",
+ "isAttr": true,
+ "type": "Boolean",
+ "default": true
+ }
+ ]
+ },
+ {
+ "name": "JobPriorized",
+ "isAbstract": true,
+ "extends": ["bpmn:Process", "camunda:AsyncCapable"],
+ "properties": [
+ {
+ "name": "jobPriority",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "SignalEventDefinition",
+ "isAbstract": true,
+ "extends": ["bpmn:SignalEventDefinition"],
+ "properties": [
+ {
+ "name": "async",
+ "isAttr": true,
+ "type": "Boolean",
+ "default": false
+ }
+ ]
+ },
+ {
+ "name": "ErrorEventDefinition",
+ "isAbstract": true,
+ "extends": ["bpmn:ErrorEventDefinition"],
+ "properties": [
+ {
+ "name": "errorCodeVariable",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "errorMessageVariable",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "PotentialStarter",
+ "superClass": ["Element"],
+ "properties": [
+ {
+ "name": "resourceAssignmentExpression",
+ "type": "bpmn:ResourceAssignmentExpression"
+ }
+ ]
+ },
+ {
+ "name": "FormSupported",
+ "isAbstract": true,
+ "extends": ["bpmn:StartEvent", "bpmn:UserTask"],
+ "properties": [
+ {
+ "name": "formHandlerClass",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "formKey",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "TemplateSupported",
+ "isAbstract": true,
+ "extends": ["bpmn:Process", "bpmn:FlowElement"],
+ "properties": [
+ {
+ "name": "modelerTemplate",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "Initiator",
+ "isAbstract": true,
+ "extends": ["bpmn:StartEvent"],
+ "properties": [
+ {
+ "name": "initiator",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "ScriptTask",
+ "isAbstract": true,
+ "extends": ["bpmn:ScriptTask"],
+ "properties": [
+ {
+ "name": "resultVariable",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "resource",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "Process",
+ "isAbstract": true,
+ "extends": ["bpmn:Process"],
+ "properties": [
+ {
+ "name": "candidateStarterGroups",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "candidateStarterUsers",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "versionTag",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "historyTimeToLive",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "EscalationEventDefinition",
+ "isAbstract": true,
+ "extends": ["bpmn:EscalationEventDefinition"],
+ "properties": [
+ {
+ "name": "escalationCodeVariable",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "FormalExpression",
+ "isAbstract": true,
+ "extends": ["bpmn:FormalExpression"],
+ "properties": [
+ {
+ "name": "resource",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "Assignable",
+ "extends": ["bpmn:UserTask"],
+ "properties": [
+ {
+ "name": "assignee",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "candidateUsers",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "candidateGroups",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "dueDate",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "followUpDate",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "priority",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "CallActivity",
+ "extends": ["bpmn:CallActivity"],
+ "properties": [
+ {
+ "name": "calledElementBinding",
+ "isAttr": true,
+ "type": "String",
+ "default": "latest"
+ },
+ {
+ "name": "calledElementVersion",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "calledElementTenantId",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "caseRef",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "caseBinding",
+ "isAttr": true,
+ "type": "String",
+ "default": "latest"
+ },
+ {
+ "name": "caseVersion",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "caseTenantId",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "variableMappingClass",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "variableMappingDelegateExpression",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "ServiceTaskLike",
+ "extends": [
+ "bpmn:ServiceTask",
+ "bpmn:BusinessRuleTask",
+ "bpmn:SendTask",
+ "bpmn:MessageEventDefinition"
+ ],
+ "properties": [
+ {
+ "name": "expression",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "class",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "delegateExpression",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "resultVariable",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "DmnCapable",
+ "extends": ["bpmn:BusinessRuleTask"],
+ "properties": [
+ {
+ "name": "decisionRef",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "decisionRefBinding",
+ "isAttr": true,
+ "type": "String",
+ "default": "latest"
+ },
+ {
+ "name": "decisionRefVersion",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "mapDecisionResult",
+ "isAttr": true,
+ "type": "String",
+ "default": "resultList"
+ },
+ {
+ "name": "decisionRefTenantId",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "ExternalCapable",
+ "extends": ["camunda:ServiceTaskLike"],
+ "properties": [
+ {
+ "name": "type",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "topic",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "TaskPriorized",
+ "extends": ["bpmn:Process", "camunda:ExternalCapable"],
+ "properties": [
+ {
+ "name": "taskPriority",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "Properties",
+ "superClass": ["Element"],
+ "meta": {
+ "allowedIn": ["*"]
+ },
+ "properties": [
+ {
+ "name": "values",
+ "type": "Property",
+ "isMany": true
+ }
+ ]
+ },
+ {
+ "name": "Property",
+ "superClass": ["Element"],
+ "properties": [
+ {
+ "name": "id",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "name",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "value",
+ "type": "String",
+ "isAttr": true
+ }
+ ]
+ },
+ {
+ "name": "Connector",
+ "superClass": ["Element"],
+ "meta": {
+ "allowedIn": [
+ "bpmn:ServiceTask",
+ "bpmn:BusinessRuleTask",
+ "bpmn:SendTask"
+ ]
+ },
+ "properties": [
+ {
+ "name": "inputOutput",
+ "type": "InputOutput"
+ },
+ {
+ "name": "connectorId",
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "WorkflowActivity",
+ "superClass": ["Element"],
+ "meta": {
+ "allowedIn": [
+ "bpmn:ServiceTask",
+ "bpmn:BusinessRuleTask",
+ "bpmn:SendTask"
+ ]
+ },
+ "properties": [
+ {
+ "name": "activityId",
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "InputOutput",
+ "superClass": ["Element"],
+ "meta": {
+ "allowedIn": [
+ "bpmn:Task",
+ "bpmn:UserTask",
+ "bpmn:ServiceTask",
+ "bpmn:SendTask",
+ "bpmn:BusinessRuleTask",
+ "bpmn:ReceiveTask",
+ "bpmn:ScriptTask",
+ "bpmn:ManualTask",
+ "bpmn:GlobalUserTask",
+ "bpmn:GlobalScriptTask",
+ "bpmn:GlobalBusinessRuleTask",
+ "bpmn:GlobalTask",
+ "bpmn:GlobalManualTask",
+ "bpmn:SubProcess",
+ "bpmn:Transaction",
+ "bpmn:IntermediateCatchEvent",
+ "bpmn:IntermediateThrowEvent",
+ "bpmn:EndEvent",
+ "bpmn:ThrowEvent",
+ "bpmn:CatchEvent",
+ "bpmn:ImplicitThrowEvent",
+ "bpmn:CallActivity"
+ ]
+ },
+ "properties": [
+ {
+ "name": "inputOutput",
+ "type": "InputOutput"
+ },
+ {
+ "name": "connectorId",
+ "type": "String"
+ },
+ {
+ "name": "inputParameters",
+ "isMany": true,
+ "type": "InputParameter"
+ },
+ {
+ "name": "outputParameters",
+ "isMany": true,
+ "type": "OutputParameter"
+ }
+ ]
+ },
+ {
+ "name": "InputOutputParameter",
+ "properties": [
+ {
+ "name": "name",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "workflowSource",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "workflowTarget",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "value",
+ "isBody": true,
+ "type": "String"
+ },
+ {
+ "name": "definition",
+ "type": "InputOutputParameterDefinition"
+ }
+ ]
+ },
+ {
+ "name": "InputOutputParameterDefinition",
+ "isAbstract": true
+ },
+ {
+ "name": "List",
+ "superClass": ["InputOutputParameterDefinition"],
+ "properties": [
+ {
+ "name": "items",
+ "isMany": true,
+ "type": "InputOutputParameterDefinition"
+ }
+ ]
+ },
+ {
+ "name": "Map",
+ "superClass": ["InputOutputParameterDefinition"],
+ "properties": [
+ {
+ "name": "entries",
+ "isMany": true,
+ "type": "Entry"
+ }
+ ]
+ },
+ {
+ "name": "Entry",
+ "properties": [
+ {
+ "name": "key",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "value",
+ "isBody": true,
+ "type": "String"
+ },
+ {
+ "name": "definition",
+ "type": "InputOutputParameterDefinition"
+ }
+ ]
+ },
+ {
+ "name": "Value",
+ "superClass": ["InputOutputParameterDefinition"],
+ "properties": [
+ {
+ "name": "id",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "name",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "value",
+ "isBody": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "Script",
+ "superClass": ["InputOutputParameterDefinition"],
+ "properties": [
+ {
+ "name": "scriptFormat",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "resource",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "value",
+ "isBody": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "Field",
+ "superClass": ["Element"],
+ "meta": {
+ "allowedIn": [
+ "bpmn:ServiceTask",
+ "bpmn:BusinessRuleTask",
+ "bpmn:SendTask"
+ ]
+ },
+ "properties": [
+ {
+ "name": "name",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "expression",
+ "type": "String"
+ },
+ {
+ "name": "stringValue",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "string",
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "InputParameter",
+ "superClass": ["InputOutputParameter"]
+ },
+ {
+ "name": "OutputParameter",
+ "superClass": ["InputOutputParameter"]
+ },
+ {
+ "name": "Collectable",
+ "isAbstract": true,
+ "extends": ["bpmn:MultiInstanceLoopCharacteristics"],
+ "superClass": ["camunda:AsyncCapable"],
+ "properties": [
+ {
+ "name": "collection",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "elementVariable",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "FailedJobRetryTimeCycle",
+ "superClass": ["Element"],
+ "meta": {
+ "allowedIn": [
+ "bpmn:Task",
+ "bpmn:ServiceTask",
+ "bpmn:SendTask",
+ "bpmn:UserTask",
+ "bpmn:BusinessRuleTask",
+ "bpmn:ScriptTask",
+ "bpmn:ReceiveTask",
+ "bpmn:CallActivity",
+ "bpmn:TimerEventDefinition",
+ "bpmn:SignalEventDefinition",
+ "bpmn:MultiInstanceLoopCharacteristics"
+ ]
+ },
+ "properties": [
+ {
+ "name": "body",
+ "isBody": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "ExecutionListener",
+ "superClass": ["Element"],
+ "meta": {
+ "allowedIn": [
+ "bpmn:Task",
+ "bpmn:ServiceTask",
+ "bpmn:UserTask",
+ "bpmn:BusinessRuleTask",
+ "bpmn:ScriptTask",
+ "bpmn:ReceiveTask",
+ "bpmn:ManualTask",
+ "bpmn:ExclusiveGateway",
+ "bpmn:SequenceFlow",
+ "bpmn:ParallelGateway",
+ "bpmn:InclusiveGateway",
+ "bpmn:EventBasedGateway",
+ "bpmn:StartEvent",
+ "bpmn:IntermediateCatchEvent",
+ "bpmn:IntermediateThrowEvent",
+ "bpmn:EndEvent",
+ "bpmn:BoundaryEvent",
+ "bpmn:CallActivity",
+ "bpmn:SubProcess"
+ ]
+ },
+ "properties": [
+ {
+ "name": "expression",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "class",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "delegateExpression",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "event",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "script",
+ "type": "Script"
+ },
+ {
+ "name": "fields",
+ "type": "Field",
+ "isMany": true
+ }
+ ]
+ },
+ {
+ "name": "TaskListener",
+ "superClass": ["Element"],
+ "meta": {
+ "allowedIn": ["bpmn:UserTask"]
+ },
+ "properties": [
+ {
+ "name": "expression",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "class",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "delegateExpression",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "event",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "script",
+ "type": "Script"
+ },
+ {
+ "name": "fields",
+ "type": "Field",
+ "isMany": true
+ }
+ ]
+ },
+ {
+ "name": "FormProperty",
+ "superClass": ["Element"],
+ "meta": {
+ "allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"]
+ },
+ "properties": [
+ {
+ "name": "id",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "name",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "type",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "required",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "readable",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "writable",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "variable",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "expression",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "datePattern",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "default",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "values",
+ "type": "Value",
+ "isMany": true
+ }
+ ]
+ },
+ {
+ "name": "FormData",
+ "superClass": ["Element"],
+ "meta": {
+ "allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"]
+ },
+ "properties": [
+ {
+ "name": "fields",
+ "type": "FormField",
+ "isMany": true
+ },
+ {
+ "name": "businessKey",
+ "type": "String",
+ "isAttr": true
+ }
+ ]
+ },
+ {
+ "name": "FormField",
+ "superClass": ["Element"],
+ "properties": [
+ {
+ "name": "id",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "label",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "type",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "datePattern",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "defaultValue",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "properties",
+ "type": "Properties"
+ },
+ {
+ "name": "validation",
+ "type": "Validation"
+ },
+ {
+ "name": "values",
+ "type": "Value",
+ "isMany": true
+ }
+ ]
+ },
+ {
+ "name": "Validation",
+ "superClass": ["Element"],
+ "properties": [
+ {
+ "name": "constraints",
+ "type": "Constraint",
+ "isMany": true
+ }
+ ]
+ },
+ {
+ "name": "Constraint",
+ "superClass": ["Element"],
+ "properties": [
+ {
+ "name": "name",
+ "type": "String",
+ "isAttr": true
+ },
+ {
+ "name": "config",
+ "type": "String",
+ "isAttr": true
+ }
+ ]
+ },
+ {
+ "name": "ConditionalEventDefinition",
+ "isAbstract": true,
+ "extends": ["bpmn:ConditionalEventDefinition"],
+ "properties": [
+ {
+ "name": "variableName",
+ "isAttr": true,
+ "type": "String"
+ },
+ {
+ "name": "variableEvent",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ }
+ ],
+ "emumerations": []
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/WorkflowPropertiesProvider.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/WorkflowPropertiesProvider.js
new file mode 100644
index 00000000..d6b3b274
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/WorkflowPropertiesProvider.js
@@ -0,0 +1,300 @@
+import inherits from 'inherits';
+import CamundaPropertiesProvider from 'bpmn-js-properties-panel/lib/provider/camunda/CamundaPropertiesProvider';
+
+import { is, getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
+
+import asyncCapableHelper from 'bpmn-js-properties-panel/lib/helper/AsyncCapableHelper';
+import eventDefinitionHelper from 'bpmn-js-properties-panel/lib/helper/EventDefinitionHelper';
+import implementationTypeHelper from 'bpmn-js-properties-panel/lib/helper/ImplementationTypeHelper';
+
+import idProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/IdProps';
+import nameProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/NameProps';
+import processProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/ProcessProps';
+import linkProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/LinkProps';
+import eventProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/EventProps';
+import documentationProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/DocumentationProps';
+
+import elementTemplateChooserProps from 'bpmn-js-properties-panel/lib/provider/camunda/element-templates/parts/ChooserProps';
+import elementTemplateCustomProps from 'bpmn-js-properties-panel/lib/provider/camunda/element-templates/parts/CustomProps';
+
+import versionTag from 'bpmn-js-properties-panel/lib/provider/camunda/parts/VersionTagProps';
+import userTaskProps from 'bpmn-js-properties-panel/lib/provider/camunda/parts/UserTaskProps';
+import scriptProps from 'bpmn-js-properties-panel/lib/provider/camunda/parts/ScriptTaskProps';
+import callActivityProps from 'bpmn-js-properties-panel/lib/provider/camunda/parts/CallActivityProps';
+import conditionalProps from 'bpmn-js-properties-panel/lib/provider/camunda/parts/ConditionalProps';
+import startEventInitiator from 'bpmn-js-properties-panel/lib/provider/camunda/parts/StartEventInitiator';
+import multiInstanceProps from 'bpmn-js-properties-panel/lib/provider/camunda/parts/MultiInstanceLoopProps';
+import asynchronousContinuationProps from 'bpmn-js-properties-panel/lib/provider/camunda/parts/AsynchronousContinuationProps';
+import jobConfiguration from 'bpmn-js-properties-panel/lib/provider/camunda/parts/JobConfigurationProps';
+import externalTaskConfiguration from 'bpmn-js-properties-panel/lib/provider/camunda/parts/ExternalTaskConfigurationProps';
+import candidateStarter from 'bpmn-js-properties-panel/lib/provider/camunda/parts/CandidateStarterProps';
+import historyTimeToLive from 'bpmn-js-properties-panel/lib/provider/camunda/parts/HistoryTimeToLiveProps';
+
+import createInputOutputTabGroups from './parts/createInputOutputTabGroups';
+import workflowServiceTaskDelegateProps from './parts/WorkflowServiceTaskDelegateProps';
+
+const PROCESS_KEY_HINT = 'This maps to the process definition key.';
+
+const isExternalTaskPriorityEnabled = function(element) {
+ const businessObject = getBusinessObject(element);
+
+ // show only if element is a process, a participant ...
+ if (
+ is(element, 'bpmn:Process') ||
+ (is(element, 'bpmn:Participant') && businessObject.get('processRef'))
+ ) {
+ return true;
+ }
+
+ const externalBo = implementationTypeHelper.getServiceTaskLikeBusinessObject(
+ element
+ ),
+ isExternalTask =
+ implementationTypeHelper.getImplementationType(externalBo) ===
+ 'external';
+
+ // ... or an external task with selected external implementation type
+ return (
+ !!implementationTypeHelper.isExternalCapable(externalBo) &&
+ isExternalTask
+ );
+};
+
+const isJobConfigEnabled = element => {
+ const businessObject = getBusinessObject(element);
+
+ if (
+ is(element, 'bpmn:Process') ||
+ (is(element, 'bpmn:Participant') && businessObject.get('processRef'))
+ ) {
+ return true;
+ }
+
+ // async behavior
+ const bo = getBusinessObject(element);
+ if (
+ asyncCapableHelper.isAsyncBefore(bo) ||
+ asyncCapableHelper.isAsyncAfter(bo)
+ ) {
+ return true;
+ }
+
+ // timer definition
+ if (is(element, 'bpmn:Event')) {
+ return !!eventDefinitionHelper.getTimerEventDefinition(element);
+ }
+
+ return false;
+};
+
+function createGeneralTabGroups(
+ element,
+ config,
+ bpmnFactory,
+ elementRegistry,
+ elementTemplates,
+ translate
+) {
+ // refer to target element for external labels
+ element = element.labelTarget || element;
+
+ const generalGroup = {
+ id: 'general',
+ label: translate('General'),
+ entries: []
+ };
+
+ let idOptions;
+ let processOptions;
+
+ if (is(element, 'bpmn:Process')) {
+ idOptions = { description: PROCESS_KEY_HINT };
+ }
+
+ if (is(element, 'bpmn:Participant')) {
+ processOptions = { processIdDescription: PROCESS_KEY_HINT };
+ }
+
+ idProps(generalGroup, element, translate, idOptions);
+ nameProps(generalGroup, element, translate);
+ processProps(generalGroup, element, translate, processOptions);
+ versionTag(generalGroup, element, translate);
+ elementTemplateChooserProps(
+ generalGroup,
+ element,
+ elementTemplates,
+ translate
+ );
+
+ const customFieldsGroups = elementTemplateCustomProps(
+ element,
+ elementTemplates,
+ bpmnFactory,
+ translate
+ );
+
+ const detailsGroup = {
+ id: 'details',
+ label: translate('Details'),
+ entries: []
+ };
+ workflowServiceTaskDelegateProps(
+ detailsGroup,
+ element,
+ config,
+ bpmnFactory,
+ translate
+ );
+ userTaskProps(detailsGroup, element, translate);
+ scriptProps(detailsGroup, element, bpmnFactory, translate);
+ linkProps(detailsGroup, element, translate);
+ callActivityProps(detailsGroup, element, bpmnFactory, translate);
+ eventProps(detailsGroup, element, bpmnFactory, elementRegistry, translate);
+ conditionalProps(detailsGroup, element, bpmnFactory, translate);
+ startEventInitiator(detailsGroup, element, translate); // this must be the last element of the details group!
+
+ const multiInstanceGroup = {
+ id: 'multiInstance',
+ label: translate('Multi Instance'),
+ entries: []
+ };
+ multiInstanceProps(multiInstanceGroup, element, bpmnFactory, translate);
+
+ const asyncGroup = {
+ id: 'async',
+ label: translate('Asynchronous Continuations'),
+ entries: []
+ };
+ asynchronousContinuationProps(asyncGroup, element, bpmnFactory, translate);
+
+ const jobConfigurationGroup = {
+ id: 'jobConfiguration',
+ label: translate('Job Configuration'),
+ entries: [],
+ enabled: isJobConfigEnabled
+ };
+ jobConfiguration(jobConfigurationGroup, element, bpmnFactory, translate);
+
+ const externalTaskGroup = {
+ id: 'externalTaskConfiguration',
+ label: translate('External Task Configuration'),
+ entries: [],
+ enabled: isExternalTaskPriorityEnabled
+ };
+ externalTaskConfiguration(
+ externalTaskGroup,
+ element,
+ bpmnFactory,
+ translate
+ );
+
+ const candidateStarterGroup = {
+ id: 'candidateStarterConfiguration',
+ label: translate('Candidate Starter Configuration'),
+ entries: []
+ };
+ candidateStarter(candidateStarterGroup, element, bpmnFactory, translate);
+
+ const historyTimeToLiveGroup = {
+ id: 'historyConfiguration',
+ label: translate('History Configuration'),
+ entries: []
+ };
+ historyTimeToLive(historyTimeToLiveGroup, element, bpmnFactory, translate);
+
+ const documentationGroup = {
+ id: 'documentation',
+ label: translate('Documentation'),
+ entries: []
+ };
+ documentationProps(documentationGroup, element, bpmnFactory, translate);
+
+ const groups = [];
+ groups.push(generalGroup);
+ customFieldsGroups.forEach(function(group) {
+ groups.push(group);
+ });
+ groups.push(detailsGroup);
+ groups.push(externalTaskGroup);
+ groups.push(multiInstanceGroup);
+ groups.push(asyncGroup);
+ groups.push(jobConfigurationGroup);
+ groups.push(candidateStarterGroup);
+ groups.push(historyTimeToLiveGroup);
+ groups.push(documentationGroup);
+
+ return groups;
+}
+
+function WorkflowPropertiesProvider(
+ config,
+ eventBus,
+ bpmnFactory,
+ elementRegistry,
+ elementTemplates,
+ translate
+) {
+ CamundaPropertiesProvider.call(
+ this,
+ eventBus,
+ bpmnFactory,
+ elementRegistry,
+ elementTemplates,
+ translate
+ );
+
+ this.getTabs = function(element) {
+ const camundaPropertiesProvider = new CamundaPropertiesProvider(
+ eventBus,
+ bpmnFactory,
+ elementRegistry,
+ elementTemplates,
+ translate
+ );
+
+ const tabs = camundaPropertiesProvider
+ .getTabs(element)
+ .filter(tab => tab.id !== 'general' && tab.id !== 'input-output');
+
+ const generalTab = {
+ id: 'general',
+ label: translate('General'),
+ groups: createGeneralTabGroups(
+ element,
+ config,
+ bpmnFactory,
+ elementRegistry,
+ elementTemplates,
+ translate
+ )
+ };
+
+ const inputOutputTab = {
+ id: 'input-output',
+ label: translate('Input/Output'),
+ groups: createInputOutputTabGroups(
+ element,
+ bpmnFactory,
+ elementRegistry,
+ translate,
+ config
+ )
+ };
+ tabs.unshift(inputOutputTab);
+ tabs.unshift(generalTab);
+ return tabs;
+ };
+}
+
+WorkflowPropertiesProvider.$inject = [
+ 'config.workflow',
+ 'eventBus',
+ 'bpmnFactory',
+ 'elementRegistry',
+ 'elementTemplates',
+ 'translate'
+];
+
+inherits(WorkflowPropertiesProvider, CamundaPropertiesProvider);
+
+export default WorkflowPropertiesProvider;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/index.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/index.js
new file mode 100644
index 00000000..bc60c581
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/index.js
@@ -0,0 +1,10 @@
+import ElementTemplates from 'bpmn-js-properties-panel/lib/provider/camunda/element-templates';
+import Translate from 'diagram-js/lib/i18n/translate';
+
+import WorkflowPropertiesProvider from './WorkflowPropertiesProvider';
+
+export default {
+ __depends__: [ElementTemplates, Translate],
+ __init__: ['propertiesProvider'],
+ propertiesProvider: ['type', WorkflowPropertiesProvider]
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/InputOutputParameterProps.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/InputOutputParameterProps.js
new file mode 100644
index 00000000..d221c6e3
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/InputOutputParameterProps.js
@@ -0,0 +1,21 @@
+import inputOutputParameter from './implementation/InputOutputParameter';
+import assign from 'lodash.assign';
+
+export default function(
+ group,
+ element,
+ bpmnFactory,
+ options,
+ translate,
+ config
+) {
+ group.entries = group.entries.concat(
+ inputOutputParameter(
+ element,
+ bpmnFactory,
+ assign({}, options),
+ translate,
+ config
+ )
+ );
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/InputOutputProps.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/InputOutputProps.js
new file mode 100644
index 00000000..bdcbab46
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/InputOutputProps.js
@@ -0,0 +1,13 @@
+'use strict';
+
+import inputOutput from './implementation/InputOutput';
+
+export default function(group, element, bpmnFactory, translate) {
+ const inputOutputEntry = inputOutput(element, bpmnFactory, {}, translate);
+
+ group.entries = group.entries.concat(inputOutputEntry.entries);
+
+ return {
+ getSelectedParameter: inputOutputEntry.getSelectedParameter
+ };
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/WorkflowServiceTaskDelegateProps.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/WorkflowServiceTaskDelegateProps.js
new file mode 100644
index 00000000..bc871357
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/WorkflowServiceTaskDelegateProps.js
@@ -0,0 +1,123 @@
+import inherits from 'inherits';
+
+import ImplementationTypeHelper from 'bpmn-js-properties-panel/lib/helper/ImplementationTypeHelper';
+import ServiceTaskDelegateProps from 'bpmn-js-properties-panel/lib/provider/camunda/parts/ServiceTaskDelegateProps';
+import workflowImplementationType from './implementation/WorkflowImplementationType';
+import workflowActivity from './implementation/WorkflowActivity';
+import {
+ implementationType as implementationTypeConst,
+ serviceTaskEntries
+} from './implementation/implementationConstants';
+import Delegate from './implementation/Delegate';
+import ResultVariable from './implementation/ResultVariable';
+
+const getImplementationType = element => {
+ let implementationType = ImplementationTypeHelper.getImplementationType(
+ element
+ );
+
+ if (!implementationType || implementationType === 'expression') {
+ const bo = getBusinessObject(element);
+ if (bo) {
+ if (
+ typeof bo.get(implementationTypeConst.ACTIVITY) !== 'undefined'
+ ) {
+ return 'workflowActivity';
+ }
+ }
+ }
+
+ return implementationType;
+};
+
+const hideResultVariable = element => {
+ return getImplementationType(element) !== 'expression';
+};
+
+const getBusinessObject = element =>
+ ImplementationTypeHelper.getServiceTaskLikeBusinessObject(element);
+
+const isDmnCapable = element => ImplementationTypeHelper.isDmnCapable(element);
+
+const isExternalCapable = element =>
+ ImplementationTypeHelper.isExternalCapable(element);
+
+const isServiceTaskLike = element =>
+ ImplementationTypeHelper.isServiceTaskLike(element);
+
+function WorkflowServiceTaskDelegateProps(
+ group,
+ element,
+ config,
+ bpmnFactory,
+ translate
+) {
+ ServiceTaskDelegateProps.call(this, group, element, bpmnFactory, translate);
+
+ if (isServiceTaskLike(getBusinessObject(element))) {
+ group.entries = group.entries.filter(
+ entry =>
+ entry.id !== serviceTaskEntries.IMPLEMENTATION &&
+ entry.id !== serviceTaskEntries.DELEGATE &&
+ entry.id !== serviceTaskEntries.RESULT_VARIABLE
+ );
+
+ group.entries = group.entries.concat(
+ workflowActivity(
+ element,
+ config,
+ bpmnFactory,
+ {
+ getBusinessObject: getBusinessObject,
+ getImplementationType: getImplementationType
+ },
+ translate
+ )
+ );
+
+ group.entries = group.entries.concat(
+ Delegate(
+ element,
+ bpmnFactory,
+ {
+ getBusinessObject: getBusinessObject,
+ getImplementationType: getImplementationType
+ },
+ translate
+ )
+ );
+
+ group.entries = group.entries.concat(
+ ResultVariable(
+ element,
+ bpmnFactory,
+ {
+ getBusinessObject: getBusinessObject,
+ getImplementationType: getImplementationType,
+ hideResultVariable: hideResultVariable
+ },
+ translate
+ )
+ );
+ group.entries = group.entries.concat(
+ workflowImplementationType(
+ element,
+ bpmnFactory,
+ {
+ getBusinessObject: getBusinessObject,
+ getImplementationType: getImplementationType,
+ hasDmnSupport: isDmnCapable(element),
+ hasExternalSupport: isExternalCapable(
+ getBusinessObject(element)
+ ),
+ hasServiceTaskLikeSupport: true
+ },
+ translate
+ )
+ );
+ }
+}
+
+inherits(WorkflowServiceTaskDelegateProps, ServiceTaskDelegateProps);
+
+export default WorkflowServiceTaskDelegateProps;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/createInputOutputTabGroups.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/createInputOutputTabGroups.js
new file mode 100644
index 00000000..3dede1a9
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/createInputOutputTabGroups.js
@@ -0,0 +1,58 @@
+import inputOutputParameter from './InputOutputParameterProps';
+import inputOutput from './InputOutputProps';
+const is = require('bpmn-js/lib/util/ModelUtil').is;
+
+var getInputOutputParameterLabel = function(param, translate) {
+ if (is(param, 'camunda:InputParameter')) {
+ return translate('Input Parameter');
+ }
+
+ if (is(param, 'camunda:OutputParameter')) {
+ return translate('Output Parameter');
+ }
+
+ return '';
+};
+
+export default function createInputOutputTabGroups(
+ element,
+ bpmnFactory,
+ elementRegistry,
+ translate,
+ config
+) {
+ var inputOutputGroup = {
+ id: 'input-output',
+ label: translate('Parameters'),
+ entries: []
+ };
+
+ var options = inputOutput(
+ inputOutputGroup,
+ element,
+ bpmnFactory,
+ translate
+ );
+ var inputOutputParameterGroup = {
+ id: 'input-output-parameter',
+ entries: [],
+ enabled: function(element, node) {
+ return options.getSelectedParameter(element, node);
+ },
+ label: function(element, node) {
+ var param = options.getSelectedParameter(element, node);
+ return getInputOutputParameterLabel(param, translate);
+ }
+ };
+
+ inputOutputParameter(
+ inputOutputParameterGroup,
+ element,
+ bpmnFactory,
+ options,
+ translate,
+ config
+ );
+
+ return [inputOutputGroup, inputOutputParameterGroup];
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/Delegate.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/Delegate.js
new file mode 100644
index 00000000..f6a0b247
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/Delegate.js
@@ -0,0 +1,78 @@
+'use strict';
+
+import entryFactory from 'bpmn-js-properties-panel/lib/factory/EntryFactory';
+import cmdHelper from 'bpmn-js-properties-panel/lib/helper/CmdHelper';
+
+const DELEGATE_TYPES = ['class', 'expression', 'delegateExpression'];
+
+const PROPERTIES = {
+ class: 'camunda:class',
+ expression: 'camunda:expression',
+ delegateExpression: 'camunda:delegateExpression'
+};
+
+function isDelegate(type) {
+ return DELEGATE_TYPES.indexOf(type) !== -1;
+}
+
+function getAttribute(type) {
+ return PROPERTIES[type];
+}
+
+export default function(element, bpmnFactory, options, translate) {
+ var getImplementationType = options.getImplementationType,
+ getBusinessObject = options.getBusinessObject;
+
+ function getDelegationLabel(type) {
+ switch (type) {
+ case 'class':
+ return translate('Java Class');
+ case 'expression':
+ return translate('Expression');
+ case 'delegateExpression':
+ return translate('Delegate Expression');
+ default:
+ return '';
+ }
+ }
+
+ var delegateEntry = entryFactory.textField({
+ id: 'delegate',
+ label: translate('Value'),
+ dataValueLabel: 'delegationLabel',
+ modelProperty: 'delegate',
+
+ get: function(element) {
+ var bo = getBusinessObject(element);
+ var type = getImplementationType(element);
+ var attr = getAttribute(type);
+ var label = getDelegationLabel(type);
+ return {
+ delegate: bo.get(attr),
+ delegationLabel: label
+ };
+ },
+
+ set: function(element, values) {
+ var bo = getBusinessObject(element);
+ var type = getImplementationType(element);
+ var attr = getAttribute(type);
+ var prop = {};
+ prop[attr] = values.delegate || '';
+ return cmdHelper.updateBusinessObject(element, bo, prop);
+ },
+
+ validate: function(element, values) {
+ return isDelegate(getImplementationType(element)) &&
+ !values.delegate
+ ? { delegate: 'Must provide a value' }
+ : {};
+ },
+
+ hidden: function(element) {
+ return !isDelegate(getImplementationType(element));
+ }
+ });
+
+ return [delegateEntry];
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutput.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutput.js
new file mode 100644
index 00000000..2bbef4f2
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutput.js
@@ -0,0 +1,289 @@
+import inputOutputHelper from './InputOutputHelper';
+var getBusinessObject = require('bpmn-js/lib/util/ModelUtil').getBusinessObject;
+
+var elementHelper = require('bpmn-js-properties-panel/lib/helper/ElementHelper'),
+ extensionElementsHelper = require('bpmn-js-properties-panel/lib/helper/ExtensionElementsHelper'),
+ cmdHelper = require('bpmn-js-properties-panel/lib/helper/CmdHelper');
+
+var extensionElementsEntry = require('bpmn-js-properties-panel/lib/provider/camunda/parts/implementation//ExtensionElements');
+
+function getInputOutput(element, insideConnector) {
+ return inputOutputHelper.getInputOutput(element, insideConnector);
+}
+
+function getConnector(element) {
+ return inputOutputHelper.getConnector(element);
+}
+
+function getInputParameters(element, insideConnector) {
+ return inputOutputHelper.getInputParameters(element, insideConnector);
+}
+
+function getOutputParameters(element, insideConnector) {
+ return inputOutputHelper.getOutputParameters(element, insideConnector);
+}
+
+function getInputParameter(element, insideConnector, idx) {
+ return inputOutputHelper.getInputParameter(element, insideConnector, idx);
+}
+
+function getOutputParameter(element, insideConnector, idx) {
+ return inputOutputHelper.getOutputParameter(element, insideConnector, idx);
+}
+
+export function createElement(type, parent, factory, properties) {
+ const el = elementHelper.createElement(type, properties, parent, factory);
+ return el;
+}
+
+export function createInputOutput(parent, bpmnFactory, properties) {
+ return createElement(
+ 'camunda:InputOutput',
+ parent,
+ bpmnFactory,
+ properties
+ );
+}
+
+function createParameter(type, parent, bpmnFactory, properties) {
+ return createElement(type, parent, bpmnFactory, properties);
+}
+
+function ensureInputOutputSupported(element, insideConnector) {
+ return inputOutputHelper.isInputOutputSupported(element, insideConnector);
+}
+
+function ensureOutparameterSupported(element, insideConnector) {
+ return inputOutputHelper.areOutputParametersSupported(
+ element,
+ insideConnector
+ );
+}
+
+export default function(element, bpmnFactory, options, translate) {
+ var TYPE_LABEL = {
+ 'camunda:Map': translate('Map'),
+ 'camunda:List': translate('List'),
+ 'camunda:Script': translate('Script')
+ };
+
+ options = options || {};
+
+ var insideConnector = !!options.insideConnector,
+ idPrefix = options.idPrefix || '';
+
+ var getSelected = function(element, node) {
+ var selection = (inputEntry &&
+ inputEntry.getSelected(element, node)) || { idx: -1 };
+
+ var parameter = getInputParameter(
+ element,
+ insideConnector,
+ selection.idx
+ );
+ if (!parameter && outputEntry) {
+ selection = outputEntry.getSelected(element, node);
+ parameter = getOutputParameter(
+ element,
+ insideConnector,
+ selection.idx
+ );
+ }
+ return parameter;
+ };
+
+ var result = {
+ getSelectedParameter: getSelected
+ };
+
+ var entries = (result.entries = []);
+
+ if (!ensureInputOutputSupported(element)) {
+ return result;
+ }
+
+ var newElement = function(type, prop, elementData) {
+ return function(element, extensionElements, value) {
+ var commands = [];
+
+ var inputOutput = getInputOutput(element, insideConnector);
+ if (!inputOutput) {
+ var parent = !insideConnector
+ ? extensionElements
+ : getConnector(element);
+
+ inputOutput = createInputOutput(parent, bpmnFactory, {
+ inputParameters: [],
+ outputParameters: []
+ });
+
+ if (!insideConnector) {
+ commands.push(
+ cmdHelper.addAndRemoveElementsFromList(
+ element,
+ extensionElements,
+ 'values',
+ 'extensionElements',
+ [inputOutput],
+ []
+ )
+ );
+ } else {
+ commands.push(
+ cmdHelper.updateBusinessObject(element, parent, {
+ inputOutput: inputOutput
+ })
+ );
+ }
+ }
+
+ var newElem = elementData
+ ? createParameter(type, inputOutput, bpmnFactory, elementData)
+ : createParameter(type, inputOutput, bpmnFactory, {
+ name: value
+ });
+
+ commands.push(
+ cmdHelper.addElementsTolist(element, inputOutput, prop, [
+ newElem
+ ])
+ );
+
+ return commands;
+ };
+ };
+
+ var removeElement = function(getter, prop, otherProp) {
+ return function(element, extensionElements, value, idx) {
+ var inputOutput = getInputOutput(element, insideConnector);
+ var parameter = getter(element, insideConnector, idx);
+
+ var commands = [];
+ commands.push(
+ cmdHelper.removeElementsFromList(
+ element,
+ inputOutput,
+ prop,
+ null,
+ [parameter]
+ )
+ );
+
+ var firstLength = inputOutput.get(prop).length - 1;
+ var secondLength = (inputOutput.get(otherProp) || []).length;
+
+ if (!firstLength && !secondLength) {
+ if (!insideConnector) {
+ commands.push(
+ extensionElementsHelper.removeEntry(
+ getBusinessObject(element),
+ element,
+ inputOutput
+ )
+ );
+ } else {
+ var connector = getConnector(element);
+ commands.push(
+ cmdHelper.updateBusinessObject(element, connector, {
+ inputOutput: undefined
+ })
+ );
+ }
+ }
+
+ return commands;
+ };
+ };
+
+ var setOptionLabelValue = function(getter) {
+ return function(element, node, option, property, value, idx) {
+ var parameter = getter(element, insideConnector, idx);
+
+ var suffix = 'Text';
+
+ var definition = parameter.get('definition');
+ if (typeof definition !== 'undefined') {
+ var type = definition.$type;
+ suffix = TYPE_LABEL[type];
+ }
+
+ option.text = (value || '') + ' : ' + suffix;
+ };
+ };
+
+ // input parameters ///////////////////////////////////////////////////////////////
+
+ var inputEntry = extensionElementsEntry(element, bpmnFactory, {
+ id: idPrefix + 'inputs',
+ label: translate('Input Parameters'),
+ modelProperty: 'name',
+ prefix: 'Input',
+ resizable: true,
+
+ createExtensionElement: inputOutputHelper.isCreateDeleteSupported(
+ element
+ )
+ ? newElement('camunda:InputParameter', 'inputParameters')
+ : undefined,
+ removeExtensionElement: inputOutputHelper.isCreateDeleteSupported(
+ element
+ )
+ ? removeElement(
+ getInputParameter,
+ 'inputParameters',
+ 'outputParameters'
+ )
+ : undefined,
+
+ getExtensionElements: function(element) {
+ return getInputParameters(element, insideConnector);
+ },
+
+ onSelectionChange: function(element, node) {
+ outputEntry && outputEntry.deselect(element, node);
+ },
+
+ setOptionLabelValue: setOptionLabelValue(getInputParameter)
+ });
+ entries.push(inputEntry);
+
+ // output parameters ///////////////////////////////////////////////////////
+
+ if (ensureOutparameterSupported(element, insideConnector)) {
+ var outputEntry = extensionElementsEntry(element, bpmnFactory, {
+ id: idPrefix + 'outputs',
+ label: translate('Output Parameters'),
+ modelProperty: 'name',
+ prefix: 'Output',
+ resizable: true,
+
+ createExtensionElement: inputOutputHelper.isCreateDeleteSupported(
+ element
+ )
+ ? newElement('camunda:OutputParameter', 'outputParameters')
+ : undefined,
+ removeExtensionElement: inputOutputHelper.isCreateDeleteSupported(
+ element
+ )
+ ? removeElement(
+ getOutputParameter,
+ 'outputParameters',
+ 'inputParameters'
+ )
+ : inputOutputHelper.isCreateDeleteSupported(element),
+
+ getExtensionElements: function(element) {
+ return getOutputParameters(element, insideConnector);
+ },
+
+ onSelectionChange: function(element, node) {
+ inputEntry.deselect(element, node);
+ },
+
+ setOptionLabelValue: setOptionLabelValue(getOutputParameter)
+ });
+ entries.push(outputEntry);
+ }
+
+ return result;
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutputHelper.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutputHelper.js
new file mode 100644
index 00000000..595ab799
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutputHelper.js
@@ -0,0 +1,173 @@
+var ModelUtil = require('bpmn-js/lib/util/ModelUtil'),
+ is = ModelUtil.is,
+ getBusinessObject = ModelUtil.getBusinessObject;
+
+var extensionElementsHelper = require('bpmn-js-properties-panel/lib/helper/ExtensionElementsHelper'),
+ implementationTypeHelper = require('bpmn-js-properties-panel/lib/helper/ImplementationTypeHelper');
+import { implementationType } from './implementationConstants';
+
+var InputOutputHelper = {};
+
+function getElements(bo, type, prop) {
+ var elems = extensionElementsHelper.getExtensionElements(bo, type) || [];
+ return !prop ? elems : (elems[0] || {})[prop] || [];
+}
+
+function getParameters(element, prop, insideConnector) {
+ var inputOutput = InputOutputHelper.getInputOutput(
+ element,
+ insideConnector
+ );
+ return (inputOutput && inputOutput.get(prop)) || [];
+}
+
+/**
+ * Get a inputOutput from the business object
+ *
+ * @param {djs.model.Base} element
+ * @param {boolean} insideConnector
+ *
+ * @return {ModdleElement} the inputOutput object
+ */
+InputOutputHelper.getInputOutput = function(element, insideConnector) {
+ if (!insideConnector) {
+ var bo = getBusinessObject(element);
+ return (getElements(bo, 'camunda:InputOutput') || [])[0];
+ }
+ var connector = this.getConnector(element);
+
+ return connector && connector.get('inputOutput');
+};
+
+/**
+ * Get a connector from the business object
+ *
+ * @param {djs.model.Base} element
+ *
+ * @return {ModdleElement} the connector object
+ */
+InputOutputHelper.getConnector = function(element) {
+ var bo = implementationTypeHelper.getServiceTaskLikeBusinessObject(element);
+ return bo && (getElements(bo, 'camunda:Connector') || [])[0];
+};
+
+/**
+ * Return all input parameters existing in the business object, and
+ * an empty array if none exist.
+ *
+ * @param {djs.model.Base} element
+ * @param {boolean} insideConnector
+ *
+ * @return {Array} a list of input parameter objects
+ */
+InputOutputHelper.getInputParameters = function(element, insideConnector) {
+ return getParameters.apply(this, [
+ element,
+ 'inputParameters',
+ insideConnector
+ ]);
+};
+
+/**
+ * Return all output parameters existing in the business object, and
+ * an empty array if none exist.
+ *
+ * @param {djs.model.Base} element
+ * @param {boolean} insideConnector
+ *
+ * @return {Array} a list of output parameter objects
+ */
+InputOutputHelper.getOutputParameters = function(element, insideConnector) {
+ return getParameters.apply(this, [
+ element,
+ 'outputParameters',
+ insideConnector
+ ]);
+};
+
+/**
+ * Get a input parameter from the business object at given index
+ *
+ * @param {djs.model.Base} element
+ * @param {boolean} insideConnector
+ * @param {number} idx
+ *
+ * @return {ModdleElement} input parameter
+ */
+InputOutputHelper.getInputParameter = function(element, insideConnector, idx) {
+ return this.getInputParameters(element, insideConnector)[idx];
+};
+
+/**
+ * Get a output parameter from the business object at given index
+ *
+ * @param {djs.model.Base} element
+ * @param {boolean} insideConnector
+ * @param {number} idx
+ *
+ * @return {ModdleElement} output parameter
+ */
+InputOutputHelper.getOutputParameter = function(element, insideConnector, idx) {
+ return this.getOutputParameters(element, insideConnector)[idx];
+};
+
+/**
+ * Returns 'true' if the given element supports inputOutput
+ *
+ * @param {djs.model.Base} element
+ * @param {boolean} insideConnector
+ *
+ * @return {boolean} a boolean value
+ */
+InputOutputHelper.isInputOutputSupported = function(element, insideConnector) {
+ var bo = getBusinessObject(element);
+ return (
+ insideConnector ||
+ is(bo, 'bpmn:Process') ||
+ (is(bo, 'bpmn:FlowNode') &&
+ !is(bo, 'bpmn:StartEvent') &&
+ !is(bo, 'bpmn:BoundaryEvent') &&
+ !(is(bo, 'bpmn:SubProcess') && bo.get('triggeredByEvent')))
+ );
+};
+
+/**
+ * Returns 'true' if the given element supports output parameters
+ *
+ * @param {djs.model.Base} element
+ * @param {boolean} insideConnector
+ *
+ * @return {boolean} a boolean value
+ */
+InputOutputHelper.areOutputParametersSupported = function(
+ element,
+ insideConnector
+) {
+ var bo = getBusinessObject(element);
+ return (
+ insideConnector || (!is(bo, 'bpmn:EndEvent') && !bo.loopCharacteristics)
+ );
+};
+
+InputOutputHelper.isCreateDeleteSupported = function(element) {
+ const bo = getBusinessObject(element);
+ return (
+ (element.type !== 'bpmn:ServiceTask' ||
+ !bo[implementationType.ACTIVITY]) &&
+ element.type !== 'bpmn:Process'
+ );
+};
+
+InputOutputHelper.isWorkflowTargetSupported = function(element, selected) {
+ const bo = getBusinessObject(element);
+ return (
+ is(bo, 'bpmn:ServiceTask') && is(selected, 'camunda:OutputParameter')
+ );
+};
+
+InputOutputHelper.isWorkflowSourceSupported = function(element, selected) {
+ const bo = getBusinessObject(element);
+ return is(bo, 'bpmn:ServiceTask') && is(selected, 'camunda:InputParameter');
+};
+
+export default InputOutputHelper;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutputParameter.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutputParameter.js
new file mode 100644
index 00000000..10e258e3
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutputParameter.js
@@ -0,0 +1,424 @@
+import inputOutputHelper from './InputOutputHelper';
+
+var is = require('bpmn-js/lib/util/ModelUtil').is;
+
+var elementHelper = require('bpmn-js-properties-panel/lib/helper/ElementHelper'),
+ cmdHelper = require('bpmn-js-properties-panel/lib/helper/CmdHelper'),
+ utils = require('bpmn-js-properties-panel/lib/Utils');
+
+var entryFactory = require('bpmn-js-properties-panel/lib/factory/EntryFactory'),
+ script = require('bpmn-js-properties-panel/lib/provider/camunda/parts/implementation/Script')(
+ 'scriptFormat',
+ 'value',
+ true
+ );
+
+function createElement(type, parent, factory, properties) {
+ return elementHelper.createElement(type, properties, parent, factory);
+}
+
+function isScript(elem) {
+ return is(elem, 'camunda:Script');
+}
+
+function isList(elem) {
+ return is(elem, 'camunda:List');
+}
+
+function isMap(elem) {
+ return is(elem, 'camunda:Map');
+}
+
+function ensureInputOutputSupported(element, insideConnector) {
+ return inputOutputHelper.isInputOutputSupported(element, insideConnector);
+}
+
+export default function(element, bpmnFactory, options, translate) {
+ var typeInfo = {
+ 'camunda:Map': {
+ value: 'map',
+ label: translate('Map')
+ },
+ 'camunda:List': {
+ value: 'list',
+ label: translate('List')
+ },
+ 'camunda:Script': {
+ value: 'script',
+ label: translate('Script')
+ }
+ };
+
+ options = options || {};
+
+ var insideConnector = !!options.insideConnector,
+ idPrefix = options.idPrefix || '';
+
+ var getSelected = options.getSelectedParameter;
+
+ if (!ensureInputOutputSupported(element, insideConnector)) {
+ return [];
+ }
+
+ var entries = [];
+
+ var isSelected = function(element, node) {
+ return getSelected(element, node);
+ };
+
+ // parameter name ////////////////////////////////////////////////////////
+
+ entries.push(
+ entryFactory.validationAwareTextField({
+ id: idPrefix + 'parameterName',
+ label: 'Name',
+ modelProperty: 'name',
+
+ getProperty: function(element, node) {
+ return (getSelected(element, node) || {}).name;
+ },
+
+ setProperty: function(element, values, node) {
+ var param = getSelected(element, node);
+ return cmdHelper.updateBusinessObject(element, param, values);
+ },
+
+ validate: function(element, values, node) {
+ var bo = getSelected(element, node);
+
+ var validation = {};
+ if (bo) {
+ var nameValue = values.name;
+
+ if (nameValue) {
+ if (utils.containsSpace(nameValue)) {
+ validation.name = 'Name must not contain spaces';
+ }
+ } else {
+ validation.name = 'Parameter must have a name';
+ }
+ }
+
+ return validation;
+ },
+
+ hidden: function(element, node) {
+ return !isSelected(element, node);
+ },
+ disabled: function(element) {
+ return !inputOutputHelper.isCreateDeleteSupported(element);
+ }
+ })
+ );
+
+ // parameter type //////////////////////////////////////////////////////
+
+ var selectOptions = [
+ { value: 'text', name: 'Text' },
+ { value: 'script', name: 'Script' },
+ { value: 'list', name: 'List' },
+ { value: 'map', name: 'Map' }
+ ];
+
+ entries.push(
+ entryFactory.selectBox({
+ id: idPrefix + 'parameterType',
+ label: 'Type',
+ selectOptions: selectOptions,
+ modelProperty: 'parameterType',
+
+ get: function(element, node) {
+ var bo = getSelected(element, node);
+
+ var parameterType = 'text';
+
+ if (typeof bo !== 'undefined') {
+ var definition = bo.get('definition');
+ if (typeof definition !== 'undefined') {
+ var type = definition.$type;
+ parameterType = typeInfo[type].value;
+ }
+ }
+
+ return {
+ parameterType: parameterType
+ };
+ },
+
+ set: function(element, values, node) {
+ var bo = getSelected(element, node);
+
+ var properties = {
+ value: undefined,
+ definition: undefined
+ };
+
+ var createParameterTypeElem = function(type) {
+ return createElement(type, bo, bpmnFactory);
+ };
+
+ var parameterType = values.parameterType;
+
+ if (parameterType === 'script') {
+ properties.definition = createParameterTypeElem(
+ 'camunda:Script'
+ );
+ } else if (parameterType === 'list') {
+ properties.definition = createParameterTypeElem(
+ 'camunda:List'
+ );
+ } else if (parameterType === 'map') {
+ properties.definition = createParameterTypeElem(
+ 'camunda:Map'
+ );
+ }
+
+ return cmdHelper.updateBusinessObject(element, bo, properties);
+ },
+
+ show: function(element, node) {
+ return isSelected(element, node);
+ },
+ disabled: function(element) {
+ return !inputOutputHelper.isCreateDeleteSupported(element);
+ }
+ })
+ );
+
+ // parameter value (type = text) ///////////////////////////////////////////////////////
+
+ entries.push(
+ entryFactory.textBox({
+ id: idPrefix + 'parameterType-text',
+ label: 'Value',
+ modelProperty: 'value',
+ get: function(element, node) {
+ return {
+ value: (getSelected(element, node) || {}).value
+ };
+ },
+
+ set: function(element, values, node) {
+ var param = getSelected(element, node);
+ values.value = values.value || undefined;
+ return cmdHelper.updateBusinessObject(element, param, values);
+ },
+
+ show: function(element, node) {
+ var bo = getSelected(element, node);
+ return bo && !bo.definition;
+ }
+ })
+ );
+
+ // parameter value (type = script) ///////////////////////////////////////////////////////
+
+ entries.push({
+ id: idPrefix + 'parameterType-script',
+ html: '<div data-show="isScript">' + script.template + '</div>',
+ get: function(element, node) {
+ var bo = getSelected(element, node);
+ return bo && isScript(bo.definition)
+ ? script.get(element, bo.definition)
+ : {};
+ },
+
+ set: function(element, values, node) {
+ var bo = getSelected(element, node);
+ var update = script.set(element, values);
+ return cmdHelper.updateBusinessObject(
+ element,
+ bo.definition,
+ update
+ );
+ },
+
+ validate: function(element, values, node) {
+ var bo = getSelected(element, node);
+ return bo && isScript(bo.definition)
+ ? script.validate(element, bo.definition)
+ : {};
+ },
+
+ isScript: function(element, node) {
+ var bo = getSelected(element, node);
+ return bo && isScript(bo.definition);
+ },
+
+ script: script
+ });
+
+ // parameter value (type = list) ///////////////////////////////////////////////////////
+
+ entries.push(
+ entryFactory.table({
+ id: idPrefix + 'parameterType-list',
+ modelProperties: ['value'],
+ labels: ['Value'],
+
+ getElements: function(element, node) {
+ var bo = getSelected(element, node);
+
+ if (bo && isList(bo.definition)) {
+ return bo.definition.items;
+ }
+
+ return [];
+ },
+
+ updateElement: function(element, values, node, idx) {
+ var bo = getSelected(element, node);
+ var item = bo.definition.items[idx];
+ return cmdHelper.updateBusinessObject(element, item, values);
+ },
+
+ addElement: function(element, node) {
+ var bo = getSelected(element, node);
+ var newValue = createElement(
+ 'camunda:Value',
+ bo.definition,
+ bpmnFactory,
+ { value: undefined }
+ );
+ return cmdHelper.addElementsTolist(
+ element,
+ bo.definition,
+ 'items',
+ [newValue]
+ );
+ },
+
+ removeElement: function(element, node, idx) {
+ var bo = getSelected(element, node);
+ return cmdHelper.removeElementsFromList(
+ element,
+ bo.definition,
+ 'items',
+ null,
+ [bo.definition.items[idx]]
+ );
+ },
+
+ editable: function(element, node, prop, idx) {
+ var bo = getSelected(element, node);
+ var item = bo.definition.items[idx];
+ return !isMap(item) && !isList(item) && !isScript(item);
+ },
+
+ setControlValue: function(element, node, input, prop, value, idx) {
+ var bo = getSelected(element, node);
+ var item = bo.definition.items[idx];
+
+ if (!isMap(item) && !isList(item) && !isScript(item)) {
+ input.value = value;
+ } else {
+ input.value = typeInfo[item.$type].label;
+ }
+ },
+
+ show: function(element, node) {
+ var bo = getSelected(element, node);
+ return bo && bo.definition && isList(bo.definition);
+ }
+ })
+ );
+
+ // parameter value (type = map) ///////////////////////////////////////////////////////
+
+ entries.push(
+ entryFactory.table({
+ id: idPrefix + 'parameterType-map',
+ modelProperties: ['key', 'value'],
+ labels: ['Key', 'Value'],
+ addLabel: 'Add Entry',
+
+ getElements: function(element, node) {
+ var bo = getSelected(element, node);
+
+ if (bo && isMap(bo.definition)) {
+ return bo.definition.entries;
+ }
+
+ return [];
+ },
+
+ updateElement: function(element, values, node, idx) {
+ var bo = getSelected(element, node);
+ var entry = bo.definition.entries[idx];
+
+ if (
+ isMap(entry.definition) ||
+ isList(entry.definition) ||
+ isScript(entry.definition)
+ ) {
+ values = {
+ key: values.key
+ };
+ }
+
+ return cmdHelper.updateBusinessObject(element, entry, values);
+ },
+
+ addElement: function(element, node) {
+ var bo = getSelected(element, node);
+ var newEntry = createElement(
+ 'camunda:Entry',
+ bo.definition,
+ bpmnFactory,
+ { key: undefined, value: undefined }
+ );
+ return cmdHelper.addElementsTolist(
+ element,
+ bo.definition,
+ 'entries',
+ [newEntry]
+ );
+ },
+
+ removeElement: function(element, node, idx) {
+ var bo = getSelected(element, node);
+ return cmdHelper.removeElementsFromList(
+ element,
+ bo.definition,
+ 'entries',
+ null,
+ [bo.definition.entries[idx]]
+ );
+ },
+
+ editable: function(element, node, prop, idx) {
+ var bo = getSelected(element, node);
+ var entry = bo.definition.entries[idx];
+ return (
+ prop === 'key' ||
+ (!isMap(entry.definition) &&
+ !isList(entry.definition) &&
+ !isScript(entry.definition))
+ );
+ },
+
+ setControlValue: function(element, node, input, prop, value, idx) {
+ var bo = getSelected(element, node);
+ var entry = bo.definition.entries[idx];
+
+ if (
+ prop === 'key' ||
+ (!isMap(entry.definition) &&
+ !isList(entry.definition) &&
+ !isScript(entry.definition))
+ ) {
+ input.value = value;
+ } else {
+ input.value = typeInfo[entry.definition.$type].label;
+ }
+ },
+
+ show: function(element, node) {
+ var bo = getSelected(element, node);
+ return bo && bo.definition && isMap(bo.definition);
+ }
+ })
+ );
+
+ return entries;
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutputUpdater.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutputUpdater.js
new file mode 100644
index 00000000..056a2dba
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/InputOutputUpdater.js
@@ -0,0 +1,74 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 cmdHelper from 'bpmn-js-properties-panel/lib/helper/CmdHelper';
+import { createInputOutput, createElement } from './InputOutput';
+import InputOutputHelper from './InputOutputHelper';
+import { INPUT, OUTPUT } from './implementationConstants';
+
+export default ({ element, bo, bpmnFactory, activityInputsOutputs }) => {
+ const commands = [];
+ const existedInputOutput = InputOutputHelper.getInputOutput(element);
+
+ let newInputOutput = createInputOutput(element, bpmnFactory, {
+ inputParameters: [],
+ outputParameters: []
+ });
+
+ const inputs = activityInputsOutputs.inputs.map(({ name, value }) =>
+ createElement(INPUT, newInputOutput, bpmnFactory, {
+ name,
+ type: 'Text',
+ value
+ })
+ );
+
+ const outputs = activityInputsOutputs.outputs.map(({ name, value }) =>
+ createElement(OUTPUT, newInputOutput, bpmnFactory, {
+ name,
+ type: 'Text',
+ value
+ })
+ );
+
+ newInputOutput.inputParameters = inputs;
+ newInputOutput.outputParameters = outputs;
+
+ const objectToRemove = existedInputOutput ? [existedInputOutput] : [];
+ const extensionElements =
+ bo.extensionElements ||
+ createElement('bpmn:ExtensionElements', bo, bpmnFactory, []);
+
+ if (!bo.extensionElements) {
+ commands.push(
+ cmdHelper.updateBusinessObject(element, bo, {
+ extensionElements
+ })
+ );
+ }
+
+ commands.push(
+ cmdHelper.addAndRemoveElementsFromList(
+ element,
+ extensionElements,
+ 'values',
+ 'extensionElements',
+ [newInputOutput],
+ objectToRemove
+ )
+ );
+ return commands;
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/ResultVariable.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/ResultVariable.js
new file mode 100644
index 00000000..a0b425fe
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/ResultVariable.js
@@ -0,0 +1,52 @@
+'use strict';
+
+import { is } from 'bpmn-js/lib/util/ModelUtil';
+
+import assign from 'lodash.assign';
+
+var entryFactory = require('bpmn-js-properties-panel/lib/factory/EntryFactory'),
+ cmdHelper = require('bpmn-js-properties-panel/lib/helper/CmdHelper');
+
+export default function(element, bpmnFactory, options, translate) {
+ var getBusinessObject = options.getBusinessObject,
+ hideResultVariable = options.hideResultVariable,
+ id = options.id || 'resultVariable';
+
+ var resultVariableEntry = entryFactory.textField({
+ id: id,
+ label: translate('Result Variable'),
+ modelProperty: 'resultVariable',
+
+ get: function(element) {
+ var bo = getBusinessObject(element);
+ return { resultVariable: bo.get('camunda:resultVariable') };
+ },
+
+ set: function(element, values) {
+ var bo = getBusinessObject(element);
+
+ var resultVariable = values.resultVariable || undefined;
+
+ var props = {
+ 'camunda:resultVariable': resultVariable
+ };
+
+ if (is(bo, 'camunda:DmnCapable') && !resultVariable) {
+ props = assign(
+ { 'camunda:mapDecisionResult': 'resultList' },
+ props
+ );
+ }
+
+ return cmdHelper.updateBusinessObject(element, bo, props);
+ },
+
+ hidden: function() {
+ if (typeof hideResultVariable === 'function') {
+ return hideResultVariable.apply(resultVariableEntry, arguments);
+ }
+ }
+ });
+
+ return [resultVariableEntry];
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/WorkflowActivity.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/WorkflowActivity.js
new file mode 100644
index 00000000..6616f6a4
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/WorkflowActivity.js
@@ -0,0 +1,89 @@
+import entryFactory from 'bpmn-js-properties-panel/lib/factory/EntryFactory';
+import cmdHelper from 'bpmn-js-properties-panel/lib/helper/CmdHelper';
+import {
+ implementationType,
+ IMPLEMENTATION_TYPE_VALUE,
+ SERVICE_TASK_NAME
+} from './implementationConstants';
+
+import InputOutputUpdater from './InputOutputUpdater';
+
+const workflowActivity = (element, config, bpmnFactory, options, translate) => {
+ const { getImplementationType, getBusinessObject } = options;
+
+ const isWorkflowActivity = element =>
+ getImplementationType(element) === 'workflowActivity';
+
+ const workflowActivityEntry = entryFactory.selectBox({
+ id: 'activitySelect',
+ label: translate('Activity Spec'),
+ selectOptions: config.activities,
+ emptyParameter: true,
+ modelProperty: 'workflowActivity',
+
+ get: function(element) {
+ var bo = getBusinessObject(element);
+ const value = bo.get(implementationType.ACTIVITY);
+ const activityValue =
+ value && value.indexOf(IMPLEMENTATION_TYPE_VALUE) > -1
+ ? value.substr(IMPLEMENTATION_TYPE_VALUE.length)
+ : '';
+
+ return {
+ workflowActivity: activityValue
+ };
+ },
+
+ set: function(element, values) {
+ var bo = getBusinessObject(element);
+
+ const commands = [];
+ const dataForUpdate = {};
+
+ const activityInputsOutputs = config.getActivityInputsOutputs(
+ values.workflowActivity
+ );
+
+ dataForUpdate[
+ implementationType.ACTIVITY
+ ] = `${IMPLEMENTATION_TYPE_VALUE}${values.workflowActivity}`;
+
+ dataForUpdate[implementationType.EXPRESSION] =
+ implementationType.EXPRESSION_VALUE;
+
+ dataForUpdate[SERVICE_TASK_NAME] = values.workflowActivity;
+
+ commands.push(
+ cmdHelper.updateBusinessObject(element, bo, dataForUpdate)
+ );
+ return [
+ ...commands,
+ ...InputOutputUpdater({
+ element,
+ bo,
+ bpmnFactory,
+ activityInputsOutputs,
+ commands
+ })
+ ];
+ },
+
+ validate: function(element, values) {
+ const hasErrors =
+ isWorkflowActivity(element) && !values.workflowActivity;
+ config.validationUpdate(element, !hasErrors);
+
+ return hasErrors
+ ? { workflowActivity: 'Must provide a value' }
+ : {};
+ },
+
+ hidden: function(element) {
+ return !isWorkflowActivity(element);
+ }
+ });
+
+ return [workflowActivityEntry];
+};
+
+export default workflowActivity;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/WorkflowImplementationType.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/WorkflowImplementationType.js
new file mode 100644
index 00000000..729cc22b
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/WorkflowImplementationType.js
@@ -0,0 +1,226 @@
+var entryFactory = require('bpmn-js-properties-panel/lib/factory/EntryFactory'),
+ cmdHelper = require('bpmn-js-properties-panel/lib/helper/CmdHelper'),
+ extensionElementsHelper = require('bpmn-js-properties-panel/lib/helper/ExtensionElementsHelper'),
+ elementHelper = require('bpmn-js-properties-panel/lib/helper/ElementHelper');
+
+var assign = require('lodash.assign');
+var map = require('lodash.map');
+import { implementationType } from './implementationConstants';
+
+var DEFAULT_DELEGATE_PROPS = ['class', 'expression', 'delegateExpression'];
+
+var DELEGATE_PROPS = {
+ 'camunda:class': undefined,
+ 'camunda:expression': undefined,
+ 'camunda:delegateExpression': undefined,
+ 'camunda:resultVariable': undefined
+};
+
+var DMN_CAPABLE_PROPS = {
+ 'camunda:decisionRef': undefined,
+ 'camunda:decisionRefBinding': 'latest',
+ 'camunda:decisionRefVersion': undefined,
+ 'camunda:mapDecisionResult': 'resultList',
+ 'camunda:decisionRefTenantId': undefined
+};
+
+var EXTERNAL_CAPABLE_PROPS = {
+ 'camunda:type': undefined,
+ 'camunda:topic': undefined
+};
+
+const ACTIVITY_PROPS = {};
+
+ACTIVITY_PROPS[implementationType] = undefined;
+
+export default function(element, bpmnFactory, options, translate) {
+ var DEFAULT_OPTIONS = [
+ { value: 'class', name: translate('Java Class') },
+ { value: 'expression', name: translate('Expression') },
+ { value: 'delegateExpression', name: translate('Delegate Expression') }
+ ];
+
+ var DMN_OPTION = [{ value: 'dmn', name: translate('DMN') }];
+
+ var EXTERNAL_OPTION = [{ value: 'external', name: translate('External') }];
+
+ var CONNECTOR_OPTION = [
+ { value: 'connector', name: translate('Connector') }
+ ];
+
+ var SCRIPT_OPTION = [{ value: 'script', name: translate('Script') }];
+
+ var ACTIVITY_OPTION = [
+ { value: 'workflowActivity', name: translate('Activity') }
+ ];
+
+ var getType = options.getImplementationType,
+ getBusinessObject = options.getBusinessObject;
+
+ var hasDmnSupport = options.hasDmnSupport,
+ hasExternalSupport = options.hasExternalSupport,
+ hasServiceTaskLikeSupport = options.hasServiceTaskLikeSupport,
+ hasScriptSupport = options.hasScriptSupport;
+
+ var entries = [];
+
+ var selectOptions = DEFAULT_OPTIONS.concat([]);
+
+ if (hasDmnSupport) {
+ selectOptions = selectOptions.concat(DMN_OPTION);
+ }
+
+ if (hasExternalSupport) {
+ selectOptions = selectOptions.concat(EXTERNAL_OPTION);
+ }
+
+ if (hasServiceTaskLikeSupport) {
+ selectOptions = selectOptions.concat(CONNECTOR_OPTION);
+ }
+
+ if (hasScriptSupport) {
+ selectOptions = selectOptions.concat(SCRIPT_OPTION);
+ }
+
+ selectOptions = selectOptions.concat(ACTIVITY_OPTION);
+
+ selectOptions.push({ value: '' });
+
+ entries.push(
+ entryFactory.selectBox({
+ id: 'implementation',
+ label: translate('Implementation'),
+ selectOptions: selectOptions,
+ modelProperty: 'implType',
+
+ get: function(element) {
+ return {
+ implType: getType(element) || ''
+ };
+ },
+
+ set: function(element, values) {
+ var bo = getBusinessObject(element);
+ var oldType = getType(element);
+ var newType = values.implType;
+ var props = assign({}, DELEGATE_PROPS);
+
+ if (DEFAULT_DELEGATE_PROPS.indexOf(newType) !== -1) {
+ var newValue = '';
+ if (DEFAULT_DELEGATE_PROPS.indexOf(oldType) !== -1) {
+ newValue = bo.get('camunda:' + oldType);
+ }
+
+ props['camunda:' + newType] = newValue;
+ }
+
+ if (hasDmnSupport) {
+ props = assign(props, DMN_CAPABLE_PROPS);
+ if (newType === 'dmn') {
+ props['camunda:decisionRef'] = '';
+ }
+ }
+
+ if (hasExternalSupport) {
+ props = assign(props, EXTERNAL_CAPABLE_PROPS);
+ if (newType === 'external') {
+ props['camunda:type'] = 'external';
+ props['camunda:topic'] = '';
+ }
+ }
+
+ if (hasScriptSupport) {
+ props['camunda:script'] = undefined;
+
+ if (newType === 'script') {
+ props['camunda:script'] = elementHelper.createElement(
+ 'camunda:Script',
+ {},
+ bo,
+ bpmnFactory
+ );
+ }
+ }
+ props = assign(props, ACTIVITY_PROPS);
+ props[implementationType.ACTIVITY] = undefined;
+
+ var commands = [];
+ if (newType === 'workflowActivity') {
+ props[implementationType.ACTIVITY] = '';
+ props[implementationType.RESULT_VARIABLE] = undefined;
+ props[implementationType.EXPRESSION] = undefined;
+ } else {
+ var inputsOutputs = extensionElementsHelper.getExtensionElements(
+ bo,
+ 'camunda:InputOutput'
+ );
+ commands.push(
+ map(inputsOutputs, function(inputOutput) {
+ return extensionElementsHelper.removeEntry(
+ bo,
+ element,
+ inputOutput
+ );
+ })
+ );
+ }
+
+ commands.push(
+ cmdHelper.updateBusinessObject(element, bo, props)
+ );
+
+ if (hasServiceTaskLikeSupport) {
+ var connectors = extensionElementsHelper.getExtensionElements(
+ bo,
+ 'camunda:Connector'
+ );
+ commands.push(
+ map(connectors, function(connector) {
+ return extensionElementsHelper.removeEntry(
+ bo,
+ element,
+ connector
+ );
+ })
+ );
+
+ if (newType === 'connector') {
+ var extensionElements = bo.get('extensionElements');
+ if (!extensionElements) {
+ extensionElements = elementHelper.createElement(
+ 'bpmn:ExtensionElements',
+ { values: [] },
+ bo,
+ bpmnFactory
+ );
+ commands.push(
+ cmdHelper.updateBusinessObject(element, bo, {
+ extensionElements: extensionElements
+ })
+ );
+ }
+ var connector = elementHelper.createElement(
+ 'camunda:Connector',
+ {},
+ extensionElements,
+ bpmnFactory
+ );
+ commands.push(
+ cmdHelper.addAndRemoveElementsFromList(
+ element,
+ extensionElements,
+ 'values',
+ 'extensionElements',
+ [connector],
+ []
+ )
+ );
+ }
+ }
+ return commands;
+ }
+ })
+ );
+
+ return entries;
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/implementationConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/implementationConstants.js
new file mode 100644
index 00000000..efc70800
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-properties-provider/provider/camunda/parts/implementation/implementationConstants.js
@@ -0,0 +1,34 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const implementationType = {
+ ACTIVITY: 'implementation',
+ EXPRESSION: 'camunda:expression',
+ EXPRESSION_VALUE: '${ExecuteActivity.execute(execution)}',
+ RESULT_VARIABLE: 'camunda:resultVariable'
+};
+
+export const IMPLEMENTATION_TYPE_VALUE = 'activity:';
+export const SERVICE_TASK_NAME = 'name';
+
+export const serviceTaskEntries = {
+ IMPLEMENTATION: 'implementation',
+ DELEGATE: 'delegate',
+ RESULT_VARIABLE: 'resultVariable'
+};
+
+export const INPUT = 'camunda:InputParameter';
+export const OUTPUT = 'camunda:OutputParameter';
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/newDiagram.bpmn b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/newDiagram.bpmn
new file mode 100644
index 00000000..6abaf8df
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/newDiagram.bpmn
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn">
+ <bpmn2:process id="Process_1" isExecutable="true">
+ </bpmn2:process>
+ <bpmndi:BPMNDiagram id="BPMNDiagram_1">
+ <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
+ </bpmndi:BPMNPlane>
+ </bpmndi:BPMNDiagram>
+</bpmn2:definitions>
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/readOnly.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/readOnly.js
new file mode 100644
index 00000000..f1da7dcf
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/composition/readOnly.js
@@ -0,0 +1,134 @@
+import forEach from 'lodash.foreach';
+
+const HIGH_PRIORITY = 10001;
+
+function ReadOnly(
+ eventBus,
+ contextPad,
+ dragging,
+ directEditing,
+ editorActions,
+ modeling,
+ palette,
+ paletteProvider
+) {
+ this._readOnly = false;
+ this._eventBus = eventBus;
+
+ let self = this;
+ eventBus.on('readOnly.changed', HIGH_PRIORITY, function(e) {
+ self._readOnly = e.readOnly;
+
+ if (e.readOnly) {
+ directEditing.cancel();
+ contextPad.close();
+ dragging.cancel();
+ }
+
+ palette._update();
+ });
+
+ function intercept(obj, fnName, cb) {
+ var fn = obj[fnName];
+ obj[fnName] = function() {
+ return cb.call(this, fn, arguments);
+ };
+ }
+
+ function ignoreWhenReadOnly(obj, fnName) {
+ intercept(obj, fnName, function(fn, args) {
+ if (self._readOnly) {
+ return;
+ }
+
+ return fn.apply(this, args);
+ });
+ }
+
+ function throwIfReadOnly(obj, fnName) {
+ intercept(obj, fnName, function(fn, args) {
+ if (self._readOnly) {
+ throw new Error('model is read-only');
+ }
+
+ return fn.apply(this, args);
+ });
+ }
+
+ ignoreWhenReadOnly(contextPad, 'open');
+
+ ignoreWhenReadOnly(dragging, 'init');
+
+ ignoreWhenReadOnly(directEditing, 'activate');
+
+ ignoreWhenReadOnly(editorActions._actions, 'undo');
+ ignoreWhenReadOnly(editorActions._actions, 'redo');
+ ignoreWhenReadOnly(editorActions._actions, 'copy');
+ ignoreWhenReadOnly(editorActions._actions, 'paste');
+ ignoreWhenReadOnly(editorActions._actions, 'removeSelection');
+ // BpmnEditorActions
+ ignoreWhenReadOnly(editorActions._actions, 'spaceTool');
+ ignoreWhenReadOnly(editorActions._actions, 'lassoTool');
+ ignoreWhenReadOnly(editorActions._actions, 'globalConnectTool');
+ ignoreWhenReadOnly(editorActions._actions, 'distributeElements');
+ ignoreWhenReadOnly(editorActions._actions, 'alignElements');
+ ignoreWhenReadOnly(editorActions._actions, 'directEditing');
+
+ throwIfReadOnly(modeling, 'moveShape');
+ throwIfReadOnly(modeling, 'updateAttachment');
+ throwIfReadOnly(modeling, 'moveElements');
+ throwIfReadOnly(modeling, 'moveConnection');
+ throwIfReadOnly(modeling, 'layoutConnection');
+ throwIfReadOnly(modeling, 'createConnection');
+ throwIfReadOnly(modeling, 'createShape');
+ throwIfReadOnly(modeling, 'createLabel');
+ throwIfReadOnly(modeling, 'appendShape');
+ throwIfReadOnly(modeling, 'removeElements');
+ throwIfReadOnly(modeling, 'distributeElements');
+ throwIfReadOnly(modeling, 'removeShape');
+ throwIfReadOnly(modeling, 'removeConnection');
+ throwIfReadOnly(modeling, 'replaceShape');
+ throwIfReadOnly(modeling, 'pasteElements');
+ throwIfReadOnly(modeling, 'alignElements');
+ throwIfReadOnly(modeling, 'resizeShape');
+ throwIfReadOnly(modeling, 'createSpace');
+ throwIfReadOnly(modeling, 'updateWaypoints');
+ throwIfReadOnly(modeling, 'reconnectStart');
+ throwIfReadOnly(modeling, 'reconnectEnd');
+
+ intercept(paletteProvider, 'getPaletteEntries', function(fn, args) {
+ var entries = fn.apply(this, args);
+ if (self._readOnly) {
+ forEach(entries, function(value, key) {
+ delete entries[key];
+ });
+ }
+ return entries;
+ });
+}
+
+ReadOnly.$inject = [
+ 'eventBus',
+ 'contextPad',
+ 'dragging',
+ 'directEditing',
+ 'editorActions',
+ 'modeling',
+ 'palette',
+ 'paletteProvider'
+];
+
+module.exports = ReadOnly;
+
+ReadOnly.prototype.readOnly = function(readOnly) {
+ var newValue = !!readOnly,
+ oldValue = !!this._readOnly;
+
+ if (readOnly === undefined || newValue === oldValue) {
+ return oldValue;
+ }
+
+ this._readOnly = newValue;
+ this._eventBus.fire('readOnly.changed', { readOnly: newValue });
+ return newValue;
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/CreateVersion.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/CreateVersion.js
new file mode 100644
index 00000000..2635fa36
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/CreateVersion.js
@@ -0,0 +1,53 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
+
+import { hideModalAction } from 'shared/modal/modalWrapperActions';
+import CreateVersionView from 'features/version/create/CreateVersionView';
+import {
+ newVersionAction,
+ submitVersionAction
+} from 'features/version/create/createVersionConstants';
+import {
+ getWorkflowId,
+ getLatestBaseId
+} from 'features/workflow/overview/overviewSelectors';
+
+function mapStateToProps(state) {
+ return {
+ workflowId: getWorkflowId(state),
+ baseVersionId: getLatestBaseId(state)
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ submitNewVersion: payload => {
+ dispatch(submitVersionAction(payload));
+ dispatch(hideModalAction());
+ },
+ closeCreateVersionModal: () => dispatch(hideModalAction()),
+ versionDetailsChanged: payload => dispatch(newVersionAction(payload))
+ };
+}
+
+export default withRouter(
+ connect(
+ mapStateToProps,
+ mapDispatchToProps
+ )(CreateVersionView)
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/CreateVersionView.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/CreateVersionView.jsx
new file mode 100644
index 00000000..101b442d
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/CreateVersionView.jsx
@@ -0,0 +1,95 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { I18n } from 'react-redux-i18n';
+import { Button } from 'onap-ui-react';
+import Description from 'shared/components/Description';
+import Select from 'shared/components/Select/index';
+import { VERSION_LEVEL_LIST } from 'wfapp/appConstants';
+
+class CreateVersionView extends Component {
+ static propTypes = {
+ versionCategories: PropTypes.array,
+ closeCreateVersionModal: PropTypes.func,
+ versionDetailsChanged: PropTypes.func,
+ submitNewVersion: PropTypes.func,
+ workflowId: PropTypes.string,
+ baseVersionId: PropTypes.string,
+ history: PropTypes.object
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ newVersion: ''
+ };
+ }
+ handleSubmitForm = () => {
+ const {
+ submitNewVersion,
+ workflowId,
+ baseVersionId,
+ history
+ } = this.props;
+ submitNewVersion({
+ description: this.state.newVersion.description,
+ workflowId: workflowId,
+ baseId: baseVersionId || null,
+ history: history
+ });
+ };
+
+ versionDetailsChanged = val => {
+ this.setState({ newVersion: val });
+ };
+
+ render() {
+ const { closeCreateVersionModal } = this.props;
+ return (
+ <form onSubmit={this.handleSubmitForm} autoComplete="off">
+ <div className="new-version-page custom-modal-wrapper">
+ <div className="form-custom-modal">
+ <Select
+ dataObj={VERSION_LEVEL_LIST}
+ selectedItem={VERSION_LEVEL_LIST[0].value}
+ label={I18n.t('version.category')}
+ disabled
+ />
+ <Description
+ name="version-description"
+ description={this.state.newVersion.description}
+ dataTestId="new-version-description"
+ onDataChange={this.versionDetailsChanged}
+ />
+ </div>
+ <div className="modal-action-bar sdc-modal__footer">
+ <Button btnType="primary">
+ {I18n.t('buttons.createBtn')}
+ </Button>
+ <Button
+ btnType="secondary"
+ onClick={closeCreateVersionModal}>
+ {I18n.t('buttons.closeBtn')}
+ </Button>
+ </div>
+ </div>
+ </form>
+ );
+ }
+}
+
+export default CreateVersionView;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/__tests__/CreateVersionView_snapshot-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/__tests__/CreateVersionView_snapshot-test.js
new file mode 100644
index 00000000..13faac14
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/__tests__/CreateVersionView_snapshot-test.js
@@ -0,0 +1,28 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 renderer from 'react-test-renderer';
+
+import CreateVersionView from '../CreateVersionView';
+
+describe('Create new version snapshot', () => {
+ it('renders correctly', () => {
+ const tree = renderer.create(<CreateVersionView />).toJSON();
+
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/__tests__/__snapshots__/CreateVersionView_snapshot-test.js.snap b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/__tests__/__snapshots__/CreateVersionView_snapshot-test.js.snap
new file mode 100644
index 00000000..4e17b936
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/__tests__/__snapshots__/CreateVersionView_snapshot-test.js.snap
@@ -0,0 +1,81 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Create new version snapshot renders correctly 1`] = `
+<form
+ autoComplete="off"
+ onSubmit={[Function]}
+>
+ <div
+ className="new-version-page custom-modal-wrapper"
+ >
+ <div
+ className="form-custom-modal"
+ >
+ <div
+ className="select-wrapper version-selector sdc-input"
+ >
+ <label>
+ category
+ </label>
+ <select
+ className="inputinput-selector"
+ data-test-id="vc-select-box"
+ disabled={true}
+ value={undefined}
+ >
+ <option
+ data-test-id="vc-option"
+ value="2"
+ >
+ Major
+ </option>
+ <option
+ data-test-id="vc-option"
+ value="1"
+ >
+ Minor
+ </option>
+ </select>
+ </div>
+ <div
+ className="description-part"
+ >
+ <div
+ className="sdc-textarea"
+ >
+ <div
+ className="sdc-textarea__label"
+ >
+ description
+ </div>
+ <textarea
+ className="custom-textarea field-section sdc-textarea__textarea"
+ data-test-id="new-version-description"
+ disabled={false}
+ onChange={[Function]}
+ value={undefined}
+ />
+ </div>
+ </div>
+ </div>
+ <div
+ className="modal-action-bar sdc-modal__footer"
+ >
+ <button
+ className="sdc-button sdc-button__primary "
+ disabled={false}
+ onClick={undefined}
+ >
+ createBtn
+ </button>
+ <button
+ className="sdc-button sdc-button__secondary "
+ disabled={false}
+ onClick={undefined}
+ >
+ closeBtn
+ </button>
+ </div>
+ </div>
+</form>
+`;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/createVersionConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/createVersionConstants.js
new file mode 100644
index 00000000..5f72ff7a
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/createVersionConstants.js
@@ -0,0 +1,30 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createAction } from 'redux-actions';
+
+export const VERSION_DETAILS_CHANGED = 'create/DETAILS_CHANGED';
+export const SUBMIT_VERSION = 'create/SUBMIT_VERSION';
+
+export const newVersionAction = createAction(
+ VERSION_DETAILS_CHANGED,
+ payload => payload
+);
+
+export const submitVersionAction = createAction(
+ SUBMIT_VERSION,
+ payload => payload
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/views/NewVersionContainer.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/views/NewVersionContainer.jsx
new file mode 100644
index 00000000..4d884730
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/create/views/NewVersionContainer.jsx
@@ -0,0 +1,45 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import { I18n } from 'react-redux-i18n';
+import { SVGIcon } from 'onap-ui-react';
+
+const NewVersionContainer = ({
+ onCreateVersion,
+ isVersionsCertifies,
+ isArchive
+}) => {
+ const disableClass = isVersionsCertifies && !isArchive;
+ const newVersionDisabledClass = disableClass ? '' : 'newVersionDisabled';
+ const svgColor = disableClass ? 'primary' : 'secondary';
+ return (
+ <div className={`create-new-version ${newVersionDisabledClass}`}>
+ <div className="create-item-plus-icon" onClick={onCreateVersion}>
+ <SVGIcon name="plus" color={svgColor} />
+ {I18n.t('workflow.overview.newVersion')}
+ </div>
+ </div>
+ );
+};
+
+NewVersionContainer.propTypes = {
+ onCreateVersion: PropTypes.func,
+ isVersionsCertifies: PropTypes.bool,
+ isArchive: PropTypes.bool
+};
+
+export default NewVersionContainer;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/general/General.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/general/General.js
new file mode 100644
index 00000000..ecf84750
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/general/General.js
@@ -0,0 +1,39 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { connect } from 'react-redux';
+
+import GeneralView from 'features/version/general/GeneralView';
+import {
+ getVersionInfo,
+ getIsCertified
+} from 'features/version/general/generalSelectors';
+import { workflowVersionDetailsChangedAction } from 'features/version/versionConstants';
+import { isWorkflowArchive } from 'features/workflow/workflowSelectors';
+const mapStateToProps = state => ({
+ versionInfo: getVersionInfo(state),
+ isReadOnly: getIsCertified(state) || isWorkflowArchive(state)
+});
+
+const mapDispatchToProps = dispatch => ({
+ onDataChange: payload =>
+ dispatch(workflowVersionDetailsChangedAction(payload))
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(GeneralView);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/general/GeneralView.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/general/GeneralView.js
new file mode 100644
index 00000000..e069ffb0
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/general/GeneralView.js
@@ -0,0 +1,61 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import { I18n } from 'react-redux-i18n';
+
+import Description from 'shared/components/Description';
+import { VersionInfo, LabeledValue } from 'shared/components/VersionInfo';
+
+const GeneralView = ({ onDataChange, versionInfo, isReadOnly }) => {
+ const modifiedValue = I18n.l(versionInfo.modificationTime, {
+ dateFormat: 'date.short'
+ });
+ const createdValue = I18n.l(versionInfo.creationTime, {
+ dateFormat: 'date.short'
+ });
+
+ return (
+ <div className="general-page">
+ <div className="general-page-content">
+ <Description
+ description={versionInfo.description}
+ onDataChange={onDataChange}
+ disabled={isReadOnly}
+ />
+ <VersionInfo>
+ <LabeledValue
+ title={I18n.t('workflow.general.created')}
+ value={createdValue}
+ />
+ <LabeledValue
+ title={I18n.t('workflow.general.modified')}
+ value={modifiedValue}
+ />
+ </VersionInfo>
+ </div>
+ </div>
+ );
+};
+
+GeneralView.propTypes = {
+ onDataChange: PropTypes.func,
+ versionInfo: PropTypes.object,
+ isReadOnly: PropTypes.bool
+};
+
+export default GeneralView;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/general/generalSelectors.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/general/generalSelectors.js
new file mode 100644
index 00000000..cc817fc2
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/general/generalSelectors.js
@@ -0,0 +1,33 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createSelector } from 'reselect';
+
+import { versionState } from 'features/version/versionConstants';
+
+export const getVersionInfo = state => state && state.currentVersion.general;
+
+export const getVersionsState = createSelector(
+ state => state && state.currentVersion.general.state
+);
+
+export const getIsCertified = createSelector(
+ getVersionInfo,
+ versionInfo =>
+ versionInfo &&
+ versionInfo.state &&
+ versionInfo.state.toLowerCase() === versionState.CERTIFIED
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/InputOutput.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/InputOutput.js
new file mode 100644
index 00000000..c9cd5756
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/InputOutput.js
@@ -0,0 +1,83 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { connect } from 'react-redux';
+import {
+ showAlertModalAction,
+ hideModalAction
+} from 'shared/modal/modalWrapperActions';
+
+import {
+ getIsShowInputs,
+ getSearch,
+ getDataRows,
+ getTypes,
+ getError
+} from 'features/version/inputOutput/inputOutputSelectors';
+import { getIsCertified } from 'features/version/general/generalSelectors';
+import {
+ changeError,
+ showInputs,
+ showOutputs,
+ search,
+ add,
+ changeName,
+ changeType,
+ changeMandatory,
+ remove
+} from 'features/version/inputOutput/inputOutputActions';
+import { isWorkflowArchive } from 'features/workflow/workflowSelectors';
+import InputOutputView from 'features/version/inputOutput/InputOutputView';
+
+const mapStateToProps = state => ({
+ isShowInputs: getIsShowInputs(state),
+ search: getSearch(state),
+ dataRows: getDataRows(state),
+ types: getTypes(state),
+ error: getError(state),
+ isReadOnly: getIsCertified(state) || isWorkflowArchive(state)
+});
+
+const mapDispatchToProps = dispatch => ({
+ handleChangeError: payload => dispatch(changeError(payload)),
+ handleShowInputs: () => dispatch(showInputs()),
+ handleShowOutputs: () => dispatch(showOutputs()),
+ handleSearch: value => dispatch(search(value)),
+ handleAdd: () => dispatch(add()),
+ handleNameChange: (name, key) => dispatch(changeName(name, key)),
+ handleTypeChange: (type, key) => dispatch(changeType(type, key)),
+ handleMandatoryChange: (mandatory, key) =>
+ dispatch(changeMandatory(mandatory, key)),
+ handleRemove: (alertProps, key) => {
+ if (alertProps) {
+ return dispatch(
+ showAlertModalAction({
+ ...alertProps,
+ withButtons: true,
+ actionButtonClick: () =>
+ dispatch(hideModalAction()) && dispatch(remove(key))
+ })
+ );
+ }
+
+ return dispatch(remove(key));
+ }
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(InputOutputView);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/InputOutputView.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/InputOutputView.jsx
new file mode 100644
index 00000000..61e34990
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/InputOutputView.jsx
@@ -0,0 +1,234 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import { Translate, I18n } from 'react-redux-i18n';
+import cn from 'classnames';
+import { SVGIcon } from 'onap-ui-react';
+
+import Scrollbars from 'shared/scroll/Scrollbars';
+import SearchInput from 'shared/searchInput/SearchInput';
+import { getValidationsError } from 'features/version/inputOutput/inputOutputValidations';
+import Tab from 'features/version/inputOutput/views/Tab';
+import TableHead from 'features/version/inputOutput/views/TableHead';
+import TableBody from 'features/version/inputOutput/views/TableBody';
+import NoDataRow from 'features/version/inputOutput/views/NoDataRow';
+import DataRow from 'features/version/inputOutput/views/DataRow';
+
+class InputOutputView extends React.Component {
+ componentDidUpdate() {
+ const { dataRows, error, handleChangeError } = this.props;
+
+ const validationsError = getValidationsError(dataRows);
+
+ const isDiff = Object.keys(validationsError).some(errorKey => {
+ if (!error.hasOwnProperty(errorKey)) {
+ return true;
+ }
+
+ return (
+ JSON.stringify(validationsError[errorKey].sort()) !==
+ JSON.stringify(error[errorKey].sort())
+ );
+ });
+
+ if (isDiff) {
+ handleChangeError(validationsError);
+ }
+ }
+
+ handleInputsTabClick = () => {
+ if (!this.props.isShowInputs) {
+ this.props.handleShowInputs();
+ }
+ };
+
+ handleOutputsTabClick = () => {
+ if (this.props.isShowInputs) {
+ this.props.handleShowOutputs();
+ }
+ };
+
+ handleSearchChange = value => {
+ this.props.handleSearch(value);
+ };
+
+ handleNameChange = key => name => {
+ this.props.handleNameChange(name, key);
+ };
+
+ handleTypeChange = key => event => {
+ this.props.handleTypeChange(event.target.value, key);
+ };
+
+ handleMandatoryChange = key => value => {
+ this.props.handleMandatoryChange(value, key);
+ };
+
+ handleRemoveClick = key => () => {
+ const { name } = this.props.dataRows[key];
+
+ let alertProps = false;
+
+ if (name.replace(/\s+/g, '')) {
+ const title = I18n.t('workflow.inputOutput.DELETE');
+ const body = I18n.t('workflow.inputOutput.confirmDelete', {
+ name: name.replace(/s+$/g, '')
+ });
+ const closeButtonText = I18n.t('workflow.inputOutput.CANCEL');
+ const actionButtonText = title;
+
+ alertProps = {
+ title,
+ body,
+ closeButtonText,
+ actionButtonText
+ };
+ }
+
+ this.props.handleRemove(alertProps, key);
+ };
+
+ renderDataRows = () => {
+ const { dataRows, types, error } = this.props;
+
+ if (dataRows.length < 1) {
+ return (
+ <NoDataRow>
+ <Translate value="workflow.inputOutput.noData" />
+ </NoDataRow>
+ );
+ }
+
+ return dataRows.map((data, i) => {
+ let errorMessage = '';
+
+ if (
+ error.invalidCharacters &&
+ error.invalidCharacters.includes(i)
+ ) {
+ errorMessage = I18n.t(
+ 'workflow.errorMessages.invalidCharacters'
+ );
+ } else if (error.alreadyExists && error.alreadyExists.includes(i)) {
+ errorMessage = I18n.t('workflow.errorMessages.alreadyExists');
+ } else if (error.emptyName && error.emptyName.includes(i)) {
+ errorMessage = I18n.t('workflow.errorMessages.emptyName');
+ }
+
+ return (
+ <DataRow
+ key={`data.${i}`}
+ data={data}
+ types={types}
+ nameErrorMessage={errorMessage}
+ dataTestId="wf-input-output-row"
+ handleNameChange={this.handleNameChange(i)}
+ handleTypeChange={this.handleTypeChange(i)}
+ handleMandatoryChange={this.handleMandatoryChange(i)}
+ handleRemoveClick={this.handleRemoveClick(i)}
+ />
+ );
+ });
+ };
+
+ render() {
+ const { isShowInputs, search, handleAdd, isReadOnly } = this.props;
+
+ const addLabel = isShowInputs
+ ? I18n.t('workflow.inputOutput.addInput')
+ : I18n.t('workflow.inputOutput.addOutput');
+
+ const dataRowsView = this.renderDataRows();
+
+ return (
+ <div className="input-output">
+ <div className="input-output__header">
+ <Tab
+ isActive={isShowInputs}
+ dataTestId="wf-input-output-inputs"
+ handleTabClick={this.handleInputsTabClick}>
+ <Translate value="workflow.inputOutput.inputs" />
+ </Tab>
+ <Tab
+ isActive={!isShowInputs}
+ dataTestId="wf-input-output-outputs"
+ handleTabClick={this.handleOutputsTabClick}>
+ <Translate value="workflow.inputOutput.outputs" />
+ </Tab>
+ <div className="input-output__header__right">
+ <div className="input-output__search">
+ <SearchInput
+ dataTestId="wf-input-output-search"
+ onChange={this.handleSearchChange}
+ value={search}
+ />
+ </div>
+ <div
+ className={cn('input-output__add', {
+ disabled: isReadOnly
+ })}
+ data-test-id="wf-input-output-add"
+ onClick={handleAdd}>
+ <SVGIcon
+ label={addLabel}
+ labelPosition="right"
+ color="primary"
+ name="plusThin"
+ />
+ </div>
+ </div>
+ </div>
+ <div className="input-output__table">
+ <TableHead />
+ <TableBody isReadOnly={isReadOnly}>
+ <Scrollbars className="scrollbars">
+ {dataRowsView}
+ </Scrollbars>
+ </TableBody>
+ </div>
+ </div>
+ );
+ }
+}
+
+InputOutputView.propTypes = {
+ isShowInputs: PropTypes.bool,
+ search: PropTypes.string,
+ dataRows: PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string,
+ type: PropTypes.string,
+ mandatory: PropTypes.bool
+ })
+ ),
+ types: PropTypes.array,
+ error: PropTypes.object,
+ isReadOnly: PropTypes.bool,
+ handleChangeError: PropTypes.func,
+ handleShowInputs: PropTypes.func,
+ handleShowOutputs: PropTypes.func,
+ handleSearch: PropTypes.func,
+ handleAdd: PropTypes.func,
+ handleCurrentDataChange: PropTypes.func,
+ handleNameChange: PropTypes.func,
+ handleTypeChange: PropTypes.func,
+ handleMandatoryChange: PropTypes.func,
+ handleRemove: PropTypes.func
+};
+
+export default InputOutputView;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/__tests__/inputOutputActions-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/__tests__/inputOutputActions-test.js
new file mode 100644
index 00000000..465602fc
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/__tests__/inputOutputActions-test.js
@@ -0,0 +1,150 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 {
+ NAME_MAX_LEN,
+ STRING,
+ SET_INPUTS_OUTPUTS,
+ CHANGE_ERROR,
+ SHOW_INPUTS,
+ SHOW_OUTPUTS,
+ SEARCH,
+ ADD,
+ CHANGE_NAME,
+ CHANGE_TYPE,
+ CHANGE_MANDATORY,
+ REMOVE
+} from 'features/version/inputOutput/inputOutputConstants';
+import {
+ setInputsOutputs,
+ changeError,
+ showInputs,
+ showOutputs,
+ search,
+ add,
+ changeName,
+ changeType,
+ changeMandatory,
+ remove
+} from 'features/version/inputOutput/inputOutputActions';
+
+describe('Input/Output Actions', () => {
+ it('should have `setInputsOutputs` action', () => {
+ const inputs = [
+ {
+ name: 'Input',
+ type: STRING,
+ mandatory: false
+ }
+ ];
+
+ const outputs = [
+ {
+ name: 'Output',
+ type: STRING,
+ mandatory: false
+ }
+ ];
+
+ const expected = {
+ type: SET_INPUTS_OUTPUTS,
+ payload: {
+ inputs,
+ outputs
+ }
+ };
+
+ expect(setInputsOutputs({ inputs, outputs })).toEqual(expected);
+ });
+
+ it('should have `changeError` action', () => {
+ const payload = { key: 'value' };
+
+ const expected = { type: CHANGE_ERROR, payload };
+
+ expect(changeError(payload)).toEqual(expected);
+ });
+
+ it('should have `showInputs` action', () => {
+ const expected = { type: SHOW_INPUTS };
+
+ expect(showInputs()).toEqual(expected);
+ });
+
+ it('should have `showOutputs` action', () => {
+ const expected = { type: SHOW_OUTPUTS };
+
+ expect(showOutputs()).toEqual(expected);
+ });
+
+ it('should have `search` action', () => {
+ const payload = 'Search Value';
+
+ const expected = { type: SEARCH, payload };
+
+ expect(search(payload)).toEqual(expected);
+ });
+
+ it('should have `add` action', () => {
+ const expected = { type: ADD };
+
+ expect(add()).toEqual(expected);
+ });
+
+ it('should have `changeName` action', () => {
+ let name = 'This is a long name more that more more more and more';
+ let key = 1;
+
+ const expected = {
+ type: CHANGE_NAME,
+ payload: {
+ name: name.substr(0, NAME_MAX_LEN),
+ key
+ }
+ };
+
+ expect(changeName(name, key)).toEqual(expected);
+ });
+
+ it('should have `changeType` action', () => {
+ const type = 'String';
+ const key = 1;
+
+ const expected = { type: CHANGE_TYPE, payload: { type, key } };
+
+ expect(changeType(type, key)).toEqual(expected);
+ });
+
+ it('should have `changeMandatory` action', () => {
+ const mandatory = true;
+ const key = 1;
+
+ const expected = {
+ type: CHANGE_MANDATORY,
+ payload: { mandatory, key }
+ };
+
+ expect(changeMandatory(mandatory, key)).toEqual(expected);
+ });
+
+ it('should have `remove` action', () => {
+ const payload = 1;
+
+ const expected = { type: REMOVE, payload };
+
+ expect(remove(payload)).toEqual(expected);
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/__tests__/inputOutputReducer-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/__tests__/inputOutputReducer-test.js
new file mode 100644
index 00000000..b923f4a4
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/__tests__/inputOutputReducer-test.js
@@ -0,0 +1,191 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 {
+ STRING,
+ INPUTS,
+ OUTPUTS
+} from 'features/version/inputOutput/inputOutputConstants';
+import inputOutputReducer, {
+ initialState,
+ defaultInputOutput
+} from 'features/version/inputOutput/inputOutputReducer';
+import {
+ setInputsOutputs,
+ changeError,
+ showInputs,
+ showOutputs,
+ search,
+ add,
+ changeName,
+ changeType,
+ changeMandatory,
+ remove
+} from 'features/version/inputOutput/inputOutputActions';
+
+describe('Input/Output Reducer', () => {
+ it('should return initialState', () => {
+ expect(inputOutputReducer(undefined, {})).toEqual(initialState);
+ });
+
+ it('should set inputs/outputs', () => {
+ const payload = {
+ inputs: [
+ {
+ name: 'Input',
+ type: STRING,
+ mandatory: false
+ }
+ ],
+ outputs: {
+ name: 'Output',
+ type: STRING,
+ mandatory: false
+ }
+ };
+
+ expect(
+ inputOutputReducer(undefined, setInputsOutputs(payload))
+ ).toEqual({ ...initialState, ...payload });
+ });
+
+ it('should change input/output error', () => {
+ const payload = {
+ alreadyExists: [1, 2],
+ invalidCharacteres: [3, 4]
+ };
+ [INPUTS, OUTPUTS].forEach(current => {
+ const state = { ...initialState, current };
+ expect(inputOutputReducer(state, changeError(payload))).toEqual({
+ ...state,
+ error: {
+ ...state.error,
+ [current]: payload
+ }
+ });
+ });
+ });
+
+ it('should show inputs', () => {
+ expect(inputOutputReducer(undefined, showInputs())).toEqual({
+ ...initialState,
+ current: INPUTS
+ });
+ });
+
+ it('should show outputs', () => {
+ expect(inputOutputReducer(undefined, showOutputs())).toEqual({
+ ...initialState,
+ current: OUTPUTS
+ });
+ });
+
+ it('should add input/output', () => {
+ [INPUTS, OUTPUTS].forEach(current => {
+ const state = { ...initialState, current };
+ expect(inputOutputReducer(state, add())).toEqual({
+ ...state,
+ [current]: [...state[current], defaultInputOutput[current]]
+ });
+ });
+ });
+
+ it('should add search', () => {
+ const payload = 'Search string';
+ expect(inputOutputReducer(undefined, search(payload))).toEqual({
+ ...initialState,
+ search: payload
+ });
+ });
+
+ it('should change input/output name/type/mandatory', () => {
+ const name = 'New name';
+ const type = 'New Type';
+ const mandatory = true;
+ const key = 0;
+ const state = {
+ ...initialState,
+ [INPUTS]: [
+ {
+ name: 'Old name',
+ type: 'Old type',
+ mandatory: false
+ }
+ ],
+ [OUTPUTS]: [
+ {
+ name: 'Old name',
+ type: 'Old type',
+ mandatory: false
+ }
+ ]
+ };
+ [INPUTS, OUTPUTS].forEach(current => {
+ [
+ {
+ action: changeName(name, key),
+ field: 'name',
+ value: name
+ },
+ {
+ action: changeType(type, key),
+ field: 'type',
+ value: type
+ },
+ {
+ action: changeMandatory(mandatory, key),
+ field: 'mandatory',
+ value: mandatory
+ }
+ ].forEach(actionMap => {
+ const actual = inputOutputReducer(
+ { ...state, current },
+ actionMap.action
+ )[current][key][actionMap.field];
+
+ const expected = actionMap.value;
+
+ expect(actual).toEqual(expected);
+ });
+ });
+ });
+
+ it('should remove input/output ', () => {
+ const key = 0;
+ const state = {
+ ...initialState,
+ [INPUTS]: [
+ {
+ name: 'Name',
+ type: 'String',
+ mandatory: true
+ }
+ ],
+ [OUTPUTS]: [
+ {
+ name: 'Name',
+ type: 'String',
+ mandatory: true
+ }
+ ]
+ };
+ [INPUTS, OUTPUTS].forEach(current => {
+ expect(
+ inputOutputReducer({ ...state, current }, remove(key))[current]
+ ).toEqual([]);
+ });
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/__tests__/inputOutputSelectors-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/__tests__/inputOutputSelectors-test.js
new file mode 100644
index 00000000..8e9bdbd4
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/__tests__/inputOutputSelectors-test.js
@@ -0,0 +1,149 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 {
+ getInputOutput,
+ getCurrent,
+ getIsShowInputs,
+ getSearch,
+ getDataRows,
+ getTypes,
+ getError
+} from 'features/version/inputOutput/inputOutputSelectors';
+
+describe('Input/Output Selectors', () => {
+ const state = {
+ currentVersion: {
+ general: {
+ id: '1e659854c7e240c881f1dd8d5bd833cc',
+ name: '1.0',
+ description: 'Initial version',
+ baseId: null,
+ creationTime: '2018-07-19T13:09:39.066+0000',
+ modificationTime: '2018-07-19T13:09:39.355+0000',
+ state: 'DRAFT',
+ inputs: [],
+ outputs: []
+ },
+ inputOutput: {
+ current: 'outputs',
+ inputs: [
+ {
+ name: 'IP Address',
+ value: 'String',
+ mandatory: true,
+ type: 'Integer'
+ },
+ {
+ name: 'MAC Address',
+ value: 'String',
+ mandatory: false,
+ type: 'Integer'
+ },
+ {
+ name: 'IP',
+ value: 'String',
+ mandatory: true,
+ type: 'Boolean'
+ },
+ {
+ name: 'IP',
+ value: 'String',
+ mandatory: false
+ },
+ {
+ name: '',
+ value: 'String',
+ mandatory: false
+ },
+ {
+ name: '',
+ value: 'String',
+ mandatory: false
+ }
+ ],
+ outputs: [
+ {
+ name: 'IP Address',
+ value: 'String',
+ mandatory: true
+ },
+ {
+ name: 'IP',
+ value: 'String',
+ mandatory: true
+ },
+ {
+ name: 'IP',
+ value: 'String',
+ mandatory: false,
+ type: 'Boolean'
+ }
+ ],
+ search: 'IP',
+ types: ['String', 'Boolean', 'Integer', 'Float'],
+ error: {
+ inputs: {
+ alreadyExists: [1, 2],
+ invalidCharacters: []
+ },
+ outputs: {
+ alreadyExists: [1, 2],
+ invalidCharacters: []
+ }
+ }
+ }
+ }
+ };
+
+ it('should `getInputOutput`', () => {
+ expect(getInputOutput(state)).toEqual(state.currentVersion.inputOutput);
+ });
+
+ it('should `getCurrent`', () => {
+ expect(getCurrent(state)).toEqual(
+ state.currentVersion.inputOutput.current
+ );
+ });
+
+ it('should `getIsShowInputs`', () => {
+ expect(getIsShowInputs(state)).toBeFalsy();
+ });
+
+ it('should `getSearch`', () => {
+ expect(getSearch(state)).toEqual(
+ state.currentVersion.inputOutput.search
+ );
+ });
+
+ it('should `getDataRows`', () => {
+ expect(getDataRows(state)).toEqual(
+ state.currentVersion.inputOutput.outputs
+ );
+ });
+
+ it('should `getTypes`', () => {
+ expect(getTypes(state)).toEqual(state.currentVersion.inputOutput.types);
+ });
+
+ it('should `getError`', () => {
+ expect(getError(state)).toEqual(
+ state.currentVersion.inputOutput.error[
+ state.currentVersion.inputOutput.current
+ ]
+ );
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputActions.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputActions.js
new file mode 100644
index 00000000..a9548592
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputActions.js
@@ -0,0 +1,53 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createActions } from 'redux-actions';
+
+import {
+ NAME_MAX_LEN,
+ NAMESPACE
+} from 'features/version/inputOutput/inputOutputConstants';
+
+export const {
+ [NAMESPACE]: {
+ setInputsOutputs,
+ changeError,
+ showInputs,
+ showOutputs,
+ search,
+ add,
+ changeName,
+ changeType,
+ changeMandatory,
+ remove
+ }
+} = createActions({
+ [NAMESPACE]: {
+ SET_INPUTS_OUTPUTS: undefined,
+ CHANGE_ERROR: undefined,
+ SHOW_INPUTS: undefined,
+ SHOW_OUTPUTS: undefined,
+ SEARCH: undefined,
+ ADD: undefined,
+ CHANGE_NAME: (name, key) => ({
+ name: name.substr(0, NAME_MAX_LEN),
+ key
+ }),
+ CHANGE_TYPE: (type, key) => ({ type, key }),
+ CHANGE_MANDATORY: (mandatory, key) => ({ mandatory, key }),
+ REMOVE: undefined
+ }
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputConstants.js
new file mode 100644
index 00000000..30f80a6b
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputConstants.js
@@ -0,0 +1,39 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const NAME_MAX_LEN = 50;
+
+export const INPUTS = 'inputs';
+export const OUTPUTS = 'outputs';
+
+export const STRING = 'string';
+export const DEFAULT_STRING = 'STRING';
+export const BOOLEAN = 'boolean';
+export const INTEGER = 'integer';
+export const FLOAT = 'float';
+
+export const NAMESPACE = 'inputOutput';
+
+export const SET_INPUTS_OUTPUTS = `${NAMESPACE}/SET_INPUTS_OUTPUTS`;
+export const CHANGE_ERROR = `${NAMESPACE}/CHANGE_ERROR`;
+export const SHOW_INPUTS = `${NAMESPACE}/SHOW_INPUTS`;
+export const SHOW_OUTPUTS = `${NAMESPACE}/SHOW_OUTPUTS`;
+export const SEARCH = `${NAMESPACE}/SEARCH`;
+export const ADD = `${NAMESPACE}/ADD`;
+export const CHANGE_NAME = `${NAMESPACE}/CHANGE_NAME`;
+export const CHANGE_TYPE = `${NAMESPACE}/CHANGE_TYPE`;
+export const CHANGE_MANDATORY = `${NAMESPACE}/CHANGE_MANDATORY`;
+export const REMOVE = `${NAMESPACE}/REMOVE`;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputReducer.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputReducer.js
new file mode 100644
index 00000000..881322fa
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputReducer.js
@@ -0,0 +1,130 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 {
+ INPUTS,
+ OUTPUTS,
+ STRING,
+ DEFAULT_STRING,
+ BOOLEAN,
+ INTEGER,
+ FLOAT,
+ SET_INPUTS_OUTPUTS,
+ CHANGE_ERROR,
+ SHOW_INPUTS,
+ SHOW_OUTPUTS,
+ SEARCH,
+ ADD,
+ CHANGE_NAME,
+ CHANGE_TYPE,
+ CHANGE_MANDATORY,
+ REMOVE
+} from 'features/version/inputOutput/inputOutputConstants';
+
+export const defaultInputOutput = {
+ [INPUTS]: {
+ name: '',
+ type: DEFAULT_STRING,
+ mandatory: false
+ },
+ [OUTPUTS]: {
+ name: '',
+ type: DEFAULT_STRING,
+ mandatory: false
+ }
+};
+
+export const initialState = {
+ current: INPUTS,
+ [INPUTS]: [],
+ [OUTPUTS]: [],
+ search: '',
+ types: [STRING, BOOLEAN, INTEGER, FLOAT],
+ error: {
+ [INPUTS]: {},
+ [OUTPUTS]: {}
+ }
+};
+
+const inputOutputReducer = (state = initialState, action) => {
+ const { type, payload } = action;
+ switch (type) {
+ case SET_INPUTS_OUTPUTS:
+ return {
+ ...initialState,
+ ...payload
+ };
+
+ case CHANGE_ERROR:
+ return {
+ ...state,
+ error: {
+ ...state.error,
+ [state.current]: payload
+ }
+ };
+
+ case SHOW_INPUTS:
+ return { ...state, current: INPUTS };
+
+ case SHOW_OUTPUTS:
+ return { ...state, current: OUTPUTS };
+
+ case SEARCH:
+ return { ...state, search: payload };
+
+ case ADD:
+ return {
+ ...state,
+ [state.current]: [
+ ...state[state.current],
+ defaultInputOutput[state.current]
+ ]
+ };
+
+ /* eslint-disable no-case-declarations */
+ case CHANGE_NAME:
+ case CHANGE_TYPE:
+ case CHANGE_MANDATORY:
+ const { key, ...rest } = payload;
+ return {
+ ...state,
+ [state.current]: state[state.current].map(
+ (row, index) =>
+ key === index
+ ? {
+ ...row,
+ ...rest
+ }
+ : row
+ )
+ };
+ /* eslint-enable no-case-declarations */
+
+ case REMOVE:
+ return {
+ ...state,
+ [state.current]: state[state.current].filter(
+ (_, index) => index !== payload
+ )
+ };
+
+ default:
+ return state;
+ }
+};
+
+export default inputOutputReducer;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputSelectors.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputSelectors.js
new file mode 100644
index 00000000..3a6c9e8f
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputSelectors.js
@@ -0,0 +1,106 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createSelector } from 'reselect';
+import isEmpty from 'lodash.isempty';
+
+import { INPUTS } from 'features/version/inputOutput/inputOutputConstants';
+
+export const getInputOutput = state => state.currentVersion.inputOutput;
+export const getInputs = createSelector(getInputOutput, data => data.inputs);
+export const getOutputs = createSelector(getInputOutput, data => data.outputs);
+export const getInputOutputForComposition = state => ({
+ inputs: getInputs(state).map(item => ({
+ ...item,
+ type: item.type.toLowerCase()
+ })),
+ outputs: getOutputs(state).map(item => ({
+ ...item,
+ type: item.type.toLowerCase()
+ }))
+});
+export const getCurrent = createSelector(
+ getInputOutput,
+ inputOutput => inputOutput.current
+);
+
+export const getIsShowInputs = createSelector(
+ getCurrent,
+ current => current === INPUTS
+);
+
+export const getSearch = createSelector(
+ getInputOutput,
+ inputOutput => inputOutput.search
+);
+
+export const getDataRows = createSelector(
+ [getInputOutput, getCurrent],
+ (inputOutput, current) => {
+ if (inputOutput.search) {
+ return inputOutput[current].filter(dataRow =>
+ dataRow.name
+ .toLowerCase()
+ .includes(inputOutput.search.toLowerCase())
+ );
+ }
+
+ return inputOutput[current];
+ }
+);
+
+export const getTypes = createSelector(
+ getInputOutput,
+ inputOutput => inputOutput.types
+);
+
+export const getError = createSelector(
+ [getInputOutput, getCurrent],
+ (inputOutput, current) => inputOutput.error[current]
+);
+
+export const getErrorsInputOutput = createSelector(
+ getInputOutput,
+ ({ error }) => error
+);
+
+export const getInputErrors = createSelector(
+ getErrorsInputOutput,
+ ({ inputs }) =>
+ !isEmpty(inputs) &&
+ Boolean(
+ inputs.alreadyExists.length ||
+ inputs.invalidCharacters.length ||
+ inputs.emptyName.length
+ )
+);
+
+export const getOutputErrors = createSelector(
+ getErrorsInputOutput,
+ ({ outputs }) =>
+ !isEmpty(outputs) &&
+ Boolean(
+ outputs.alreadyExists.length ||
+ outputs.invalidCharacters.length ||
+ outputs.emptyName.length
+ )
+);
+
+export const getIOErrors = createSelector(
+ getInputErrors,
+ getOutputErrors,
+ (inputsErrors, outputsErrors) => inputsErrors || outputsErrors
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputValidations.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputValidations.js
new file mode 100644
index 00000000..d4057879
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/inputOutputValidations.js
@@ -0,0 +1,64 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const getValidationsError = dataRows => {
+ const error = {};
+
+ const groupBy = dataRows.reduce((result, value, key) => {
+ const groupKey = value.name.toLowerCase();
+
+ if (groupKey) {
+ if (result.hasOwnProperty(groupKey)) {
+ result[groupKey].push(key);
+ } else {
+ result[groupKey] = [key];
+ }
+ }
+ return result;
+ }, {});
+
+ error.alreadyExists = Object.keys(groupBy).reduce((result, value) => {
+ if (groupBy[value].length > 1) {
+ result = [...result, ...groupBy[value]];
+ }
+
+ return result;
+ }, []);
+
+ error.emptyName = dataRows.reduce((result, value, key) => {
+ const name = value.name;
+
+ if (!name) {
+ result.push(key);
+ }
+
+ return result;
+ }, []);
+
+ error.invalidCharacters = dataRows.reduce((result, value, key) => {
+ const groupKey = value.name;
+
+ if (groupKey) {
+ if (!/^[\w\d]+$/.test(groupKey)) {
+ result.push(key);
+ }
+ }
+
+ return result;
+ }, []);
+
+ return error;
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/DataRow.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/DataRow.jsx
new file mode 100644
index 00000000..70103f87
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/DataRow.jsx
@@ -0,0 +1,87 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+
+import { Input, Checkbox, SVGIcon } from 'onap-ui-react';
+
+const DataRow = ({
+ data: { name, type, mandatory },
+ types,
+ nameErrorMessage,
+ dataTestId,
+ handleNameChange,
+ handleNameBlur,
+ handleTypeChange,
+ handleMandatoryChange,
+ handleRemoveClick
+}) => (
+ <div className="input-output__tr">
+ <div className="input-output__td">
+ <Input
+ errorMessage={nameErrorMessage}
+ data-test-id={`${dataTestId}-name`}
+ onChange={handleNameChange}
+ onBlur={handleNameBlur}
+ type="text"
+ value={name}
+ />
+ </div>
+ <div className="input-output__td">
+ <select
+ className="input-output-select"
+ value={type}
+ data-test-id={`${dataTestId}-select`}
+ onChange={handleTypeChange}>
+ {types.map((type, i) => (
+ <option key={`type.${i}`} value={type.toUpperCase()}>
+ {type}
+ </option>
+ ))}
+ </select>
+ </div>
+ <div className="input-output__td input-output__td--unflex">
+ <Checkbox
+ value="myVal"
+ data-test-id={`${dataTestId}-mandatory`}
+ onChange={handleMandatoryChange}
+ checked={mandatory}
+ />
+ </div>
+ <div className="input-output__td input-output__td--unflex input-output__td--icon">
+ <SVGIcon
+ name="trashO"
+ data-test-id={`${dataTestId}-delete`}
+ onClick={handleRemoveClick}
+ />
+ </div>
+ </div>
+);
+
+DataRow.propTypes = {
+ data: PropTypes.object,
+ types: PropTypes.array,
+ nameErrorMessage: PropTypes.string,
+ dataTestId: PropTypes.string,
+ handleNameChange: PropTypes.func,
+ handleNameBlur: PropTypes.func,
+ handleTypeChange: PropTypes.func,
+ handleMandatoryChange: PropTypes.func,
+ handleRemoveClick: PropTypes.func
+};
+
+export default DataRow;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/NoDataRow.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/NoDataRow.jsx
new file mode 100644
index 00000000..af75c79e
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/NoDataRow.jsx
@@ -0,0 +1,32 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+
+const NoDataRow = ({ children }) => (
+ <div className="input-output__tr input-output__tr--no-hover">
+ <div className="input-output__td input-output__td--empty">
+ {children}
+ </div>
+ </div>
+);
+
+NoDataRow.propTypes = {
+ children: PropTypes.node
+};
+
+export default NoDataRow;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/Tab.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/Tab.js
new file mode 100644
index 00000000..ad56dc15
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/Tab.js
@@ -0,0 +1,43 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import cn from 'classnames';
+
+const Tab = ({ children, isActive, dataTestId, handleTabClick }) => {
+ const className = cn('input-output__tab', {
+ 'input-output__tab--active': isActive
+ });
+
+ return (
+ <div
+ className={className}
+ data-test-id={`${dataTestId}-tab`}
+ onClick={handleTabClick}>
+ {children}
+ </div>
+ );
+};
+
+Tab.propTypes = {
+ children: PropTypes.node,
+ isActive: PropTypes.bool,
+ dataTestId: PropTypes.string,
+ handleTabClick: PropTypes.func
+};
+
+export default Tab;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/TableBody.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/TableBody.jsx
new file mode 100644
index 00000000..ed11bbc5
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/TableBody.jsx
@@ -0,0 +1,50 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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.
+*/
+/* eslint-disable no-unused-vars */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import cn from 'classnames';
+
+class TableBody extends React.Component {
+ handleNameInputChange = params => {
+ console.log('handleNameInputChange', { params });
+ };
+
+ handleMandatoryCheckboxChange = params => {
+ console.log('handleMandatoryCheckboxChange: ', { params });
+ };
+
+ render() {
+ const { isReadOnly, children } = this.props;
+
+ return (
+ <div
+ className={cn('input-output__table__tbody', {
+ disabled: isReadOnly
+ })}>
+ {children}
+ </div>
+ );
+ }
+}
+
+TableBody.propTypes = {
+ isReadOnly: PropTypes.bool,
+ children: PropTypes.node
+};
+
+export default TableBody;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/TableHead.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/TableHead.jsx
new file mode 100644
index 00000000..37a8cb43
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/inputOutput/views/TableHead.jsx
@@ -0,0 +1,41 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { Translate } from 'react-redux-i18n';
+
+export default class TableHead extends React.Component {
+ render() {
+ return (
+ <div className="input-output__table__thead">
+ <div className="input-output__tr input-output__tr--no-hover">
+ <div className="input-output__th">
+ <Translate value="workflow.inputOutput.name" />
+ </div>
+ <div className="input-output__th">
+ <Translate value="workflow.inputOutput.type" />
+ </div>
+ <div className="input-output__th input-output__th--unflex">
+ <Translate value="workflow.inputOutput.mandatory" />
+ </div>
+ <div className="input-output__th input-output__th--unflex input-output__th--icon">
+ &#8226;&#8226;&#8226;
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js
new file mode 100644
index 00000000..b3a2e13a
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js
@@ -0,0 +1,82 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 RestfulAPIUtil from 'services/restAPIUtil';
+import Configuration from 'config/Configuration.js';
+import { CERTIFY_JSON } from 'features/version/versionController/versionControllerConstants';
+
+function baseUrl(workflowId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/workflows/${workflowId}/versions`;
+}
+
+const Api = {
+ fetchVersion: ({ workflowId, versionId }) => {
+ return RestfulAPIUtil.fetch(`${baseUrl(workflowId)}/${versionId}`);
+ },
+ createNewVersion: ({ workflowId, baseId, description }) => {
+ const urlParams = baseId ? `?baseVersionId=${baseId}` : ``;
+ return RestfulAPIUtil.post(`${baseUrl(workflowId)}${urlParams}`, {
+ description
+ });
+ },
+ updateVersion: ({ workflowId, ...payload }) => {
+ return RestfulAPIUtil.put(
+ `${baseUrl(workflowId)}/${payload.params.id}`,
+ {
+ ...payload.params
+ }
+ );
+ },
+ fetchVersionArtifact: ({ workflowId, versionId }) => {
+ return RestfulAPIUtil.fetch(
+ `${baseUrl(workflowId)}/${versionId}/artifact`
+ );
+ },
+ updateVersionArtifact: ({
+ workflowId,
+ versionId,
+ workflowName,
+ versionName,
+ payload
+ }) => {
+ let formData = new FormData();
+ var blob = new Blob([payload], { type: 'text/xml' });
+ formData.append(
+ 'fileToUpload',
+ blob,
+ `${workflowName}-${versionName}.bpmn`
+ );
+
+ return RestfulAPIUtil.put(
+ `${baseUrl(workflowId)}/${versionId}/artifact`,
+ formData
+ );
+ },
+ deleteVersionArtifact: ({ workflowId, versionId }) => {
+ return RestfulAPIUtil.delete(
+ `${baseUrl(workflowId)}/${versionId}/artifact`
+ );
+ },
+ certifyVersion: ({ workflowId, versionId }) => {
+ return RestfulAPIUtil.post(
+ `${baseUrl(workflowId)}/${versionId}/state`,
+ CERTIFY_JSON
+ );
+ }
+};
+
+export default Api;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionConstants.js
new file mode 100644
index 00000000..5b945a02
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionConstants.js
@@ -0,0 +1,59 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createAction } from 'redux-actions';
+
+export const SET_CURRENT_VERSION = 'workflow/version/SET_CURRENT_VERSION';
+export const FETCH_REQUESTED = 'workflow/version/FETCH_REQUESTED';
+export const DETAILS_CHANGED = 'workflow/version/DETAILS_CHANGED';
+export const FETCH_REQUESTED_FAILED = 'workflow/version/FETCH_REQUESTED_FAILED';
+export const VERSION_STATE_CHANGED = 'workflow/version/VERSION_STATE_CHANGED';
+export const TOGGLE_COMPOSITION_UPDATE =
+ 'workflow/version/TOGGLE_COMPOSITION_UPDATE';
+export const SET_OPERRATION_MODE = 'workflow/version/SET_OPERRATION_MODE';
+
+export const workflowVersionFetchRequestedAction = createAction(
+ FETCH_REQUESTED
+);
+
+export const workflowVersionDetailsChangedAction = createAction(
+ DETAILS_CHANGED
+);
+
+export const setWorkflowVersionAction = createAction(SET_CURRENT_VERSION);
+export const fetchWorkflowVersionActionFailed = createAction(
+ FETCH_REQUESTED_FAILED,
+ error => error
+);
+
+export const versionStateChangedAction = createAction(
+ VERSION_STATE_CHANGED,
+ payload => payload
+);
+
+export const toggleCompositionUpdate = createAction(
+ TOGGLE_COMPOSITION_UPDATE,
+ payload => ({ isCompositionUpdating: payload })
+);
+
+export const setOperationModeAction = createAction(SET_OPERRATION_MODE);
+
+export const getIsCompositionUpdating = state =>
+ state.currentVersion.general.isCompositionUpdating;
+
+export const versionState = {
+ DRAFT: 'draft',
+ CERTIFIED: 'certified'
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionController.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionController.js
new file mode 100644
index 00000000..8c37a0e3
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionController.js
@@ -0,0 +1,73 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { connect } from 'react-redux';
+import { getSavedObjParams } from 'features/version/versionController/versionControllerSelectors';
+import VersionControllerView from 'features/version/versionController/VersionControllerView';
+import {
+ getVersions,
+ getSortedVersions
+} from 'features/workflow/overview/overviewSelectors';
+import {
+ isWorkflowArchive,
+ getWorkflowId,
+ getWorkflowName
+} from 'features/workflow/workflowSelectors';
+import {
+ saveParamsAction,
+ certifyVersionAction
+} from 'features/version/versionController/versionControllerConstants';
+import {
+ workflowVersionFetchRequestedAction,
+ toggleCompositionUpdate,
+ getIsCompositionUpdating
+} from 'features/version/versionConstants';
+import { getIsCertified } from 'features/version/general/generalSelectors';
+import { getIOErrors } from 'features/version/inputOutput/inputOutputSelectors';
+import { getCompositionHasErrors } from 'features/version/composition/compositionSelectors';
+import { pluginContextSelector } from 'wfapp/pluginContext/pluginContextSelector';
+
+function mapStateToProps(state) {
+ return {
+ workflowName: getWorkflowName(state),
+ workflowId: getWorkflowId(state),
+ versionsList: getSortedVersions(state),
+ savedParams: getSavedObjParams(state),
+ hasErrors: getIOErrors(state) || getCompositionHasErrors(state),
+ isCertifyDisable: getIsCertified(state),
+ isArchive: isWorkflowArchive(state),
+ currentWorkflowVersion: state.currentVersion.general,
+ pluginContext: pluginContextSelector(state),
+ isCompositionUpdating: getIsCompositionUpdating(state)
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ getVersions: () => dispatch(getVersions),
+ saveParamsToServer: params => dispatch(saveParamsAction(params)),
+ certifyVersion: payload => dispatch(certifyVersionAction(payload)),
+ changeVersion: payload =>
+ dispatch(workflowVersionFetchRequestedAction(payload)),
+ toggleCompositionUpdate: payload =>
+ dispatch(toggleCompositionUpdate(payload))
+ };
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(VersionControllerView);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionControllerView.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionControllerView.jsx
new file mode 100644
index 00000000..730d92fb
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionControllerView.jsx
@@ -0,0 +1,171 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+import ActionButtons from 'features/version/versionController/views/ActionButtons';
+import OperationModeButtons from 'features/version/versionController/views/OperationModeButtons';
+import VersionContainer from 'features/version/versionController/views/VersionsContainer';
+import WorkflowTitle from 'features/version/versionController/views/WorkflowTitle';
+import { PluginPubSub } from 'shared/pubsub/plugin-pubsub.ts';
+import {
+ notificationType,
+ CATALOG_PATH
+} from 'wfapp/pluginContext/pluginContextConstants';
+export default class VersionControllerView extends Component {
+ static propTypes = {
+ location: PropTypes.object,
+ workflowName: PropTypes.string,
+ currentWorkflowVersion: PropTypes.object,
+ viewableVersions: PropTypes.arrayOf(Object),
+ getVersions: PropTypes.func,
+ versionsList: PropTypes.array,
+ history: PropTypes.object,
+ getOverview: PropTypes.func,
+ match: PropTypes.object,
+ savedParams: PropTypes.object,
+ saveParamsToServer: PropTypes.func,
+ workflowId: PropTypes.string,
+ certifyVersion: PropTypes.func,
+ changeVersion: PropTypes.func,
+ isCertifyDisable: PropTypes.bool,
+ hasErrors: PropTypes.bool,
+ isArchive: PropTypes.bool,
+ operationMode: PropTypes.bool,
+ pluginContext: PropTypes.object,
+ isCompositionUpdating: PropTypes.bool,
+ toggleCompositionUpdate: PropTypes.func
+ };
+
+ constructor(props) {
+ super(props);
+ }
+
+ routeToOverview = () => {
+ const { history, match } = this.props;
+ const workflowId = match.params.workflowId;
+ history.push(`/workflows/workflow/${workflowId}/overview`);
+ };
+
+ sendSaveParamsToServer = () => {
+ const {
+ savedParams,
+ saveParamsToServer,
+ workflowId,
+ workflowName
+ } = this.props;
+ saveParamsToServer({ params: savedParams, workflowId, workflowName });
+ };
+ handleSendMsgToCatalog = () => {
+ const {
+ pluginContext: { eventsClientId, parentUrl },
+ workflowId,
+ isCertifyDisable
+ } = this.props;
+ const client = new PluginPubSub(eventsClientId, parentUrl);
+ client.notify(notificationType.CLOSE, {
+ isCompleted: isCertifyDisable,
+ workflowId,
+ path: CATALOG_PATH
+ });
+ };
+ certifyVersion = () => {
+ const {
+ certifyVersion,
+ workflowId,
+ currentWorkflowVersion,
+ savedParams,
+ workflowName
+ } = this.props;
+ certifyVersion({
+ workflowId,
+ workflowName,
+ versionId: currentWorkflowVersion.id,
+ params: savedParams
+ });
+ };
+
+ versionChangeCallback = versionId => {
+ const { changeVersion, workflowId } = this.props;
+ changeVersion({ versionId, workflowId });
+ };
+
+ undoClickCallback = () => {
+ const {
+ currentWorkflowVersion,
+ changeVersion,
+ workflowId
+ } = this.props;
+ changeVersion({ versionId: currentWorkflowVersion.id, workflowId });
+ };
+
+ render() {
+ const {
+ currentWorkflowVersion,
+ workflowName,
+ versionsList,
+ hasErrors,
+ isCertifyDisable,
+ isArchive,
+ operationMode,
+ isCompositionUpdating,
+ toggleCompositionUpdate
+ } = this.props;
+ const isReadonly = isCertifyDisable || hasErrors || isArchive;
+ return (
+ <div className="version-controller-bar">
+ <WorkflowTitle workflowName={workflowName} />
+ <div
+ className={`vc-container ${
+ operationMode ? 'vs-container-operation' : ''
+ }`}>
+ {!operationMode && (
+ <VersionContainer
+ currentWorkflowVersion={currentWorkflowVersion}
+ viewableVersions={versionsList}
+ onOverviewClick={this.routeToOverview}
+ onVersionSelectChange={this.versionChangeCallback}
+ isArchive={isArchive}
+ />
+ )}
+ {operationMode && (
+ <OperationModeButtons
+ sendMsgToCatalog={this.handleSendMsgToCatalog}
+ saveDisabled={isReadonly}
+ onSaveClick={this.sendSaveParamsToServer}
+ onCertifyClick={this.certifyVersion}
+ />
+ )}
+ {!operationMode && (
+ <ActionButtons
+ isCompositionUpdating={isCompositionUpdating}
+ saveDisabled={isReadonly}
+ onSaveClick={this.sendSaveParamsToServer}
+ certifyDisabled={isReadonly}
+ onCertifyClick={this.certifyVersion}
+ onUndoClick={this.undoClickCallback}
+ toggleCompositionUpdate={toggleCompositionUpdate}
+ />
+ )}
+ </div>
+ </div>
+ );
+ }
+}
+
+VersionControllerView.defaultProps = {
+ getVersions: () => {}
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/__tests__/VersionControllerView_snapshot-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/__tests__/VersionControllerView_snapshot-test.js
new file mode 100644
index 00000000..2bcfa300
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/__tests__/VersionControllerView_snapshot-test.js
@@ -0,0 +1,59 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 renderer from 'react-test-renderer';
+
+import VersionsContainer from 'features/version/versionController/views/VersionsContainer';
+
+describe('Version Controller View Snapshot', () => {
+ it('renders correctly', () => {
+ const versionList = [
+ {
+ id: '7b5f6b086613470985082df2c0f6c713',
+ name: '1.0',
+ description:
+ 'Initial version, bug fix for previous version that fixed an exception when the port was occupied',
+ status: 'Draft',
+ state: 'Draft',
+ creationTime: 1530687330460,
+ modificationTime: 1530687330575,
+ archivedStatus: 'ACTIVE'
+ },
+ {
+ id: '7b5f6b086613470985082df2c0f6c666',
+ name: '2.0',
+ description:
+ 'Test version, bug fix for previous version that fixed an exception when the port was occupied',
+ status: 'Draft',
+ state: 'Draft',
+ creationTime: 1530687330461,
+ modificationTime: 1530687330576,
+ archivedStatus: 'ACTIVE',
+ baseId: '7b5f6b086613470985082df2c0f6c713'
+ }
+ ];
+ const tree = renderer
+ .create(
+ <VersionsContainer
+ viewableVersions={versionList}
+ currentWorkflowVersion={versionList[0]}
+ />
+ )
+ .toJSON();
+
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/__tests__/__snapshots__/VersionControllerView_snapshot-test.js.snap b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/__tests__/__snapshots__/VersionControllerView_snapshot-test.js.snap
new file mode 100644
index 00000000..ea135fce
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/__tests__/__snapshots__/VersionControllerView_snapshot-test.js.snap
@@ -0,0 +1,39 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Version Controller View Snapshot renders correctly 1`] = `
+<div
+ className="version-section-wrapper"
+>
+ <div
+ className="version-status-container"
+ >
+ version
+ <select
+ className="version-selector"
+ data-test-id="vc-versions-select-box"
+ onChange={[Function]}
+ value="7b5f6b086613470985082df2c0f6c713"
+ >
+ <option
+ data-test-id="vc-version-option"
+ value="7b5f6b086613470985082df2c0f6c713"
+ >
+ 1.0 DRAFT
+ </option>
+ <option
+ data-test-id="vc-version-option"
+ value="7b5f6b086613470985082df2c0f6c666"
+ >
+ 2.0 Draft
+ </option>
+ </select>
+ <span
+ className="version-selector-more-versions"
+ data-test-id="vc-versions-page-link"
+ onClick={undefined}
+ >
+ viewOverview
+ </span>
+ </div>
+</div>
+`;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerConstants.js
new file mode 100644
index 00000000..57aef602
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerConstants.js
@@ -0,0 +1,29 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createAction } from 'redux-actions';
+export const SAVE_ACTION = 'versionController/SAVE';
+export const CERTIFY_ACTION = 'versionController/CERTIFY';
+export const UNDO_ACTION = 'versionController/UNDO';
+export const CERTIFY_JSON = {
+ name: 'CERTIFIED'
+};
+
+export const saveParamsAction = createAction(SAVE_ACTION, payload => payload);
+export const certifyVersionAction = createAction(
+ CERTIFY_ACTION,
+ payload => payload
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js
new file mode 100644
index 00000000..19c8bdc2
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js
@@ -0,0 +1,36 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createSelector } from 'reselect';
+import {
+ getInputs,
+ getOutputs
+} from 'features/version/inputOutput/inputOutputSelectors';
+import { getVersionInfo } from 'features/version/general/generalSelectors';
+import { getComposition } from 'features/version/composition/compositionSelectors';
+
+export const getSavedObjParams = createSelector(
+ getOutputs,
+ getInputs,
+ getComposition,
+ getVersionInfo,
+ (outputs, inputs, composition, general) => ({
+ outputs,
+ inputs,
+ composition,
+ ...general
+ })
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/ActionButtons.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/ActionButtons.js
new file mode 100644
index 00000000..0a9c2b1a
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/ActionButtons.js
@@ -0,0 +1,92 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { I18n } from 'react-redux-i18n';
+import { Button } from 'onap-ui-react';
+import PropTypes from 'prop-types';
+import SvgButton from 'features/version/versionController/views/SvgButton';
+import CompositionUpdate from 'features/version/composition/CompositionUpdate';
+
+const ActionButtons = props => {
+ const {
+ onSaveClick,
+ certifyDisabled,
+ onCertifyClick,
+ isCompositionUpdating,
+ toggleCompositionUpdate,
+ onUndoClick,
+ saveDisabled
+ } = props;
+
+ return (
+ <div className="save-submit-cancel-container">
+ <div className="action-buttons">
+ <div className="select-action-buttons">
+ <div className={'separator vc-separator'} />
+ <SvgButton
+ dataTestId="vc-save-btn"
+ name="version-controller-save"
+ tooltipText={I18n.t('buttons.saveBtn')}
+ disabled={saveDisabled}
+ onClick={onSaveClick}
+ />
+
+ <div className={'separator vc-separator'} />
+
+ <SvgButton
+ dataTestId="vc-undo-btn"
+ name="version-controller-undo"
+ tooltipText={I18n.t('buttons.undoBtn')}
+ disabled={certifyDisabled}
+ onClick={onUndoClick}
+ />
+
+ <div className={'separator vc-separator'} />
+
+ <Button
+ className="certifyBtn"
+ btnType="primary"
+ disabled={certifyDisabled}
+ onClick={() => toggleCompositionUpdate(true)}>
+ {I18n.t('buttons.certifyBtn')}
+ </Button>
+
+ {isCompositionUpdating && (
+ <CompositionUpdate
+ certifyBack={() => {
+ toggleCompositionUpdate(false);
+ onCertifyClick();
+ }}
+ />
+ )}
+ </div>
+ </div>
+ </div>
+ );
+};
+
+ActionButtons.propTypes = {
+ onSaveClick: PropTypes.func,
+ certifyDisabled: PropTypes.bool,
+ onCertifyClick: PropTypes.func,
+ onUndoClick: PropTypes.func,
+ saveDisabled: PropTypes.bool,
+ isCompositionUpdating: PropTypes.bool,
+ toggleCompositionUpdate: PropTypes.func
+};
+
+export default ActionButtons;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/OperationModeButtons.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/OperationModeButtons.js
new file mode 100644
index 00000000..4d992adc
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/OperationModeButtons.js
@@ -0,0 +1,70 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { I18n } from 'react-redux-i18n';
+import { Button } from 'onap-ui-react';
+import PropTypes from 'prop-types';
+import SvgButton from 'features/version/versionController/views/SvgButton';
+
+const OperationModeButtons = props => {
+ const {
+ onSaveClick,
+ saveDisabled,
+ sendMsgToCatalog,
+ onCertifyClick
+ } = props;
+ return (
+ <div className="save-submit-cancel-container">
+ <div className="action-buttons">
+ <div className="select-action-buttons">
+ <SvgButton
+ dataTestId="vc-save-btn"
+ name="version-controller-save"
+ tooltipText={I18n.t('buttons.saveBtn')}
+ disabled={saveDisabled}
+ onClick={onSaveClick}
+ />
+
+ <Button
+ disabled={saveDisabled}
+ className="certifyBtn"
+ btnType="primary"
+ onClick={onCertifyClick}>
+ {I18n.t('buttons.completeBtn')}
+ </Button>
+
+ <SvgButton
+ tooltipText={I18n.t('buttons.backToCatalog')}
+ className="vs-back-btn"
+ dataTestId="vc-back-btn"
+ name="upload"
+ onClick={sendMsgToCatalog}
+ />
+ </div>
+ </div>
+ </div>
+ );
+};
+
+OperationModeButtons.propTypes = {
+ onSaveClick: PropTypes.func,
+ saveDisabled: PropTypes.bool,
+ sendMsgToCatalog: PropTypes.func,
+ onCertifyClick: PropTypes.func
+};
+
+export default OperationModeButtons;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/SvgButton.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/SvgButton.js
new file mode 100644
index 00000000..41bdeb81
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/SvgButton.js
@@ -0,0 +1,62 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { SVGIcon } from 'onap-ui-react';
+import PropTypes from 'prop-types';
+
+const SvgButton = props => {
+ const {
+ className = '',
+ name,
+ tooltipText,
+ disabled,
+ onClick,
+ dataTestId,
+ actiontype
+ } = props;
+ let onClickAction = disabled ? () => {} : () => onClick(actiontype);
+ return (
+ <div
+ className={`action-button-wrapper ${
+ disabled ? 'disabled' : 'clickable'
+ }`}
+ onClick={onClickAction}>
+ <div className="action-buttons-svg">
+ <SVGIcon
+ className={className}
+ label={tooltipText}
+ labelPosition="bottom"
+ labelClassName="action-button-label"
+ data-test-id={dataTestId}
+ name={name}
+ disabled={disabled}
+ />
+ </div>
+ </div>
+ );
+};
+
+SvgButton.propTypes = {
+ name: PropTypes.string,
+ tooltipText: PropTypes.string,
+ disabled: PropTypes.bool,
+ onClick: PropTypes.func,
+ dataTestId: PropTypes.string,
+ actiontype: PropTypes.string,
+ className: PropTypes.string
+};
+
+export default SvgButton;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/VersionButton.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/VersionButton.js
new file mode 100644
index 00000000..19e148cb
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/VersionButton.js
@@ -0,0 +1,38 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 } from 'onap-ui-react';
+import { I18n } from 'react-redux-i18n';
+import PropTypes from 'prop-types';
+
+const VersionButton = props => {
+ const { onClick, actiontype } = props;
+ let onClickAction = () => onClick(actiontype);
+ return (
+ <div>
+ <Button btnType="primary" onClick={onClickAction}>
+ {I18n.t('buttons.certifyBtn')}
+ </Button>
+ </div>
+ );
+};
+
+VersionButton.propTypes = {
+ onClick: PropTypes.func,
+ actiontype: PropTypes.string
+};
+
+export default VersionButton;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/VersionSelect.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/VersionSelect.js
new file mode 100644
index 00000000..d8a6d02e
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/VersionSelect.js
@@ -0,0 +1,69 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import isEmpty from 'lodash.isempty';
+
+const VersionSelect = props => {
+ const {
+ currentWorkflowVersion,
+ viewableVersions,
+ onVersionSelectChange
+ } = props;
+
+ function onChangeHandler(ev) {
+ const versionIndex = Object.keys(viewableVersions).find(
+ key => viewableVersions[key].id === ev.target.value
+ );
+ const currentVersion = viewableVersions[versionIndex].id;
+ onVersionSelectChange(currentVersion);
+ }
+
+ return (
+ <select
+ className="version-selector"
+ key={'selector'}
+ value={currentWorkflowVersion.id}
+ onChange={onChangeHandler}
+ data-test-id="vc-versions-select-box">
+ {!isEmpty(viewableVersions) &&
+ viewableVersions.map(item => {
+ const displayedName = `${item.name} ${
+ currentWorkflowVersion.id === item.id
+ ? currentWorkflowVersion.state.toUpperCase()
+ : item.state
+ }`;
+ return (
+ <option
+ key={'versionSelect' + item.id}
+ value={item.id}
+ data-test-id="vc-version-option">
+ {displayedName}
+ </option>
+ );
+ })}
+ </select>
+ );
+};
+
+VersionSelect.propTypes = {
+ currentWorkflowVersion: PropTypes.object,
+ viewableVersions: PropTypes.arrayOf(Object),
+ onVersionSelectChange: PropTypes.func
+};
+
+export default VersionSelect;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/VersionsContainer.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/VersionsContainer.js
new file mode 100644
index 00000000..c84ab31d
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/VersionsContainer.js
@@ -0,0 +1,60 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 VersionSelect from 'features/version/versionController/views/VersionSelect';
+import { I18n } from 'react-redux-i18n';
+import PropTypes from 'prop-types';
+import ArchiveLabel from 'shared/archiveLabel/ArchiveLabel';
+
+const VersionContainer = props => {
+ const {
+ currentWorkflowVersion,
+ viewableVersions,
+ onOverviewClick,
+ onVersionSelectChange,
+ isArchive
+ } = props;
+
+ return (
+ <div className="version-section-wrapper">
+ <div className="version-status-container">
+ {I18n.t('workflow.version')}
+ <VersionSelect
+ currentWorkflowVersion={currentWorkflowVersion}
+ viewableVersions={viewableVersions}
+ onVersionSelectChange={onVersionSelectChange}
+ />
+ <span
+ className="version-selector-more-versions"
+ data-test-id="vc-versions-page-link"
+ onClick={onOverviewClick}>
+ {I18n.t('workflow.overview.viewOverview')}
+ </span>
+ {isArchive && <ArchiveLabel />}
+ </div>
+ </div>
+ );
+};
+
+VersionContainer.propTypes = {
+ currentWorkflowVersion: PropTypes.object,
+ viewableVersions: PropTypes.arrayOf(Object),
+ onOverviewClick: PropTypes.func,
+ onVersionSelectChange: PropTypes.func,
+ isArchive: PropTypes.bool
+};
+
+export default VersionContainer;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/WorkflowTitle.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/WorkflowTitle.js
new file mode 100644
index 00000000..230ecf08
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/WorkflowTitle.js
@@ -0,0 +1,34 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+
+const WorkflowTitle = props => {
+ const { workflowName } = props;
+ return (
+ <div className="version-section-wrapper">
+ <div className="group-name-wrapper">
+ <div className="group-name">{workflowName || ''}</div>
+ </div>
+ </div>
+ );
+};
+
+WorkflowTitle.propTypes = {
+ workflowName: PropTypes.string
+};
+
+export default WorkflowTitle;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionModeReducer.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionModeReducer.js
new file mode 100644
index 00000000..9d4d67ea
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionModeReducer.js
@@ -0,0 +1,10 @@
+import { SET_OPERRATION_MODE } from './versionConstants';
+
+export default (state = false, action) => {
+ switch (action.type) {
+ case SET_OPERRATION_MODE:
+ return true;
+ default:
+ return state;
+ }
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionReducer.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionReducer.js
new file mode 100644
index 00000000..d05af2d7
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionReducer.js
@@ -0,0 +1,51 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 {
+ SET_CURRENT_VERSION,
+ DETAILS_CHANGED,
+ VERSION_STATE_CHANGED,
+ TOGGLE_COMPOSITION_UPDATE
+} from 'features/version/versionConstants';
+
+const initialState = {
+ isCompositionUpdating: false
+};
+
+function versionReducer(state = initialState, action) {
+ switch (action.type) {
+ case SET_CURRENT_VERSION:
+ return action.payload;
+ case DETAILS_CHANGED:
+ return {
+ ...state,
+ ...action.payload
+ };
+ case VERSION_STATE_CHANGED:
+ return {
+ ...state,
+ ...action.payload
+ };
+ case TOGGLE_COMPOSITION_UPDATE:
+ return {
+ ...state,
+ isCompositionUpdating: action.payload.isCompositionUpdating
+ };
+ default:
+ return state;
+ }
+}
+
+export default versionReducer;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js
new file mode 100644
index 00000000..56dd7a5d
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js
@@ -0,0 +1,167 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { all, call, put, takeEvery, takeLatest } from 'redux-saga/effects';
+import { I18n } from 'react-redux-i18n';
+
+import { genericNetworkErrorAction } from 'src/appConstants';
+import {
+ setWorkflowVersionAction,
+ versionStateChangedAction,
+ FETCH_REQUESTED
+} from 'features/version/versionConstants';
+import { setInputsOutputs } from 'features/version/inputOutput/inputOutputActions';
+import { SUBMIT_VERSION } from 'features/version/create/createVersionConstants';
+import {
+ SAVE_ACTION,
+ CERTIFY_ACTION
+} from 'features/version/versionController/versionControllerConstants';
+import versionApi from 'features/version/versionApi';
+import { notificationActions } from 'shared/notifications/notificationsActions';
+import { versionState } from 'features/version/versionConstants';
+import overviewApi from '../workflow/overview/overviewApi';
+import { versionListFetchAction } from '../workflow/overview/overviewConstansts';
+import {
+ updateComposition,
+ deleteCompositionArtifact
+} from 'features/version/composition/compositionActions';
+import { getActivitiesList } from 'features/activities/activitiesActions';
+
+/**
+ * Composition validation - converting artifact string to xml
+ * and checking if bpmn diagram has only one child
+ * @param composition
+ * @returns {boolean}
+ */
+function validateCurrentArtifact(composition) {
+ const parser = new DOMParser();
+ const xml = parser.parseFromString(composition, 'text/xml');
+ return Boolean(
+ xml.getElementsByTagName('bpmndi:BPMNPlane').BPMNPlane_1.children.length
+ );
+}
+
+function* fetchVersion(action) {
+ try {
+ yield put(getActivitiesList());
+ const data = yield call(versionApi.fetchVersion, action.payload);
+ const { inputs, outputs, ...rest } = data;
+ let composition;
+
+ if (rest.hasArtifact) {
+ composition = yield call(
+ versionApi.fetchVersionArtifact,
+ action.payload
+ );
+ } else {
+ //Clearing the store from old artifact using init the default
+ yield put(deleteCompositionArtifact());
+ }
+ yield all([
+ put(setWorkflowVersionAction(rest)),
+ put(setInputsOutputs({ inputs, outputs })),
+ composition && put(updateComposition(composition))
+ ]);
+ } catch (error) {
+ yield put(genericNetworkErrorAction(error));
+ }
+}
+
+function* watchSubmitVersion(action) {
+ try {
+ const { workflowId, history } = action.payload;
+ const data = yield call(versionApi.createNewVersion, action.payload);
+ const versions = yield call(overviewApi.getVersions, workflowId);
+ yield put(versionListFetchAction(versions));
+ yield call(
+ history.push(`/workflows/workflow/${workflowId}/version/${data.id}`)
+ );
+ } catch (error) {
+ yield put(genericNetworkErrorAction(error));
+ }
+}
+
+function* watchUpdateVersion(action) {
+ try {
+ const {
+ workflowId,
+ workflowName,
+ params: { composition, ...versionData }
+ } = action.payload;
+ const isArtifactValid = validateCurrentArtifact(composition);
+ yield call(versionApi.updateVersion, {
+ workflowId,
+ params: versionData
+ });
+ yield put(
+ notificationActions.showSuccess({
+ title: I18n.t('workflow.confirmationMessages.updateTitle'),
+ message: I18n.t('workflow.confirmationMessages.updateMessage')
+ })
+ );
+ if (isArtifactValid) {
+ yield call(versionApi.updateVersionArtifact, {
+ workflowId,
+ workflowName,
+ versionName: versionData.name.split('.').join('_'),
+ versionId: versionData.id,
+ payload: composition
+ });
+ } else {
+ yield call(versionApi.deleteVersionArtifact, {
+ workflowId,
+ versionId: versionData.id
+ });
+ }
+ return isArtifactValid;
+ } catch (error) {
+ yield put(genericNetworkErrorAction(error));
+ }
+}
+
+function* watchCertifyVersion(action) {
+ try {
+ const isArtifactValid = yield call(watchUpdateVersion, action);
+ if (!isArtifactValid)
+ throw new Error('Could not update empty artifact');
+ yield call(versionApi.certifyVersion, {
+ ...action.payload
+ });
+ yield put(versionStateChangedAction({ state: versionState.CERTIFIED }));
+ yield put(
+ notificationActions.showSuccess({
+ title: I18n.t('workflow.confirmationMessages.certifyTitle'),
+ message: I18n.t('workflow.confirmationMessages.certifyMessage')
+ })
+ );
+ } catch (error) {
+ yield put(
+ notificationActions.showError({
+ title: I18n.t('workflow.confirmationMessages.certifyTitle'),
+ message: I18n.t('workflow.composition.certifyArtifact')
+ })
+ );
+ yield put(genericNetworkErrorAction(error));
+ }
+}
+
+function* versionSaga() {
+ yield takeLatest(FETCH_REQUESTED, fetchVersion);
+ yield takeEvery(SUBMIT_VERSION, watchSubmitVersion);
+ yield takeEvery(SAVE_ACTION, watchUpdateVersion);
+ yield takeEvery(CERTIFY_ACTION, watchCertifyVersion);
+}
+
+export default versionSaga;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/CreateWorkflow.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/CreateWorkflow.js
new file mode 100644
index 00000000..190677fe
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/CreateWorkflow.js
@@ -0,0 +1,61 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
+
+import { i18nSelector } from 'wfapp/appSelectors';
+import { hideModalAction } from 'shared/modal/modalWrapperActions';
+import CreateWorkflowView from 'features/workflow/create/CreateWorkflowView';
+import { getWorkflowParams } from 'features/workflow/create/createWorkflowSelector';
+import {
+ getWorkflowDescription,
+ getWorkflowName
+} from 'features/workflow/workflowSelectors';
+import {
+ inputChangeAction,
+ submitWorkflowAction,
+ clearValidationError
+} from 'features/workflow/create/createWorkflowConstants';
+import { clearWorkflowAction } from 'features/workflow/workflowConstants';
+
+function mapStateToProps(state) {
+ return {
+ translation: i18nSelector(state),
+ workflowDescription: getWorkflowDescription(state),
+ workflowName: getWorkflowName(state),
+ workflowParams: getWorkflowParams(state),
+ errorMessage: state.workflow.data.error
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ submitWorkflow: payload => {
+ dispatch(submitWorkflowAction(payload));
+ },
+ closeCreateWorkflowModal: () => dispatch(hideModalAction()),
+ clearValidationError: () => dispatch(clearValidationError()),
+ workflowInputChange: payload => dispatch(inputChangeAction(payload)),
+ clearWorkflow: () => dispatch(clearWorkflowAction)
+ };
+}
+
+export default withRouter(
+ connect(
+ mapStateToProps,
+ mapDispatchToProps
+ )(CreateWorkflowView)
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/CreateWorkflowView.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/CreateWorkflowView.jsx
new file mode 100644
index 00000000..9af92dbb
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/CreateWorkflowView.jsx
@@ -0,0 +1,101 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Input, Button } from 'onap-ui-react';
+import { I18n } from 'react-redux-i18n';
+import Description from 'shared/components/Description';
+
+class CreateWorkflowView extends Component {
+ static propTypes = {
+ submitWorkflow: PropTypes.func,
+ workflowInputChange: PropTypes.func,
+ workflowDescription: PropTypes.string,
+ workflowName: PropTypes.string,
+ closeCreateWorkflowModal: PropTypes.func,
+ workflowParams: PropTypes.object,
+ history: PropTypes.object,
+ errorMessage: PropTypes.string,
+ clearValidationError: PropTypes.func,
+ clearWorkflow: PropTypes.func
+ };
+
+ componentDidMount() {
+ const { clearValidationError, clearWorkflow } = this.props;
+ clearValidationError();
+ clearWorkflow();
+ }
+ handleSubmitForm = e => {
+ e.preventDefault();
+ const { workflowParams, history, submitWorkflow } = this.props;
+ submitWorkflow({ ...workflowParams, history });
+ };
+
+ render() {
+ const {
+ workflowInputChange,
+ workflowDescription,
+ workflowName,
+ closeCreateWorkflowModal,
+ errorMessage
+ } = this.props;
+ return (
+ <form onSubmit={this.handleSubmitForm} autoComplete="off">
+ <div className="new-workflow-page custom-modal-wrapper">
+ <div className="form-custom-modal">
+ <Input
+ name="workflowName"
+ value={workflowName || ''}
+ type="text"
+ label={I18n.t('workflow.general.name')}
+ onChange={val =>
+ workflowInputChange({
+ name: val
+ })
+ }
+ errorMessage={errorMessage}
+ isRequired
+ />
+ <Description
+ value={workflowDescription || ''}
+ label={I18n.t('workflow.general.description')}
+ onDataChange={workflowInputChange}
+ />
+ </div>
+ <div className="modal-action-bar sdc-modal__footer">
+ <Button btnType="primary">
+ {I18n.t('buttons.createBtn')}
+ </Button>
+ <Button
+ btnType="secondary"
+ onClick={closeCreateWorkflowModal}>
+ {I18n.t('buttons.closeBtn')}
+ </Button>
+ </div>
+ </div>
+ </form>
+ );
+ }
+}
+
+CreateWorkflowView.defaultProps = {
+ submitWorkflow: () => {},
+ workflowInputChange: () => {},
+ closeCreateWorkflowModal: () => {},
+ clearWorkflow: () => {}
+};
+
+export default CreateWorkflowView;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/CreateWorkflowView_snapshot-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/CreateWorkflowView_snapshot-test.js
new file mode 100644
index 00000000..e34cea96
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/CreateWorkflowView_snapshot-test.js
@@ -0,0 +1,36 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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.
+*/
+'use strict';
+
+import React from 'react';
+import renderer from 'react-test-renderer';
+
+import CreateWorkflowView from 'features/workflow/create/CreateWorkflowView';
+
+describe('New Workflow View Snapshot', () => {
+ it('renders correctly', () => {
+ const tree = renderer
+ .create(
+ <CreateWorkflowView
+ clearValidationError={() => {}}
+ clearWorkflow={() => {}}
+ />
+ )
+ .toJSON();
+
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/__snapshots__/CreateWorkflowView_snapshot-test.js.snap b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/__snapshots__/CreateWorkflowView_snapshot-test.js.snap
new file mode 100644
index 00000000..e26da0d3
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/__snapshots__/CreateWorkflowView_snapshot-test.js.snap
@@ -0,0 +1,83 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`New Workflow View Snapshot renders correctly 1`] = `
+<form
+ autoComplete="off"
+ onSubmit={[Function]}
+>
+ <div
+ className="new-workflow-page custom-modal-wrapper"
+ >
+ <div
+ className="form-custom-modal"
+ >
+ <div
+ className="sdc-input "
+ >
+ <label
+ className="sdc-input__label required"
+ htmlFor="workflowName"
+ >
+ name
+ </label>
+ <div
+ className="sdc-input-wrapper"
+ >
+ <input
+ className="sdc-input__input "
+ data-test-id={undefined}
+ disabled={false}
+ id="workflowName"
+ name="workflowName"
+ onBlur={[Function]}
+ onChange={[Function]}
+ onKeyDown={[Function]}
+ placeholder={undefined}
+ readOnly={false}
+ type="text"
+ value=""
+ />
+ </div>
+ </div>
+ <div
+ className="description-part"
+ >
+ <div
+ className="sdc-textarea"
+ >
+ <div
+ className="sdc-textarea__label"
+ >
+ description
+ </div>
+ <textarea
+ className="custom-textarea field-section sdc-textarea__textarea"
+ data-test-id="description"
+ disabled={false}
+ onChange={[Function]}
+ value={undefined}
+ />
+ </div>
+ </div>
+ </div>
+ <div
+ className="modal-action-bar sdc-modal__footer"
+ >
+ <button
+ className="sdc-button sdc-button__primary "
+ disabled={false}
+ onClick={undefined}
+ >
+ createBtn
+ </button>
+ <button
+ className="sdc-button sdc-button__secondary "
+ disabled={false}
+ onClick={[Function]}
+ >
+ closeBtn
+ </button>
+ </div>
+ </div>
+</form>
+`;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/createWorkflowSaga-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/createWorkflowSaga-test.js
new file mode 100644
index 00000000..244a66de
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/createWorkflowSaga-test.js
@@ -0,0 +1,76 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { call, takeEvery } from 'redux-saga/effects';
+import {
+ watchWorkflow,
+ watchSubmitWorkflow
+} from 'features/workflow/create/createWorkflowSaga';
+import { put } from 'redux-saga/effects';
+import newWorkflowApi from 'features/workflow/create/createWorkflowApi';
+import { SUBMIT_WORKFLOW } from 'features/workflow/create/createWorkflowConstants';
+import { submitVersionAction } from 'features/version/create/createVersionConstants';
+import { NEW_VERSION } from 'features/workflow/create/createWorkflowConstants';
+import {
+ setWorkflowAction,
+ clearWorkflowAction
+} from 'features/workflow/workflowConstants';
+import { genericNetworkErrorAction } from 'wfapp/appConstants';
+
+describe('New workflow saga test', () => {
+ it('Create new workflow', () => {
+ const gen = watchWorkflow();
+ expect(gen.next().value).toEqual(
+ takeEvery(SUBMIT_WORKFLOW, watchSubmitWorkflow)
+ );
+ expect(gen.next().done).toEqual(true);
+ });
+
+ it('Submit new workflow', () => {
+ const action = {
+ payload: {
+ name: 'workflow1',
+ description: 'description'
+ }
+ };
+ const gen = watchSubmitWorkflow(action);
+
+ /**
+ * expecting the error message to return as undefined
+ * from validateNameField method
+ */
+ expect(gen.next().value).toEqual(undefined);
+ expect(gen.next().value).toEqual(
+ call(newWorkflowApi.createNewWorkflow, action.payload)
+ );
+ const history = undefined,
+ workflowId = undefined;
+ expect(gen.next(action.payload).value).toEqual(
+ put(submitVersionAction({ history, workflowId, ...NEW_VERSION }))
+ );
+ expect(gen.next().value).toEqual(
+ put(setWorkflowAction({ ...action.payload, id: undefined }))
+ );
+ //handling errors
+ expect(gen.throw({ error: 'error' }).value).toEqual(
+ put(clearWorkflowAction)
+ );
+ expect(gen.next().value).toEqual(
+ put(genericNetworkErrorAction({ error: 'error' }))
+ );
+ expect(gen.next().done).toBe(true);
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/createWorkflowSelector-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/createWorkflowSelector-test.js
new file mode 100644
index 00000000..909c876a
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/__tests__/createWorkflowSelector-test.js
@@ -0,0 +1,51 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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.
+*/
+
+'use strict';
+import {
+ getWorkflowName,
+ getWorkflowDescription
+} from 'features/workflow/workflowSelectors';
+import { getWorkflowParams } from 'features/workflow/create/createWorkflowSelector';
+
+describe('New workflow selectors', () => {
+ const workflow = {
+ data: {
+ name: 'workflow1',
+ description: 'description'
+ }
+ };
+
+ it('return workflow name', () => {
+ const state = { workflow };
+ expect(getWorkflowName(state)).toEqual(workflow.data.name);
+ });
+
+ it('return workflow description', () => {
+ const state = { workflow };
+ expect(getWorkflowDescription(state)).toEqual(
+ workflow.data.description
+ );
+ });
+
+ it('return workflow server params', () => {
+ const state = { workflow };
+ expect(getWorkflowParams(state)).toEqual({
+ name: getWorkflowName(state),
+ description: getWorkflowDescription(state)
+ });
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowApi.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowApi.js
new file mode 100644
index 00000000..b97226a3
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowApi.js
@@ -0,0 +1,30 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 RestfulAPIUtil from 'services/restAPIUtil';
+import Configuration from 'config/Configuration.js';
+
+function baseUrl() {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/workflows/`;
+}
+
+const Api = {
+ createNewWorkflow: data => {
+ return RestfulAPIUtil.post(baseUrl(), data);
+ }
+};
+
+export default Api;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowConstants.js
new file mode 100644
index 00000000..de18a1b4
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowConstants.js
@@ -0,0 +1,44 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const NEW_VERSION = {
+ baseId: null,
+ description: null
+};
+export const MIN_NAME_LENGTH = 6;
+export const MAX_NAME_LENGTH = 40;
+export const CHARS_VALIDATION_EXP = /^[\w\s\d]+$/;
+export const WORKFLOW_INPUT_CHANGE = 'createWorkflow/INPUT_CHANGE';
+export const SUBMIT_WORKFLOW = 'createWorkflow/SUBMIT_WORKFLOW';
+export const VALIDATION_ERROR = 'createWorkflow/VALIDATION_ERROR';
+export const CLEAR_VALIDATION_ERROR = 'createWorkflow/CLEAR_VALIDATION_ERROR';
+
+export const inputChangeAction = payload => ({
+ type: WORKFLOW_INPUT_CHANGE,
+ payload
+});
+
+export const submitWorkflowAction = payload => ({
+ type: SUBMIT_WORKFLOW,
+ payload
+});
+
+export const putValidationError = payload => ({
+ type: VALIDATION_ERROR,
+ payload
+});
+
+export const clearValidationError = () => ({ type: CLEAR_VALIDATION_ERROR });
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowSaga.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowSaga.js
new file mode 100644
index 00000000..e918556b
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowSaga.js
@@ -0,0 +1,79 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { takeEvery, call, put } from 'redux-saga/effects';
+import { I18n } from 'react-redux-i18n';
+
+import {
+ SUBMIT_WORKFLOW,
+ NEW_VERSION,
+ MAX_NAME_LENGTH,
+ MIN_NAME_LENGTH,
+ CHARS_VALIDATION_EXP,
+ putValidationError
+} from 'features/workflow/create/createWorkflowConstants';
+import {
+ setWorkflowAction,
+ clearWorkflowAction
+} from 'features/workflow/workflowConstants';
+import { hideModalAction } from 'shared/modal/modalWrapperActions';
+import newWorkflowApi from 'features/workflow/create/createWorkflowApi';
+import { genericNetworkErrorAction } from 'wfapp/appConstants';
+import { submitVersionAction } from 'features/version/create/createVersionConstants';
+
+export function* watchSubmitWorkflow(action) {
+ try {
+ const { name } = action.payload;
+ const validationError = yield validateNameField(name);
+ if (validationError) {
+ yield put(putValidationError(validationError));
+ } else {
+ const workflow = yield call(
+ newWorkflowApi.createNewWorkflow,
+ action.payload
+ );
+ //Calling to create empty version
+ const workflowId = workflow.id;
+ const { history } = action.payload;
+ yield put(
+ submitVersionAction({ history, workflowId, ...NEW_VERSION })
+ );
+ yield put(setWorkflowAction(workflow));
+ yield put(hideModalAction());
+ }
+ } catch (error) {
+ yield put(clearWorkflowAction);
+ yield put(genericNetworkErrorAction(error));
+ }
+}
+
+export function validateNameField(name) {
+ let errorMessage;
+ if (!name) {
+ errorMessage = I18n.t('workflow.errorMessages.emptyName');
+ } else if (!CHARS_VALIDATION_EXP.test(name)) {
+ errorMessage = I18n.t('workflow.errorMessages.invalidCharacters');
+ } else if (name.length < MIN_NAME_LENGTH || name.length > MAX_NAME_LENGTH) {
+ errorMessage = I18n.t('workflow.errorMessages.nameFieldLength', {
+ minValue: 6,
+ maxValue: 40
+ });
+ }
+ return errorMessage;
+}
+
+export function* watchWorkflow() {
+ yield takeEvery(SUBMIT_WORKFLOW, watchSubmitWorkflow);
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowSelector.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowSelector.js
new file mode 100644
index 00000000..01f52fc6
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/create/createWorkflowSelector.js
@@ -0,0 +1,31 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createSelector } from 'reselect';
+import {
+ getTrimWorkflowName,
+ getWorkflowDescription
+} from 'features/workflow/workflowSelectors';
+
+export const getWorkflowParams = createSelector(
+ getTrimWorkflowName,
+ getWorkflowDescription,
+ (name, description) => {
+ return {
+ name,
+ description
+ };
+ }
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/Overview.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/Overview.js
new file mode 100644
index 00000000..90b69763
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/Overview.js
@@ -0,0 +1,86 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { connect } from 'react-redux';
+import { I18n } from 'react-redux-i18n';
+
+import OverviewView from 'features/workflow/overview/OverviewView';
+import {
+ getSortedVersions,
+ getSelectedVersionId,
+ getWorkflowData,
+ getAllIsVersionsCertifies
+} from 'features/workflow/overview/overviewSelectors';
+import { isWorkflowArchive } from 'features/workflow/workflowSelectors';
+import {
+ getVersionsAction,
+ updateWorkflowAction,
+ archiveWorkflowAction,
+ restoreWorkflowAction
+} from 'features/workflow/overview/overviewConstansts';
+import { NEW_VERSION_MODAL } from 'shared/modal/modalWrapperComponents';
+import {
+ showCustomModalAction,
+ showAlertModalAction,
+ hideModalAction
+} from 'shared/modal/modalWrapperActions';
+import { inputChangeAction } from 'features/workflow/create/createWorkflowConstants';
+
+function mapStateToProps(state) {
+ return {
+ versions: getSortedVersions(state),
+ selectedVersion: getSelectedVersionId(state),
+ workflow: getWorkflowData(state),
+ isVersionsCertifies: getAllIsVersionsCertifies(state),
+ isArchive: isWorkflowArchive(state)
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ getOverview: workflowId => dispatch(getVersionsAction(workflowId)),
+ showNewVersionModal: () =>
+ dispatch(
+ showCustomModalAction({
+ customComponentName: NEW_VERSION_MODAL,
+ title: 'New Version'
+ })
+ ),
+ workflowInputChange: payload => dispatch(inputChangeAction(payload)),
+ updateWorkflow: payload => dispatch(updateWorkflowAction(payload)),
+ archiveWorkflow: payload => {
+ dispatch(
+ showAlertModalAction({
+ title: I18n.t('workflow.overview.archive'),
+ body: I18n.t('workflow.overview.confirmArchive', {
+ name: payload.name
+ }),
+ withButtons: true,
+ actionButtonText: I18n.t('workflow.overview.archive'),
+ actionButtonClick: () => {
+ dispatch(archiveWorkflowAction(payload));
+ dispatch(hideModalAction());
+ }
+ })
+ );
+ },
+ restoreWorkflow: payload => dispatch(restoreWorkflowAction(payload))
+ };
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(OverviewView);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/OverviewView.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/OverviewView.jsx
new file mode 100644
index 00000000..952e93cf
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/OverviewView.jsx
@@ -0,0 +1,142 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+import WorkflowDetails from 'features/workflow/overview/views/WorkflowDetails';
+import WorkflowVersions from 'features/workflow/overview/views/WorkflowVersions';
+import WorkflowHeader from 'features/workflow/overview/views/WorkflowHeader';
+
+class OverviewView extends Component {
+ static propTypes = {
+ getOverview: PropTypes.func,
+ versions: PropTypes.array,
+ onCreateVersion: PropTypes.func,
+ selectedVersion: PropTypes.string,
+ workflow: PropTypes.object,
+ history: PropTypes.object,
+ showNewVersionModal: PropTypes.func,
+ isVersionsCertifies: PropTypes.bool,
+ location: PropTypes.object,
+ match: PropTypes.object,
+ updateWorkflow: PropTypes.func,
+ workflowInputChange: PropTypes.func,
+ archiveWorkflow: PropTypes.func,
+ restoreWorkflow: PropTypes.func,
+ isArchive: PropTypes.bool
+ };
+
+ constructor(props) {
+ super(props);
+ }
+
+ getOverviewFromRouter = () => {
+ const { match } = this.props;
+ const workflowId = match.params.workflowId;
+ this.props.getOverview(workflowId);
+ };
+
+ componentDidMount() {
+ this.getOverviewFromRouter();
+ }
+
+ onSelectVersionFromTable = data => {
+ const { history } = this.props;
+ history.push('version/' + data.id);
+ };
+
+ onCreateNewVersionFromTable = () => {
+ const { showNewVersionModal } = this.props;
+ showNewVersionModal();
+ };
+
+ onUpdateWorkflow = payload => {
+ const { updateWorkflow, workflow } = this.props;
+ updateWorkflow({ ...workflow, ...payload });
+ };
+
+ workflowDetailsChanged = payload => {
+ const { workflowInputChange } = this.props;
+ workflowInputChange({ ...payload });
+ };
+ onArchiveWorkflow = () => {
+ const { archiveWorkflow, workflow, history } = this.props;
+
+ archiveWorkflow({ id: workflow.id, name: workflow.name, history });
+ };
+ onRestoreWorkflow = () => {
+ const { restoreWorkflow, workflow, history } = this.props;
+ restoreWorkflow({ id: workflow.id, history });
+ };
+ render() {
+ const {
+ versions,
+ selectedVersion,
+ workflow,
+ isVersionsCertifies,
+ history,
+ isArchive
+ } = this.props;
+ const nodeVersions = versions.map(version => ({
+ id: version.id,
+ name: version.name,
+ parent: version.baseId || ''
+ }));
+
+ return (
+ <div className="overview-page">
+ <WorkflowHeader
+ isArchive={isArchive}
+ archiveWorkflow={this.onArchiveWorkflow}
+ restoreWorkflow={this.onRestoreWorkflow}
+ history={history}
+ name={workflow.name}
+ />
+ <div className="overview-content">
+ <WorkflowDetails
+ isArchive={isArchive}
+ name={workflow.name}
+ description={workflow.description}
+ modified={workflow.modified}
+ created={workflow.created}
+ workflowDetailsChanged={this.workflowDetailsChanged}
+ updateWorkflow={this.onUpdateWorkflow}
+ />
+
+ <div className={'separator overview-separator'} />
+ <WorkflowVersions
+ isArchive={isArchive}
+ nodeVersions={nodeVersions}
+ versions={versions}
+ onCreateVersion={this.onCreateNewVersionFromTable}
+ onSelectVersion={this.onSelectVersionFromTable}
+ selectedVersion={selectedVersion}
+ isVersionsCertifies={isVersionsCertifies}
+ />
+ </div>
+ </div>
+ );
+ }
+}
+
+OverviewView.defaultProps = {
+ versions: [],
+ getOverview: () => {},
+ selectedVersion: ''
+};
+
+export default OverviewView;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/OverviewView_snapshot-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/OverviewView_snapshot-test.js
new file mode 100644
index 00000000..5897a91f
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/OverviewView_snapshot-test.js
@@ -0,0 +1,39 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 renderer from 'react-test-renderer';
+
+import OverviewView from 'features/workflow/overview/OverviewView';
+
+describe('OverviewView Snapshot', () => {
+ it('renders correctly', () => {
+ const workflow = {
+ name: 'wf1',
+ description: 'desc 1',
+ id: 'id1'
+ };
+ const match = {
+ params: {
+ workflowId: 'id1'
+ }
+ };
+ const tree = renderer
+ .create(<OverviewView workflow={workflow} match={match} />)
+ .toJSON();
+
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/__snapshots__/OverviewView_snapshot-test.js.snap b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/__snapshots__/OverviewView_snapshot-test.js.snap
new file mode 100644
index 00000000..a54b832c
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/__snapshots__/OverviewView_snapshot-test.js.snap
@@ -0,0 +1,217 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`OverviewView Snapshot renders correctly 1`] = `
+<div
+ className="overview-page"
+>
+ <div
+ className="overview-header"
+ >
+ <div
+ className="title"
+ >
+ wf1
+ -
+ title
+ </div>
+ <div
+ className="header-buttons"
+ >
+ <div
+ className="svg-icon-wrapper go-catalog-btn clickable right"
+ disabled={undefined}
+ onClick={[Function]}
+ >
+ <test-file-stub
+ className="svg-icon __back"
+ />
+ <span
+ className="svg-icon-label "
+ >
+ backBtnLabel
+ </span>
+ </div>
+ <div
+ className="svg-icon-wrapper archive-btn clickable bottom"
+ disabled={undefined}
+ onClick={[Function]}
+ title="Archive workflow"
+ >
+ <test-file-stub
+ className="svg-icon __archiveBox"
+ />
+
+ </div>
+ </div>
+ </div>
+ <div
+ className="overview-content"
+ >
+ <div
+ className="workflow-details"
+ >
+ <form
+ onSubmit={[Function]}
+ >
+ <div
+ className="sdc-input "
+ >
+ <label
+ className="sdc-input__label required"
+ htmlFor="workflowName"
+ >
+ name
+ </label>
+ <div
+ className="sdc-input-wrapper"
+ >
+ <input
+ className="sdc-input__input "
+ data-test-id={undefined}
+ disabled={true}
+ id="workflowName"
+ name="workflowName"
+ onBlur={[Function]}
+ onChange={[Function]}
+ onKeyDown={[Function]}
+ placeholder={undefined}
+ readOnly={false}
+ type="text"
+ value="wf1"
+ />
+ </div>
+ </div>
+ <div
+ className="description-part"
+ >
+ <div
+ className="sdc-textarea"
+ >
+ <div
+ className="sdc-textarea__label"
+ >
+ description
+ </div>
+ <textarea
+ className="custom-textarea field-section sdc-textarea__textarea"
+ data-test-id="description"
+ disabled={false}
+ onChange={[Function]}
+ value="desc 1"
+ />
+ </div>
+ </div>
+ <div
+ className="save-description"
+ >
+ <button
+ className="sdc-button sdc-button__primary "
+ disabled={false}
+ onClick={undefined}
+ >
+ saveBtn
+ </button>
+ </div>
+ </form>
+ </div>
+ <div
+ className="separator overview-separator"
+ />
+ <div
+ className="workflow-versions"
+ >
+ <div
+ className="versions-page-view"
+ >
+ <div
+ className="create-new-version newVersionDisabled"
+ >
+ <div
+ className="create-item-plus-icon"
+ onClick={[Function]}
+ >
+ <div
+ className="svg-icon-wrapper __secondary bottom"
+ disabled={undefined}
+ onClick={undefined}
+ >
+ <test-file-stub
+ className="svg-icon __plus"
+ />
+
+ </div>
+ newVersion
+ </div>
+ </div>
+ <div
+ className="versions-page-list-and-tree"
+ >
+ <div
+ className="version-tree-wrapper"
+ >
+ <div
+ className="version-tree-title-container"
+ >
+ <div
+ className="version-tree-title"
+ >
+ Version Tree
+ </div>
+ </div>
+ <div
+ className="tree-view versions-tree-container "
+ >
+ <svg
+ className="versions-tree"
+ width={200}
+ />
+ </div>
+ </div>
+ <div
+ className="version-list"
+ >
+ <div
+ className="version-item-row header-row "
+ data-test-id="version-item-row"
+ onClick={[Function]}
+ >
+ <div
+ className="version-item-field header-field item-version"
+ >
+ Version
+ </div>
+ <div
+ className="version-item-field header-field item-status"
+ >
+ Status
+ </div>
+ <div
+ className="version-item-field header-field"
+ >
+ <span
+ className={undefined}
+ style={undefined}
+ >
+ lastEdited
+ </span>
+ </div>
+ <div
+ className="version-item-field header-field"
+ >
+ <div
+ className="description-text"
+ >
+ Description
+ </div>
+ </div>
+ </div>
+ <div
+ className="version-list-items"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/overviewReducer-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/overviewReducer-test.js
new file mode 100644
index 00000000..87600be2
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/__tests__/overviewReducer-test.js
@@ -0,0 +1,96 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 overviewReducer from '../overviewReducer';
+import { versionListFetchAction } from '../overviewConstansts';
+
+describe('Overview reducer', () => {
+ it('check fetch versions', () => {
+ const versionResponse = {
+ total: 2,
+ size: 0,
+ page: 0,
+ items: [
+ {
+ id: '99adf5bc36764628b8018033d285b591',
+ name: '1.0',
+ description: 'Initial versionewewe',
+ baseId: '',
+ state: 'CERTIFIED',
+ inputs: [
+ {
+ id: '08274a71d7e34d4e96878aa5fb1ed9bd',
+ name: 'wewe',
+ type: 'INTEGER',
+ mandatory: true
+ },
+ {
+ id: '7a0b9e33ea0244c2a05c03b96207f1c8',
+ name: 'eee',
+ type: 'BOOLEAN',
+ mandatory: false
+ }
+ ],
+ outputs: [
+ {
+ id: 'a5314bbd67ff4e6091385aaa82ebb266',
+ name: 'e',
+ type: 'FLOAT',
+ mandatory: false
+ }
+ ],
+ creationTime: '2018-07-25T07:36:10.112+0000',
+ modificationTime: '2018-07-25T07:36:48.663+0000'
+ },
+ {
+ id: 'cd8156bfb250475dac1e2681a9f2a74f',
+ name: '2.0',
+ description: 'versio2neee',
+ baseId: '99adf5bc36764628b8018033d285b591',
+ state: 'CERTIFIED',
+ inputs: [
+ {
+ id: '08274a71d7e34d4e96878aa5fb1ed9bd',
+ name: 'wewe',
+ type: 'INTEGER',
+ mandatory: true
+ },
+ {
+ id: '7a0b9e33ea0244c2a05c03b96207f1c8',
+ name: 'eee',
+ type: 'BOOLEAN',
+ mandatory: false
+ }
+ ],
+ outputs: [
+ {
+ id: 'a5314bbd67ff4e6091385aaa82ebb266',
+ name: 'e',
+ type: 'FLOAT',
+ mandatory: false
+ }
+ ],
+ creationTime: '2018-07-25T07:36:58.978+0000',
+ modificationTime: '2018-07-25T07:37:09.041+0000'
+ }
+ ]
+ };
+
+ expect(
+ overviewReducer([], versionListFetchAction(versionResponse))
+ ).toEqual([...versionResponse.items]);
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewApi.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewApi.js
new file mode 100644
index 00000000..d48c214b
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewApi.js
@@ -0,0 +1,46 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 RestfulAPIUtil from 'services/restAPIUtil';
+import Configuration from 'config/Configuration.js';
+
+function baseUrl() {
+ const restPrefix = Configuration.get('restPrefix');
+
+ return `${restPrefix}/workflows/`;
+}
+
+const Api = {
+ getVersions: id => {
+ return RestfulAPIUtil.get(`${baseUrl()}${id}/versions`);
+ },
+ getWorkflow: id => {
+ return RestfulAPIUtil.get(`${baseUrl()}${id}`);
+ },
+ updateWorkflow: ({ id, description, name }) => {
+ return RestfulAPIUtil.put(`${baseUrl()}${id}`, {
+ name,
+ description
+ });
+ },
+ archiveRestoreWorkflow: ({ id, type }) => {
+ return RestfulAPIUtil.post(`${baseUrl()}${id}/archiving`, {
+ status: type
+ });
+ }
+};
+
+export default Api;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewConstansts.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewConstansts.js
new file mode 100644
index 00000000..17bd2fd2
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewConstansts.js
@@ -0,0 +1,55 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createAction } from 'redux-actions';
+import { WORKFLOW_STATUS } from 'features/workflow/workflowConstants';
+
+export const FETCH_VERSION_LIST = 'overview/FETCH_VERSION_LIST';
+export const GET_OVERVIEW = 'overview/GET_OVERVIEW';
+export const UPDATE_WORKFLOW = 'overview/UPDATE_WORKFLOW';
+export const ARCHIVE_WORKFLOW = 'overview/ARCHIVE_WORKFLOW';
+export const RESTORE_WORKFLOW = 'overview/RESTORE_WORKFLOW';
+
+export const versionListFetchAction = payload => ({
+ type: FETCH_VERSION_LIST,
+ payload
+});
+
+export const getVersionsAction = workflowId => ({
+ type: GET_OVERVIEW,
+ payload: workflowId
+});
+
+export const updateWorkflowAction = createAction(
+ UPDATE_WORKFLOW,
+ payload => payload
+);
+
+export const archiveWorkflowAction = payload => ({
+ type: ARCHIVE_WORKFLOW,
+ payload: {
+ ...payload,
+ type: WORKFLOW_STATUS.ARCHIVE
+ }
+});
+
+export const restoreWorkflowAction = payload => ({
+ type: RESTORE_WORKFLOW,
+ payload: {
+ ...payload,
+ type: WORKFLOW_STATUS.ACTIVE
+ }
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewReducer.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewReducer.js
new file mode 100644
index 00000000..bab482a4
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewReducer.js
@@ -0,0 +1,26 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { FETCH_VERSION_LIST } from 'features/workflow/overview/overviewConstansts';
+
+export default function overviewReducer(state = [], action) {
+ switch (action.type) {
+ case FETCH_VERSION_LIST:
+ return [...action.payload.items];
+ default:
+ return state;
+ }
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewSagas.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewSagas.js
new file mode 100644
index 00000000..792d4c0c
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewSagas.js
@@ -0,0 +1,104 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { call, takeEvery, put, select } from 'redux-saga/effects';
+
+import { genericNetworkErrorAction } from 'wfapp/appConstants';
+import overviewApi from 'features/workflow/overview/overviewApi';
+import {
+ versionListFetchAction,
+ getVersionsAction,
+ GET_OVERVIEW,
+ UPDATE_WORKFLOW,
+ ARCHIVE_WORKFLOW,
+ RESTORE_WORKFLOW
+} from 'features/workflow/overview/overviewConstansts';
+import { setWorkflowAction } from 'features/workflow/workflowConstants';
+import { notificationActions } from 'shared/notifications/notificationsActions';
+import { fetchWorkflow } from 'features/catalog/catalogActions';
+import { I18n } from 'react-redux-i18n';
+
+export function* getOverview({ payload }) {
+ try {
+ const versions = yield call(overviewApi.getVersions, payload);
+ yield put(versionListFetchAction(versions));
+ const workflow = yield call(overviewApi.getWorkflow, payload);
+ yield put(setWorkflowAction(workflow));
+ } catch (error) {
+ yield put(genericNetworkErrorAction(error));
+ }
+}
+
+export function* updateWorkflow(action) {
+ try {
+ yield call(overviewApi.updateWorkflow, action.payload);
+ yield put(
+ notificationActions.showSuccess({
+ title: I18n.t('workflow.overview.updateTitle'),
+ message: I18n.t('workflow.overview.updateNotification')
+ })
+ );
+ } catch (e) {
+ yield put(genericNetworkErrorAction(e));
+ }
+}
+
+export function* archiveRestoreWorkflow(action) {
+ try {
+ const { ...data } = action.payload;
+ yield call(overviewApi.archiveRestoreWorkflow, data);
+ const {
+ catalog: { sort, status },
+ searchNameFilter = ''
+ } = yield select();
+
+ yield put(
+ fetchWorkflow({
+ sort,
+ searchNameFilter,
+ status: status
+ })
+ );
+ } catch (e) {
+ yield put(genericNetworkErrorAction(e));
+ }
+}
+
+export function* restoreWorkflow(action) {
+ const { id } = action.payload;
+ yield archiveRestoreWorkflow(action);
+ yield put(getVersionsAction(id));
+}
+
+export function* archiveWorkflow(action) {
+ const { history } = action.payload;
+ yield archiveRestoreWorkflow(action);
+ yield put(
+ notificationActions.showSuccess({
+ title: I18n.t('workflow.overview.archiveTitle'),
+ message: I18n.t('workflow.overview.archiveNotification', {
+ name: action.payload.name
+ })
+ })
+ );
+ history.push('/workflows/');
+}
+
+export function* watchOverview() {
+ yield takeEvery(GET_OVERVIEW, getOverview);
+ yield takeEvery(UPDATE_WORKFLOW, updateWorkflow);
+ yield takeEvery(ARCHIVE_WORKFLOW, archiveWorkflow);
+ yield takeEvery(RESTORE_WORKFLOW, restoreWorkflow);
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewSelectors.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewSelectors.js
new file mode 100644
index 00000000..d6bb1915
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/overviewSelectors.js
@@ -0,0 +1,62 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createSelector } from 'reselect';
+import { getWorkflowParams } from 'features/workflow/create/createWorkflowSelector';
+import { versionState } from 'features/version/versionConstants';
+
+export const getVersions = state => state && state.workflow.versions;
+export const getSortedVersions = createSelector(
+ getVersions,
+ versions => versions && versions.sort((a, b) => a.name - b.name)
+);
+export const getSelectedVersionId = state =>
+ state && state.currentVersion.general.id;
+export const getWorkflowCreationTime = state =>
+ state && state.workflow.data.creationTime;
+
+export const getWorkflowModificationTime = state =>
+ state && state.workflow.data.modificationTime;
+
+export const getWorkflowId = state => state && state.workflow.data.id;
+export const getAllIsVersionsCertifies = createSelector(
+ getSortedVersions,
+ versions =>
+ versions &&
+ versions.filter(
+ version => version.state.toLowerCase() === versionState.CERTIFIED
+ ).length === versions.length
+);
+
+export const getWorkflowData = createSelector(
+ getWorkflowParams,
+ getWorkflowCreationTime,
+ getWorkflowModificationTime,
+ getWorkflowId,
+ (params, creationTime, modificationTime, id) => {
+ return {
+ ...params,
+ created: creationTime,
+ modified: modificationTime,
+ id
+ };
+ }
+);
+
+export const getLatestBaseId = createSelector(
+ getSortedVersions,
+ versions => versions.length && versions[versions.length - 1].id
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/VersionList/VersionListItem.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/VersionList/VersionListItem.jsx
new file mode 100644
index 00000000..aa852922
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/VersionList/VersionListItem.jsx
@@ -0,0 +1,74 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import { Localize, Translate } from 'react-redux-i18n';
+
+const VersionListItem = ({ data, onSelectVersion, isHeader, isSelected }) => {
+ let { modificationTime, name, state, description } = data;
+ const modificationText = !isHeader ? (
+ <Localize value={modificationTime} dateFormat="date.long" />
+ ) : (
+ <Translate value="workflow.overview.lastEdited" />
+ );
+
+ function onVersionClick() {
+ onSelectVersion(data);
+ }
+
+ return (
+ <div
+ data-test-id="version-item-row"
+ className={`version-item-row ${
+ isHeader ? 'header-row' : 'clickable'
+ } ${isSelected ? 'selected' : ''}`}
+ onClick={onVersionClick}>
+ <div
+ className={`version-item-field ${
+ isHeader ? 'header-field item-version' : 'item-version'
+ }`}>
+ {name}
+ </div>
+ <div
+ className={`version-item-field ${
+ isHeader ? 'header-field item-status' : 'item-status'
+ }`}>
+ {state}
+ </div>
+ <div
+ className={`version-item-field ${
+ isHeader ? 'header-field' : 'item-last-edited'
+ }`}>
+ {modificationText}
+ </div>
+ <div
+ className={`version-item-field ${
+ isHeader ? 'header-field' : 'item-description'
+ }`}>
+ <div className="description-text">{description}</div>
+ </div>
+ </div>
+ );
+};
+
+VersionListItem.propTypes = {
+ data: PropTypes.object,
+ onSelectVersion: PropTypes.func,
+ isHeader: PropTypes.bool,
+ isSelected: PropTypes.bool
+};
+
+export default VersionListItem;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/VersionList/index.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/VersionList/index.js
new file mode 100644
index 00000000..801c6693
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/VersionList/index.js
@@ -0,0 +1,51 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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 PropTypes from 'prop-types';
+
+import VersionListItem from 'features/workflow/overview/views/VersionList/VersionListItem';
+
+const VersionList = ({ versions, onSelectVersion, selectedVersion }) => (
+ <div className="version-list">
+ <VersionListItem
+ data={{
+ name: 'Version',
+ state: 'Status',
+ description: 'Description'
+ }}
+ isHeader
+ />
+ <div className="version-list-items">
+ {versions.map(version => (
+ <VersionListItem
+ key={version.id}
+ data={version}
+ onSelectVersion={onSelectVersion}
+ isSelected={selectedVersion === version.id}
+ />
+ ))}
+ </div>
+ </div>
+);
+
+VersionList.propTypes = {
+ versions: PropTypes.array,
+ onSelectVersion: PropTypes.func,
+ selectedVersion: PropTypes.string
+};
+
+export default VersionList;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/VersionTree.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/VersionTree.jsx
new file mode 100644
index 00000000..cd3be771
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/VersionTree.jsx
@@ -0,0 +1,43 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 Tree from 'shared/tree/Tree';
+import PropTypes from 'prop-types';
+
+const VersionTree = ({ nodeVersions, selectedVersion }) => {
+ return (
+ <div className="version-tree-wrapper">
+ <div className="version-tree-title-container">
+ <div className="version-tree-title">Version Tree</div>
+ </div>
+ <Tree
+ name={'versions-tree'}
+ width={200}
+ allowScaleWidth={false}
+ nodes={nodeVersions}
+ selectedNodeId={selectedVersion}
+ />
+ </div>
+ );
+};
+
+VersionTree.propTypes = {
+ nodeVersions: PropTypes.array,
+ selectedVersion: PropTypes.string
+};
+
+export default VersionTree;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/WorkflowDetails.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/WorkflowDetails.jsx
new file mode 100644
index 00000000..38f6af06
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/WorkflowDetails.jsx
@@ -0,0 +1,75 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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, { Component } from 'react';
+import { I18n } from 'react-redux-i18n';
+import { Input, Button } from 'onap-ui-react';
+import PropTypes from 'prop-types';
+
+import Description from 'shared/components/Description';
+
+class WorkflowDetails extends Component {
+ handleSubmitForm = e => {
+ e.preventDefault();
+ const { description } = this.props;
+ this.props.updateWorkflow(description);
+ };
+
+ render() {
+ const {
+ name,
+ description,
+ workflowDetailsChanged,
+ isArchive
+ } = this.props;
+ return (
+ <div className="workflow-details">
+ <form onSubmit={this.handleSubmitForm}>
+ <Input
+ name="workflowName"
+ value={name || ''}
+ type="text"
+ label={I18n.t('workflow.general.name')}
+ isRequired
+ disabled
+ />
+ <Description
+ disabled={isArchive}
+ description={description}
+ onDataChange={workflowDetailsChanged}
+ />
+ <div className="save-description">
+ <Button disabled={isArchive} btnType="primary">
+ {I18n.t('buttons.saveBtn')}
+ </Button>
+ </div>
+ </form>
+ </div>
+ );
+ }
+}
+
+WorkflowDetails.propTypes = {
+ name: PropTypes.string,
+ created: PropTypes.string,
+ modified: PropTypes.string,
+ description: PropTypes.string,
+ defaultDescription: PropTypes.string,
+ updateWorkflow: PropTypes.func,
+ workflowDetailsChanged: PropTypes.func,
+ isArchive: PropTypes.bool
+};
+
+export default WorkflowDetails;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/WorkflowHeader.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/WorkflowHeader.jsx
new file mode 100644
index 00000000..46a1d94a
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/WorkflowHeader.jsx
@@ -0,0 +1,106 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import { I18n } from 'react-redux-i18n';
+import { SVGIcon, Button } from 'onap-ui-react';
+import ArchiveLabel from 'shared/archiveLabel/ArchiveLabel';
+
+const Buttons = ({ history, archiveWorkflow, restoreWorkflow, isArchive }) => (
+ <div className="header-buttons">
+ <SVGIcon
+ onClick={() => history.push('/workflows/')}
+ label={I18n.t('workflow.overview.backBtnLabel')}
+ className="go-catalog-btn"
+ labelPosition="right"
+ name="back"
+ />
+ <ArchiveBtn
+ isArchive={isArchive}
+ restoreWorkflow={restoreWorkflow}
+ archiveWorkflow={archiveWorkflow}
+ />
+ </div>
+);
+
+Buttons.propTypes = {
+ history: PropTypes.object,
+ archiveWorkflow: PropTypes.func,
+ isArchive: PropTypes.bool,
+ restoreWorkflow: PropTypes.func
+};
+
+const Title = ({ name, isArchive }) => (
+ <div className="title">
+ {name} - {I18n.t('workflow.overview.title')}
+ {isArchive && <ArchiveLabel />}
+ </div>
+);
+Title.propTypes = {
+ name: PropTypes.string,
+ isArchive: PropTypes.bool
+};
+
+const ArchiveBtn = ({ isArchive, archiveWorkflow, restoreWorkflow }) => {
+ return !isArchive ? (
+ <SVGIcon
+ onClick={archiveWorkflow}
+ title="Archive workflow"
+ className="archive-btn"
+ name="archiveBox"
+ />
+ ) : (
+ <Button className="restore-btn" onClick={restoreWorkflow}>
+ RESTORE
+ </Button>
+ );
+};
+ArchiveBtn.propTypes = {
+ status: PropTypes.string,
+ archiveWorkflow: PropTypes.func,
+ restoreWorkflow: PropTypes.func,
+ isArchive: PropTypes.bool
+};
+
+const WorkflowHeader = ({
+ name,
+ history,
+ archiveWorkflow,
+ restoreWorkflow,
+ isArchive
+}) => {
+ return (
+ <div className="overview-header">
+ <Title isArchive={isArchive} name={name} />
+ <Buttons
+ restoreWorkflow={restoreWorkflow}
+ archiveWorkflow={archiveWorkflow}
+ isArchive={isArchive}
+ history={history}
+ />
+ </div>
+ );
+};
+
+WorkflowHeader.propTypes = {
+ name: PropTypes.string,
+ history: PropTypes.object,
+ archiveWorkflow: PropTypes.func,
+ restoreWorkflow: PropTypes.func,
+ isArchive: PropTypes.bool
+};
+
+export default WorkflowHeader;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/WorkflowVersions.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/WorkflowVersions.jsx
new file mode 100644
index 00000000..18e33bf1
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/overview/views/WorkflowVersions.jsx
@@ -0,0 +1,67 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+
+import VersionList from 'features/workflow/overview/views/VersionList';
+import VersionTree from 'features/workflow/overview/views//VersionTree';
+import NewVersionContainer from 'features/version/create/views/NewVersionContainer';
+
+const WorkflowVersions = ({
+ nodeVersions,
+ versions,
+ onCreateVersion,
+ onSelectVersion,
+ selectedVersion,
+ isVersionsCertifies,
+ isArchive
+}) => {
+ return (
+ <div className="workflow-versions">
+ <div className="versions-page-view">
+ <NewVersionContainer
+ onCreateVersion={onCreateVersion}
+ isVersionsCertifies={isVersionsCertifies}
+ isArchive={isArchive}
+ />
+ <div className="versions-page-list-and-tree">
+ <VersionTree
+ nodeVersions={nodeVersions}
+ selectedVersion={selectedVersion}
+ />
+ <VersionList
+ versions={versions}
+ selectedVersion={selectedVersion}
+ onSelectVersion={onSelectVersion}
+ />
+ </div>
+ </div>
+ </div>
+ );
+};
+
+WorkflowVersions.propTypes = {
+ nodeVersions: PropTypes.array,
+ versions: PropTypes.array,
+ onCreateVersion: PropTypes.func,
+ onSelectVersion: PropTypes.func,
+ selectedVersion: PropTypes.string,
+ isVersionsCertifies: PropTypes.bool,
+ isArchive: PropTypes.bool
+};
+
+export default WorkflowVersions;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/workflowConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/workflowConstants.js
new file mode 100644
index 00000000..a9f4aa51
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/workflowConstants.js
@@ -0,0 +1,27 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createAction } from 'redux-actions';
+
+export const WORKFLOW_STATUS = {
+ ACTIVE: 'ACTIVE',
+ ARCHIVE: 'ARCHIVED'
+};
+
+export const CLEAR_WORKFLOW_DATA = 'workflow/CLEAR_WORKFLOW_DATA';
+export const SET_WORKFLOW = 'workflow/SET_WORKFLOW';
+
+export const setWorkflowAction = createAction(SET_WORKFLOW, payload => payload);
+export const clearWorkflowAction = { type: CLEAR_WORKFLOW_DATA };
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/workflowReducer.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/workflowReducer.js
new file mode 100644
index 00000000..70d6e5be
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/workflowReducer.js
@@ -0,0 +1,60 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 {
+ WORKFLOW_INPUT_CHANGE,
+ VALIDATION_ERROR,
+ CLEAR_VALIDATION_ERROR
+} from 'features/workflow/create/createWorkflowConstants';
+import {
+ SET_WORKFLOW,
+ CLEAR_WORKFLOW_DATA
+} from 'features/workflow/workflowConstants';
+
+function workflowReducer(state = {}, action) {
+ switch (action.type) {
+ case WORKFLOW_INPUT_CHANGE:
+ return {
+ ...state,
+ ...action.payload
+ };
+ case CLEAR_WORKFLOW_DATA:
+ return {};
+ case SET_WORKFLOW:
+ return {
+ ...action.payload
+ };
+ //TODO change it when BE is done
+ // return {
+ // ...action.payload,
+ // status: 'archive'
+ // };
+ case VALIDATION_ERROR:
+ return {
+ ...state,
+ error: action.payload
+ };
+ case CLEAR_VALIDATION_ERROR:
+ return {
+ ...state,
+ error: ''
+ };
+ default:
+ return state;
+ }
+}
+
+export default workflowReducer;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/workflowSelectors.js b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/workflowSelectors.js
new file mode 100644
index 00000000..53487012
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/features/workflow/workflowSelectors.js
@@ -0,0 +1,34 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { WORKFLOW_STATUS } from './workflowConstants';
+export const getWorkflowName = state =>
+ state && state.workflow.data.name && state.workflow.data.name;
+export const getTrimWorkflowName = state =>
+ state && state.workflow.data.name && state.workflow.data.name.trim();
+export const getWorkflowId = state => state && state.workflow.data.id;
+export const getWorkflowDescription = state =>
+ state && state.workflow.data.description;
+export const getWorkflowStatus = state =>
+ state &&
+ state.workflow &&
+ state.workflow.data &&
+ state.workflow.data.status;
+export const isWorkflowArchive = state =>
+ state &&
+ state.workflow &&
+ state.workflow.data &&
+ state.workflow.data.archiving &&
+ state.workflow.data.archiving === WORKFLOW_STATUS.ARCHIVE;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/i18n/I18n.js b/sdc-workflow-designer-ui/src/main/frontend/src/i18n/I18n.js
new file mode 100644
index 00000000..fdf28ffd
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/i18n/I18n.js
@@ -0,0 +1,31 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 languagesData from 'wfapp/i18n/languages.json';
+import merge from 'lodash.merge';
+import setPath from 'lodash.set';
+
+let languagesObj = {};
+let language = null;
+let key = null;
+for (language in languagesData) {
+ let langData = {};
+ for (key in languagesData[language]) {
+ setPath(langData, key, languagesData[language][key]);
+ }
+ languagesObj[language] = langData;
+}
+export default merge({}, languagesObj);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/i18n/i18next-scanner.config.js b/sdc-workflow-designer-ui/src/main/frontend/src/i18n/i18next-scanner.config.js
new file mode 100644
index 00000000..a626211a
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/i18n/i18next-scanner.config.js
@@ -0,0 +1,44 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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.
+*/
+module.exports = {
+ options: {
+ debug: true,
+ func: {
+ list: ['i18next.t', 'i18n.t'],
+ extensions: ['.js', '.jsx']
+ },
+ trans: {
+ extensions: ['.js', '.jsx'],
+ fallbackKey: (ns, value) => {
+ return value;
+ }
+ },
+ lngs: ['en', 'de'],
+ ns: ['locale', 'resource'],
+ defaultNs: 'resource',
+ defaultValue: '__STRING_NOT_TRANSLATED__',
+ resource: {
+ loadPath: 'i18n/{{lng}}/{{ns}}.json',
+ savePath: 'i18n/{{lng}}/{{ns}}.json'
+ },
+ nsSeparator: false, // namespace separator
+ keySeparator: false, // key separator
+ interpolation: {
+ prefix: '{{',
+ suffix: '}}'
+ }
+ }
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/i18n/languages.json b/sdc-workflow-designer-ui/src/main/frontend/src/i18n/languages.json
new file mode 100644
index 00000000..9485681a
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/i18n/languages.json
@@ -0,0 +1,97 @@
+{
+ "en": {
+ "catalog": {
+ "elementFound": "%{count} Elements found",
+ "elementFound_0": "No Elements found",
+ "elementFound_1": "%{count} Element found",
+ "orderBy": "Order By",
+ "alphabeticalOrder": "Alphabetical order",
+ "addWorkflow": "Add Workflow"
+ },
+ "date": {
+ "short": "D/MM/YYYY hh:mm A",
+ "long": "MMMM Do, YYYY, hh:mm A"
+ },
+ "buttons": {
+ "submitBtn": "Submit",
+ "saveBtn": "Save",
+ "createBtn": "Create",
+ "cancelBtn": "Cancel",
+ "certifyBtn": "Certify",
+ "completeBtn": "Complete",
+ "undoBtn": "Undo",
+ "closeBtn": "Close",
+ "okBtn": "Ok",
+ "backToCatalog": "Catalog"
+ },
+ "form": {
+ "name": "Name",
+ "description": "Description"
+ },
+ "workflow": {
+ "general": {
+ "headerTitle": "General",
+ "name": "Name",
+ "description": "Description",
+ "created": "Created",
+ "modified": "Modified"
+ },
+ "version": "Version",
+ "sideBar": {
+ "general": "General",
+ "inputOutput": "Input / Output",
+ "composition": "Composition"
+ },
+ "overview": {
+ "viewOverview": "Workflow Overview",
+ "versionList": "Versions",
+ "newVersion": "Create New Version",
+ "title": "Overview",
+ "lastEdited": "Last Edited On",
+ "backBtnLabel": "WORKFLOW CATALOG",
+ "archived": "Archived",
+ "archive": "ARCHIVE",
+ "archiveTitle": "Archive",
+ "updateTitle": "Update Workflow",
+ "updateNotification": "Successfully updated",
+ "archiveNotification": "\"%{name}\" successfully archived",
+ "confirmArchive": "Are you sure you want to archive \"%{name}\"?"
+ },
+ "inputOutput": {
+ "name": "Name",
+ "type": "Type",
+ "mandatory": "Mandatory",
+ "noData": "No Parameters to Show",
+ "addInput": "Add Input",
+ "addOutput": "Add Output",
+ "inputs": "Inputs",
+ "outputs": "Outputs",
+ "DELETE": "DELETE",
+ "CANCEL": "CANCEL",
+ "confirmDelete": "Are you sure you want to delete \"%{name}\"?"
+ },
+ "errorMessages": {
+ "alreadyExists": "Already exists",
+ "invalidCharacters": "Alphanumeric and underscore only",
+ "emptyName": "Field is required",
+ "nameFieldLength": "Name must be at least %{minValue} characters and no more than %{maxValue} characters"
+ },
+ "composition": {
+ "bpmnError" : "BPMN.IO Error",
+ "exportErrorMsg": "Could not export diagram",
+ "saveErrorMsg": "Could not save diagram",
+ "importErrorMsg": "Could not import diagram",
+ "certifyArtifact": "Could not certify empty artifact"
+ },
+ "confirmationMessages": {
+ "certifyTitle": "Certify Version",
+ "certifyMessage": "Successfully certified",
+ "updateTitle": "Update Workflow Version",
+ "updateMessage": "Successfully updated"
+ }
+ },
+ "version": {
+ "category": "Version Category"
+ }
+ }
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/i18n/translationSaga.js b/sdc-workflow-designer-ui/src/main/frontend/src/i18n/translationSaga.js
new file mode 100644
index 00000000..9c972ea7
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/i18n/translationSaga.js
@@ -0,0 +1,24 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { put } from 'redux-saga/effects';
+import { loadTranslations, setLocale } from 'react-redux-i18n';
+
+import translationsObject from 'i18n/I18n';
+import { LANG } from 'wfapp/appConstants';
+export function* initTranslationSaga() {
+ yield put(loadTranslations(translationsObject));
+ yield put(setLocale(LANG));
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/index.js b/sdc-workflow-designer-ui/src/main/frontend/src/index.js
new file mode 100644
index 00000000..46a87603
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/index.js
@@ -0,0 +1,40 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 ReactDOM from 'react-dom';
+import { Provider } from 'react-redux';
+import { BrowserRouter } from 'react-router-dom';
+
+import Notifications from 'shared/notifications/Notifications';
+import Loader from 'shared/loader/Loader';
+import App from 'wfapp/App';
+import ModalWrapper from 'shared/modal/ModalWrapper';
+import store from './store';
+
+ReactDOM.render(
+ <Provider store={store}>
+ <BrowserRouter>
+ <React.Fragment>
+ <App />
+ <Notifications />
+ <ModalWrapper />
+ <Loader />
+ </React.Fragment>
+ </BrowserRouter>
+ </Provider>,
+ document.getElementById('root')
+);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextActions.js b/sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextActions.js
new file mode 100644
index 00000000..553b8911
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextActions.js
@@ -0,0 +1,21 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { SET_CONTEXT } from './pluginContextConstants';
+
+export const setPluginContext = payload => ({
+ type: SET_CONTEXT,
+ payload
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextConstants.js
new file mode 100644
index 00000000..0f8f409f
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextConstants.js
@@ -0,0 +1,22 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const SET_CONTEXT = 'pluginContext/SET_CONTEXT';
+
+export const notificationType = {
+ READY: 'READY',
+ CLOSE: 'CLOSE'
+};
+export const CATALOG_PATH = 'workspace.interface_operation';
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextReducer.js b/sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextReducer.js
new file mode 100644
index 00000000..6e61a3bd
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextReducer.js
@@ -0,0 +1,25 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { SET_CONTEXT } from './pluginContextConstants';
+export default (state = {}, action) => {
+ switch (action.type) {
+ case SET_CONTEXT: {
+ return action.payload;
+ }
+ default:
+ return state;
+ }
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextSelector.js b/sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextSelector.js
new file mode 100644
index 00000000..65599c55
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextSelector.js
@@ -0,0 +1,16 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const pluginContextSelector = state => state && state.pluginContext;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/rootReducers.js b/sdc-workflow-designer-ui/src/main/frontend/src/rootReducers.js
new file mode 100644
index 00000000..33fd8a82
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/rootReducers.js
@@ -0,0 +1,51 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { combineReducers } from 'redux';
+import { i18nReducer } from 'react-redux-i18n';
+
+import catalog from 'features/catalog/catalogReducer';
+import inputOutput from 'features/version/inputOutput/inputOutputReducer';
+import notificationsReducer from 'shared/notifications/notificationsReducer';
+import versionReducer from 'features/version/versionReducer';
+import loader from 'shared/loader/LoaderReducer';
+import modal from 'shared/modal/modalWrapperReducer';
+import overviewReducer from 'features/workflow/overview/overviewReducer';
+import workflowReducer from 'features/workflow/workflowReducer';
+import compositionReducer from 'features/version/composition/compositionReducer';
+import activitiesReducer from 'features/activities/activitiesReducer';
+import operationModeReducer from 'features/version/versionModeReducer';
+import pluginContextReducer from './pluginContext/pluginContextReducer';
+
+export default combineReducers({
+ i18n: i18nReducer,
+ catalog,
+ notifications: notificationsReducer,
+ currentVersion: combineReducers({
+ general: versionReducer,
+ inputOutput,
+ composition: compositionReducer,
+ operationMode: operationModeReducer
+ }),
+ workflow: combineReducers({
+ data: workflowReducer,
+ versions: overviewReducer
+ }),
+ pluginContext: pluginContextReducer,
+ activities: activitiesReducer,
+ loader,
+ modal
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/rootSaga.js b/sdc-workflow-designer-ui/src/main/frontend/src/rootSaga.js
new file mode 100644
index 00000000..0ea0782a
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/rootSaga.js
@@ -0,0 +1,38 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { all, fork } from 'redux-saga/effects';
+
+import { initTranslationSaga } from 'i18n/translationSaga';
+import catalogSaga from 'features/catalog/catalogSagas';
+import { watchWorkflow } from 'features/workflow/create/createWorkflowSaga';
+import { watchNotifications } from 'shared/notifications/notificationsSagas';
+import versionSaga from 'features/version/versionSaga';
+import activitiesSaga from 'features/activities/activitiesSaga';
+
+import { watchOverview } from 'features/workflow/overview/overviewSagas';
+
+export default function* rootSaga() {
+ yield all([
+ fork(initTranslationSaga),
+ fork(catalogSaga),
+ fork(watchWorkflow),
+ fork(watchNotifications),
+ fork(versionSaga),
+ fork(watchOverview),
+ fork(activitiesSaga)
+ ]);
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/routes.js b/sdc-workflow-designer-ui/src/main/frontend/src/routes.js
new file mode 100644
index 00000000..4be77016
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/routes.js
@@ -0,0 +1,59 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 Catalog from 'features/catalog/Catalog';
+import Version from 'features/version/Version';
+import GeneralView from 'features/version/general/General';
+import OverviewView from 'features/workflow/overview/Overview';
+import InputOutput from 'features/version/inputOutput/InputOutput';
+import Composition from 'features/version/composition/Composition';
+
+export const routes = [
+ {
+ path: '/workflows/workflow/:workflowId/version/:versionId',
+ component: Version,
+ routes: [
+ {
+ path: '/',
+ exact: true,
+ component: GeneralView,
+ i18nName: 'workflow.sideBar.general',
+ id: 'GENERAL'
+ },
+ {
+ path: '/input-output',
+ component: InputOutput,
+ i18nName: 'workflow.sideBar.inputOutput',
+ id: 'INPUT_OUTPUT'
+ },
+ {
+ path: '/composition',
+ component: Composition,
+ i18nName: 'workflow.sideBar.composition',
+ id: 'COMPOSITION'
+ }
+ ]
+ },
+ {
+ path: '/workflows',
+ exact: true,
+ component: Catalog
+ },
+ {
+ path: '/workflows/workflow/:workflowId/overview',
+ component: OverviewView
+ }
+];
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/services/restAPIUtil.js b/sdc-workflow-designer-ui/src/main/frontend/src/services/restAPIUtil.js
new file mode 100644
index 00000000..08262fed
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/services/restAPIUtil.js
@@ -0,0 +1,150 @@
+/*
+ * Copyright © 2018 European Support Limited
+ *
+ * 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 uuid from 'uuid-js';
+import md5 from 'md5';
+import axios from 'axios';
+
+import store from 'wfapp/store';
+import { USER_ID } from 'wfapp/appConstants';
+
+import {
+ sendRequestAction,
+ recieveResponseAction
+} from 'shared/loader/LoaderConstants';
+
+import Configuration from 'config/Configuration.js';
+import errorResponseHandler from 'shared/errorResponseHandler/errorResponseHandler';
+
+//methods
+const GET = 'GET';
+const POST = 'POST';
+const PUT = 'PUT';
+const DELETE = 'DELETE';
+const PATCH = 'PATCH';
+const BINARY = 'binary';
+
+const AUTHORIZATION_HEADER = 'X-AUTH-TOKEN';
+const STORAGE_AUTH_KEY = 'sdc-auth-token';
+const REQUEST_ID_HEADER = 'X-ECOMP-RequestID';
+const CONTENT_MD5_HEADER = 'Content-MD5';
+
+function applySecurity(options, data) {
+ let headers = options.headers || (options.headers = {});
+ if (options.isAnonymous) {
+ return;
+ }
+
+ let authToken = localStorage.getItem(STORAGE_AUTH_KEY);
+ if (authToken) {
+ headers[AUTHORIZATION_HEADER] = authToken;
+ }
+
+ let catalogApiHeaders = Configuration.get('CatalogApiHeaders'),
+ catalogUidHeader = catalogApiHeaders && catalogApiHeaders.userId;
+ if (catalogUidHeader) {
+ headers[catalogUidHeader.name] = catalogUidHeader.value;
+ }
+
+ headers[REQUEST_ID_HEADER] = uuid.create().toString();
+ if (options.md5) {
+ let headers = options.headers;
+ headers[CONTENT_MD5_HEADER] = window.btoa(
+ md5(JSON.stringify(data)).toLowerCase()
+ );
+ }
+
+ if (localStorage.getItem(USER_ID)) {
+ headers[USER_ID] = localStorage.getItem(USER_ID);
+ }
+}
+
+function handleSuccess(responseHeaders, requestHeaders) {
+ let authToken = responseHeaders[AUTHORIZATION_HEADER];
+ let prevToken = requestHeaders && requestHeaders[AUTHORIZATION_HEADER];
+ if (authToken && authToken !== prevToken) {
+ if (authToken === 'null') {
+ localStorage.removeItem(STORAGE_AUTH_KEY);
+ } else {
+ localStorage.setItem(STORAGE_AUTH_KEY, authToken);
+ }
+ }
+}
+
+class RestAPIUtil {
+ async handleRequest(url, type, options = {}, data = {}) {
+ applySecurity(options, data);
+
+ let config = {
+ method: type,
+ url: url,
+ headers: options.headers,
+ data: data
+ };
+
+ store.dispatch(sendRequestAction(url));
+
+ if (options.dataType === BINARY) {
+ config.responseType = 'arraybuffer';
+ }
+ try {
+ const result = await axios(config);
+ store.dispatch(recieveResponseAction(url));
+ if (options.dataType !== BINARY) {
+ handleSuccess(result.headers, result.config.headers);
+ }
+ return options.dataType === BINARY
+ ? {
+ blob: new Blob([result.data]),
+ headers: result.headers
+ }
+ : result.data;
+ } catch (error) {
+ store.dispatch(recieveResponseAction(url));
+ errorResponseHandler(error.response);
+ return Promise.reject({
+ responseJSON: error.response.data
+ });
+ }
+ }
+
+ fetch(url, options) {
+ return this.handleRequest(url, GET, options);
+ }
+
+ get(url, options) {
+ return this.fetch(url, options);
+ }
+
+ post(url, data, options) {
+ return this.handleRequest(url, POST, options, data);
+ }
+
+ put(url, data, options) {
+ return this.handleRequest(url, PUT, options, data);
+ }
+
+ delete(url, options) {
+ return this.handleRequest(url, DELETE, options);
+ }
+
+ patch(url, options) {
+ return this.handleRequest(url, PATCH, options);
+ }
+}
+
+const instance = new RestAPIUtil();
+
+export default instance;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/archiveLabel/ArchiveLabel.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/archiveLabel/ArchiveLabel.js
new file mode 100644
index 00000000..45b39090
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/archiveLabel/ArchiveLabel.js
@@ -0,0 +1,8 @@
+import React from 'react';
+import { I18n } from 'react-redux-i18n';
+
+const ArchiveLabel = () => (
+ <div className="archive-label">{I18n.t('workflow.overview.archived')}</div>
+);
+
+export default ArchiveLabel;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/components/Description/index.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/components/Description/index.js
new file mode 100644
index 00000000..7ff4d328
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/components/Description/index.js
@@ -0,0 +1,50 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import { I18n } from 'react-redux-i18n';
+
+const Description = ({ description, onDataChange, dataTestId, disabled }) => (
+ <div className="description-part">
+ <div className="sdc-textarea">
+ <div className="sdc-textarea__label">
+ {I18n.t('workflow.general.description')}
+ </div>
+ <textarea
+ value={description}
+ data-test-id={dataTestId || 'description'}
+ onChange={event => {
+ onDataChange({ description: event.target.value });
+ }}
+ className="custom-textarea field-section sdc-textarea__textarea"
+ disabled={disabled}
+ />
+ </div>
+ </div>
+);
+
+Description.propTypes = {
+ description: PropTypes.string,
+ onDataChange: PropTypes.func,
+ dataTestId: PropTypes.string,
+ disabled: PropTypes.bool
+};
+
+Description.defaultProps = {
+ disabled: false
+};
+
+export default Description;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/components/Select/index.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/components/Select/index.js
new file mode 100644
index 00000000..df30fad9
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/components/Select/index.js
@@ -0,0 +1,54 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import isEmpty from 'lodash.isempty';
+
+const Select = props => {
+ const { dataObj, selectedItemValue, disabled, label } = props;
+ return (
+ <div className="select-wrapper version-selector sdc-input">
+ {label && <label>{label}</label>}
+ <select
+ className="inputinput-selector"
+ key={'selector'}
+ value={selectedItemValue}
+ disabled={disabled}
+ data-test-id="vc-select-box">
+ {!isEmpty(dataObj) &&
+ dataObj.map(item => {
+ return (
+ <option
+ key={'select-item' + item.id}
+ value={item.id}
+ data-test-id="vc-option">
+ {item.displayed || item.name}
+ </option>
+ );
+ })}
+ </select>
+ </div>
+ );
+};
+
+Select.propTypes = {
+ dataObj: PropTypes.arrayOf(Object),
+ selectedItemValue: PropTypes.string,
+ disabled: PropTypes.bool,
+ label: PropTypes.string
+};
+
+export default Select;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/components/VersionInfo/index.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/components/VersionInfo/index.js
new file mode 100644
index 00000000..2c6bba24
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/components/VersionInfo/index.js
@@ -0,0 +1,43 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+export const LabeledValue = ({ title, value }) => (
+ <React.Fragment>
+ <div className="label">{title}</div>
+ <div className="value selectable">{value}</div>
+ </React.Fragment>
+);
+
+LabeledValue.propTypes = {
+ title: PropTypes.string,
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.object])
+};
+
+export const VersionInfo = ({ children }) => (
+ <div className="version-info-part">{children}</div>
+);
+
+VersionInfo.defaultProps = {
+ created: '',
+ modified: ''
+};
+
+VersionInfo.propTypes = {
+ created: PropTypes.string,
+ modified: PropTypes.string,
+ children: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/errorResponseHandler/errorResponseHandler.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/errorResponseHandler/errorResponseHandler.js
new file mode 100644
index 00000000..f8c8f327
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/errorResponseHandler/errorResponseHandler.js
@@ -0,0 +1,31 @@
+/*
+ * Copyright © 2018 European Support Limited
+ *
+ * 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 store from 'wfapp/store';
+import { showErrorModalAction } from 'shared/modal/modalWrapperActions';
+
+export default error => {
+ const errorData = {
+ title: error.statusText,
+ body: error.data ? error.data.message : 'GENERIC ERROR'
+ };
+ store.dispatch(
+ showErrorModalAction({
+ ...errorData,
+ withButtons: true,
+ closeButtonText: 'Ok'
+ })
+ );
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/Loader.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/Loader.jsx
new file mode 100644
index 00000000..0b4c9f42
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/Loader.jsx
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2016-2017 European Support Limited
+ *
+ * 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 PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+
+export const mapStateToProps = ({ loader: { isLoading } }) => {
+ return {
+ isLoading
+ };
+};
+
+export class Loader extends React.Component {
+ shouldComponentUpdate(nextProps) {
+ return nextProps.isLoading !== this.props.isLoading;
+ }
+
+ render() {
+ let { isLoading } = this.props;
+ return (
+ <div className="onboarding-loader">
+ {isLoading && (
+ <div className="onboarding-loader-backdrop">
+ <div className="tlv-loader large" />
+ </div>
+ )}
+ </div>
+ );
+ }
+}
+
+Loader.propTypes = {
+ isLoading: PropTypes.bool
+};
+
+export default connect(mapStateToProps)(Loader);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderConstants.js
new file mode 100644
index 00000000..02ce84ca
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderConstants.js
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2016-2017 European Support Limited
+ *
+ * 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 const actionTypes = {
+ SHOW: 'SHOW',
+ HIDE: 'HIDE',
+
+ SEND_REQUEST: 'loader/SEND_REQUEST',
+ RECEIVE_RESPONSE: 'loader/RECEIVE_RESPONSE'
+};
+
+export const initialState = {
+ fetchingRequests: 0,
+ currentlyFetching: [],
+ isLoading: false
+};
+
+export const sendRequestAction = url => ({
+ type: actionTypes.SEND_REQUEST,
+ url
+});
+
+export const recieveResponseAction = url => ({
+ type: actionTypes.RECEIVE_RESPONSE,
+ url
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderReducer.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderReducer.js
new file mode 100644
index 00000000..d883492e
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderReducer.js
@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2016-2017 European Support Limited
+ *
+ * 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 { actionTypes, initialState } from 'shared/loader/LoaderConstants.js';
+
+export default (state = initialState, action) => {
+ let fetchingRequests = state.fetchingRequests;
+ let newArray;
+ switch (action.type) {
+ case actionTypes.SEND_REQUEST:
+ fetchingRequests++;
+ newArray = state.currentlyFetching.slice();
+ newArray.splice(0, 0, action.url);
+ return {
+ fetchingRequests: fetchingRequests,
+ currentlyFetching: newArray,
+ isLoading: true
+ };
+ case actionTypes.RECEIVE_RESPONSE:
+ fetchingRequests--;
+
+ newArray = state.currentlyFetching.filter(item => {
+ return item !== action.url;
+ });
+ return {
+ currentlyFetching: newArray,
+ fetchingRequests: fetchingRequests,
+ isLoading: fetchingRequests !== 0
+ };
+ case actionTypes.SHOW:
+ return { isLoading: true };
+ case actionTypes.HIDE:
+ return { isLoading: false };
+ default:
+ return state;
+ }
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/__tests__/loaderReducer-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/__tests__/loaderReducer-test.js
new file mode 100644
index 00000000..3ae40361
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/loader/__tests__/loaderReducer-test.js
@@ -0,0 +1,56 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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.
+*/
+
+'use strict';
+
+import loaderReducer from 'shared/loader/LoaderReducer';
+import {
+ initialState,
+ sendRequestAction,
+ recieveResponseAction
+} from 'shared/loader/LoaderConstants';
+
+describe('Loader reducer', () => {
+ it('return initial state', () => {
+ expect(loaderReducer(undefined, {})).toEqual(initialState);
+ });
+
+ it('send request action test', () => {
+ const url = 'TEST';
+ const loaderData = {
+ fetchingRequests: 1,
+ currentlyFetching: [url],
+ isLoading: true
+ };
+
+ expect(loaderReducer(initialState, sendRequestAction(url))).toEqual(
+ loaderData
+ );
+ });
+
+ it('recieve response action test', () => {
+ const url = 'TEST';
+ const loaderData = {
+ fetchingRequests: 1,
+ currentlyFetching: [url],
+ isLoading: true
+ };
+
+ expect(loaderReducer(loaderData, recieveResponseAction(url))).toEqual(
+ initialState
+ );
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapper.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapper.js
new file mode 100644
index 00000000..899ff217
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapper.js
@@ -0,0 +1,35 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { connect } from 'react-redux';
+
+import ModalWrapperView from 'shared/modal/ModalWrapperView';
+import { hideModalAction } from 'shared/modal/modalWrapperActions';
+
+const mapStateToProps = state => ({
+ modal: state.modal
+});
+
+const mapDispatchToProps = dispatch => ({
+ hideModal: () => dispatch(hideModalAction())
+});
+
+const ModalWrapper = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ModalWrapperView);
+
+export default ModalWrapper;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapperView.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapperView.jsx
new file mode 100644
index 00000000..c83a0acc
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapperView.jsx
@@ -0,0 +1,99 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+
+import {
+ Modal,
+ ModalHeader,
+ ModalTitle,
+ ModalBody,
+ ModalFooter
+} from 'onap-ui-react';
+
+import modalWrapperComponents from 'shared/modal/modalWrapperComponents';
+
+class ModalWrapperView extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleClose = this.handleClose.bind(this);
+ }
+
+ handleClose() {
+ const { hideModal, onClose } = this.props;
+
+ hideModal();
+
+ if (typeof onClose === 'function') {
+ onClose();
+ }
+ }
+
+ render() {
+ const { modal } = this.props;
+
+ if (!modal || !modal.type) {
+ return null;
+ }
+
+ const {
+ size,
+ type,
+ title,
+ body,
+ withButtons,
+ actionButtonText,
+ actionButtonClick,
+ closeButtonText,
+ customComponentName,
+ customComponentProps
+ } = modal;
+
+ const CustomComponent =
+ customComponentName && modalWrapperComponents[customComponentName];
+
+ return (
+ <Modal show size={size} type={type}>
+ <ModalHeader onClose={this.handleClose} type={type}>
+ {title && <ModalTitle>{title}</ModalTitle>}
+ </ModalHeader>
+ {body && <ModalBody>{body}</ModalBody>}
+ {CustomComponent && (
+ <CustomComponent {...customComponentProps} />
+ )}
+ {withButtons && (
+ <ModalFooter
+ actionButtonText={actionButtonText}
+ actionButtonClick={actionButtonClick}
+ closeButtonText={closeButtonText}
+ onClose={this.handleClose}
+ withButtons
+ />
+ )}
+ </Modal>
+ );
+ }
+}
+
+ModalWrapperView.propTypes = {
+ hideModal: PropTypes.func,
+ onClose: PropTypes.func,
+ modal: PropTypes.object
+};
+
+export default ModalWrapperView;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperActions-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperActions-test.js
new file mode 100644
index 00000000..0be53de4
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperActions-test.js
@@ -0,0 +1,85 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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.
+*/
+
+'use strict';
+
+import {
+ SHOW_MODAL,
+ HIDE_MODAL,
+ showInfoModalAction,
+ showAlertModalAction,
+ showErrorModalAction,
+ showCustomModalAction,
+ hideModalAction
+} from 'shared/modal/modalWrapperActions';
+
+describe('Modal Wrapper Actions', () => {
+ const actions = [
+ { fn: showInfoModalAction, type: 'info', size: 'small' },
+ { fn: showAlertModalAction, type: 'alert', size: 'small' },
+ { fn: showErrorModalAction, type: 'error', size: 'small' },
+ { fn: showCustomModalAction, type: 'custom', size: 'medium' }
+ ];
+
+ it('returns correct action', () => {
+ actions.forEach(action => {
+ const payload = {
+ title: 'Title',
+ body: 'Body',
+ withButtons: true,
+ actionButtonText: 'Action Button Text',
+ actionButtonClick: () => {},
+ closeButtonText: 'Close Button Text'
+ };
+
+ const expected = {
+ type: SHOW_MODAL,
+ payload: {
+ size: action.size,
+ ...payload,
+ type: action.type
+ }
+ };
+
+ expect(action.fn(payload)).toEqual(expected);
+ });
+ });
+
+ it('returns correct size in action', () => {
+ actions.forEach(action => {
+ const size = 'large';
+
+ const payload = {
+ size
+ };
+
+ const expected = {
+ type: SHOW_MODAL,
+ payload: { size, type: action.type }
+ };
+
+ expect(action.fn(payload)).toEqual(expected);
+ });
+ });
+
+ it('returns hide modal action', () => {
+ const expected = {
+ type: HIDE_MODAL
+ };
+
+ expect(hideModalAction()).toEqual(expected);
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperReducer-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperReducer-test.js
new file mode 100644
index 00000000..c33190c5
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperReducer-test.js
@@ -0,0 +1,61 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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.
+*/
+
+'use strict';
+
+import modalWrapperReducer from 'shared/modal/modalWrapperReducer';
+import {
+ showInfoModalAction,
+ hideModalAction
+} from 'shared/modal/modalWrapperActions';
+
+describe('Modal Wrapper Reducer', () => {
+ it('returns the initial state', () => {
+ const state = {
+ type: 'info',
+ title: 'Title'
+ };
+ expect(modalWrapperReducer(state, {})).toEqual(state);
+ });
+
+ it('returns correct state for show modal action', () => {
+ const payload = {
+ size: 'medium',
+ title: 'Title'
+ };
+
+ const action = showInfoModalAction(payload);
+
+ const expected = {
+ ...payload,
+ type: 'info'
+ };
+
+ expect(modalWrapperReducer({}, action)).toEqual(expected);
+ });
+
+ it('returns correct state for hide modal action', () => {
+ const state = {
+ size: 'medium',
+ title: 'Title',
+ type: 'info'
+ };
+
+ const action = hideModalAction();
+
+ expect(modalWrapperReducer(state, action)).toEqual({});
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperActions.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperActions.js
new file mode 100644
index 00000000..fe0ca0a5
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperActions.js
@@ -0,0 +1,46 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createAction } from 'redux-actions';
+
+export const SHOW_MODAL = 'modal/SHOW_MODAL';
+export const HIDE_MODAL = 'modal/HIDE_MODAL';
+
+export const showInfoModalAction = createAction(SHOW_MODAL, payload => ({
+ size: 'small',
+ ...payload,
+ type: 'info'
+}));
+
+export const showAlertModalAction = createAction(SHOW_MODAL, payload => ({
+ size: 'small',
+ ...payload,
+ type: 'alert'
+}));
+
+export const showErrorModalAction = createAction(SHOW_MODAL, payload => ({
+ size: 'small',
+ ...payload,
+ type: 'error'
+}));
+
+export const showCustomModalAction = createAction(SHOW_MODAL, payload => ({
+ size: 'medium',
+ ...payload,
+ type: 'custom'
+}));
+
+export const hideModalAction = createAction(HIDE_MODAL);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperComponents.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperComponents.js
new file mode 100644
index 00000000..1e051b2f
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperComponents.js
@@ -0,0 +1,27 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 NewWorkflow from 'features/workflow/create/CreateWorkflow';
+import NewVersion from 'features/version/create/CreateVersion';
+
+export const NEW_WORKFLOW_MODAL = 'NEW_WORKFLOW_MODAL';
+export const NEW_VERSION_MODAL = 'NEW_VERSION_MODAL';
+
+const modalWrapperComponents = {
+ NEW_WORKFLOW_MODAL: NewWorkflow,
+ NEW_VERSION_MODAL: NewVersion
+};
+
+export default modalWrapperComponents;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperReducer.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperReducer.js
new file mode 100644
index 00000000..600998b6
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperReducer.js
@@ -0,0 +1,32 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { SHOW_MODAL, HIDE_MODAL } from 'shared/modal/modalWrapperActions';
+
+const modalWrapperReducer = (state = {}, action) => {
+ const { type, payload } = action;
+
+ switch (type) {
+ case SHOW_MODAL:
+ return { ...payload };
+ case HIDE_MODAL:
+ return {};
+ default:
+ return state;
+ }
+};
+
+export default modalWrapperReducer;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationLink.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationLink.jsx
new file mode 100644
index 00000000..6688cb55
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationLink.jsx
@@ -0,0 +1,48 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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 PropTypes from 'prop-types';
+import classnames from 'classnames';
+
+function getItemDataTestId(itemId) {
+ return itemId.split('|')[0];
+}
+
+const NavigationLink = ({ item, activeItemId, onClick }) => {
+ return (
+ <div
+ key={'navAction_' + item.id}
+ className={classnames('navigation-group-item-name', {
+ selected: item.id === activeItemId,
+ disabled: item.disabled,
+ 'bold-name': item.expanded,
+ hidden: item.hidden
+ })}
+ onClick={event => onClick(event, item)}
+ data-test-id={'navbar-group-item-' + getItemDataTestId(item.id)}>
+ {item.name}
+ </div>
+ );
+};
+
+NavigationLink.propTypes = {
+ item: PropTypes.object,
+ activeItemId: PropTypes.string,
+ onClick: PropTypes.func
+};
+
+export default NavigationLink;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenu.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenu.jsx
new file mode 100644
index 00000000..f8a7d46f
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenu.jsx
@@ -0,0 +1,39 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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 PropTypes from 'prop-types';
+import NavigationMenuItems from './NavigationMenuItems';
+
+const NavigationMenu = ({ menu, activeItemId, onNavigationItemClick }) => {
+ return (
+ <div className="navigation-group" key={menu.id}>
+ <NavigationMenuItems
+ items={menu.items}
+ activeItemId={activeItemId}
+ onNavigationItemClick={onNavigationItemClick}
+ />
+ </div>
+ );
+};
+
+NavigationMenu.propTypes = {
+ menu: PropTypes.object,
+ activeItemId: PropTypes.string.isRequired,
+ onNavigationItemClick: PropTypes.func
+};
+
+export default NavigationMenu;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItem.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItem.jsx
new file mode 100644
index 00000000..540e09c7
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItem.jsx
@@ -0,0 +1,44 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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 PropTypes from 'prop-types';
+import classnames from 'classnames';
+import NavigationLink from './NavigationLink';
+
+const NavigationMenuItem = ({ onNavigationItemClick, item, activeItemId }) => {
+ return (
+ <div
+ className={classnames('navigation-group-item', {
+ 'selected-item': item.id === activeItemId
+ })}
+ key={'item_' + item.id}>
+ <NavigationLink
+ item={item}
+ activeItemId={activeItemId}
+ onClick={onNavigationItemClick}
+ />
+ </div>
+ );
+};
+
+NavigationMenuItem.propTypes = {
+ onNavigationItemClick: PropTypes.func,
+ item: PropTypes.object,
+ activeItemId: PropTypes.string
+};
+
+export default NavigationMenuItem;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItems.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItems.jsx
new file mode 100644
index 00000000..56bad084
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItems.jsx
@@ -0,0 +1,47 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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 PropTypes from 'prop-types';
+import NavigationMenuItem from './NavigationMenuItem';
+
+const NavigationMenuItems = ({
+ items,
+ activeItemId,
+ onNavigationItemClick
+}) => {
+ return (
+ <div className="navigation-group-items">
+ {items &&
+ items.map(item => (
+ <NavigationMenuItem
+ key={'menuItem_' + item.id}
+ item={item}
+ activeItemId={activeItemId}
+ onNavigationItemClick={onNavigationItemClick}
+ />
+ ))}
+ </div>
+ );
+};
+
+NavigationMenuItems.propTypes = {
+ items: PropTypes.array,
+ activeItemId: PropTypes.string,
+ onNavigationItemClick: PropTypes.func
+};
+
+export default NavigationMenuItems;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/index.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/index.js
new file mode 100644
index 00000000..b3af6f47
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/index.js
@@ -0,0 +1,73 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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, { Component } from 'react';
+import PropTypes from 'prop-types';
+import NavigationMenu from './NavigationMenu';
+
+class NavigationSideBar extends Component {
+ static propTypes = {
+ activeItemId: PropTypes.string,
+ onSelect: PropTypes.func,
+ onToggle: PropTypes.func,
+ groups: PropTypes.array,
+ disabled: PropTypes.bool,
+ onNavigationItemClick: PropTypes.func
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ activeItemId: null
+ };
+ this.handleItemClicked = this.handleItemClicked.bind(this);
+ }
+
+ render() {
+ let { groups, activeItemId, disabled = false } = this.props;
+
+ return (
+ <div
+ className={`navigation-side-content ${
+ disabled ? 'disabled' : ''
+ }`}>
+ {groups.map(group => (
+ <NavigationMenu
+ menu={group}
+ activeItemId={activeItemId}
+ onNavigationItemClick={this.handleItemClicked}
+ key={'menu_' + group.id}
+ />
+ ))}
+ </div>
+ );
+ }
+
+ handleItemClicked(event, item) {
+ event.stopPropagation();
+ if (this.props.onToggle) {
+ this.props.onToggle(this.props.groups, item.id);
+ }
+ if (item.onSelect) {
+ item.onSelect();
+ }
+ if (this.props.onSelect) {
+ this.props.onSelect(item);
+ }
+ }
+}
+
+export default NavigationSideBar;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/Notifications.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/Notifications.js
new file mode 100644
index 00000000..5be899a9
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/Notifications.js
@@ -0,0 +1,36 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { connect } from 'react-redux';
+import NotificationsView from 'shared/notifications/NotificationsView';
+import { notificationActions } from 'shared/notifications/notificationsActions';
+
+const mapStateToProps = state => {
+ return {
+ notifications: state.notifications
+ };
+};
+
+function mapDispatchToProps(dispatch) {
+ return {
+ removeNotifications: payload =>
+ dispatch(notificationActions.removeNotification(payload))
+ };
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(NotificationsView);
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/NotificationsView.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/NotificationsView.jsx
new file mode 100644
index 00000000..44812897
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/NotificationsView.jsx
@@ -0,0 +1,56 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import { Notification } from 'onap-ui-react';
+import { CSSTransition, TransitionGroup } from 'react-transition-group';
+
+export default class NotificationsView extends PureComponent {
+ static propTypes = {
+ removeNotifications: PropTypes.func,
+ notifications: PropTypes.array
+ };
+
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ const { notifications, removeNotifications } = this.props;
+ return (
+ <div className="workflow-notifications-container position-top-right">
+ <TransitionGroup>
+ {notifications.map(item => (
+ <CSSTransition
+ in={true}
+ timeout={500}
+ unmountOnExit
+ classNames="react-transition"
+ key={`notification-transition-${item.id}`}>
+ <Notification
+ key={item.id}
+ type={item.type}
+ title={item.title}
+ message={item.message}
+ onClick={() => removeNotifications(item)}
+ />
+ </CSSTransition>
+ ))}
+ </TransitionGroup>
+ </div>
+ );
+ }
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/NotificationView_snapshot-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/NotificationView_snapshot-test.js
new file mode 100644
index 00000000..616f10ff
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/NotificationView_snapshot-test.js
@@ -0,0 +1,30 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 renderer from 'react-test-renderer';
+
+import NotificationsView from 'shared/notifications/NotificationsView';
+
+describe('Notification View Snapshot', () => {
+ it('renders correctly', () => {
+ const tree = renderer
+ .create(<NotificationsView notifications={[]} />)
+ .toJSON();
+
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/__snapshots__/NotificationView_snapshot-test.js.snap b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/__snapshots__/NotificationView_snapshot-test.js.snap
new file mode 100644
index 00000000..eb4a573f
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/__snapshots__/NotificationView_snapshot-test.js.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Notification View Snapshot renders correctly 1`] = `
+<div
+ className="workflow-notifications-container position-top-right"
+>
+ <div />
+</div>
+`;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/notificationsReducer-test.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/notificationsReducer-test.js
new file mode 100644
index 00000000..d308d22d
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/notificationsReducer-test.js
@@ -0,0 +1,61 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 notificationsReducer from 'shared/notifications/notificationsReducer';
+import { actionTypes } from 'shared/notifications/notificationsConstants';
+
+describe('Notifications Reducer', () => {
+ it('Returns empty state array', () => {
+ expect(notificationsReducer(undefined, {})).toEqual([]);
+ });
+ it('Add notification obj to the notification arr in store', () => {
+ const action = {
+ type: actionTypes.ADD_NOTIFICATION,
+ payload: {
+ title: 'Notification1',
+ message: 'This is a test',
+ type: 'info',
+ id: '4a2b0038-e442-4ccb-b577-5d04eee6fdfb',
+ timeout: 200
+ }
+ };
+ expect(notificationsReducer([], action)).toEqual([action.payload]);
+ });
+ it('Remove notification instance from store', () => {
+ const resultState = [
+ {
+ title: 'Notification1',
+ message: 'This is a test',
+ type: 'info',
+ id: '4a2b0038-e442-4ccb-b577-5d04eee6fdfb',
+ timeout: 200
+ }
+ ];
+ const removeAction = {
+ type: actionTypes.REMOVE_NOTIFICATION,
+ payload: {
+ title: 'Notification1',
+ message: 'This is a test',
+ type: 'info',
+ id: '4a2b0038-e442-4ccb-b577-5d04eee6fdfb',
+ timeout: 200
+ }
+ };
+ expect(notificationsReducer([], removeAction)).toEqual([]);
+ expect(notificationsReducer(resultState, removeAction)).toEqual(
+ resultState.filter(item => item.id !== removeAction.payload.id)
+ );
+ });
+});
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsActions.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsActions.js
new file mode 100644
index 00000000..84f9da83
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsActions.js
@@ -0,0 +1,60 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { actionTypes } from 'shared/notifications/notificationsConstants';
+import UUID from 'uuid-js';
+
+export const notificationActions = {
+ showNotification: item => ({
+ type: actionTypes.ADD_NOTIFICATION,
+ payload: {
+ ...item,
+ id: UUID.create().toString()
+ }
+ }),
+
+ showSuccess: ({ title, message, timeout }) =>
+ notificationActions.showNotification({
+ title,
+ message,
+ timeout,
+ type: 'success'
+ }),
+ showInfo: ({ title, message, timeout }) =>
+ notificationActions.showNotification({
+ title,
+ message,
+ timeout,
+ type: 'info'
+ }),
+ showWarning: ({ title, message, timeout }) =>
+ notificationActions.showNotification({
+ title,
+ message,
+ timeout,
+ type: 'warning'
+ }),
+ showError: ({ title, message, timeout }) =>
+ notificationActions.showNotification({
+ title,
+ message,
+ timeout,
+ type: 'error'
+ }),
+ removeNotification: item => ({
+ type: actionTypes.REMOVE_NOTIFICATION,
+ payload: item
+ })
+};
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsConstants.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsConstants.js
new file mode 100644
index 00000000..3e166424
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsConstants.js
@@ -0,0 +1,21 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 const actionTypes = {
+ ADD_NOTIFICATION: 'notification/ADD_NOTIFICATION',
+ REMOVE_NOTIFICATION: 'notification/REMOVE_NOTIFICATION'
+};
+export const notificationTimeout = 4000;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsReducer.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsReducer.js
new file mode 100644
index 00000000..f0989cf5
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsReducer.js
@@ -0,0 +1,30 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { actionTypes } from 'shared/notifications/notificationsConstants';
+
+function notificationsReducer(state = [], action) {
+ switch (action.type) {
+ case actionTypes.ADD_NOTIFICATION:
+ return [...state, action.payload];
+ case actionTypes.REMOVE_NOTIFICATION:
+ return state.filter(item => item.id !== action.payload.id);
+ default:
+ return state;
+ }
+}
+
+export default notificationsReducer;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsSagas.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsSagas.js
new file mode 100644
index 00000000..c30930a2
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsSagas.js
@@ -0,0 +1,33 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { takeEvery, put } from 'redux-saga/effects';
+import { delay } from 'redux-saga';
+import {
+ actionTypes,
+ notificationTimeout
+} from 'shared/notifications/notificationsConstants.js';
+import { notificationActions } from 'shared/notifications/notificationsActions';
+
+export function* handleComingNotifications(params) {
+ const { timeout, ...data } = params;
+ const interval = timeout || notificationTimeout;
+ yield delay(interval);
+ yield put(notificationActions.removeNotification(data.payload));
+}
+
+export function* watchNotifications() {
+ yield takeEvery(actionTypes.ADD_NOTIFICATION, handleComingNotifications);
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/pubsub/base-pubsub.ts b/sdc-workflow-designer-ui/src/main/frontend/src/shared/pubsub/base-pubsub.ts
new file mode 100644
index 00000000..41e8039d
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/pubsub/base-pubsub.ts
@@ -0,0 +1,127 @@
+declare const window: Window;
+
+export class BasePubSub {
+
+ subscribers: Map<string, ISubscriber>;
+ eventsCallbacks: Array<Function>;
+ clientId: string;
+ eventsToWait: Map<string, Array<string>>;
+ lastEventNotified: string;
+
+ constructor(pluginId: string) {
+ this.subscribers = new Map<string, ISubscriber>();
+ this.eventsCallbacks = [];
+ this.eventsToWait = new Map<string, Array<string>>();
+ this.clientId = pluginId;
+ this.lastEventNotified = "";
+ this.onMessage = this.onMessage.bind(this);
+
+ window.addEventListener("message", this.onMessage);
+ }
+
+ public register(subscriberId: string, subscriberWindow: Window, subscriberUrl: string) {
+ const subscriber = {
+ window: subscriberWindow,
+ locationUrl: subscriberUrl || subscriberWindow.location.href
+ } as ISubscriber;
+
+ this.subscribers.set(subscriberId, subscriber);
+ }
+
+ public unregister(subscriberId: string) {
+ this.subscribers.delete(subscriberId);
+ }
+
+ public on(callback: Function) {
+ let functionExists = this.eventsCallbacks.find((func: Function) => {
+ return callback.toString() == func.toString()
+ });
+
+ if (!functionExists) {
+ this.eventsCallbacks.push(callback);
+ }
+ }
+
+ public off(callback: Function) {
+ let index = this.eventsCallbacks.indexOf(callback);
+ this.eventsCallbacks.splice(index, 1)
+ }
+
+ public notify(eventType:string, eventData?:any) {
+ let eventObj = {
+ type: eventType,
+ data: eventData,
+ originId: this.clientId
+ } as IPubSubEvent;
+
+ this.subscribers.forEach( (subscriber: ISubscriber, subscriberId: string) => {
+ subscriber.window.postMessage(eventObj, subscriber.locationUrl);
+ });
+
+ this.lastEventNotified = eventType;
+
+ return {
+ subscribe: function(callbackFn) {
+
+ if(this.subscribers.size !== 0) {
+ let subscribersToNotify = Array.from(this.subscribers.keys());
+
+ const checkNotifyComplete = (subscriberId: string) => {
+
+ let index = subscribersToNotify.indexOf(subscriberId);
+ subscribersToNotify.splice(index, 1);
+
+ if (subscribersToNotify.length === 0) {
+ callbackFn();
+ }
+ };
+
+ this.subscribers.forEach((subscriber: ISubscriber, subscriberId: string) => {
+ if (this.eventsToWait.has(subscriberId) && this.eventsToWait.get(subscriberId).indexOf(eventType) !== -1) {
+
+ const actionCompletedFunction = (eventData, subId = subscriberId) => {
+ if (eventData.type == "ACTION_COMPLETED") {
+ checkNotifyComplete(subId);
+ }
+ this.off(actionCompletedFunction);
+
+ };
+ this.on(actionCompletedFunction);
+ }
+ else {
+ checkNotifyComplete(subscriberId);
+ }
+ });
+ }
+ else {
+ callbackFn();
+ }
+ }.bind(this)
+ }
+ }
+
+ public isWaitingForEvent(eventName: string) : boolean {
+ return Array.from(this.eventsToWait.values()).some((eventsList: Array<string>) =>
+ eventsList.indexOf(eventName) !== -1
+ );
+ }
+
+ protected onMessage(event: any) {
+ if (this.subscribers.has(event.data.originId)) {
+ this.eventsCallbacks.forEach((callback: Function) => {
+ callback(event.data, event);
+ })
+ }
+ }
+}
+
+export interface IPubSubEvent {
+ type: string;
+ originId: string;
+ data: any;
+}
+
+export interface ISubscriber {
+ window: Window;
+ locationUrl: string;
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/pubsub/plugin-pubsub.ts b/sdc-workflow-designer-ui/src/main/frontend/src/shared/pubsub/plugin-pubsub.ts
new file mode 100644
index 00000000..8f99e659
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/pubsub/plugin-pubsub.ts
@@ -0,0 +1,30 @@
+import {BasePubSub} from 'shared/pubsub/base-pubsub';
+
+declare const window: Window;
+
+export class PluginPubSub extends BasePubSub {
+
+
+ constructor(pluginId: string, parentUrl: string, eventsToWait?: Array<string>) {
+ super(pluginId);
+ this.register('sdc-hub', window.parent, parentUrl);
+ this.subscribe(eventsToWait);
+ }
+
+ public subscribe(eventsToWait?: Array<string>) {
+ const registerData = {
+ pluginId: this.clientId,
+ eventsToWait: eventsToWait || []
+ };
+
+ this.notify('PLUGIN_REGISTER', registerData);
+ }
+
+ public unsubscribe() {
+ const unregisterData = {
+ pluginId: this.clientId
+ };
+
+ this.notify('PLUGIN_UNREGISTER', unregisterData);
+ }
+}
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/scroll/InfiniteScroll.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/scroll/InfiniteScroll.js
new file mode 100644
index 00000000..04d00120
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/scroll/InfiniteScroll.js
@@ -0,0 +1,161 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+
+class InfiniteScroll extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ initialLoad: false
+ };
+ }
+ componentDidMount() {
+ this.scrollEl = this.getScrollElement();
+ this.attachScrollListener();
+ this.setState({ initialLoad: true });
+ }
+
+ componentDidUpdate() {
+ this.attachScrollListener();
+ }
+
+ componentWillUnmount() {
+ this.detachScrollListener();
+ }
+
+ getParentElement(el) {
+ return el.parentNode;
+ }
+
+ getScrollElement() {
+ if (this.props.useWindow) {
+ return window;
+ }
+
+ return this.getParentElement(this.scrollComponent);
+ }
+
+ detachScrollListener() {
+ this.scrollEl.removeEventListener(
+ 'scroll',
+ this.scrollListener,
+ this.props.useCapture
+ );
+ window.removeEventListener(
+ 'resize',
+ this.scrollListener,
+ this.props.useCapture
+ );
+ }
+
+ attachScrollListener() {
+ if (!this.props.hasMore || !this.scrollEl) {
+ return;
+ }
+
+ const options = {
+ capture: this.props.useCapture,
+ passive: true
+ };
+
+ this.scrollEl.addEventListener('scroll', this.scrollListener, options);
+ window.addEventListener('resize', this.scrollListener, options);
+
+ this.scrollListener();
+ }
+
+ scrollListener = () => {
+ const el = this.scrollComponent;
+ const scrollEl = window;
+ const parentNode = this.getParentElement(el);
+
+ let offset;
+ if (this.props.useWindow) {
+ const doc =
+ document.documentElement ||
+ document.body.parentNode ||
+ document.body;
+ const scrollTop =
+ scrollEl.pageYOffset !== undefined
+ ? scrollEl.pageYOffset
+ : doc.scrollTop;
+
+ offset =
+ this.calculateTopPosition(el) +
+ (el.offsetHeight - scrollTop - window.innerHeight);
+ } else {
+ offset =
+ el.scrollHeight -
+ parentNode.scrollTop -
+ parentNode.clientHeight;
+ }
+
+ // Here we make sure the element is visible as well as checking the offset
+ if (offset < Number(this.props.threshold) && el.offsetParent !== null) {
+ this.detachScrollListener();
+ // Call loadMore after detachScrollListener to allow for non-async loadMore functions
+ if (
+ typeof this.props.loadMore === 'function' &&
+ this.state.initialLoad
+ ) {
+ this.props.loadMore();
+ }
+ }
+ };
+
+ calculateTopPosition(el) {
+ if (!el) {
+ return 0;
+ }
+ return el.offsetTop + this.calculateTopPosition(el.offsetParent);
+ }
+
+ render() {
+ const { children, element } = this.props;
+
+ const props = {
+ ref: node => {
+ this.scrollComponent = node;
+ }
+ };
+
+ const childrenArray = [children];
+
+ return React.createElement(element, props, childrenArray);
+ }
+}
+
+InfiniteScroll.propTypes = {
+ children: PropTypes.node.isRequired,
+ element: PropTypes.node,
+ hasMore: PropTypes.bool,
+ loadMore: PropTypes.func.isRequired,
+ threshold: PropTypes.number,
+ useCapture: PropTypes.bool,
+ useWindow: PropTypes.bool
+};
+
+InfiniteScroll.defaultProps = {
+ element: 'div',
+ hasMore: false,
+ threshold: 250,
+ useWindow: true,
+ useCapture: false
+};
+
+export default InfiniteScroll;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/scroll/Scrollbars.js b/sdc-workflow-designer-ui/src/main/frontend/src/shared/scroll/Scrollbars.js
new file mode 100644
index 00000000..e5ec8cee
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/scroll/Scrollbars.js
@@ -0,0 +1,58 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import PerfectScrollbar from 'perfect-scrollbar';
+
+class Scrollbars extends React.Component {
+ containerRef = React.createRef();
+ scrollbars = null;
+
+ componentDidMount() {
+ const container = this.containerRef.current;
+
+ this.scrollbars = new PerfectScrollbar(container);
+ }
+
+ componentDidUpdate() {
+ if (this.scrollbars) {
+ this.scrollbars.update();
+ }
+ }
+
+ componentWillUnmount() {
+ this.scrollbars.destroy();
+ this.scrollbars = null;
+ }
+
+ render() {
+ const { children, className } = this.props;
+
+ return (
+ <div className={className} ref={this.containerRef}>
+ {children}
+ </div>
+ );
+ }
+}
+
+Scrollbars.propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string
+};
+
+export default Scrollbars;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/searchInput/SearchInput.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/shared/searchInput/SearchInput.jsx
new file mode 100644
index 00000000..a4ae2cd9
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/searchInput/SearchInput.jsx
@@ -0,0 +1,131 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 PropTypes from 'prop-types';
+import { SVGIcon } from 'onap-ui-react';
+
+class ExpandableInput extends React.Component {
+ static propTypes = {
+ dataTestId: PropTypes.string,
+ onChange: PropTypes.func,
+ value: PropTypes.string
+ };
+
+ state = { showInput: !!this.props.value };
+
+ handleRef = input => {
+ this.domNode = input;
+
+ this.domNode && this.domNode.focus();
+ };
+
+ showInput = () => {
+ this.setState({ showInput: true });
+ };
+
+ hideInput = () => {
+ this.setState({ showInput: false });
+ };
+
+ closeInput = () => {
+ if (!this.props.value) {
+ this.hideInput();
+ }
+ };
+
+ getValue = () => {
+ return this.props.value;
+ };
+
+ handleChange = e => {
+ this.props.onChange(e.target.value);
+ };
+
+ handleClose() {
+ this.props.onChange('');
+
+ this.hideInput();
+ }
+
+ handleKeyDown = e => {
+ if (e.key === 'Escape') {
+ e.preventDefault();
+
+ this.handleClose();
+ }
+ };
+
+ handleBlur = () => {
+ if (!this.props.value) {
+ this.setState({ showInput: false });
+ }
+ };
+
+ render() {
+ let { value, dataTestId } = this.props;
+
+ const { showInput } = this.state;
+
+ if (!showInput) {
+ return (
+ <div className="search-input-top">
+ <SVGIcon
+ className="search-input-wrapper closed"
+ name="search"
+ data-test-id={dataTestId}
+ onClick={this.showInput}
+ />
+ </div>
+ );
+ }
+
+ return (
+ <div className="search-input-top">
+ <div className="search-input-wrapper opened">
+ <div className="search-input-control">
+ <input
+ type="text"
+ value={value}
+ ref={this.handleRef}
+ className="input-control"
+ data-test-id={`${dataTestId}-input-control`}
+ onChange={this.handleChange}
+ onKeyDown={this.handleKeyDown}
+ onBlur={this.handleBlur}
+ />
+ </div>
+ {value && (
+ <SVGIcon
+ data-test-id={`${dataTestId}-close-search`}
+ onClick={() => this.handleClose()}
+ name="close"
+ />
+ )}
+ {!value && (
+ <SVGIcon
+ name="search"
+ data-test-id={`${dataTestId}-blur-search`}
+ onClick={this.handleBlur}
+ />
+ )}
+ </div>
+ </div>
+ );
+ }
+}
+
+export default ExpandableInput;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/shared/tree/Tree.jsx b/sdc-workflow-designer-ui/src/main/frontend/src/shared/tree/Tree.jsx
new file mode 100644
index 00000000..60ef9646
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/shared/tree/Tree.jsx
@@ -0,0 +1,231 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { select } from 'd3-selection';
+import { tree, stratify } from 'd3-hierarchy';
+function diagonal(d) {
+ const offset = 50;
+ return (
+ 'M' +
+ d.y +
+ ',' +
+ d.x +
+ 'C' +
+ (d.parent.y + offset) +
+ ',' +
+ d.x +
+ ' ' +
+ (d.parent.y + offset) +
+ ',' +
+ d.parent.x +
+ ' ' +
+ d.parent.y +
+ ',' +
+ d.parent.x
+ );
+}
+
+const nodeRadius = 8;
+const verticalSpaceBetweenNodes = 70;
+const NARROW_HORIZONTAL_SPACES = 47;
+const WIDE_HORIZONTAL_SPACES = 65;
+
+const stratifyFn = stratify()
+ .id(d => d.id)
+ .parentId(d => d.parent);
+
+class Tree extends Component {
+ static propTypes = {
+ name: PropTypes.string,
+ width: PropTypes.number,
+ allowScaleWidth: PropTypes.bool,
+ nodes: PropTypes.arrayOf(
+ PropTypes.shape({
+ id: PropTypes.string,
+ name: PropTypes.string,
+ parent: PropTypes.string
+ })
+ ),
+ selectedNodeId: PropTypes.string,
+ onNodeClick: PropTypes.func,
+ onRenderedBeyondWidth: PropTypes.func,
+ scrollable: PropTypes.bool,
+ toWiden: PropTypes.bool
+ };
+
+ static defaultProps = {
+ width: 500,
+ allowScaleWidth: true,
+ name: 'default-name'
+ };
+
+ render() {
+ let { width, name, scrollable = false } = this.props;
+ return (
+ <div
+ className={`tree-view ${name}-container ${
+ scrollable ? 'scrollable' : ''
+ }`}>
+ <svg width={width} className={name} />
+ </div>
+ );
+ }
+
+ componentDidMount() {
+ this.renderTree();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (
+ this.props.nodes.length !== prevProps.nodes.length ||
+ this.props.selectedNodeId !== prevProps.selectedNodeId
+ ) {
+ this.renderTree();
+ }
+ }
+
+ renderTree() {
+ let {
+ width,
+ nodes,
+ name,
+ allowScaleWidth,
+ selectedNodeId,
+ onRenderedBeyondWidth,
+ toWiden
+ } = this.props;
+ if (nodes.length > 0) {
+ let horizontalSpaceBetweenLeaves = toWiden
+ ? WIDE_HORIZONTAL_SPACES
+ : NARROW_HORIZONTAL_SPACES;
+ const treeFn = tree().nodeSize([
+ horizontalSpaceBetweenLeaves,
+ verticalSpaceBetweenNodes
+ ]); //.size([width - 50, height - 50])
+ let root = stratifyFn(nodes).sort((a, b) =>
+ a.data.name.localeCompare(b.data.name)
+ );
+ let svgHeight =
+ verticalSpaceBetweenNodes * root.height + nodeRadius * 6;
+
+ treeFn(root);
+
+ let nodesXValue = root.descendants().map(node => node.x);
+ let maxX = Math.max(...nodesXValue);
+ let minX = Math.min(...nodesXValue);
+
+ let svgTempWidth =
+ ((maxX - minX) / 30) * horizontalSpaceBetweenLeaves;
+ let svgWidth = svgTempWidth < width ? width - 5 : svgTempWidth;
+ const svgEL = select(`svg.${name}`);
+ const container = select(`.tree-view.${name}-container`);
+ svgEL.html('');
+ svgEL.attr('height', svgHeight);
+ let canvasWidth = width;
+ if (svgTempWidth > width) {
+ if (allowScaleWidth) {
+ canvasWidth = svgTempWidth;
+ }
+ // we seems to have a margin of 25px that we can still see with text
+ if (
+ svgTempWidth - 25 > width &&
+ onRenderedBeyondWidth !== undefined
+ ) {
+ onRenderedBeyondWidth();
+ }
+ }
+ svgEL.attr('width', canvasWidth);
+ let rootGroup = svgEL
+ .append('g')
+ .attr(
+ 'transform',
+ `translate(${svgWidth / 2 + nodeRadius},${nodeRadius *
+ 4}) rotate(90)`
+ );
+
+ // handle link
+ rootGroup
+ .selectAll('.link')
+ .data(root.descendants().slice(1))
+ .enter()
+ .append('path')
+ .attr('class', 'link')
+ .attr('d', diagonal);
+
+ let node = rootGroup
+ .selectAll('.node')
+ .data(root.descendants())
+ .enter()
+ .append('g')
+ .attr(
+ 'class',
+ node =>
+ `node ${node.children ? ' has-children' : ' leaf'} ${
+ node.id === selectedNodeId ? 'selectedNode' : ''
+ } ${this.props.onNodeClick ? 'clickable' : ''}`
+ )
+ .attr(
+ 'transform',
+ node => 'translate(' + node.y + ',' + node.x + ')'
+ )
+ .on('click', node => this.onNodeClick(node));
+
+ node.append('circle')
+ .attr('r', nodeRadius)
+ .attr('class', 'outer-circle');
+ node.append('circle')
+ .attr('r', nodeRadius - 3)
+ .attr('class', 'inner-circle');
+
+ node.append('text')
+ .attr('y', nodeRadius / 4 + 1)
+ .attr('x', -nodeRadius * 1.8)
+ .text(node => node.data.name)
+ .attr('transform', 'rotate(-90)');
+
+ let selectedNode = selectedNodeId
+ ? root.descendants().find(node => node.id === selectedNodeId)
+ : null;
+ if (selectedNode) {
+ container.property(
+ 'scrollLeft',
+ svgWidth / 4 +
+ (svgWidth / 4 - 100) -
+ (selectedNode.x / 30) * horizontalSpaceBetweenLeaves
+ );
+ container.property(
+ 'scrollTop',
+ (selectedNode.y / 100) * verticalSpaceBetweenNodes
+ );
+ } else {
+ container.property(
+ 'scrollLeft',
+ svgWidth / 4 + (svgWidth / 4 - 100)
+ );
+ }
+ }
+ }
+
+ onNodeClick(node) {
+ if (this.props.onNodeClick) {
+ this.props.onNodeClick(node.data);
+ }
+ }
+}
+
+export default Tree;
diff --git a/sdc-workflow-designer-ui/src/main/frontend/src/store.js b/sdc-workflow-designer-ui/src/main/frontend/src/store.js
new file mode 100644
index 00000000..90b55bbd
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/main/frontend/src/store.js
@@ -0,0 +1,42 @@
+/*
+* Copyright © 2018 European Support Limited
+*
+* 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 { createStore, applyMiddleware, compose } from 'redux';
+import thunk from 'redux-thunk';
+import createSagaMiddleware from 'redux-saga';
+import { syncTranslationWithStore } from 'react-redux-i18n';
+
+import reducers from './rootReducers';
+import rootSaga from './rootSaga';
+
+const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
+const sagaMiddleware = createSagaMiddleware();
+
+const middleware = [thunk, sagaMiddleware];
+
+const store = createStore(
+ reducers,
+ composeEnhancers(applyMiddleware(...middleware))
+);
+
+const rootSagaTask = sagaMiddleware.run(rootSaga);
+syncTranslationWithStore(store);
+
+rootSagaTask.done.catch(function(err) {
+ console.log(err);
+});
+
+export default store;