aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/RestUtils.java46
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/WorkflowController.java14
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/WorkflowVersionController.java12
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/Paging.java12
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/Sorting.java4
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/VersionStatesFormatter.java36
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/ParameterEntity.java2
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/Workflow.java2
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/WorkflowVersion.java3
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/impl/WorkflowManagerImpl.java19
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/impl/WorkflowVersionManagerImpl.java7
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/types/PagingRequest.java20
-rw-r--r--workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/types/Sort.java2
-rw-r--r--workflow-designer-be/src/test/java/org/onap/sdc/workflow/RestPath.java4
-rw-r--r--workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/WorkflowControllerTest.java187
-rw-r--r--workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/types/PagingTest.java71
-rw-r--r--workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/types/SortingTest.java42
-rw-r--r--workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/types/VersionStatesFormatterTest.java45
-rw-r--r--workflow-designer-be/src/test/java/org/onap/sdc/workflow/services/impl/WorkflowManagerImplTest.java227
-rw-r--r--workflow-designer-be/src/test/java/org/onap/sdc/workflow/services/impl/WorkflowVersionManagerImplTest.java3
-rw-r--r--workflow-designer-ui/src/main/frontend/.gitignore2
-rw-r--r--workflow-designer-ui/src/main/frontend/index.html4
-rw-r--r--workflow-designer-ui/src/main/frontend/package.json1
-rw-r--r--workflow-designer-ui/src/main/frontend/resources/scss/components/_notifications.scss1
-rw-r--r--workflow-designer-ui/src/main/frontend/resources/scss/features/_composition.scss41
-rw-r--r--workflow-designer-ui/src/main/frontend/resources/scss/style.scss2
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/Composition.js47
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js141
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButton.js33
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButtonsPanel.js52
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionActions.js21
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionConstants.js16
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionReducer.js27
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionSelectors.js17
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomContextPadProvider.js43
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomElementFactory.js101
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomPalette.js151
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRenderer.js176
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRules.js136
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomUpdater.js136
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/index.js22
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/index.js99
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js15
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js5
-rw-r--r--workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js29
-rw-r--r--workflow-designer-ui/src/main/frontend/src/i18n/languages.json3
-rw-r--r--workflow-designer-ui/src/main/frontend/src/rootReducers.js5
-rw-r--r--workflow-designer-ui/src/main/frontend/src/routes.js4
-rw-r--r--workflow-designer-ui/src/main/frontend/webpack.config.js9
-rw-r--r--workflow-designer-ui/src/main/frontend/yarn.lock4
50 files changed, 1808 insertions, 293 deletions
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/RestUtils.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/RestUtils.java
deleted file mode 100644
index d8577c1c..00000000
--- a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/RestUtils.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.
- */
-package org.onap.sdc.workflow.api;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.onap.sdc.workflow.persistence.types.WorkflowVersionState;
-import org.openecomp.sdc.logging.api.Logger;
-import org.openecomp.sdc.logging.api.LoggerFactory;
-
-public class RestUtils {
-
- private RestUtils() {
- }
-
- private static final Logger LOGGER = LoggerFactory.getLogger(RestUtils.class);
-
- public static Set<WorkflowVersionState> formatVersionStates(String versionStateFilter) {
- Set<WorkflowVersionState> filter;
- try {
- filter = versionStateFilter == null ? null :
- Arrays.stream(versionStateFilter.split(",")).map(WorkflowVersionState::valueOf)
- .collect(Collectors.toSet());
- } catch (Exception e) {
- LOGGER.info(
- "version state filter value is invalid and cannot be formatted to a set of version states, therefore it is set to empty set");
- filter = Collections.emptySet();
- }
- return filter;
- }
-}
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/WorkflowController.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/WorkflowController.java
index 77f6e6df..690b2075 100644
--- a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/WorkflowController.java
+++ b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/WorkflowController.java
@@ -20,7 +20,6 @@ import static org.onap.sdc.workflow.api.RestParams.LIMIT;
import static org.onap.sdc.workflow.api.RestParams.OFFSET;
import static org.onap.sdc.workflow.api.RestParams.SORT;
import static org.onap.sdc.workflow.api.RestParams.USER_ID_HEADER;
-import static org.onap.sdc.workflow.api.RestUtils.formatVersionStates;
import static org.onap.sdc.workflow.services.types.PagingConstants.DEFAULT_LIMIT;
import static org.onap.sdc.workflow.services.types.PagingConstants.DEFAULT_OFFSET;
@@ -31,6 +30,7 @@ import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.onap.sdc.workflow.api.types.Paging;
import org.onap.sdc.workflow.api.types.Sorting;
+import org.onap.sdc.workflow.api.types.VersionStatesFormatter;
import org.onap.sdc.workflow.persistence.types.Workflow;
import org.onap.sdc.workflow.services.WorkflowManager;
import org.onap.sdc.workflow.services.WorkflowVersionManager;
@@ -53,6 +53,7 @@ import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import springfox.documentation.annotations.ApiIgnore;
@RequestMapping("/workflows")
@Api("Workflows")
@@ -71,6 +72,8 @@ public class WorkflowController {
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation("List workflows")
@ApiImplicitParams({
+ @ApiImplicitParam(name = "versionState", dataType = "string", paramType = "query",
+ allowableValues = "DRAFT,CERTIFIED", value = "Filter by version state"),
@ApiImplicitParam(name = OFFSET, dataType = "string", paramType = "query", defaultValue = "0",
value = "Index of the starting item"),
@ApiImplicitParam(name = LIMIT, dataType = "string", paramType = "query", defaultValue = "200",
@@ -78,12 +81,11 @@ public class WorkflowController {
@ApiImplicitParam(name = SORT, dataType = "string", paramType = "query", defaultValue = "name:asc",
value = "Sorting criteria in the format: property:(asc|desc). Default sort order is ascending.",
allowableValues = "name:asc,name:desc")})
- public Page<Workflow> list(@ApiParam(value = "Filter by version state", allowableValues = "DRAFT,CERTIFIED")
- @RequestParam(name = "versionState", required = false) String versionStateFilter,
- @ApiParam(hidden = true) Paging paging,
- @ApiParam(hidden = true) Sorting sorting,
+ public Page<Workflow> list(@ApiIgnore VersionStatesFormatter versionStateFilter,
+ @ApiIgnore Paging paging,
+ @ApiIgnore Sorting sorting,
@RequestHeader(USER_ID_HEADER) String user) {
- return workflowManager.list(formatVersionStates(versionStateFilter), initRequestSpec(paging, sorting));
+ return workflowManager.list(versionStateFilter.getVersionStates(), initRequestSpec(paging, sorting));
}
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/WorkflowVersionController.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/WorkflowVersionController.java
index ba15f9f7..df4c9fe3 100644
--- a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/WorkflowVersionController.java
+++ b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/WorkflowVersionController.java
@@ -16,14 +16,14 @@
package org.onap.sdc.workflow.api;
-import static org.onap.sdc.workflow.api.RestUtils.formatVersionStates;
import static org.onap.sdc.workflow.api.RestParams.USER_ID_HEADER;
import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiParam;
import org.onap.sdc.workflow.api.types.CollectionResponse;
import org.onap.sdc.workflow.api.types.VersionStateDto;
+import org.onap.sdc.workflow.api.types.VersionStatesFormatter;
import org.onap.sdc.workflow.persistence.types.ArtifactEntity;
import org.onap.sdc.workflow.persistence.types.WorkflowVersion;
import org.onap.sdc.workflow.services.WorkflowVersionManager;
@@ -50,6 +50,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
+import springfox.documentation.annotations.ApiIgnore;
@RequestMapping("/workflows/{workflowId}/versions")
@Api("Workflow versions")
@@ -71,13 +72,14 @@ public class WorkflowVersionController {
this.validator = validator;
}
+ @ApiImplicitParam(name = "state", dataType = "string", paramType = "query",
+ allowableValues = "DRAFT,CERTIFIED", value = "Filter by state")
@GetMapping
@ApiOperation("List workflow versions")
public CollectionResponse<WorkflowVersion> list(@PathVariable("workflowId") String workflowId,
- @ApiParam(value = "Filter by state", allowableValues = "DRAFT,CERTIFIED")
- @RequestParam(value = "state", required = false) String stateFilter,
+ @ApiIgnore VersionStatesFormatter stateFilter,
@RequestHeader(USER_ID_HEADER) String user) {
- return new CollectionResponse<>(workflowVersionManager.list(workflowId, formatVersionStates(stateFilter)));
+ return new CollectionResponse<>(workflowVersionManager.list(workflowId, stateFilter.getVersionStates()));
}
@PostMapping
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/Paging.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/Paging.java
index 3d91c8e0..0a27e1e6 100644
--- a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/Paging.java
+++ b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/Paging.java
@@ -16,14 +16,16 @@ public class Paging {
}
public void setLimit(String limit) {
- getIntValue(limit).map(integer -> integer > MAX_LIMIT ? MAX_LIMIT : integer)
- .ifPresent(integer -> this.limit = integer);
+ getIntValue(limit).map(integer -> integer > MAX_LIMIT ? MAX_LIMIT : integer).ifPresent(integer -> {
+ if (integer != 0) {
+ this.limit = integer;
+ }
+ });
}
- private Optional<Integer> getIntValue(String value) {
- int intValue;
+ private static Optional<Integer> getIntValue(String value) {
try {
- intValue = Integer.parseInt(value);
+ int intValue = Integer.parseInt(value);
return intValue < 0 ? Optional.empty() : Optional.of(intValue);
} catch (NumberFormatException e) {
return Optional.empty();
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/Sorting.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/Sorting.java
index 7bb43d0b..f02a05fe 100644
--- a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/Sorting.java
+++ b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/Sorting.java
@@ -18,11 +18,11 @@ public class Sorting {
private List<Sort> sorts = Collections.emptyList();
public void setSort(String sortString) {
- this.sorts = Arrays.stream(sortString.split(SORTS_DELIMITER)).map(this::formatSort).filter(Objects::nonNull)
+ this.sorts = Arrays.stream(sortString.split(SORTS_DELIMITER)).map(Sorting::formatSort).filter(Objects::nonNull)
.collect(Collectors.toList());
}
- private Sort formatSort(String sort) {
+ private static Sort formatSort(String sort) {
String[] tokens = sort.split(DIRECTION_DELIMITER);
try {
return new Sort(tokens[0], ASCENDING_ORDER.equalsIgnoreCase(tokens[1]));
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/VersionStatesFormatter.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/VersionStatesFormatter.java
new file mode 100644
index 00000000..5467dee8
--- /dev/null
+++ b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/VersionStatesFormatter.java
@@ -0,0 +1,36 @@
+package org.onap.sdc.workflow.api.types;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+import lombok.Getter;
+import org.onap.sdc.workflow.persistence.types.WorkflowVersionState;
+import org.openecomp.sdc.logging.api.Logger;
+import org.openecomp.sdc.logging.api.LoggerFactory;
+
+@Getter
+public class VersionStatesFormatter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(VersionStatesFormatter.class);
+
+ private Set<WorkflowVersionState> versionStates = null;
+
+ public void setVersionState(String value) {
+ this.versionStates = formatString(value);
+ }
+
+ public void setState(String value) {
+ this.versionStates = formatString(value);
+ }
+
+ private static Set<WorkflowVersionState> formatString(String value) {
+ try {
+ return value == null ? null : Arrays.stream(value.split(",")).map(WorkflowVersionState::valueOf)
+ .collect(Collectors.toSet());
+ } catch (Exception ignore) {
+ LOGGER.info(
+ "value is invalid and cannot be formatted to a set of version states, therefore it set to null");
+ return null;
+ }
+ }
+}
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/ParameterEntity.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/ParameterEntity.java
index 7c957d85..5bf30773 100644
--- a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/ParameterEntity.java
+++ b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/ParameterEntity.java
@@ -25,7 +25,7 @@ public class ParameterEntity {
private String id;
@NotNull(message = "Parameter name may not be null")
- @Pattern(regexp = "[A-Za-z0-9_]*", message = "The field must contain only letters, digits and underscores")
+ @Pattern(regexp = "[A-Za-z0-9_ ]*", message = "Parameter name must contain only letters, digits and underscores")
private String name;
@NotNull
private ParameterType type;
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/Workflow.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/Workflow.java
index b2fc6f59..4b9f3449 100644
--- a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/Workflow.java
+++ b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/Workflow.java
@@ -31,7 +31,7 @@ public class Workflow {
private String id;
@NotNull(message = "Workflow name may not be null")
@Size(max = 80, message = "Workflow name must be less than 80 characters")
- @Pattern(regexp = "[A-Za-z0-9_]*", message = "Workflow name must contain only letters, digits and underscores")
+ @Pattern(regexp = "[A-Za-z0-9_ ]*", message = "Workflow name must contain only letters, digits and underscores")
private String name;
private String description;
private Set<WorkflowVersionState> versionStates;
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/WorkflowVersion.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/WorkflowVersion.java
index 17f95ae1..e3bbd646 100644
--- a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/WorkflowVersion.java
+++ b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/persistence/types/WorkflowVersion.java
@@ -19,6 +19,7 @@ package org.onap.sdc.workflow.persistence.types;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import javax.validation.Valid;
import lombok.Data;
@@ -31,7 +32,9 @@ public class WorkflowVersion {
private String baseId;
private WorkflowVersionState state;
private boolean hasArtifact;
+ @Valid
private Collection<ParameterEntity> inputs = Collections.emptyList();
+ @Valid
private Collection<ParameterEntity> outputs = Collections.emptyList();
private Date creationTime;
private Date modificationTime;
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/impl/WorkflowManagerImpl.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/impl/WorkflowManagerImpl.java
index 23979a5d..a3092042 100644
--- a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/impl/WorkflowManagerImpl.java
+++ b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/impl/WorkflowManagerImpl.java
@@ -135,14 +135,14 @@ public class WorkflowManagerImpl implements WorkflowManager {
itemManager.update(item);
}
- private RequestSpec getRequestSpec(RequestSpec requestSpec) {
+ private static RequestSpec getRequestSpec(RequestSpec requestSpec) {
if (requestSpec == null) {
return WORKSPACES_DEFAULT_REQUEST_SPEC;
}
if (requestSpec.getPaging() == null) {
requestSpec.setPaging(WORKSPACES_DEFAULT_REQUEST_SPEC.getPaging());
- } else if (requestSpec.getPaging().getLimit() > MAX_LIMIT) {
- requestSpec.getPaging().setLimit(MAX_LIMIT);
+ } else {
+ handlePagingRequestValues(requestSpec.getPaging());
}
if (requestSpec.getSorting() == null) {
requestSpec.setSorting(WORKSPACES_DEFAULT_REQUEST_SPEC.getSorting());
@@ -150,7 +150,18 @@ public class WorkflowManagerImpl implements WorkflowManager {
return requestSpec;
}
- private Comparator<Workflow> getWorkflowComparator(SortingRequest sorting) {
+ private static void handlePagingRequestValues(PagingRequest paging) {
+ if (paging.getOffset() == null) {
+ paging.setOffset(DEFAULT_OFFSET);
+ }
+ if (paging.getLimit() == null) {
+ paging.setLimit(DEFAULT_LIMIT);
+ } else if (paging.getLimit() > MAX_LIMIT) {
+ paging.setLimit(MAX_LIMIT);
+ }
+ }
+
+ private static Comparator<Workflow> getWorkflowComparator(SortingRequest sorting) {
Boolean byNameAscending = sorting.getSorts().stream()
.filter(sort -> WORKSPACES_SORT_PROPERTY.equalsIgnoreCase(sort.getProperty()))
.findFirst().map(Sort::isAscendingOrder).orElse(true);
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/impl/WorkflowVersionManagerImpl.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/impl/WorkflowVersionManagerImpl.java
index 56ba7ac9..14e28744 100644
--- a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/impl/WorkflowVersionManagerImpl.java
+++ b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/impl/WorkflowVersionManagerImpl.java
@@ -18,7 +18,6 @@ package org.onap.sdc.workflow.services.impl;
import static org.onap.sdc.workflow.persistence.types.WorkflowVersionState.CERTIFIED;
-import com.amdocs.zusammen.datatypes.response.ErrorCode;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
@@ -45,7 +44,6 @@ import org.onap.sdc.workflow.services.exceptions.VersionModificationException;
import org.onap.sdc.workflow.services.exceptions.VersionStateModificationException;
import org.onap.sdc.workflow.services.impl.mappers.VersionMapper;
import org.onap.sdc.workflow.services.impl.mappers.VersionStateMapper;
-import org.openecomp.sdc.common.errors.SdcRuntimeException;
import org.openecomp.sdc.logging.api.Logger;
import org.openecomp.sdc.logging.api.LoggerFactory;
import org.openecomp.sdc.versioning.VersioningManager;
@@ -186,7 +184,10 @@ public class WorkflowVersionManagerImpl implements WorkflowVersionManager {
ArtifactEntity artifactEntity =
new ArtifactEntity(StringUtils.cleanPath(artifact.getOriginalFilename()), artifactData);
artifactRepository.update(workflowId, versionId, artifactEntity);
- versioningManager.publish(workflowId, new Version(versionId), "Update Artifact");
+ Version updatedVersion = versioningManager.get(workflowId, new Version(versionId));
+ if(updatedVersion.getState().isDirty()) {
+ versioningManager.publish(workflowId, updatedVersion, "Update artifact");
+ }
} catch (IOException e) {
LOGGER.error(String.format("Upload Artifact failed for workflow id %s and version id %s",
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/types/PagingRequest.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/types/PagingRequest.java
index f06822fa..da46cc2c 100644
--- a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/types/PagingRequest.java
+++ b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/types/PagingRequest.java
@@ -1,15 +1,23 @@
package org.onap.sdc.workflow.services.types;
-import lombok.Data;
+import lombok.Getter;
-@Data
+@Getter
public class PagingRequest {
- private int offset;
- private int limit;
+ private Integer offset;
+ private Integer limit;
public PagingRequest(int offset, int limit) {
- this.offset = offset;
- this.limit = limit;
+ setOffset(offset);
+ setLimit(limit);
+ }
+
+ public void setOffset(int offset) {
+ this.offset = offset < 0 ? null : offset;
+ }
+
+ public void setLimit(int limit) {
+ this.limit = limit <= 0 ? null : limit;
}
}
diff --git a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/types/Sort.java b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/types/Sort.java
index 563c9ad0..88151a0e 100644
--- a/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/types/Sort.java
+++ b/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/types/Sort.java
@@ -1,8 +1,10 @@
package org.onap.sdc.workflow.services.types;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
@Getter
+@EqualsAndHashCode
public class Sort {
private String property;
diff --git a/workflow-designer-be/src/test/java/org/onap/sdc/workflow/RestPath.java b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/RestPath.java
index a9408f15..4301feb8 100644
--- a/workflow-designer-be/src/test/java/org/onap/sdc/workflow/RestPath.java
+++ b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/RestPath.java
@@ -48,10 +48,6 @@ public class RestPath {
return WORKFLOWS_URL;
}
- public static String getWorkflowsWithVersionStateFilterPath(String versionState) {
- return String.format(WORKFLOWS_WITH_VERSION_STATE_FILTER_URL, versionState);
- }
-
public static String getWorkflowPath(String workflowId) {
return String.format(WORKFLOW_URL_FORMATTER, workflowId);
}
diff --git a/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/WorkflowControllerTest.java b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/WorkflowControllerTest.java
index 95f7fffa..f8d2aec8 100644
--- a/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/WorkflowControllerTest.java
+++ b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/WorkflowControllerTest.java
@@ -4,7 +4,6 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.onap.sdc.workflow.TestUtil.createWorkflow;
@@ -21,25 +20,29 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import com.amdocs.zusammen.datatypes.Id;
import com.amdocs.zusammen.datatypes.item.Item;
import com.google.gson.Gson;
-import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.onap.sdc.workflow.RestPath;
import org.onap.sdc.workflow.api.exceptionshandlers.CustomizedResponseEntityExceptionHandler;
import org.onap.sdc.workflow.persistence.types.Workflow;
-import org.onap.sdc.workflow.persistence.types.WorkflowVersionState;
import org.onap.sdc.workflow.services.WorkflowManager;
import org.onap.sdc.workflow.services.types.Page;
import org.onap.sdc.workflow.services.types.PagingRequest;
+import org.onap.sdc.workflow.services.types.RequestSpec;
+import org.onap.sdc.workflow.services.types.Sort;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@RunWith(MockitoJUnitRunner.class)
@@ -55,11 +58,13 @@ public class WorkflowControllerTest {
private MockMvc mockMvc;
+ @Mock
+ private WorkflowManager workflowManagerMock;
+ @Captor
+ private ArgumentCaptor<RequestSpec> requestSpecArg;
@InjectMocks
private WorkflowController workflowController;
- @Mock
- private WorkflowManager workflowManagerMock;
@Before
public void setUp() {
@@ -99,109 +104,92 @@ public class WorkflowControllerTest {
}
@Test
- public void shouldReturn5WorkflowWhen5WorkflowsExists() throws Exception {
- int numOfWorkflows = 5;
- Page<Workflow> workflowMocks = createWorkflows(numOfWorkflows);
- doReturn(workflowMocks).when(workflowManagerMock).list(any(), any());
- mockMvc.perform(get(RestPath.getWorkflowsPath()).header(USER_ID_HEADER, USER_ID).contentType(APPLICATION_JSON))
- .andDo(print()).andExpect(status().isOk()).andExpect(jsonPath("$.items", hasSize(numOfWorkflows)));
- }
-
- @Test
- public void listWithValidVersionStateFilter() throws Exception {
- int numOfWorkflows = 3;
- Page<Workflow> workflows = createWorkflows(numOfWorkflows);
- doReturn(workflows).when(workflowManagerMock)
- .list(eq(Collections.singleton(WorkflowVersionState.CERTIFIED)), any());
- mockMvc.perform(
- get(RestPath.getWorkflowsWithVersionStateFilterPath("CERTIFIED")).header(USER_ID_HEADER, USER_ID)
- .contentType(APPLICATION_JSON))
- .andDo(print()).andExpect(status().isOk()).andExpect(jsonPath("$.paging.total", is(numOfWorkflows)))
- .andExpect(jsonPath("$.items", hasSize(numOfWorkflows)));
- }
-
- @Test
- public void listWithInvalidVersionStateFilter() throws Exception {
- int numOfWorkflows = 0;
- Page<Workflow> workflows = createWorkflows(numOfWorkflows);
- doReturn(workflows).when(workflowManagerMock).list(eq(Collections.emptySet()), any());
+ public void listWhenExist() throws Exception {
+ mockManagerList3();
+ ResultActions result = mockMvc.perform(
+ get(RestPath.getWorkflowsPath()).header(USER_ID_HEADER, USER_ID).contentType(APPLICATION_JSON))
+ .andDo(print()).andExpect(status().isOk())
+ .andExpect(jsonPath("$.items", hasSize(3)));
+ for (int i = 0; i < 3; i++) {
+ result.andExpect(jsonPath(String.format("$.items[%s].id", i), is(String.valueOf(i + 1))));
+ }
- mockMvc.perform(
- get(RestPath.getWorkflowsWithVersionStateFilterPath("gibberish")).header(USER_ID_HEADER, USER_ID)
- .contentType(APPLICATION_JSON))
- .andDo(print()).andExpect(status().isOk()).andExpect(jsonPath("$.paging.total", is(numOfWorkflows)));
+ verify(workflowManagerMock).list(any(), requestSpecArg.capture());
+ assertRequestSpec(requestSpecArg.getValue(), DEFAULT_OFFSET, DEFAULT_LIMIT, Collections.emptyList());
}
@Test
- public void shouldReturnSortedLimitOffsetAppliedWorkflows() throws Exception {
- Page<Workflow> workflowMocks = createLimit2AndOffset1For5WorkflowList();
- doReturn(workflowMocks).when(workflowManagerMock).list(any(), any());
+ public void listWhenPagingAndSortingAreSet() throws Exception {
+ mockManagerList3();
mockMvc.perform(get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "2", "1"))
.header(USER_ID_HEADER, USER_ID).contentType(APPLICATION_JSON)).andDo(print())
- .andExpect(status().isOk()).andExpect(jsonPath("$.items", hasSize(2)));
+ .andExpect(status().isOk()).andExpect(jsonPath("$.items", hasSize(3)));
+ verify(workflowManagerMock).list(any(), requestSpecArg.capture());
+ assertRequestSpec(requestSpecArg.getValue(), 1, 2, Collections.singletonList(new Sort("name", true)));
}
-/* @Test
+ @Test
public void shouldReturnResultsWithDefaultWhenLimitIsNegative() throws Exception {
- Page<Workflow> workflowMocks = createLimit2AndOffset1For5WorkflowList();
- doReturn(workflowMocks).when(workflowManagerMock).list(any(), any());
+ mockManagerList3();
mockMvc.perform(get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "-2", "1"))
.header(USER_ID_HEADER, USER_ID).contentType(APPLICATION_JSON)).andDo(print())
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.paging.offset", is(1)))
- .andExpect(jsonPath("$.paging.limit", is(DEFAULT_LIMIT)))
- .andExpect(jsonPath("$.paging.total", is(2)));
- }*/
+ .andExpect(status().isOk()).andExpect(jsonPath("$.items", hasSize(3)));
+ verify(workflowManagerMock).list(any(), requestSpecArg.capture());
+ assertRequestSpec(requestSpecArg.getValue(), 1, DEFAULT_LIMIT,
+ Collections.singletonList(new Sort("name", true)));
+ }
-/* @Test
+ @Test
public void shouldFallbackOnDefaultOffsetWhenOffsetIsNegative() throws Exception {
- mockMvc.perform(
- get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "2", "-1"))
- .header(USER_ID_HEADER, USER_ID).contentType(APPLICATION_JSON)).andDo(print())
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.paging.offset", is(DEFAULT_OFFSET)))
- .andExpect(jsonPath("$.paging.limit", is(2)))
- .andExpect(jsonPath("$.paging.total", is(0)));
- }*/
-
-/* @Test
+ mockManagerList3();
+ mockMvc.perform(get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "2", "-1"))
+ .header(USER_ID_HEADER, USER_ID).contentType(APPLICATION_JSON)).andDo(print())
+ .andExpect(status().isOk()).andExpect(jsonPath("$.items", hasSize(3)));
+ verify(workflowManagerMock).list(any(), requestSpecArg.capture());
+ assertRequestSpec(requestSpecArg.getValue(), DEFAULT_OFFSET, 2,
+ Collections.singletonList(new Sort("name", true)));
+ }
+
+ @Test
public void shouldFallbackOnDefaultLimitWhenLimitIsNotAnInteger() throws Exception {
- mockMvc.perform(
- get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "abc", "0"))
- .header(USER_ID_HEADER, USER_ID).contentType(APPLICATION_JSON)).andDo(print())
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.paging.offset", is(0)))
- .andExpect(jsonPath("$.paging.limit", is(DEFAULT_LIMIT)))
- .andExpect(jsonPath("$.paging.total", is(0)));
- }*/
-
-/* @Test
+ mockManagerList3();
+ mockMvc.perform(get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "abc", "0"))
+ .header(USER_ID_HEADER, USER_ID).contentType(APPLICATION_JSON)).andDo(print())
+ .andExpect(status().isOk()).andExpect(jsonPath("$.items", hasSize(3)));
+ verify(workflowManagerMock).list(any(), requestSpecArg.capture());
+ assertRequestSpec(requestSpecArg.getValue(), 0, DEFAULT_LIMIT,
+ Collections.singletonList(new Sort("name", true)));
+ }
+
+ @Test
public void shouldFallbackOnDefaultOffsetWhenOffsetIsNotAnInteger() throws Exception {
- mockMvc.perform(
- get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "2", "abc"))
- .header(USER_ID_HEADER, USER_ID).contentType(APPLICATION_JSON)).andDo(print())
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.paging.offset", is(DEFAULT_OFFSET)))
- .andExpect(jsonPath("$.paging.limit", is(2)))
- .andExpect(jsonPath("$.paging.total", is(0)));
- }*/
+ mockManagerList3();
+ mockMvc.perform(get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "2", "abc"))
+ .header(USER_ID_HEADER, USER_ID).contentType(APPLICATION_JSON)).andDo(print())
+ .andExpect(jsonPath("$.items", hasSize(3)));
+ verify(workflowManagerMock).list(any(), requestSpecArg.capture());
+ assertRequestSpec(requestSpecArg.getValue(), DEFAULT_OFFSET, 2,
+ Collections.singletonList(new Sort("name", true)));
+ }
@Test
public void shouldReturnDefaultLimitOffsetAppliedWorkflowsWhenLimitIsNotSpecified() throws Exception {
- Page<Workflow> workflowMocks = createLimit2AndOffset1For5WorkflowList();
- doReturn(workflowMocks).when(workflowManagerMock).list(any(), any());
+ mockManagerList3();
mockMvc.perform(get(RestPath.getWorkflowsPathNoSortAndLimit("1")).header(USER_ID_HEADER, USER_ID)
.contentType(APPLICATION_JSON)).andDo(print())
- .andExpect(status().isOk()).andExpect(jsonPath("$.items", hasSize(2)));
+ .andExpect(jsonPath("$.items", hasSize(3)));
+ verify(workflowManagerMock).list(any(), requestSpecArg.capture());
+ assertRequestSpec(requestSpecArg.getValue(), 1, DEFAULT_LIMIT, Collections.emptyList());
}
@Test
public void shouldReturnDefaultOffsetAppliedWorkflowsWhenOffsetIsNotSpecified() throws Exception {
- Page<Workflow> workflowMocks = createLimit1WorkflowList();
- doReturn(workflowMocks).when(workflowManagerMock).list(any(), any());
+ mockManagerList3();
mockMvc.perform(get(RestPath.getWorkflowsPathNoSortAndOffset("1")).header(USER_ID_HEADER, USER_ID)
.contentType(APPLICATION_JSON)).andDo(print())
- .andExpect(status().isOk()).andExpect(jsonPath("$.items", hasSize(1)));
+ .andExpect(status().isOk()).andExpect(jsonPath("$.items", hasSize(3)));
+ verify(workflowManagerMock).list(any(), requestSpecArg.capture());
+ assertRequestSpec(requestSpecArg.getValue(), DEFAULT_OFFSET, 1, Collections.emptyList());
}
@Test
@@ -227,24 +215,25 @@ public class WorkflowControllerTest {
assertEquals("Workflow name must contain only letters, digits and underscores", response.getContentAsString());
}
- private Page<Workflow> createWorkflows(int numOfWorkflows) {
- List<Workflow> workflows = new ArrayList<>(numOfWorkflows);
- for (int i = 0; i < numOfWorkflows; i++) {
- workflows.add(createWorkflow(i, true));
- }
- return new Page<>(workflows, new PagingRequest(0, 200), numOfWorkflows);
+ private void mockManagerList3() {
+ doReturn(new Page<>(Arrays.asList(
+ createWorkflow(1, true),
+ createWorkflow(2, true),
+ createWorkflow(3, true)),
+ new PagingRequest(DEFAULT_OFFSET, DEFAULT_LIMIT), 3))
+ .when(workflowManagerMock).list(any(), any());
}
- private Page<Workflow> createLimit2AndOffset1For5WorkflowList() {
- List<Workflow> workflows = new ArrayList<>();
- workflows.add(createWorkflow(2, true));
- workflows.add(createWorkflow(3, true));
- return new Page<>(workflows, new PagingRequest(1, 200), 5);
- }
-
- private Page<Workflow> createLimit1WorkflowList() {
- List<Workflow> workflows = new ArrayList<>();
- workflows.add(createWorkflow(0, true));
- return new Page<>(workflows, new PagingRequest(0, 1), 1);
+ private static void assertRequestSpec(RequestSpec actual, int expectedOffset, int expectedLimit,
+ List<Sort> expectedSorts) {
+ assertEquals(Integer.valueOf(expectedOffset), actual.getPaging().getOffset());
+ assertEquals(Integer.valueOf(expectedLimit), actual.getPaging().getLimit());
+ if (expectedSorts.isEmpty()) {
+ assertEquals(expectedSorts, actual.getSorting().getSorts());
+ } else {
+ for (int i = 0; i < expectedSorts.size(); i++) {
+ assertEquals(expectedSorts.get(i), actual.getSorting().getSorts().get(i));
+ }
+ }
}
} \ No newline at end of file
diff --git a/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/types/PagingTest.java b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/types/PagingTest.java
new file mode 100644
index 00000000..c2b6cef3
--- /dev/null
+++ b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/types/PagingTest.java
@@ -0,0 +1,71 @@
+package org.onap.sdc.workflow.api.types;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.onap.sdc.workflow.services.types.PagingConstants.MAX_LIMIT;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+public class PagingTest {
+
+ @InjectMocks
+ private Paging paging;
+
+ @Test
+ public void setOffsetNotNumber() {
+ paging.setOffset("aaa");
+ assertNull(paging.getOffset());
+ }
+
+ @Test
+ public void setOffsetNegative() {
+ paging.setOffset("-5");
+ assertNull(paging.getOffset());
+ }
+
+ @Test
+ public void setOffsetZero() {
+ paging.setOffset("0");
+ assertEquals(Integer.valueOf(0), paging.getOffset());
+ }
+
+ @Test
+ public void setOffsetPositive() {
+ paging.setOffset("8");
+ assertEquals(Integer.valueOf(8), paging.getOffset());
+ }
+
+ @Test
+ public void setLimitNotNumber() {
+ paging.setLimit("aaa");
+ assertNull(paging.getLimit());
+ }
+
+ @Test
+ public void setLimitNegative() {
+ paging.setLimit("-5");
+ assertNull(paging.getLimit());
+ }
+
+ @Test
+ public void setLimitZero() {
+ paging.setLimit("0");
+ assertNull(paging.getLimit());
+ }
+
+ @Test
+ public void setLimitPositive() {
+ paging.setLimit("8");
+ assertEquals(Integer.valueOf(8), paging.getLimit());
+ }
+
+ @Test
+ public void setLimitGreaterThanMax() {
+ paging.setLimit("7000");
+ assertEquals(Integer.valueOf(MAX_LIMIT), paging.getLimit());
+ }
+} \ No newline at end of file
diff --git a/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/types/SortingTest.java b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/types/SortingTest.java
new file mode 100644
index 00000000..29436449
--- /dev/null
+++ b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/types/SortingTest.java
@@ -0,0 +1,42 @@
+package org.onap.sdc.workflow.api.types;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.onap.sdc.workflow.services.types.Sort;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+public class SortingTest {
+
+ @InjectMocks
+ private Sorting sorting;
+
+ @Test
+ public void setSortInvalid() {
+ sorting.setSort("a");
+ assertEquals(Collections.emptyList(), sorting.getSorts());
+ }
+
+ @Test
+ public void setSortAsc() {
+ sorting.setSort("name:asc");
+ assertEquals(Collections.singletonList(new Sort("name", true)), sorting.getSorts());
+ }
+
+ @Test
+ public void setSortDesc() {
+ sorting.setSort("name:desc");
+ assertEquals(Collections.singletonList(new Sort("name", false)), sorting.getSorts());
+ }
+
+ @Test
+ public void setSortMoreThanOne() {
+ sorting.setSort("name:asc,date:desc");
+ assertEquals(Arrays.asList(new Sort("name", true), new Sort("date", false)), sorting.getSorts());
+ }
+} \ No newline at end of file
diff --git a/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/types/VersionStatesFormatterTest.java b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/types/VersionStatesFormatterTest.java
new file mode 100644
index 00000000..c57bd9c3
--- /dev/null
+++ b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/types/VersionStatesFormatterTest.java
@@ -0,0 +1,45 @@
+package org.onap.sdc.workflow.api.types;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.onap.sdc.workflow.persistence.types.WorkflowVersionState.CERTIFIED;
+import static org.onap.sdc.workflow.persistence.types.WorkflowVersionState.DRAFT;
+
+import java.util.Collections;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+public class VersionStatesFormatterTest {
+
+ @InjectMocks
+ private VersionStatesFormatter versionStateSet;
+
+ @Test
+ public void setVersionStateInvalid() {
+ versionStateSet.setVersionState("aaa");
+ assertNull(versionStateSet.getVersionStates());
+ }
+
+ @Test
+ public void setVersionStateDraft() {
+ versionStateSet.setVersionState("DRAFT");
+ assertEquals(Collections.singleton(DRAFT), versionStateSet.getVersionStates());
+ }
+
+ @Test
+ public void setVersionStateCertified() {
+ versionStateSet.setVersionState("CERTIFIED");
+ assertEquals(Collections.singleton(CERTIFIED), versionStateSet.getVersionStates());
+ }
+
+ @Test
+ public void setVersionStateBoth() {
+ versionStateSet.setVersionState("DRAFT,CERTIFIED");
+ assertEquals(Stream.of(DRAFT, CERTIFIED).collect(Collectors.toSet()), versionStateSet.getVersionStates());
+ }
+} \ No newline at end of file
diff --git a/workflow-designer-be/src/test/java/org/onap/sdc/workflow/services/impl/WorkflowManagerImplTest.java b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/services/impl/WorkflowManagerImplTest.java
index 0797b648..de17a049 100644
--- a/workflow-designer-be/src/test/java/org/onap/sdc/workflow/services/impl/WorkflowManagerImplTest.java
+++ b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/services/impl/WorkflowManagerImplTest.java
@@ -8,6 +8,9 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.onap.sdc.workflow.TestUtil.createItem;
import static org.onap.sdc.workflow.TestUtil.createWorkflow;
+import static org.onap.sdc.workflow.services.types.PagingConstants.DEFAULT_LIMIT;
+import static org.onap.sdc.workflow.services.types.PagingConstants.DEFAULT_OFFSET;
+import static org.onap.sdc.workflow.services.types.PagingConstants.MAX_LIMIT;
import static org.openecomp.sdc.versioning.dao.types.VersionStatus.Certified;
import java.util.ArrayList;
@@ -18,7 +21,6 @@ import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
-import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
@@ -30,6 +32,7 @@ import org.onap.sdc.workflow.services.exceptions.EntityNotFoundException;
import org.onap.sdc.workflow.services.impl.mappers.VersionStateMapper;
import org.onap.sdc.workflow.services.impl.mappers.WorkflowMapper;
import org.onap.sdc.workflow.services.types.Page;
+import org.onap.sdc.workflow.services.types.Paging;
import org.onap.sdc.workflow.services.types.PagingRequest;
import org.onap.sdc.workflow.services.types.RequestSpec;
import org.onap.sdc.workflow.services.types.Sort;
@@ -53,7 +56,7 @@ public class WorkflowManagerImplTest {
static {
List<Item> items = new ArrayList<>();
List<Workflow> mappedWorkflows = new ArrayList<>();
- for (int i = 1; i < 6; i++) {
+ for (int i = 0; i < 5; i++) {
items.add(createItem(i, true, true));
mappedWorkflows.add(createWorkflow(i, true));
}
@@ -75,17 +78,18 @@ public class WorkflowManagerImplTest {
@Test
public void shouldReturnWorkflowVersionList() {
doReturn(ITEMS).when(itemManagerMock).list(any());
- for (int i = 0; i < ITEMS.size(); i++) {
- doReturn(MAPPED_WORKFLOWS.get(i)).when(workflowMapperMock).itemToWorkflow(ITEMS.get(i));
- }
- Page<Workflow> workflows = workflowManager.list(null, createRequestSpec(20, 0, true, SORT_FIELD_NAME));
+ mockItemToWorkflowMaps();
+ RequestSpec requestSpec = createRequestSpec(0, 20, true);
+ Page<Workflow> workflows = workflowManager.list(null, requestSpec);
Map<String, Workflow> workflowById =
workflows.getItems().stream().collect(Collectors.toMap(Workflow::getId, Function.identity()));
assertEquals(ITEMS.size(), workflows.getItems().size());
- for (int i = 1; i < ITEMS.size() + 1; i++) {
+ for (int i = 0; i < ITEMS.size(); i++) {
assertTrue(workflowById.containsKey(String.valueOf(i)));
}
+ assertPaging(workflows.getPaging(), requestSpec.getPaging().getOffset(), requestSpec.getPaging().getLimit(),
+ ITEMS.size());
}
@Test
@@ -96,14 +100,17 @@ public class WorkflowManagerImplTest {
doReturn(MAPPED_WORKFLOWS.get(0)).when(workflowMapperMock).itemToWorkflow(ITEMS.get(0));
doReturn(MAPPED_WORKFLOWS.get(2)).when(workflowMapperMock).itemToWorkflow(ITEMS.get(2));
- Page<Workflow> workflows = workflowManager.list(Collections.singleton(WorkflowVersionState.CERTIFIED),
- createRequestSpec(20, 0, true, SORT_FIELD_NAME));
+ RequestSpec requestSpec = createRequestSpec(0, 20, true);
+ Page<Workflow> workflows =
+ workflowManager.list(Collections.singleton(WorkflowVersionState.CERTIFIED), requestSpec);
Map<String, Workflow> workflowById =
workflows.getItems().stream().collect(Collectors.toMap(Workflow::getId, Function.identity()));
assertEquals(2, workflows.getItems().size());
- assertTrue(workflowById.containsKey("1"));
- assertTrue(workflowById.containsKey("3"));
+ assertTrue(workflowById.containsKey("0"));
+ assertTrue(workflowById.containsKey("2"));
+
+ assertPaging(workflows.getPaging(), requestSpec.getPaging().getOffset(), requestSpec.getPaging().getLimit(), 2);
}
@Test(expected = EntityNotFoundException.class)
@@ -162,81 +169,183 @@ public class WorkflowManagerImplTest {
}
@Test
- public void shouldListAllWorkflowsWhenLimitAndOffsetAreValid() {
- RequestSpec requestSpec = createRequestSpec(5, 0, true, SORT_FIELD_NAME);
+ public void listWhenRequestSpecIsNull() {
doReturn(ITEMS).when(itemManagerMock).list(any());
- for (int i = 0; i < ITEMS.size(); i++) {
- doReturn(MAPPED_WORKFLOWS.get(i)).when(workflowMapperMock).itemToWorkflow(ITEMS.get(i));
+ mockItemToWorkflowMaps();
+ Page<Workflow> workflows = workflowManager.list(null, null);
+
+ assertEquals(ITEMS.size(), workflows.getItems().size());
+ assertPaging(workflows.getPaging(), DEFAULT_OFFSET, DEFAULT_LIMIT, ITEMS.size());
+
+ // verify sorted ascending by name
+ for (int i = DEFAULT_OFFSET; i < ITEMS.size(); i++) {
+ assertEquals("Workflow_" + i, workflows.getItems().get(i).getName());
}
- Assert.assertEquals(5, workflowManager.list(null, requestSpec).getItems().size());
}
@Test
- public void shouldListLimitFilteredWorkflowsInFirstOffsetRange() {
- RequestSpec requestSpec = createRequestSpec(3, 0, true, SORT_FIELD_NAME);
+ public void listWhenPagingIsNull() {
doReturn(ITEMS).when(itemManagerMock).list(any());
- for (int i = 0; i < ITEMS.size(); i++) {
- doReturn(MAPPED_WORKFLOWS.get(i)).when(workflowMapperMock).itemToWorkflow(ITEMS.get(i));
- }
- Assert.assertEquals(3, workflowManager.list(null, requestSpec).getItems().size());
+ mockItemToWorkflowMaps();
+ Page<Workflow> workflows = workflowManager.list(null, new RequestSpec(null,
+ SortingRequest.builder().sort(new Sort(SORT_FIELD_NAME, true)).build()));
+
+ assertEquals(ITEMS.size(), workflows.getItems().size());
+ assertPaging(workflows.getPaging(), DEFAULT_OFFSET, DEFAULT_LIMIT, ITEMS.size());
}
-/* @Test
- public void shouldListLimitFilteredWorkflowsInSecondOffsetRange() {
- RequestSpec requestSpec = createRequestSpec(3, 1, true, SORT_FIELD_NAME);
+ @Test
+ public void listWhenOffsetAndLimitAreNull() {
doReturn(ITEMS).when(itemManagerMock).list(any());
- for (int i = 0; i < ITEMS.size(); i++) {
- doReturn(MAPPED_WORKFLOWS.get(i)).when(workflowMapperMock).itemToWorkflow(ITEMS.get(i));
- }
- Assert.assertEquals(2, workflowManager.list(null, requestSpec).getItems().size());
- }*/
+ mockItemToWorkflowMaps();
+ RequestSpec requestSpec = new RequestSpec(new PagingRequest(-2, -8),
+ SortingRequest.builder().sort(new Sort(SORT_FIELD_NAME, true)).build());
+ Page<Workflow> workflows = workflowManager.list(null, requestSpec);
+
+ assertEquals(ITEMS.size(), workflows.getItems().size());
+ assertPaging(workflows.getPaging(), DEFAULT_OFFSET, DEFAULT_LIMIT, ITEMS.size());
+ }
@Test
- public void shouldListAllWorkflowsWhenLimitGreaterThanTotalRecordsAndOffsetInRange() {
- RequestSpec requestSpec = createRequestSpec(10, 0, true, SORT_FIELD_NAME);
+ public void listWhenSortingIsNull() {
doReturn(ITEMS).when(itemManagerMock).list(any());
- for (int i = 0; i < ITEMS.size(); i++) {
- doReturn(MAPPED_WORKFLOWS.get(i)).when(workflowMapperMock).itemToWorkflow(ITEMS.get(i));
- }
- Assert.assertEquals(5, workflowManager.list(null, requestSpec).getItems().size());
+ mockItemToWorkflowMaps();
+ RequestSpec requestSpec = new RequestSpec(new PagingRequest(2, 8), null);
+ Page<Workflow> workflows = workflowManager.list(null, requestSpec);
+
+ assertEquals(3, workflows.getItems().size());
+ assertPaging(workflows.getPaging(), requestSpec.getPaging().getOffset(), requestSpec.getPaging().getLimit(),
+ ITEMS.size());
+
+ // verify sorted ascending by name
+ assertEquals("Workflow_2", workflows.getItems().get(0).getName());
+ assertEquals("Workflow_3", workflows.getItems().get(1).getName());
+ assertEquals("Workflow_4", workflows.getItems().get(2).getName());
}
@Test
- public void shouldNotListWorkflowsIfOffsetGreaterThanTotalRecords() {
- RequestSpec requestSpec = createRequestSpec(3, 6, true, SORT_FIELD_NAME);
+ public void listWhenSortingIsEmpty() {
doReturn(ITEMS).when(itemManagerMock).list(any());
- for (int i = 0; i < ITEMS.size(); i++) {
- doReturn(MAPPED_WORKFLOWS.get(i)).when(workflowMapperMock).itemToWorkflow(ITEMS.get(i));
- }
- Assert.assertEquals(0, workflowManager.list(null, requestSpec).getItems().size());
+ mockItemToWorkflowMaps();
+ RequestSpec requestSpec = new RequestSpec(new PagingRequest(2, 8), SortingRequest.builder().build());
+ Page<Workflow> workflows = workflowManager.list(null, requestSpec);
+
+ assertEquals(3, workflows.getItems().size());
+ assertPaging(workflows.getPaging(), requestSpec.getPaging().getOffset(), requestSpec.getPaging().getLimit(),
+ ITEMS.size());
+
+ // verify sorted ascending by name
+ assertEquals("Workflow_2", workflows.getItems().get(0).getName());
+ assertEquals("Workflow_3", workflows.getItems().get(1).getName());
+ assertEquals("Workflow_4", workflows.getItems().get(2).getName());
}
@Test
- public void shouldNotListWorkflowsBothLimitAndOffsetGreaterThanTotalRecords() {
- RequestSpec requestSpec = createRequestSpec(10, 10, true, SORT_FIELD_NAME);
+ public void listWhenRequestSpecIsValid() {
+ RequestSpec requestSpec = createRequestSpec(0, 5, true);
doReturn(ITEMS).when(itemManagerMock).list(any());
- for (int i = 0; i < ITEMS.size(); i++) {
- doReturn(MAPPED_WORKFLOWS.get(i)).when(workflowMapperMock).itemToWorkflow(ITEMS.get(i));
- }
- Assert.assertEquals(0, workflowManager.list(null, requestSpec).getItems().size());
+ mockItemToWorkflowMaps();
+ Page<Workflow> workflows = workflowManager.list(null, requestSpec);
+
+ assertEquals(5, workflows.getItems().size());
+ assertPaging(workflows.getPaging(), requestSpec.getPaging().getOffset(), requestSpec.getPaging().getLimit(),
+ ITEMS.size());
+ }
+
+ @Test
+ public void listWhenLimitIsLessThanTotal() {
+ RequestSpec requestSpec = createRequestSpec(0, 3, true);
+ doReturn(ITEMS).when(itemManagerMock).list(any());
+ mockItemToWorkflowMaps();
+ Page<Workflow> workflows = workflowManager.list(null, requestSpec);
+ assertEquals(3, workflows.getItems().size());
+ assertPaging(workflows.getPaging(), requestSpec.getPaging().getOffset(), requestSpec.getPaging().getLimit(),
+ ITEMS.size());
+ }
+
+
+ @Test
+ public void listWhenOffsetIsNotFirst() {
+ RequestSpec requestSpec = createRequestSpec(3, 1, true);
+ doReturn(ITEMS).when(itemManagerMock).list(any());
+ mockItemToWorkflowMaps();
+ Page<Workflow> workflows = workflowManager.list(null, requestSpec);
+ assertEquals(1, workflows.getItems().size());
+ assertPaging(workflows.getPaging(), requestSpec.getPaging().getOffset(), requestSpec.getPaging().getLimit(),
+ ITEMS.size());
+ }
+
+ @Test
+ public void listWhenLimitIsMoreThanTotal() {
+ RequestSpec requestSpec = createRequestSpec(0, 10, true);
+ doReturn(ITEMS).when(itemManagerMock).list(any());
+ mockItemToWorkflowMaps();
+ Page<Workflow> workflows = workflowManager.list(null, requestSpec);
+ assertEquals(5, workflows.getItems().size());
+ assertPaging(workflows.getPaging(), requestSpec.getPaging().getOffset(), requestSpec.getPaging().getLimit(),
+ ITEMS.size());
+ }
+
+ @Test
+ public void listWhenOffsetIsMoreThanTotal() {
+ RequestSpec requestSpec = createRequestSpec(6, 3, true);
+ doReturn(ITEMS).when(itemManagerMock).list(any());
+ mockItemToWorkflowMaps();
+ Page<Workflow> workflows = workflowManager.list(null, requestSpec);
+ assertEquals(0, workflows.getItems().size());
+ assertPaging(workflows.getPaging(), requestSpec.getPaging().getOffset(), requestSpec.getPaging().getLimit(),
+ ITEMS.size());
+ }
+
+ @Test
+ public void listWhenOffsetIsMoreThanMax() {
+ doReturn(ITEMS).when(itemManagerMock).list(any());
+ mockItemToWorkflowMaps();
+ RequestSpec requestSpec = createRequestSpec(0, 5555, true);
+ Page<Workflow> workflows = workflowManager.list(null, requestSpec);
+
+ assertEquals(ITEMS.size(), workflows.getItems().size());
+ assertPaging(workflows.getPaging(), requestSpec.getPaging().getOffset(), MAX_LIMIT, ITEMS.size());
}
-/* @Test
- public void shouldListLimitOffsetAppliedWorkflowsSortedInDescOrder() {
- RequestSpec requestSpec = createRequestSpec(2, 1, false, SORT_FIELD_NAME);
+ @Test
+ public void listWhenOffsetAndLimitAreMoreThanTotal() {
+ RequestSpec requestSpec = createRequestSpec(10, 10, true);
doReturn(ITEMS).when(itemManagerMock).list(any());
+ mockItemToWorkflowMaps();
+ Page<Workflow> workflows = workflowManager.list(null, requestSpec);
+ assertEquals(0, workflows.getItems().size());
+ assertPaging(workflows.getPaging(), requestSpec.getPaging().getOffset(), requestSpec.getPaging().getLimit(),
+ ITEMS.size());
+ }
+
+ @Test
+ public void listWhenSortedDesc() {
+ RequestSpec requestSpec = createRequestSpec(2, 1, false);
+ doReturn(ITEMS).when(itemManagerMock).list(any());
+ mockItemToWorkflowMaps();
+ Page<Workflow> workflows = workflowManager.list(null, requestSpec);
+ assertEquals(1, workflows.getItems().size());
+ assertPaging(workflows.getPaging(), requestSpec.getPaging().getOffset(), requestSpec.getPaging().getLimit(),
+ ITEMS.size());
+ Iterator<Workflow> workflowIterator = workflows.getItems().iterator();
+ assertEquals("Workflow_2", workflowIterator.next().getName());
+ }
+
+ private void mockItemToWorkflowMaps() {
for (int i = 0; i < ITEMS.size(); i++) {
doReturn(MAPPED_WORKFLOWS.get(i)).when(workflowMapperMock).itemToWorkflow(ITEMS.get(i));
}
- Page<Workflow> workflows = workflowManager.list(null, requestSpec);
- Assert.assertEquals(2, workflows.getItems().size());
- Iterator<Workflow> workflowIterator = workflows.getItems().iterator();
- Assert.assertEquals("Workflow_3", workflowIterator.next().getName());
- Assert.assertEquals("Workflow_2", workflowIterator.next().getName());
- }*/
+ }
- private RequestSpec createRequestSpec(int limit, int offset, boolean isAscending, String sortField) {
+ private static RequestSpec createRequestSpec(int offset, int limit, boolean isAscending) {
return new RequestSpec(new PagingRequest(offset, limit),
- SortingRequest.builder().sort(new Sort(sortField, isAscending)).build());
+ SortingRequest.builder().sort(new Sort(SORT_FIELD_NAME, isAscending)).build());
+ }
+
+ private static void assertPaging(Paging paging, int expectedOffset, int expectedLimit, int expectedTotal) {
+ assertEquals(expectedOffset, paging.getOffset());
+ assertEquals(expectedLimit, paging.getLimit());
+ assertEquals(expectedTotal, paging.getTotal());
}
} \ No newline at end of file
diff --git a/workflow-designer-be/src/test/java/org/onap/sdc/workflow/services/impl/WorkflowVersionManagerImplTest.java b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/services/impl/WorkflowVersionManagerImplTest.java
index 38ab8a4f..be6fe04e 100644
--- a/workflow-designer-be/src/test/java/org/onap/sdc/workflow/services/impl/WorkflowVersionManagerImplTest.java
+++ b/workflow-designer-be/src/test/java/org/onap/sdc/workflow/services/impl/WorkflowVersionManagerImplTest.java
@@ -219,6 +219,9 @@ public class WorkflowVersionManagerImplTest {
public void shouldUploadArtifact() {
Version version = new Version(VERSION1_ID);
version.setStatus(VersionStatus.Draft);
+ VersionState versionState = new VersionState();
+ versionState.setDirty(false);
+ version.setState(versionState);
doReturn(version).when(versioningManagerMock).get(eq(ITEM1_ID), eqVersion(VERSION1_ID));
doReturn(DRAFT).when(versionStateMapperMock).versionStatusToWorkflowVersionState(version.getStatus());
diff --git a/workflow-designer-ui/src/main/frontend/.gitignore b/workflow-designer-ui/src/main/frontend/.gitignore
index d2d69816..3491fa20 100644
--- a/workflow-designer-ui/src/main/frontend/.gitignore
+++ b/workflow-designer-ui/src/main/frontend/.gitignore
@@ -4,7 +4,7 @@
/node_modules
/node-install
.idea/
-
+.vscode/
# testing
/coverage
diff --git a/workflow-designer-ui/src/main/frontend/index.html b/workflow-designer-ui/src/main/frontend/index.html
index c3a6f3b6..09d2d0a9 100644
--- a/workflow-designer-ui/src/main/frontend/index.html
+++ b/workflow-designer-ui/src/main/frontend/index.html
@@ -2,9 +2,7 @@
<html>
<head>
<base href="/">
- <meta charset="utf-8">
- <link rel="stylesheet" href="https://unpkg.com/bpmn-js@2.1.0/dist/assets/diagram-js.css" />
- <link rel="stylesheet" href="https://unpkg.com/bpmn-js@2.1.0/dist/assets/bpmn-font/css/bpmn.css" />
+ <meta charset="utf-8">
<title>SDC Workflow App</title>
</head>
<body>
diff --git a/workflow-designer-ui/src/main/frontend/package.json b/workflow-designer-ui/src/main/frontend/package.json
index 529d9096..bd224ba2 100644
--- a/workflow-designer-ui/src/main/frontend/package.json
+++ b/workflow-designer-ui/src/main/frontend/package.json
@@ -23,6 +23,7 @@
"dateformat": "^3.0.3",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
+ "file-saver": "^1.3.8",
"http-proxy-middleware": "^0.17.4",
"lodash": "^3.0.1",
"md5": "^2.2.1",
diff --git a/workflow-designer-ui/src/main/frontend/resources/scss/components/_notifications.scss b/workflow-designer-ui/src/main/frontend/resources/scss/components/_notifications.scss
index cc44cfbc..855c372e 100644
--- a/workflow-designer-ui/src/main/frontend/resources/scss/components/_notifications.scss
+++ b/workflow-designer-ui/src/main/frontend/resources/scss/components/_notifications.scss
@@ -1,5 +1,6 @@
.workflow-notifications-container {
position: absolute;
+ z-index: 99999;
&.position-top-right {
right: 30px;
top: 50px;
diff --git a/workflow-designer-ui/src/main/frontend/resources/scss/features/_composition.scss b/workflow-designer-ui/src/main/frontend/resources/scss/features/_composition.scss
index 7ab294a2..a159a4b7 100644
--- a/workflow-designer-ui/src/main/frontend/resources/scss/features/_composition.scss
+++ b/workflow-designer-ui/src/main/frontend/resources/scss/features/_composition.scss
@@ -4,12 +4,43 @@
.bpmn-container {
flex-basis: 100%;
- height: 100%;
+ flex-grow: 1
}
-
- .properties-panel {
- &, .bpp-properties-panel {
- height: 100%;
+ .bpmn-sidebar {
+ height: 100%;
+ .properties-panel {
+ &, .bpp-properties-panel {
+ height: 100%;
+ }
+ }
+ .composition-buttons {
+ position: fixed;
+ background-color: #fafafa;
+ left: 265px;
+ bottom: 46px;
+ border: 1px solid lightgray;
+ width: 189px;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ height: 57px;
+ align-items: center;
+ padding: 10px;
+ .divider {
+ height: 35px;
+ border: 1px solid $silver;
+ }
+ .diagram-btn {
+
+ &:hover {
+ fill: $blue;
+ cursor: pointer;
+ }
+ .svg-icon {
+ width: 25px;
+ height: 23px;
+ }
+ }
}
}
}
diff --git a/workflow-designer-ui/src/main/frontend/resources/scss/style.scss b/workflow-designer-ui/src/main/frontend/resources/scss/style.scss
index 95828ae2..49278565 100644
--- a/workflow-designer-ui/src/main/frontend/resources/scss/style.scss
+++ b/workflow-designer-ui/src/main/frontend/resources/scss/style.scss
@@ -1,3 +1,5 @@
+@import '../../node_modules/bpmn-js/dist/assets/diagram-js.css';
+@import '../../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
@import 'common';
@import '../../node_modules/sdc-ui/lib/css/style.css';
@import 'components';
diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/Composition.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/Composition.js
new file mode 100644
index 00000000..d2c273c4
--- /dev/null
+++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/Composition.js
@@ -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 { connect } from 'react-redux';
+import { I18n } from 'react-redux-i18n';
+import { updateComposition } from './compositionActions';
+import CompositionView from './CompositionView';
+import { showErrorModalAction } from '../../../shared/modal/modalWrapperActions';
+import { getComposition } from './compositionSelectors';
+import { getWorkflowName } from '../../workflow/workflowSelectors';
+
+function mapStateToProps(state) {
+ return {
+ composition: getComposition(state),
+ name: getWorkflowName(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: 'Ok'
+ })
+ )
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(CompositionView);
diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js
index ba0351b6..d549456f 100644
--- a/workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js
+++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/CompositionView.js
@@ -5,7 +5,7 @@
* 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
+*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,
@@ -14,78 +14,151 @@
* limitations under the License.
*/
import React, { Component } from 'react';
-import { connect } from 'react-redux';
-
-import BpmnModeler from 'bpmn-js/lib/Modeler';
-// import propertiesPanelModule from 'bpmn-js-properties-panel';
+import fileSaver from 'file-saver';
+import CustomModeler from './custom-modeler';
+import propertiesPanelModule from 'bpmn-js-properties-panel';
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda';
-import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda';
-
+import camundaModuleDescriptor from 'camunda-bpmn-moddle/resources/camunda';
import newDiagramXML from './newDiagram.bpmn';
+import PropTypes from 'prop-types';
+import CompositionButtons from './components/CompositionButtonsPanel';
class CompositionView extends Component {
+ static propTypes = {
+ compositionUpdate: PropTypes.func,
+ showErrorModal: PropTypes.func,
+ composition: PropTypes.string,
+ name: PropTypes.string
+ };
constructor() {
super();
this.generatedId = 'bpmn-container' + Date.now();
+ this.fileInput = React.createRef();
+ this.state = {
+ diagram: false
+ };
}
componentDidMount() {
- this.modeler = new BpmnModeler({
+ const { composition } = this.props;
+
+ this.modeler = new CustomModeler({
propertiesPanel: {
- parent: '#js-properties-navigationSideBar'
+ parent: '#js-properties-panel'
},
additionalModules: [
- //TODO:: need to fix
- // propertiesPanelModule,
+ propertiesPanelModule,
propertiesProviderModule
],
moddleExtensions: {
- camunda: camundaModdleDescriptor
+ camunda: camundaModuleDescriptor
}
});
window.modeler = this.modeler;
this.modeler.attachTo('#' + this.generatedId);
- // let diagramXML =
- // '<?xml version="1.0" encoding="UTF-8"?>\r\n<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"\r\n xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"\r\n xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"\r\n xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"\r\n xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r\n expressionLanguage="http://www.w3.org/1999/XPath"\r\n typeLanguage="http://www.w3.org/2001/XMLSchema"\r\n targetNamespace=""\r\n xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd">\r\n<collaboration id="sid-c0e745ff-361e-4afb-8c8d-2a1fc32b1424">\r\n <participant id="sid-87F4C1D6-25E1-4A45-9DA7-AD945993D06F" name="Customer" processRef="sid-C3803939-0872-457F-8336-EAE484DC4A04">\r\n </participant>\r\n</collaboration>\r\n<process id="sid-C3803939-0872-457F-8336-EAE484DC4A04" isClosed="false" isExecutable="false" name="Customer" processType="None">\r\n <extensionElements/>\r\n <laneSet id="sid-b167d0d7-e761-4636-9200-76b7f0e8e83a">\r\n <lane id="sid-57E4FE0D-18E4-478D-BC5D-B15164E93254">\r\n <flowNodeRef>sid-D7F237E8-56D0-4283-A3CE-4F0EFE446138</flowNodeRef>\r\n <flowNodeRef>sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26</flowNodeRef>\r\n <flowNodeRef>SCAN_OK</flowNodeRef>\r\n <flowNodeRef>sid-E49425CF-8287-4798-B622-D2A7D78EF00B</flowNodeRef>\r\n <flowNodeRef>sid-E433566C-2289-4BEB-A19C-1697048900D2</flowNodeRef>\r\n <flowNodeRef>sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9</flowNodeRef>\r\n </lane>\r\n </laneSet>\r\n <startEvent id="sid-D7F237E8-56D0-4283-A3CE-4F0EFE446138" name="Notices&#10;QR code">\r\n <outgoing>sid-7B791A11-2F2E-4D80-AFB3-91A02CF2B4FD</outgoing>\r\n </startEvent>\r\n <task completionQuantity="1" id="sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26" isForCompensation="false" name="Scan QR code" startQuantity="1">\r\n <incoming>sid-4DC479E5-5C20-4948-BCFC-9EC5E2F66D8D</incoming>\r\n <outgoing>sid-EE8A7BA0-5D66-4F8B-80E3-CC2751B3856A</outgoing>\r\n </task>\r\n <exclusiveGateway gatewayDirection="Diverging" id="SCAN_OK" name="Scan successful?&#10;">\r\n <incoming>sid-EE8A7BA0-5D66-4F8B-80E3-CC2751B3856A</incoming>\r\n <outgoing>sid-8B820AF5-DC5C-4618-B854-E08B71FB55CB</outgoing>\r\n <outgoing>sid-337A23B9-A923-4CCE-B613-3E247B773CCE</outgoing>\r\n </exclusiveGateway>\r\n <task completionQuantity="1" id="sid-E49425CF-8287-4798-B622-D2A7D78EF00B" isForCompensation="false" name="Open product information in mobile app" startQuantity="1">\r\n <incoming>sid-8B820AF5-DC5C-4618-B854-E08B71FB55CB</incoming>\r\n <outgoing>sid-57EB1F24-BD94-479A-BF1F-57F1EAA19C6C</outgoing>\r\n </task>\r\n <endEvent id="sid-E433566C-2289-4BEB-A19C-1697048900D2" name="Is informed">\r\n <incoming>sid-57EB1F24-BD94-479A-BF1F-57F1EAA19C6C</incoming>\r\n </endEvent>\r\n <exclusiveGateway gatewayDirection="Converging" id="sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9">\r\n <incoming>sid-7B791A11-2F2E-4D80-AFB3-91A02CF2B4FD</incoming>\r\n <incoming>sid-337A23B9-A923-4CCE-B613-3E247B773CCE</incoming>\r\n <outgoing>sid-4DC479E5-5C20-4948-BCFC-9EC5E2F66D8D</outgoing>\r\n </exclusiveGateway>\r\n <sequenceFlow id="sid-7B791A11-2F2E-4D80-AFB3-91A02CF2B4FD" sourceRef="sid-D7F237E8-56D0-4283-A3CE-4F0EFE446138" targetRef="sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9"/>\r\n <sequenceFlow id="sid-EE8A7BA0-5D66-4F8B-80E3-CC2751B3856A" sourceRef="sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26" targetRef="SCAN_OK"/>\r\n <sequenceFlow id="sid-57EB1F24-BD94-479A-BF1F-57F1EAA19C6C" sourceRef="sid-E49425CF-8287-4798-B622-D2A7D78EF00B" targetRef="sid-E433566C-2289-4BEB-A19C-1697048900D2"/>\r\n <sequenceFlow id="sid-8B820AF5-DC5C-4618-B854-E08B71FB55CB" name="No" sourceRef="SCAN_OK" targetRef="sid-E49425CF-8287-4798-B622-D2A7D78EF00B"/>\r\n <sequenceFlow id="sid-4DC479E5-5C20-4948-BCFC-9EC5E2F66D8D" sourceRef="sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9" targetRef="sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26"/>\r\n <sequenceFlow id="sid-337A23B9-A923-4CCE-B613-3E247B773CCE" name="Yes" sourceRef="SCAN_OK" targetRef="sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9"/>\r\n</process>\r\n<bpmndi:BPMNDiagram id="sid-74620812-92c4-44e5-949c-aa47393d3830">\r\n <bpmndi:BPMNPlane bpmnElement="sid-c0e745ff-361e-4afb-8c8d-2a1fc32b1424" id="sid-cdcae759-2af7-4a6d-bd02-53f3352a731d">\r\n <bpmndi:BPMNShape bpmnElement="sid-87F4C1D6-25E1-4A45-9DA7-AD945993D06F" id="sid-87F4C1D6-25E1-4A45-9DA7-AD945993D06F_gui" isHorizontal="true">\r\n <omgdc:Bounds height="500.0" width="933.0" x="42.5" y="75.0"/>\r\n <bpmndi:BPMNLabel labelStyle="sid-84cb49fd-2f7c-44fb-8950-83c3fa153d3b">\r\n <omgdc:Bounds height="59.142852783203125" width="12.000000000000014" x="47.49999999999999" y="170.42857360839844"/>\r\n </bpmndi:BPMNLabel>\r\n </bpmndi:BPMNShape>\r\n <bpmndi:BPMNShape bpmnElement="sid-57E4FE0D-18E4-478D-BC5D-B15164E93254" id="sid-57E4FE0D-18E4-478D-BC5D-B15164E93254_gui" isHorizontal="true">\r\n <omgdc:Bounds height="250.0" width="903.0" x="72.5" y="75.0"/>\r\n </bpmndi:BPMNShape>\r\n <bpmndi:BPMNShape bpmnElement="sid-D7F237E8-56D0-4283-A3CE-4F0EFE446138" id="sid-D7F237E8-56D0-4283-A3CE-4F0EFE446138_gui">\r\n <omgdc:Bounds height="30.0" width="30.0" x="150.0" y="165.0"/>\r\n <bpmndi:BPMNLabel labelStyle="sid-e0502d32-f8d1-41cf-9c4a-cbb49fecf581">\r\n <omgdc:Bounds height="22.0" width="46.35714340209961" x="141.8214282989502" y="197.0"/>\r\n </bpmndi:BPMNLabel>\r\n </bpmndi:BPMNShape>\r\n <bpmndi:BPMNShape bpmnElement="sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26" id="sid-52EB1772-F36E-433E-8F5B-D5DFD26E6F26_gui">\r\n <omgdc:Bounds height="80.0" width="100.0" x="352.5" y="140.0"/>\r\n <bpmndi:BPMNLabel labelStyle="sid-84cb49fd-2f7c-44fb-8950-83c3fa153d3b">\r\n <omgdc:Bounds height="12.0" width="84.0" x="360.5" y="172.0"/>\r\n </bpmndi:BPMNLabel>\r\n </bpmndi:BPMNShape>\r\n <bpmndi:BPMNShape bpmnElement="SCAN_OK" id="SCAN_OK_gui" isMarkerVisible="true">\r\n <omgdc:Bounds height="40.0" width="40.0" x="550.0" y="160.0"/>\r\n <bpmndi:BPMNLabel labelStyle="sid-e0502d32-f8d1-41cf-9c4a-cbb49fecf581">\r\n <omgdc:Bounds height="12.0" width="102.0" x="521.0" y="127.0"/>\r\n </bpmndi:BPMNLabel>\r\n </bpmndi:BPMNShape>\r\n <bpmndi:BPMNShape bpmnElement="sid-E49425CF-8287-4798-B622-D2A7D78EF00B" id="sid-E49425CF-8287-4798-B622-D2A7D78EF00B_gui">\r\n <omgdc:Bounds height="80.0" width="100.0" x="687.5" y="140.0"/>\r\n <bpmndi:BPMNLabel labelStyle="sid-84cb49fd-2f7c-44fb-8950-83c3fa153d3b">\r\n <omgdc:Bounds height="36.0" width="83.14285278320312" x="695.9285736083984" y="162.0"/>\r\n </bpmndi:BPMNLabel>\r\n </bpmndi:BPMNShape>\r\n <bpmndi:BPMNShape bpmnElement="sid-E433566C-2289-4BEB-A19C-1697048900D2" id="sid-E433566C-2289-4BEB-A19C-1697048900D2_gui">\r\n <omgdc:Bounds height="28.0" width="28.0" x="865.0" y="166.0"/>\r\n <bpmndi:BPMNLabel labelStyle="sid-e0502d32-f8d1-41cf-9c4a-cbb49fecf581">\r\n <omgdc:Bounds height="11.0" width="62.857147216796875" x="847.5714263916016" y="196.0"/>\r\n </bpmndi:BPMNLabel>\r\n </bpmndi:BPMNShape>\r\n <bpmndi:BPMNShape bpmnElement="sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9" id="sid-5134932A-1863-4FFA-BB3C-A4B4078B11A9_gui" isMarkerVisible="true">\r\n <omgdc:Bounds height="40.0" width="40.0" x="240.0" y="160.0"/>\r\n </bpmndi:BPMNShape>\r\n <bpmndi:BPMNEdge bpmnElement="sid-EE8A7BA0-5D66-4F8B-80E3-CC2751B3856A" id="sid-EE8A7BA0-5D66-4F8B-80E3-CC2751B3856A_gui">\r\n <omgdi:waypoint x="452.5" y="180"/>\r\n <omgdi:waypoint x="550.0" y="180"/>\r\n </bpmndi:BPMNEdge>\r\n <bpmndi:BPMNEdge bpmnElement="sid-8B820AF5-DC5C-4618-B854-E08B71FB55CB" id="sid-8B820AF5-DC5C-4618-B854-E08B71FB55CB_gui">\r\n <omgdi:waypoint x="590.0" y="180"/>\r\n <omgdi:waypoint x="687.5" y="180"/>\r\n <bpmndi:BPMNLabel labelStyle="sid-e0502d32-f8d1-41cf-9c4a-cbb49fecf581">\r\n <omgdc:Bounds height="12.048704338048935" width="16.32155963195521" x="597.8850936986571" y="155"/>\r\n </bpmndi:BPMNLabel>\r\n </bpmndi:BPMNEdge>\r\n <bpmndi:BPMNEdge bpmnElement="sid-7B791A11-2F2E-4D80-AFB3-91A02CF2B4FD" id="sid-7B791A11-2F2E-4D80-AFB3-91A02CF2B4FD_gui">\r\n <omgdi:waypoint x="180.0" y="180"/>\r\n <omgdi:waypoint x="240.0" y="180"/>\r\n </bpmndi:BPMNEdge>\r\n <bpmndi:BPMNEdge bpmnElement="sid-4DC479E5-5C20-4948-BCFC-9EC5E2F66D8D" id="sid-4DC479E5-5C20-4948-BCFC-9EC5E2F66D8D_gui">\r\n <omgdi:waypoint x="280.0" y="180"/>\r\n <omgdi:waypoint x="352.5" y="180"/>\r\n </bpmndi:BPMNEdge>\r\n <bpmndi:BPMNEdge bpmnElement="sid-57EB1F24-BD94-479A-BF1F-57F1EAA19C6C" id="sid-57EB1F24-BD94-479A-BF1F-57F1EAA19C6C_gui">\r\n <omgdi:waypoint x="787.5" y="180.0"/>\r\n <omgdi:waypoint x="865.0" y="180.0"/>\r\n </bpmndi:BPMNEdge>\r\n <bpmndi:BPMNEdge bpmnElement="sid-337A23B9-A923-4CCE-B613-3E247B773CCE" id="sid-337A23B9-A923-4CCE-B613-3E247B773CCE_gui">\r\n <omgdi:waypoint x="570.5" y="200.0"/>\r\n <omgdi:waypoint x="570.5" y="269.0"/>\r\n <omgdi:waypoint x="260.5" y="269.0"/>\r\n <omgdi:waypoint x="260.5" y="200.0"/>\r\n <bpmndi:BPMNLabel labelStyle="sid-e0502d32-f8d1-41cf-9c4a-cbb49fecf581">\r\n <omgdc:Bounds height="21.4285888671875" width="12.0" x="550" y="205"/>\r\n </bpmndi:BPMNLabel>\r\n </bpmndi:BPMNEdge>\r\n </bpmndi:BPMNPlane>\r\n <bpmndi:BPMNLabelStyle id="sid-e0502d32-f8d1-41cf-9c4a-cbb49fecf581">\r\n <omgdc:Font isBold="false" isItalic="false" isStrikeThrough="false" isUnderline="false" name="Arial" size="11.0"/>\r\n </bpmndi:BPMNLabelStyle>\r\n <bpmndi:BPMNLabelStyle id="sid-84cb49fd-2f7c-44fb-8950-83c3fa153d3b">\r\n <omgdc:Font isBold="false" isItalic="false" isStrikeThrough="false" isUnderline="false" name="Arial" size="12.0"/>\r\n </bpmndi:BPMNLabelStyle>\r\n</bpmndi:BPMNDiagram>\r\n</definitions>\r\n\r\n';
- this.importXML(newDiagramXML);
+ this.setDiagram(composition ? composition : newDiagramXML);
+ var eventBus = this.modeler.get('eventBus');
+ eventBus.on('element.out', () => {
+ this.exportDiagramToStore();
+ });
}
- importXML(xml) {
+ setDiagram = diagram => {
+ this.setState(
+ {
+ diagram
+ },
+ this.importXML
+ );
+ };
+
+ importXML = () => {
+ const { diagram } = this.state;
let modeler = this.modeler;
- this.modeler.importXML(xml, function(err) {
+ this.modeler.importXML(diagram, err => {
if (err) {
- return console.error('could not import BPMN file');
+ //TDOD add i18n
+ return this.props.showErrorModal('could not import diagram');
}
let canvas = modeler.get('canvas');
canvas.zoom('fit-viewport');
});
- }
+ };
- exportDiagram() {
+ exportDiagramToStore = () => {
this.modeler.saveXML({ format: true }, (err, xml) => {
if (err) {
- return console.error('could not save diagram');
+ //TODO add i18n
+ return this.props.showErrorModal('could not save diagram');
}
- console.log('Exported diagram: ', xml);
+ return this.props.compositionUpdate(xml);
});
- }
+ };
+
+ exportDiagram = () => {
+ const { name, showErrorModal } = this.props;
+ this.modeler.saveXML({ format: true }, (err, xml) => {
+ if (err) {
+ //TODO add i18n
+ return showErrorModal('could not save diagram');
+ }
+ const blob = new Blob([xml], { type: 'text/html;charset=utf-8' });
+ fileSaver.saveAs(blob, `${name}-diagram.bpmn`);
+ });
+ };
+
+ loadNewDiagram = () => {
+ this.setDiagram(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.setDiagram(xml);
+ this.fileInput.value = '';
+ };
+ reader.readAsText(file);
+ };
render() {
return (
<div className="composition-view content">
- <div className="bpmn-container" id={this.generatedId} />
- <div className="properties-panel" id="js-properties-panel" />
+ <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
+ onBlur={() => {
+ this.exportDiagramToStore();
+ }}
+ className="bpmn-container"
+ id={this.generatedId}
+ />
+ <div className="bpmn-sidebar">
+ <div
+ className="properties-panel"
+ id="js-properties-panel"
+ />
+ <CompositionButtons
+ onClean={this.loadNewDiagram}
+ onDownload={this.exportDiagram}
+ onUpload={this.uploadDiagram}
+ />
+ </div>
</div>
);
}
}
-function mapStateToProps() {
- return {};
-}
-
-function mapDispatchToProps() {
- return {};
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(CompositionView);
+export default CompositionView;
diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButton.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButton.js
new file mode 100644
index 00000000..2fc6618c
--- /dev/null
+++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButton.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 React from 'react';
+import PropTypes from 'prop-types';
+import SVGIcon from 'sdc-ui/lib/react/SVGIcon';
+
+const CompositionButton = ({ onClick, name, title }) => (
+ <div onClick={onClick} className="diagram-btn">
+ <SVGIcon title={title} name={name} />
+ </div>
+);
+
+CompositionButton.propTypes = {
+ onClick: PropTypes.func,
+ className: PropTypes.string,
+ name: PropTypes.string,
+ title: PropTypes.string
+};
+
+export default CompositionButton;
diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButtonsPanel.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButtonsPanel.js
new file mode 100644
index 00000000..add64902
--- /dev/null
+++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/components/CompositionButtonsPanel.js
@@ -0,0 +1,52 @@
+/*
+* 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 }) => (
+ <div className="composition-buttons">
+ <CompositionButton
+ 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
+ data-test-id="composition-download-upload"
+ onClick={onUpload}
+ name="upload"
+ title="upload"
+ />
+ </div>
+);
+
+CompositionButtons.propTypes = {
+ onClean: PropTypes.func,
+ onUpload: PropTypes.func,
+ onDownload: PropTypes.func
+};
+export default CompositionButtons;
diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionActions.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionActions.js
new file mode 100644
index 00000000..3f1755dd
--- /dev/null
+++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionActions.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_COMPOSITION } from './compositionConstants';
+
+export const updateComposition = payload => ({
+ type: SET_COMPOSITION,
+ payload
+});
diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionConstants.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionConstants.js
new file mode 100644
index 00000000..74cab0cb
--- /dev/null
+++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionConstants.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 SET_COMPOSITION = 'composition/SET_COMPOSITION';
diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionReducer.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionReducer.js
new file mode 100644
index 00000000..9c707362
--- /dev/null
+++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionReducer.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 { SET_COMPOSITION } from './compositionConstants';
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case SET_COMPOSITION:
+ return {
+ diagram: action.payload
+ };
+ default:
+ return state;
+ }
+};
diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionSelectors.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionSelectors.js
new file mode 100644
index 00000000..7e28ca64
--- /dev/null
+++ b/workflow-designer-ui/src/main/frontend/src/features/version/composition/compositionSelectors.js
@@ -0,0 +1,17 @@
+/*
+* 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;
diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomContextPadProvider.js b/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/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/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomElementFactory.js b/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/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/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomPalette.js b/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/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/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRenderer.js b/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/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/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomRules.js b/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/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/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/CustomUpdater.js b/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/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/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/custom/index.js b/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/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/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/index.js b/workflow-designer-ui/src/main/frontend/src/features/version/composition/custom-modeler/index.js
new file mode 100644
index 00000000..86fbff6a
--- /dev/null
+++ b/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/workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js b/workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js
index 7848b1f0..4e591c9b 100644
--- a/workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js
+++ b/workflow-designer-ui/src/main/frontend/src/features/version/versionApi.js
@@ -41,6 +41,21 @@ const Api = {
}
);
},
+ fetchVersionArtifact: ({ workflowId, versionId }) => {
+ return RestfulAPIUtil.fetch(
+ `${baseUrl(workflowId)}/${versionId}/artifact`
+ );
+ },
+ updateVersionArtifact: ({ workflowId, versionId, payload }) => {
+ let formData = new FormData();
+ var blob = new Blob([payload], { type: 'text/xml' });
+ formData.append('fileToUpload', blob);
+
+ return RestfulAPIUtil.put(
+ `${baseUrl(workflowId)}/${versionId}/artifact`,
+ formData
+ );
+ },
certifyVersion: ({ workflowId, versionId }) => {
return RestfulAPIUtil.post(
`${baseUrl(workflowId)}/${versionId}/state`,
diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js b/workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js
index 9010ed15..19c8bdc2 100644
--- a/workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js
+++ b/workflow-designer-ui/src/main/frontend/src/features/version/versionController/versionControllerSelectors.js
@@ -20,14 +20,17 @@ import {
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, general) => ({
+ (outputs, inputs, composition, general) => ({
outputs,
inputs,
+ composition,
...general
})
);
diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js b/workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js
index 9ef88f9b..78b82ab1 100644
--- a/workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js
+++ b/workflow-designer-ui/src/main/frontend/src/features/version/versionSaga.js
@@ -32,14 +32,24 @@ 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 } from 'features/version/composition/compositionActions';
function* fetchVersion(action) {
try {
const data = yield call(versionApi.fetchVersion, action.payload);
const { inputs, outputs, ...rest } = data;
+ let composition = false;
+
+ if (rest.hasArtifact) {
+ composition = yield call(
+ versionApi.fetchVersionArtifact,
+ action.payload
+ );
+ }
yield all([
put(setWorkflowVersionAction(rest)),
- put(setInputsOutputs({ inputs, outputs }))
+ put(setInputsOutputs({ inputs, outputs })),
+ put(updateComposition(composition))
]);
} catch (error) {
yield put(genericNetworkErrorAction(error));
@@ -62,7 +72,22 @@ function* watchSubmitVersion(action) {
function* watchUpdateVersion(action) {
try {
- yield call(versionApi.updateVersion, action.payload);
+ //const { composition, ...versionData } = action.payload;
+ const {
+ workflowId,
+ params: { composition, ...versionData }
+ } = action.payload;
+ yield call(versionApi.updateVersion, {
+ workflowId,
+ params: versionData
+ });
+ if (composition) {
+ yield call(versionApi.updateVersionArtifact, {
+ workflowId,
+ versionId: versionData.id,
+ payload: composition
+ });
+ }
yield put(
notificationActions.showSuccess({
title: 'Update Workflow Version',
diff --git a/workflow-designer-ui/src/main/frontend/src/i18n/languages.json b/workflow-designer-ui/src/main/frontend/src/i18n/languages.json
index 986cf128..5fd444a6 100644
--- a/workflow-designer-ui/src/main/frontend/src/i18n/languages.json
+++ b/workflow-designer-ui/src/main/frontend/src/i18n/languages.json
@@ -59,6 +59,9 @@
"confirmDlete": "Are you sure you want to delete \"%{name}\"?",
"alreadyExists": "Already exists",
"invalidCharacters": "Alphanumeric and underscore only"
+ },
+ "composition": {
+ "bpmnError" : "BPMN.IO Error"
}
},
"version": {
diff --git a/workflow-designer-ui/src/main/frontend/src/rootReducers.js b/workflow-designer-ui/src/main/frontend/src/rootReducers.js
index 9dbef266..9fab8140 100644
--- a/workflow-designer-ui/src/main/frontend/src/rootReducers.js
+++ b/workflow-designer-ui/src/main/frontend/src/rootReducers.js
@@ -25,14 +25,15 @@ 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';
export default combineReducers({
i18n: i18nReducer,
catalog,
notifications: notificationsReducer,
currentVersion: combineReducers({
general: versionReducer,
- inputOutput
+ inputOutput,
+ composition: compositionReducer
}),
workflow: combineReducers({
data: workflowReducer,
diff --git a/workflow-designer-ui/src/main/frontend/src/routes.js b/workflow-designer-ui/src/main/frontend/src/routes.js
index 79abe303..ea433cca 100644
--- a/workflow-designer-ui/src/main/frontend/src/routes.js
+++ b/workflow-designer-ui/src/main/frontend/src/routes.js
@@ -19,7 +19,7 @@ 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 CompositionView from 'features/version/composition/CompositionView';
+import Composition from 'features/version/composition/Composition';
export const routes = [
{
@@ -41,7 +41,7 @@ export const routes = [
},
{
path: '/composition',
- component: CompositionView,
+ component: Composition,
i18nName: 'workflow.sideBar.composition',
id: 'COMPOSITION'
}
diff --git a/workflow-designer-ui/src/main/frontend/webpack.config.js b/workflow-designer-ui/src/main/frontend/webpack.config.js
index 23f53343..fa4e5d83 100644
--- a/workflow-designer-ui/src/main/frontend/webpack.config.js
+++ b/workflow-designer-ui/src/main/frontend/webpack.config.js
@@ -127,8 +127,13 @@ module.exports = (env, argv) => {
include: srcPath
},
{
- test: /\.woff|\.woff2$/,
- loader: 'file-loader'
+ test: /\.(eot|svg|ttf|woff|woff2)(\?.*)?$/,
+ use: [
+ {
+ loader: 'file-loader',
+ options: {}
+ }
+ ]
}
]
},
diff --git a/workflow-designer-ui/src/main/frontend/yarn.lock b/workflow-designer-ui/src/main/frontend/yarn.lock
index 7e31f62b..dbfc8bbe 100644
--- a/workflow-designer-ui/src/main/frontend/yarn.lock
+++ b/workflow-designer-ui/src/main/frontend/yarn.lock
@@ -4450,6 +4450,10 @@ file-loader@^1.1.11:
loader-utils "^1.0.2"
schema-utils "^0.4.5"
+file-saver@^1.3.8:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8"
+
filename-regex@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"