diff options
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 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? ">\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" |