This is an automated email from the ASF dual-hosted git repository. andreapatricelli pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push: new ddd30f0 [SYNCOPE-1381] User requests and user request forms management in enduser UI ddd30f0 is described below commit ddd30f0128e5ac3f1834f21c2e3accacd3a900a5 Author: Andrea Patricelli <andreapatrice...@apache.org> AuthorDate: Wed Dec 5 12:42:09 2018 +0100 [SYNCOPE-1381] User requests and user request forms management in enduser UI --- .../client/enduser/resources/BaseResource.java | 4 +- .../app/css/accessibility/accessibilityFont.css | 5 +- .../app/css/accessibility/accessibilityHC.css | 57 +++++- .../resources/META-INF/resources/app/css/app.css | 10 ++ .../META-INF/resources/app/css/editUser.css | 14 +- .../resources/META-INF/resources/app/index.html | 8 + .../resources/META-INF/resources/app/js/app.js | 69 ++++++++ .../resources/app/js/controllers/UserController.js | 19 +- .../resources/app/js/directives/bpmnProcesses.js | 39 +++++ .../resources/app/js/directives/formProperty.js | 83 +++++++++ .../resources/app/js/directives/modalContent.js | 31 ++++ .../resources/app/js/directives/modalWindow.js | 43 +++++ .../resources/app/js/directives/requestForms.js | 110 ++++++++++++ .../resources/app/js/directives/requests.js | 192 +++++++++++++++++++++ .../app/js/services/bpmnProcessService.js | 38 ++++ .../app/js/services/userRequestsService.js | 78 +++++++++ .../resources/app/languages/de/static.json | 9 +- .../resources/app/languages/en/static.json | 7 + .../resources/app/languages/it/static.json | 7 + .../resources/app/languages/ja/static.json | 7 + .../resources/app/views/bpmnProcesses.html | 31 ++++ .../META-INF/resources/app/views/formProperty.html | 92 ++++++++++ .../META-INF/resources/app/views/modalWindow.html | 30 ++++ .../META-INF/resources/app/views/requestForms.html | 61 +++++++ .../META-INF/resources/app/views/requests.html | 76 ++++++++ .../app/views/templates/editUserTemplate.html | 2 +- .../resources/app/views/user-request-forms.html | 48 ++++++ .../resources/app/views/user-requests.html | 48 ++++++ .../enduser/src/main/resources/customTemplate.json | 6 + .../syncope/client/console/pages/Flowable.java | 4 +- .../common/lib/types/FlowableEntitlement.java | 2 - .../core/flowable/impl/FlowableRuntimeUtils.java | 4 +- .../syncope/core/logic/BpmnProcessLogic.java | 2 +- ext/flowable/pom.xml | 1 + .../syncope-ext-flowable-client-enduser/pom.xml | 80 +++++++++ .../client/enduser/resources/BpmnProcessList.java | 87 ++++++++++ .../resources/UserRequestCancelResource.java | 101 +++++++++++ .../resources/UserRequestFormClaimResource.java | 91 ++++++++++ .../resources/UserRequestsFormsResource.java | 165 ++++++++++++++++++ .../enduser/resources/UserRequestsResource.java | 130 ++++++++++++++ .../resources/UserRequestsStartResource.java | 86 +++++++++ fit/enduser-reference/pom.xml | 6 + .../src/main/resources/customTemplate.json | 3 + .../src/test/resources/customTemplate.json | 6 + 44 files changed, 1974 insertions(+), 18 deletions(-) diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseResource.java index 2540f5d..9e7ebb5 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseResource.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseResource.java @@ -18,6 +18,7 @@ */ package org.apache.syncope.client.enduser.resources; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; @@ -34,7 +35,8 @@ public abstract class BaseResource extends AbstractResource { protected static final Logger LOG = LoggerFactory.getLogger(BaseResource.class); - protected static final ObjectMapper MAPPER = new ObjectMapper(); + protected static final ObjectMapper MAPPER = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); protected final boolean xsrfCheck(final HttpServletRequest request) { final String requestXSRFHeader = request.getHeader(SyncopeEnduserConstants.XSRF_HEADER_NAME); diff --git a/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityFont.css b/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityFont.css index fa4310c..0f4ef7f 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityFont.css +++ b/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityFont.css @@ -34,7 +34,10 @@ body { .suggestions, .k-notification-wrap, .btn-secondary, - .card-body #attribute .fa { +div[role="tablist"] .card-header a, +div[role="tablist"] .card-collapse, +ul.pagination, +.card-body #attribute .fa { font-size: 20px; } diff --git a/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityHC.css b/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityHC.css index 235daaf..0d9c90d 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityHC.css +++ b/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityHC.css @@ -37,7 +37,8 @@ body { #next, #save, #finish, -#resetpassword { +#resetpassword, +.modal-content { background: #464646; color: #ffffff; } @@ -104,6 +105,59 @@ span.k-dropdown, color: #ffffff; } +.modal-content { + border: solid 1px #ffffff; +} + +.btn-warning { + background-color: #70420A; +} + +.btn-warning:hover { + background-color: #F3AD57; + color: #000000; +} + +.btn-primary { + background-color: #205179; +} + +.btn-primary:hover { + background-color: #2F78B3; + color: #ffffff; +} + +.pagination>.disabled>a, .pagination>.disabled>a:focus, .pagination>.disabled>a:hover, .pagination>.disabled>span, +.pagination>.disabled>span:focus, .pagination>.disabled>span:hover { + color: #ffffff; + background-color: #343434; + border-color: #ddd; +} + +.pagination>.active>a, .pagination>.active>a:focus, .pagination>.active>a:hover, .pagination>.active>span, +.pagination>.active>span:focus, .pagination>.active>span:hover { + color: #ffffff; + background-color: #343434; + border-color: #ffffff; +} + +.pagination>.active>a, .pagination>.active>span { + color: #343434; + background-color: #ffffff; + border-color: #ffffff; +} + +.pagination>li>a, .pagination>li>span { + color: #ffffff; + background-color: #343434; + border-color: #ffffff; +} + +.pagination>li>a:focus, .pagination>li>a:hover, .pagination>li>span:focus, .pagination>li>span:hover { + color: #343434; + background-color: #ffffff; + border-color: #ffffff; +} /* Login ============================================================================= */ @@ -141,7 +195,6 @@ span.k-dropdown, #login-container .login-btn { color: #ffffff; background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#6a9647), color-stop(100%,#48543d)); - background: #464646; } diff --git a/client/enduser/src/main/resources/META-INF/resources/app/css/app.css b/client/enduser/src/main/resources/META-INF/resources/app/css/app.css index 494fb66..301a19b 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/css/app.css +++ b/client/enduser/src/main/resources/META-INF/resources/app/css/app.css @@ -97,4 +97,14 @@ under the License. background-color: #fff; border-color: #ccc; } + +.pagination-size { + margin: 20px 0; + width: 20%; +} + +.pagination-size-sm { + margin: 20px 0; + width: 12%; +} /* Useless with Bootstrap > 3 --> */ \ No newline at end of file diff --git a/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css b/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css index 536b41a..47d64d6 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css +++ b/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css @@ -179,7 +179,7 @@ span.k-datetimepicker{ width: 170%; } -#date{ +#date, #date-property{ position: relative; display: table; border-collapse: separate; @@ -309,6 +309,14 @@ div[role="tablist"] { margin-bottom: 2px; } +.schema-type #date-property { + margin-bottom: 2px; +} + +.property-label{ + margin-top: 15px +} + .multivalue input{ width: calc(100% - 70px); margin-top: 10px @@ -636,7 +644,7 @@ div[role="tablist"] { width:100px; } - #date{ + #date, #date-property{ position: relative; display: table; border-collapse: separate; @@ -686,7 +694,7 @@ div[role="tablist"] { margin-top: 0px } - #date{ + #date, #date-property{ position: relative; display: table; border-collapse: separate; diff --git a/client/enduser/src/main/resources/META-INF/resources/app/index.html b/client/enduser/src/main/resources/META-INF/resources/app/index.html index 8819fdd..7122b50 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/index.html +++ b/client/enduser/src/main/resources/META-INF/resources/app/index.html @@ -124,6 +124,8 @@ under the License. <script src="js/services/oidcProviderService.js"></script> <script src="js/services/saml2SPService.js"></script> <script src="js/services/oidcClientService.js"></script> + <script src="js/services/bpmnProcessService.js"></script> + <script src="js/services/userRequestsService.js"></script> <!--controllers--> <script src="js/controllers/HomeController.js"></script> <script src="js/controllers/LoginController.js"></script> @@ -142,6 +144,9 @@ under the License. <script src="js/directives/loader.js"></script> <script src="js/directives/captcha.js"></script> <script src="js/directives/resources.js"></script> + <script src="js/directives/requests.js"></script> + <script src="js/directives/requestForms.js"></script> + <script src="js/directives/formProperty.js"></script> <script src="js/directives/groups.js"></script> <script src="js/directives/auxClasses.js"></script> <script src="js/directives/validate.js"></script> @@ -149,6 +154,9 @@ under the License. <script src="js/directives/validateDropdown.js"></script> <script src="js/directives/fileInput.js"></script> <script src="js/directives/dynamicTemplateItem.js"></script> + <script src="js/directives/modalWindow.js"></script> + <script src="js/directives/bpmnProcesses.js"></script> + <script src="js/directives/modalContent.js"></script> <script src="js/directives/ngEnter.js"></script> <!--validator--> <script src="js/validator/validationRules.js"></script> diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js index 9e3128b..18f0d53 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js @@ -226,6 +226,26 @@ app.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', '$translate }] } }) + .state('update.userRequests', { + url: '/user-requests', + templateUrl: 'views/user-requests.html', + resolve: { + 'authenticated': ['AuthService', + function (AuthService) { + return AuthService.islogged(); + }] + } + }) + .state('update.userRequestForms', { + url: '/user-request-forms', + templateUrl: 'views/user-request-forms.html', + resolve: { + 'authenticated': ['AuthService', + function (AuthService) { + return AuthService.islogged(); + }] + } + }) .state('update.finish', { url: '/finish', templateUrl: 'views/user-form-finish.html', @@ -430,6 +450,9 @@ app.controller('ApplicationController', ['$scope', '$rootScope', 'InfoService', * Wizard steps from JSON */ $scope.wizard = response.wizard.steps; + $scope.creationWizard = $scope.clone($scope.wizard); + delete $scope.creationWizard['userRequests']; + delete $scope.creationWizard['userRequestForms']; $scope.wizardFirstStep = response.wizard.firstStep; callback($rootScope.dynTemplate); @@ -549,6 +572,15 @@ app.controller('ApplicationController', ['$scope', '$rootScope', 'InfoService', } }; + /* + * Date formatters + */ + + // from timestamp + $rootScope.formatDate = function (timestamp) { + return new Date(timestamp).toLocaleString(); + }; + /* |-------------------------------------------------------------------------- | Notification mgmt @@ -650,4 +682,41 @@ app.controller('ApplicationController', ['$scope', '$rootScope', 'InfoService', $templateCache.removeAll(); }; }; + + $scope.clone = function clone(obj) { + var copy; + + // Handle the 3 simple types, and null or undefined + if (null == obj || "object" != typeof obj) + return obj; + + // Handle Date + if (obj instanceof Date) { + copy = new Date(); + copy.setTime(obj.getTime()); + return copy; + } + + // Handle Array + if (obj instanceof Array) { + copy = []; + for (var i = 0, len = obj.length; i < len; i++) { + copy[i] = clone(obj[i]); + } + return copy; + } + + // Handle Object + if (obj instanceof Object) { + copy = {}; + for (var attr in obj) { + if (obj.hasOwnProperty(attr)) + copy[attr] = clone(obj[attr]); + } + return copy; + } + + throw new Error("Unable to copy obj! Its type isn't supported."); + }; + }]); diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js index f919b08..f81747b 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js @@ -23,10 +23,11 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$location', "$state", 'UserSelfService', 'SchemaService', 'RealmService', 'ResourceService', 'SecurityQuestionService', - 'GroupService', 'AnyService', 'UserUtil', 'GenericUtil', 'ValidationExecutor', '$translate', '$filter', + 'GroupService', 'AnyService', 'BpmnProcessService', 'UserRequestsService', 'UserUtil', 'GenericUtil', + 'ValidationExecutor', '$translate', '$filter', function ($scope, $rootScope, $location, $state, UserSelfService, SchemaService, RealmService, - ResourceService, SecurityQuestionService, GroupService, AnyService, UserUtil, GenericUtil, ValidationExecutor, - $translate, $filter) { + ResourceService, SecurityQuestionService, GroupService, AnyService, BpmnProcessService, UserRequestsService, + UserUtil, GenericUtil, ValidationExecutor, $translate, $filter) { $scope.user = {}; $scope.confirmPassword = { @@ -37,6 +38,9 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l $scope.availableRealms = []; $scope.availableSecurityQuestions = []; + $scope.bpmnProcesses = []; + $scope.userRequests = []; + $scope.userRequestsForms = []; $scope.initialSecurityQuestion = ''; $scope.captchaInput = { @@ -334,6 +338,15 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l initUserSchemas(); // initialize available resources initResources(); + // initialize user requests +// if (!$scope.createMode && $scope.wizard.userRequests) { +// console.debug("About to init user requests data"); +// initBpmnProcesses(); +// // this call will ever get the first 10 User Requests +// initUserRequests(); +// // this call will ever get the first 10 User Requests Forms +// initUserRequestsForms(); +// } }; var readUser = function () { diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/bpmnProcesses.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/bpmnProcesses.js new file mode 100644 index 0000000..58c8e71 --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/bpmnProcesses.js @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +angular.module('self').directive('bpmnProcesses', [function () { + return { + restrict: 'E', + templateUrl: 'views/bpmnProcesses.html', + scope: { + }, + controller: function ($scope) { + $scope.bpmnProcesses = $scope.$parent.$parent.resolve.bpmnProcesses; + $scope.selectedProcesses = $scope.$parent.$parent.resolve.selectedProcesses; + $scope.toggleSelection = + function (bpmnProcessKey) { + var index = $scope.selectedProcesses.indexOf(bpmnProcessKey); + if (index > -1) { + $scope.selectedProcesses.splice(index, 1); + } else { + $scope.selectedProcesses.push(bpmnProcessKey); + } + }; + }}; + }]); + diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/formProperty.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/formProperty.js new file mode 100644 index 0000000..39ad82a --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/formProperty.js @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +'use strict'; + +angular.module('self') + .directive('formProperty', ['$rootScope', function ($rootScope) { + return { + restrict: 'E', + templateUrl: 'views/formProperty.html', + scope: { + property: "=" + }, + controller: function ($scope) { + $scope.initAttribute = function () { + switch ($scope.property.type) { + case "Long": + $scope.property.value = Number($scope.property.value) || undefined; + break; + case "Enum": + if ($scope.property.required !== "true") { + $scope.property.enumValues.empty = ""; + } + $scope.enumKeys = Object.keys($scope.property.enumValues); + $scope.property.value = $scope.property.value || $scope.property.enumValues.empty; + break; + case "Dropdown": + if ($scope.property.required !== "true") { + $scope.property.dropdownValues.empty = ""; + } + $scope.dropdownKeys = Object.keys($scope.property.dropdownValues); + $scope.property.value = $scope.property.value || $scope.property.dropdownValues.empty; + break; + case "Date": + $scope.getType = function (x) { + return typeof x; + }; + $scope.isDate = function (x) { + return x instanceof Date; + }; + $scope.languageid = $rootScope.languages.selectedLanguage.id; + $scope.isDateOnly = $scope.property.datePattern.indexOf("H") === -1 + && $scope.property.datePattern.indexOf("h") === -1; + $scope.languageFormat = $scope.isDateOnly + ? $rootScope.languages.selectedLanguage.format.replace(" HH:mm", "") + : $rootScope.languages.selectedLanguage.format; + $scope.languageCulture = $rootScope.languages.selectedLanguage.culture; + // read date in milliseconds + $scope.selectedDate = $scope.property.value === null + ? undefined + : new Date($scope.property.value * 1); + $scope.bindDateToModel = function (selectedDate, extendedDate) { + if (selectedDate) { + // save date in milliseconds + $scope.property.value = new Date(extendedDate).getTime(); + } + }; + break; + case "Boolean": + $scope.property.value = $scope.property.value === "true" ? "true" : "false"; + break; + + } + }; + + } + }; + }]); \ No newline at end of file diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/modalContent.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/modalContent.js new file mode 100644 index 0000000..f0aed20 --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/modalContent.js @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +angular.module('self').directive('modalContent', ['$compile', function ($compile) { + return { + restrict: 'E', + scope: { + modalHtml: "=" + }, + replace: true, + link: function ($scope, element, attrs) { + var template = $compile($scope.modalHtml)($scope); + element.replaceWith(template); + }}; + }]); + diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/modalWindow.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/modalWindow.js new file mode 100644 index 0000000..3ad18e9 --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/modalWindow.js @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +angular.module('self').directive('modalWindow', [function () { + return { + restrict: 'E', + templateUrl: 'views/modalWindow.html', + scope: { + resolve: '<', + close: '&', + dismiss: '&' + }, + controller: function ($scope) { + $scope.init = function () { + // Please provide an init logic, by default do nothing + }; + + $scope.ok = function () { + $scope.close({$value: $scope.resolve.result}); + }; + + $scope.cancel = function () { + $scope.dismiss({$value: 'cancel'}); + }; + + }}; + }]); + diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requestForms.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requestForms.js new file mode 100644 index 0000000..4512257 --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requestForms.js @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +'use strict'; + +angular.module('self') + .directive('requestForms', ['UserRequestsService', + function (UserRequestsService) { + return { + restrict: 'E', + templateUrl: 'views/requestForms.html', + scope: { + user: "=" + }, + controller: function ($scope) { + // Initialization + $scope.query = { + user: $scope.user.username, + page: 1, + size: 10 + }; + + var calculatePages = function () { + $scope.totalPages = Math.ceil($scope.forms.totalCount / $scope.query.size); + $scope.pages = _.range(1, $scope.totalPages + 1); + }; + + // <Pagination> + $scope.reloadPage = function (page, size, successMsg) { + // update query pagination parameters + $scope.query.page = page; + $scope.query.size = size; + // recalculate pages + calculatePages(); + if (page < 1 || page > $scope.totalPages) { + return; + } + // get current page of items + + $scope.getUserRequestForms($scope.query, function (forms) { + $scope.forms = forms; + $scope.$parent.showSuccess(successMsg, $scope.$parent.notification); + }); + }; + // </Pagination> + + var init = function () { + $scope.getUserRequestForms({ + user: $scope.user.username, + page: 1, + size: 10 + }, function (requestsForms) { + $scope.forms = requestsForms; + calculatePages(); + $scope.availableSizes = [{id: 1, value: 10}, {id: 2, value: 25}, {id: 3, value: 50}]; + $scope.selectedSize = $scope.availableSizes[0]; + }); + }; + + $scope.getUserRequestForms = function (query, callback) { + UserRequestsService.getUserRequestForms(query).then(function (response) { + callback(response); + }, function (response) { + var errorMessage; + // parse error response + if (response !== undefined) { + errorMessage = response.split("ErrorMessage{{")[1]; + errorMessage = errorMessage.split("}}")[0]; + } + console.error("Error retrieving User Request Forms: ", errorMessage); + }); + }; + + init(); + + $scope.submit = function (form) { + UserRequestsService.submitForm(form).then(function (response) { + console.debug("Form successfully submitted"); + $scope.$parent.showSuccess("Form successfully submitted", $scope.$parent.notification); + $scope.reloadPage($scope.query.page, $scope.query.size, "Form successfully submitted"); + }, function (response) { + var errorMessage; + // parse error response + if (response !== undefined) { + errorMessage = response.split("ErrorMessage{{")[1]; + errorMessage = errorMessage.split("}}")[0]; + } + console.error("Error retrieving User Request Forms: ", errorMessage); + $scope.$parent.showError("Error: " + (errorMessage || response), $scope.$parent.notification); + }); + + }; + } + }; + }]); \ No newline at end of file diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requests.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requests.js new file mode 100644 index 0000000..64d15a5 --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requests.js @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +'use strict'; + +angular.module('self') + .directive('requests', ['UserRequestsService', 'BpmnProcessService', "$uibModal", "$document", '$filter', + '$rootScope', + function (UserRequestsService, BpmnProcessService, $uibModal, $document, $filter, $rootScope) { + return { + restrict: 'E', + templateUrl: 'views/requests.html', + scope: { + user: "=" + }, + controller: function ($scope) { + // Initialization + $scope.query = { + user: $scope.user.username, + page: 1, + size: 10 + }; + + var calculatePages = function () { + $scope.totalPages = Math.ceil($scope.requests.totalCount / $scope.query.size); + $scope.pages = _.range(1, $scope.totalPages + 1); + }; + + // <Pagination> + $scope.reloadPage = function (page, size, successMsg) { + // update query pagination parameters + $scope.query.page = page; + $scope.query.size = size; + // recalculate pages + calculatePages(); + if (page < 1 || page > $scope.totalPages) { + return; + } + // get current page of items + + $scope.getUserRequests($scope.query, function (requests) { + $scope.requests = requests; + if (successMsg) { + $scope.$parent.showSuccess(successMsg, $scope.$parent.notification); + } + }); + }; + // </Pagination> + + $scope.getUserRequests = function (query, callback) { + UserRequestsService.getUserRequests(query).then(function (response) { + callback(response); + }, function (response) { + var errorMessage; + // parse error response + console.log("ERROR ", response); + if (response !== undefined) { + errorMessage = response.split("ErrorMessage{{")[1]; + errorMessage = errorMessage.split("}}")[0]; + } + console.error("Error retrieving User Requests: ", errorMessage); + $scope.$parent.showError("Error: " + (errorMessage || response), $scope.$parent.notification); + }); + }; + + var init = function () { + $scope.requests = []; + $scope.getUserRequests($scope.query, function (requests) { + $scope.requests = requests; + calculatePages(); + $scope.availableSizes = [{id: 1, value: 10}, {id: 2, value: 25}, {id: 3, value: 50}]; + $scope.selectedSize = $scope.availableSizes[0]; + // date formatting + $scope.formatDate = $rootScope.formatDate; + }); + + }; + + var initBpmnProcesses = function () { + $scope.bpmnProcesses = []; + BpmnProcessService.getBpmnProcesses().then(function (response) { + $scope.bpmnProcesses = response; + }, function (response) { + // parse error response and log + if (response !== undefined) { + var errorMessages = response.toString().split("ErrorMessage{{"); + if (errorMessages.length > 1) { + console.error("Error retrieving BPMN Processes: ", response.toString() + .split("ErrorMessage{{")[1].split("}}")[0]); + } else { + console.error("Error retrieving BPMN Processes: ", errorMessages); + } + } + }); + }; + + init(); + initBpmnProcesses(); + + $scope.cancel = function (request, reason) { + console.log("Cancel request ", request.executionId, reason); + UserRequestsService.cancel(request.executionId, reason).then(function (response) { + var index = $scope.requests.result.indexOf(request); + if (index > -1) { + $scope.requests.result.splice(index, 1); + $scope.requests.totalCount--; + $scope.reloadPage($scope.query.page, $scope.query.size, + "Process " + request.executionId + " successfully canceled"); + } + }, function (response) { + var errorMessage; + // parse error response + if (response !== undefined) { + errorMessage = response.split("ErrorMessage{{")[1]; + errorMessage = errorMessage.split("}}")[0]; + } + console.error("Error canceling User Request: ", request.executionId, errorMessage); + }); + }; + + $scope.openComponentModal = function (size, parentSelector) { + $scope.selectedProcesses = []; + var parentElem = parentSelector ? + angular.element($document[0].querySelector(parentSelector)) : undefined; + var modalInstance = $uibModal.open({ + animation: true, + ariaLabelledBy: 'modal-title', + ariaDescribedBy: 'modal-body', + component: 'modalWindow', + appendTo: parentElem, + size: size, + windowClass: 'in', + backdropClass: 'in', + resolve: { + bpmnProcesses: function () { + return $scope.bpmnProcesses; + }, + selectedProcesses: function () { + return $scope.selectedProcesses; + }, + modalHtml: function () { + return '<bpmn-processes></bpmn-processes>'; + }, + title: function () { + return $filter('translate')(["SELECT_PROCESS"]).SELECT_PROCESS; + } + } + }); + + modalInstance.result.then(function () { + for (var i = 0; i < $scope.selectedProcesses.length; i++) { + startRequest(i); + } + }, function () { + }); + }; + + var startRequest = function (i) { + var currentProc = $scope.selectedProcesses[i]; + UserRequestsService.start(currentProc).then(function (response) { + console.log("Process " + currentProc + " successfully started"); + $scope.reloadPage($scope.query.page, $scope.query.size, + "Process " + currentProc + " successfully started"); + }, function (response) { + var errorMessage; + // parse error response + if (response !== undefined) { + errorMessage = response.split("ErrorMessage{{")[1]; + errorMessage = errorMessage.split("}}")[0]; + } + $scope.$parent.showError("Error: " + (errorMessage || response), $scope.$parent.notification); + console.error("Error starting User Request: ", errorMessage); + }); + }; + } + }; + }]); \ No newline at end of file diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/bpmnProcessService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/bpmnProcessService.js new file mode 100644 index 0000000..73c8d7a --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/bpmnProcessService.js @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use strict'; + +angular.module('self') + .factory('BpmnProcessService', ['$resource', '$q', '$http', + function ($resource, $q, $http) { + + var bpmnService = {}; + + bpmnService.getBpmnProcesses = function () { + return $http.get("../api/flowable/bpmnProcesses/") + .then(function (response) { + return response.data; + }, function (response) { + return $q.reject(response.data || response.statusText); + }); + }; + + return bpmnService; + }]); diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/userRequestsService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/userRequestsService.js new file mode 100644 index 0000000..b887784 --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/userRequestsService.js @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use strict'; + +angular.module('self') + .factory('UserRequestsService', ['$q', '$http', function ($q, $http) { + + var userRequestsService = {}; + + userRequestsService.getUserRequests = function (query) { + return $http.get("../api/flowable/userRequests?user=" + query.user + + (query.page ? "&page=" + query.page + (query.size ? "&size=" + query.size : "") : "") + + (query.orderBy ? "&orderBy=" + query.orderby : "")) + .then(function (response) { + return response.data; + }, function (response) { + return $q.reject(response.data || response.statusText); + }); + }; + + userRequestsService.getUserRequestForms = function (query) { + return $http.get("../api/flowable/userRequests/forms?user=" + query.user + + (query.page ? "&page=" + query.page + (query.size ? "&size=" + query.size : "") : "") + + (query.orderBy ? "&orderBy=" + query.orderby : "")) + .then(function (response) { + return response.data; + }, function (response) { + return $q.reject(response.data || response.statusText); + }); + }; + + userRequestsService.submitForm = function (form) { + return $http.post("../api/flowable/userRequests/forms", form) + .then(function (response) { + return response.data; + }, function (response) { + return $q.reject(response.data || response.statusText); + }); + }; + + userRequestsService.cancel = function (executionId, reason) { + return $http.delete("../api/flowable/userRequests?executionId=" + executionId + + (reason ? "&reason=" + reason : "")) + .then(function (response) { + return response.data; + }, function (response) { + return $q.reject(response.data || response.statusText); + }); + }; + + userRequestsService.start = function (bpmnProcess) { + return $http.post("../api/flowable/userRequests/start/" + bpmnProcess) + .then(function (response) { + return response.data; + }, function (response) { + return $q.reject(response.data || response.statusText); + }); + }; + + return userRequestsService; + }]); diff --git a/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json b/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json index 9b0c4c2..b685f6b 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json +++ b/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json @@ -54,6 +54,13 @@ "PASSWORD_UPDATED": "Passwort erfolgreich reset", "GOBACKHOME": "Hier klicken um zur Homepage zurück zu gelangen", "CONFIRM_REMOVE": "Löschen bestätigen", - "own": "Besitzen" + "SELECT_PROCESS": "Wählen Sie einen oder mehrere Prozesse aus, um die Anfragen zu starten", + "PAGE_SIZE": "Seitengröße", + "EXECUTION_ID": "Ausführungs-ID", + "START_TIME": "Startzeit", + "START": "Start", + "own": "Besitzen", + "userRequests": "Anfragen", + "userRequestForms": "Formen" } diff --git a/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json b/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json index e580e26..31574e4 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json +++ b/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json @@ -45,6 +45,8 @@ "derivedSchemas": "DerivedSchemas", "virtualSchemas": "VirtualSchemas", "resources": "Resources", + "userRequests": "Requests", + "userRequestForms": "Forms", "finish": "Finish", "RESOURCES_PLACEHOLDER": "Click to select resources...", "REALM": "Realm", @@ -54,6 +56,11 @@ "PASSWORD_UPDATED": "Password successfully reset", "GOBACKHOME": "Click on this link to go back to the home page", "CONFIRM_REMOVE": "This will remove the current value. Continue?", + "SELECT_PROCESS": "Select one (or many) processes to start request(s)", + "PAGE_SIZE": "Page size", + "EXECUTION_ID": "Execution id", + "START_TIME": "Start time", + "START": "Start", "own": "Own" } diff --git a/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json b/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json index 55b16f5..3194dfc 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json +++ b/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json @@ -45,6 +45,8 @@ "derivedSchemas": "DerivedSchemas", "virtualSchemas": "VirtualSchemas", "resources": "Risorse", + "userRequests": "Richieste", + "userRequestForms": "Form", "finish": "Fine", "RESOURCES_PLACEHOLDER": "Clicca per selezionare risorse...", "NEWUSER": "Nuovo utente", @@ -54,5 +56,10 @@ "PASSWORD_UPDATED": "Password resettata con successo", "GOBACKHOME": "Clicca su questo link per tornare alla home page", "CONFIRM_REMOVE": "Questa azione rimuoverà il valore corrente. Continuare?", + "SELECT_PROCESS": "Selezionare uno (o più) processi per far partire una richiesta(e)", + "PAGE_SIZE": "Elementi per pagina", + "EXECUTION_ID": "Id Esecuzione", + "START_TIME": "Effettuata il", + "START": "Start", "own": "Propri" } diff --git a/client/enduser/src/main/resources/META-INF/resources/app/languages/ja/static.json b/client/enduser/src/main/resources/META-INF/resources/app/languages/ja/static.json index 046b29d..51cd728 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/languages/ja/static.json +++ b/client/enduser/src/main/resources/META-INF/resources/app/languages/ja/static.json @@ -54,6 +54,13 @@ "PASSWORD_UPDATED": "正常にパスワードをリセットしました", "GOBACKHOME": "このリンクをクリックするとホームページに戻ります", "CONFIRM_REMOVE": "これにより現在の値を削除します。続行しますか?", + "SELECT_PROCESS": "プロセスを1つ(または複数)選択して、要求を開始します。", + "PAGE_SIZE": "ページサイズ", + "EXECUTION_ID": "実行ID", + "START_TIME": "始まる時間", + "START": "開始", + "userRequests": "リクエスト", + "userRequestForms": "フォーム", "own": "自分" } diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/bpmnProcesses.html b/client/enduser/src/main/resources/META-INF/resources/app/views/bpmnProcesses.html new file mode 100644 index 0000000..b2e5be8 --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/views/bpmnProcesses.html @@ -0,0 +1,31 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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. +--> +<ul> + <li style="list-style-type: none;" ng-repeat="bpmnProcess in bpmnProcesses"> + <label> + <input + type="checkbox" + name="selected[]" + value="{{bpmnProcess.key}}" + ng-checked="selectedProcesses.indexOf(bpmnProcess.key) > -1" + ng-click="toggleSelection(bpmnProcess.key)" + > {{bpmnProcess.key}} + </label> + </li> +</ul> diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/formProperty.html b/client/enduser/src/main/resources/META-INF/resources/app/views/formProperty.html new file mode 100644 index 0000000..4827a3a --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/views/formProperty.html @@ -0,0 +1,92 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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. +--> +<div ng-switch="property.type" class="schema-type"> + <input ng-switch-when="String" class="form-control" type="text" + ng-model="property.value" + ng-required="{{property.required}}" validate="true" + ng-disabled="!property.writable" + name="{{property.name}}"/> + + <div ng-switch-when="Boolean"> + <input type="checkbox" + ng-true-value="'true'" + ng-false-value="'false'" + ng-model="property.value" + ng-required="{{property.required}}" validate="true" + ng-disabled="!property.writable" + ng-init="initAttribute()" + name="{{property.name}}"/> + </div> + + <input ng-switch-when="Long" class="form-control" + type="number" + ng-model="property.value" + ng-required="{{property.required}}" validate="true" + ng-disabled="!property.writable" + ng-init="initAttribute()" + name="{{property.name}}"/> + + <div ng-switch-when="Date" id="date-property"> + <input type="text" class="dateTimePicker" + kendo-date-time-picker + ng-show="!isDateOnly" + ng-model="extendedDate" + ng-required="{{property.required}}" close-text="Close" + ng-init="initAttribute()" + ng-change="bindDateToModel(selectedDate, extendedDate)" + ng-disabled="!property.writable" + k-ng-model="selectedDate" + data-k-format="languageFormat" + /> + <input type="text" class="datePicker" + kendo-date-picker + ng-show="isDateOnly" + ng-model="extendedDate" + ng-required="{{property.required}}" close-text="Close" + ng-init="initAttribute()" + ng-change="bindDateToModel(selectedDate, extendedDate)" + ng-disabled="!property.writable" + k-ng-model="selectedDate" + data-k-format="languageFormat" + /> + </div> + + <div ng-switch-when="Enum" + ng-init="initAttribute()"> + <select class="form-control custom-select" + ng-model="property.value" + ng-required="{{property.required}}" + ng-disabled="!property.writable"> + <option ng-repeat="key in enumKeys" value="{{key}}"> + {{"empty" === key ? "" : property.enumValues[key]}} + </option> + </select> + </div> + <div ng-switch-when="Dropdown" + ng-init="initAttribute()"> + <select class="form-control custom-select" + ng-model="property.value" + ng-required="{{property.required}}" + ng-disabled="!property.writable"> + <option ng-repeat="key in dropdownKeys" value="{{key}}"> + {{"empty" === key ? "" : property.dropdownValues[key]}} + </option> + </select> + </div> +</div> \ No newline at end of file diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/modalWindow.html b/client/enduser/src/main/resources/META-INF/resources/app/views/modalWindow.html new file mode 100644 index 0000000..9f15200 --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/views/modalWindow.html @@ -0,0 +1,30 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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. +--> +<div ng-init="init()"> + <div class="modal-header"> + <h3 class="modal-title" id="modal-title">{{resolve.title}}</h3> + </div> + <div class="modal-body" id="modal-body"> + <modal-content id="modal-content" modal-html="resolve.modalHtml"></modal-content> + </div> + <div class="modal-footer"> + <button class="btn btn-primary" type="button" ng-click="ok()">{{ 'START' | translate}}</button> + <button class="btn btn-warning" type="button" ng-click="cancel()">{{'CANCEL'| translate}}</button> + </div> +</div> diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/requestForms.html b/client/enduser/src/main/resources/META-INF/resources/app/views/requestForms.html new file mode 100644 index 0000000..aa44b2c --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/views/requestForms.html @@ -0,0 +1,61 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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. +--> +<div ng-repeat="form in forms.result"> + <uib-accordion ng-if="forms.result.length"> + <div uib-accordion-group heading="{{form.bpmnProcess| translate}}" class="breadcrumb-header panel panel-default"> + <div id="attribute" class="form-group" ng-repeat="property in form.properties"> + <label class="property-label" for="property.name">{{property.name}} + <span ng-if="property.required">*</span> + </label> + <form-property property="property"></form-property> + <validation-message name="{{property.name}}"/> + </div> + <div style="text-align: right"> + <button class="btn btn-secondary btn-default" type="button" ng-click="submit(form)">{{ 'SUBMIT' | translate}}</button> + </div> + </div> + </uib-accordion> +</div> +<div class="row"> + <div class="col-md-9" style="text-align: center"> + <!-- pager --> + <ul ng-if="totalPages > 1" class="pagination"> + <li ng-class="{disabled:query.page === 1}"> + <a ng-click="reloadPage(1, query.size)">First</a> + </li> + <li ng-class="{disabled:query.page === 1}"> + <a ng-click="reloadPage(query.page - 1, query.size)">Previous</a> + </li> + <li ng-repeat="page in pages" ng-class="{active:query.page === page}"> + <a ng-click="reloadPage(page, query.size)">{{page}}</a> + </li> + <li ng-class="{disabled:query.page === totalPages}"> + <a ng-click="reloadPage(query.page + 1, query.size)">Next</a> + </li> + <li ng-class="{disabled:query.page === totalPages}"> + <a ng-click="reloadPage(totalPages, query.size)">Last</a> + </li> + </ul> + </div> + <div ng-if="totalPages > 1" class="form-group col-md-3 pagination-size-sm" style="text-align: left"> + <select class="form-control" name="sizeSelect" id="sizeSelect" + ng-options="option.value for option in availableSizes track by option.id" + ng-model="selectedSize" ng-change="reloadPage(query.page, selectedSize.value)"></select> + </div> +</div> \ No newline at end of file diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/requests.html b/client/enduser/src/main/resources/META-INF/resources/app/views/requests.html new file mode 100644 index 0000000..68c1da3 --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/views/requests.html @@ -0,0 +1,76 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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. +--> +<div ng-repeat="request in requests.result"> + <uib-accordion ng-if="requests.result.length"> + <div uib-accordion-group heading="{{request.bpmnProcess| translate}}" class="breadcrumb-header panel panel-default"> + <table class="table"> + <thead> + <tr> + <th>{{'EXECUTION_ID'| translate}}</th> + <th>{{'START_TIME'| translate}}</th> + </tr> + </thead> + + <tbody> + <tr> + <td>{{request.executionId}}</td> + <td>{{formatDate(request.startTime)}}</td> + </tr> + </tbody> + </table> + <div style="text-align: right"> + <a id="cancelRequest" class="btn btn-secondary btn-default" ng-click="cancel(request)"> + <i class="fa fa-trash" aria-hidden="true"></i> + </a> + </div> + </div> + </uib-accordion> +</div> +<div class="row"> + <div class="col-md-9" style="text-align: center"> + <!-- pager --> + <ul ng-if="totalPages > 1" class="pagination"> + <li ng-class="{disabled:query.page === 1}"> + <a ng-click="reloadPage(1, query.size)">First</a> + </li> + <li ng-class="{disabled:query.page === 1}"> + <a ng-click="reloadPage(query.page - 1, query.size)">Previous</a> + </li> + <li ng-repeat="page in pages" ng-class="{active:query.page === page}"> + <a ng-click="reloadPage(page, query.size)">{{page}}</a> + </li> + <li ng-class="{disabled:query.page === totalPages}"> + <a ng-click="reloadPage(query.page + 1, query.size)">Next</a> + </li> + <li ng-class="{disabled:query.page === totalPages}"> + <a ng-click="reloadPage(totalPages, query.size)">Last</a> + </li> + </ul> + </div> + <div ng-if="totalPages > 1" class="form-group col-md-3 pagination-size" style="text-align: left"> + <select class="form-control" name="sizeSelect" id="sizeSelect" + ng-options="option.value for option in availableSizes track by option.id" + ng-model="selectedSize" ng-change="reloadPage(query.page, selectedSize.value)"></select> + </div> +</div> +<div class="row" style="text-align: right"> + <button class="btn btn-default btn-sm" type="button" ng-click="openComponentModal()"> + <i class="fa fa-plus" title="Start requests"></i> + </button> +</div> \ No newline at end of file diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/templates/editUserTemplate.html b/client/enduser/src/main/resources/META-INF/resources/app/views/templates/editUserTemplate.html index 396bfe7..5d16434 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/views/templates/editUserTemplate.html +++ b/client/enduser/src/main/resources/META-INF/resources/app/views/templates/editUserTemplate.html @@ -40,7 +40,7 @@ under the License. <i class="fa fa-power-off" style="color:red"></i> </a> <!-- add class breadcrumb-disabled-link to buttons to prevent click --> - <a ng-repeat="(key, value) in wizard" ui-sref-active="active" ui-sref=".{{key}}" + <a ng-repeat="(key, value) in (createMode ? creationWizard : wizard)" ui-sref-active="active" ui-sref=".{{key}}" class="btn btn-secondary btn-default breadcrumb-btn-elem" ng-class="createMode && !endReached ? 'disable-link' : ''">{{key| translate}}</a> </div> diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/user-request-forms.html b/client/enduser/src/main/resources/META-INF/resources/app/views/user-request-forms.html new file mode 100644 index 0000000..4971143 --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/views/user-request-forms.html @@ -0,0 +1,48 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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. +--> +<div> + <request-forms user="user"></request-forms> +</div> + +<div id="attribute" class="form-group row justify-content-between p-0"> + <div class="col-xs-3"> + <a id="cancel" class="btn btn-danger float-left nav-button" tabindex="0" + ng-enter="logout()" ng-click="logout()"> + {{'CANCEL'| translate}} + </a> + </div> + <div class="col-xs-9"> + <div id="navButtons" class="float-left" + ng-class="(!createMode || (createMode && endReached)) ? 'col-xs-10' : 'col-xs-12'"> + <navigation-buttons-partial ng-show="createMode" base="create" current="userRequestForms" wizard="{{wizard}}"> + </navigation-buttons-partial> + <navigation-buttons-partial ng-show="!createMode" base="update" current="userRequestForms" wizard="{{wizard}}"> + </navigation-buttons-partial> + </div> + <div class="float-right p-0" + ng-class="(!createMode || (createMode && endReached)) ? 'col-xs-2' : ''" + ng-show="!createMode || (createMode && endReached)"> + <button id="finish" type="button" tabindex="0" + class="btn btn-secondary btn-default float-right nav-button" + ng-enter="finish()" ng-click="finish()"> + {{'FINISH'| translate}} + </button> + </div> + </div> +</div> \ No newline at end of file diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/user-requests.html b/client/enduser/src/main/resources/META-INF/resources/app/views/user-requests.html new file mode 100644 index 0000000..62c3e01 --- /dev/null +++ b/client/enduser/src/main/resources/META-INF/resources/app/views/user-requests.html @@ -0,0 +1,48 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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. +--> +<div id="attribute" class="form-group row upper-select ng-scope"> + <requests user="user"></requests> +</div> + +<div id="attribute" class="form-group row justify-content-between p-0"> + <div class="col-xs-3"> + <a id="cancel" class="btn btn-danger float-left nav-button" tabindex="0" + ng-enter="logout()" ng-click="logout()"> + {{'CANCEL'| translate}} + </a> + </div> + <div class="col-xs-9"> + <div id="navButtons" class="float-left" + ng-class="(!createMode || (createMode && endReached)) ? 'col-xs-10' : 'col-xs-12'"> + <navigation-buttons-partial ng-show="createMode" base="create" current="userRequests" wizard="{{wizard}}"> + </navigation-buttons-partial> + <navigation-buttons-partial ng-show="!createMode" base="update" current="userRequests" wizard="{{wizard}}"> + </navigation-buttons-partial> + </div> + <div class="float-right p-0" + ng-class="(!createMode || (createMode && endReached)) ? 'col-xs-2' : ''" + ng-show="!createMode || (createMode && endReached)"> + <button id="finish" type="button" tabindex="0" + class="btn btn-secondary btn-default float-right nav-button" + ng-enter="finish()" ng-click="finish()"> + {{'FINISH'| translate}} + </button> + </div> + </div> +</div> \ No newline at end of file diff --git a/client/enduser/src/main/resources/customTemplate.json b/client/enduser/src/main/resources/customTemplate.json index e55ed4c..12c7f3b 100644 --- a/client/enduser/src/main/resources/customTemplate.json +++ b/client/enduser/src/main/resources/customTemplate.json @@ -74,6 +74,12 @@ "resources": { "url": "/resources" }, + "userRequests": { + "url": "/user-requests" + }, + "userRequestForms": { + "url": "/user-request-forms" + }, "finish": { "url": "/finish" } diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java index 4784913..1eb40c6 100644 --- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java +++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java @@ -29,7 +29,7 @@ import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.request.mapper.parameter.PageParameters; @ExtPage(label = "Flowable", icon = "fa-briefcase", - listEntitlement = FlowableEntitlement.BPMN_PROCESS_LIST, priority = 200) + listEntitlement = FlowableEntitlement.BPMN_PROCESS_GET, priority = 200) public class Flowable extends BaseExtPage { private static final long serialVersionUID = -8781434495150074529L; @@ -49,7 +49,7 @@ public class Flowable extends BaseExtPage { }.disableCheckBoxes().build("bpmnProcessesPanel"); bpmnProcessesPanel.setOutputMarkupPlaceholderTag(true); - MetaDataRoleAuthorizationStrategy.authorize(bpmnProcessesPanel, ENABLE, FlowableEntitlement.BPMN_PROCESS_LIST); + MetaDataRoleAuthorizationStrategy.authorize(bpmnProcessesPanel, ENABLE, FlowableEntitlement.BPMN_PROCESS_GET); content.add(bpmnProcessesPanel); } diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java index 0c41c99..40943de 100644 --- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java +++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java @@ -26,8 +26,6 @@ import java.util.TreeSet; public final class FlowableEntitlement { - public static final String BPMN_PROCESS_LIST = "BPMN_PROCESS_LIST"; - public static final String BPMN_PROCESS_GET = "BPMN_PROCESS_GET"; public static final String BPMN_PROCESS_SET = "BPMN_PROCESS_SET"; diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java index 9ff621c..e7bfa00 100644 --- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java +++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java @@ -209,7 +209,9 @@ public final class FlowableRuntimeUtils { } public static void throwException(final FlowableException e, final String defaultMessage) { - if (e.getCause() instanceof SyncopeClientException) { + if (e.getCause() == null) { + throw new WorkflowException(defaultMessage, e); + } else if (e.getCause() instanceof SyncopeClientException) { throw (SyncopeClientException) e.getCause(); } else if (e.getCause() instanceof ParsingValidationException) { throw (ParsingValidationException) e.getCause(); diff --git a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java index ad76605..c28516b 100644 --- a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java +++ b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java @@ -36,7 +36,7 @@ public class BpmnProcessLogic extends AbstractTransactionalLogic<BpmnProcess> { @Autowired private BpmnProcessManager bpmnProcessManager; - @PreAuthorize("hasRole('" + FlowableEntitlement.BPMN_PROCESS_LIST + "')") + @PreAuthorize("isAuthenticated()") @Transactional(readOnly = true) public List<BpmnProcess> list() { return bpmnProcessManager.getProcesses(); diff --git a/ext/flowable/pom.xml b/ext/flowable/pom.xml index 5a804b5..9166557 100644 --- a/ext/flowable/pom.xml +++ b/ext/flowable/pom.xml @@ -44,6 +44,7 @@ under the License. <module>rest-cxf</module> <module>flowable-bpmn</module> <module>client-console</module> + <module>syncope-ext-flowable-client-enduser</module> </modules> </project> diff --git a/ext/flowable/syncope-ext-flowable-client-enduser/pom.xml b/ext/flowable/syncope-ext-flowable-client-enduser/pom.xml new file mode 100644 index 0000000..09d5844 --- /dev/null +++ b/ext/flowable/syncope-ext-flowable-client-enduser/pom.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.syncope.ext</groupId> + <artifactId>syncope-ext-flowable</artifactId> + <version>2.1.3-SNAPSHOT</version> + </parent> + + <name>Apache Syncope Ext: Flowable Client Enduser</name> + <description>Apache Syncope Ext: Flowable Client Enduser</description> + <groupId>org.apache.syncope.ext.flowable</groupId> + <artifactId>syncope-ext-flowable-client-enduser</artifactId> + <packaging>jar</packaging> + + <properties> + <rootpom.basedir>${basedir}/../../..</rootpom.basedir> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.syncope.ext.flowable</groupId> + <artifactId>syncope-ext-flowable-common-lib</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.syncope.ext.flowable</groupId> + <artifactId>syncope-ext-flowable-rest-api</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.syncope.client</groupId> + <artifactId>syncope-client-enduser</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + </dependency> + + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-checkstyle-plugin</artifactId> + </plugin> + </plugins> + <resources> + <resource> + <directory>src/main/resources</directory> + <filtering>true</filtering> + </resource> + </resources> + </build> + +</project> \ No newline at end of file diff --git a/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/BpmnProcessList.java b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/BpmnProcessList.java new file mode 100644 index 0000000..4b8d80b --- /dev/null +++ b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/BpmnProcessList.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.syncope.client.enduser.resources; + +import static org.apache.syncope.client.enduser.resources.BaseResource.LOG; +import static org.apache.syncope.client.enduser.resources.BaseResource.MAPPER; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.apache.syncope.client.enduser.SyncopeEnduserSession; +import org.apache.syncope.client.enduser.annotations.Resource; +import org.apache.syncope.common.lib.to.BpmnProcess; +import org.apache.syncope.common.rest.api.service.BpmnProcessService; +import org.apache.wicket.request.resource.AbstractResource; +import org.apache.wicket.request.resource.IResource; + +@Resource(key = "bpmnProcessesList", path = "/api/flowable/bpmnProcesses/") +public class BpmnProcessList extends BaseResource { + + private static final long serialVersionUID = 7273151109078469253L; + + @Override + protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) { + LOG.debug("List available Flowable BPMN processes definitions [{}] useful to start User Requests"); + + ResourceResponse response = new AbstractResource.ResourceResponse(); + response.setContentType(MediaType.APPLICATION_JSON); + try { + HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest(); + if (!xsrfCheck(request)) { + LOG.error("XSRF TOKEN does not match"); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), "XSRF TOKEN does not match"); + return response; + } + + final List<BpmnProcess> bpmnProcesses = SyncopeEnduserSession.get(). + getService(BpmnProcessService.class).list(); + + response.setWriteCallback(new AbstractResource.WriteCallback() { + + @Override + public void writeData(final IResource.Attributes attributes) throws IOException { + // retain also not userWorkflow processes + attributes.getResponse().write(MAPPER.writeValueAsString(bpmnProcesses == null + ? Collections.<BpmnProcess>emptyList() + : bpmnProcesses.stream().filter(bpmnProcess -> !bpmnProcess.isUserWorkflow()).collect( + Collectors.toList()))); + } + }); + + response.setContentType(MediaType.APPLICATION_JSON); + response.setTextEncoding(StandardCharsets.UTF_8.name()); + response.setStatusCode(Response.Status.OK.getStatusCode()); + } catch (Exception e) { + LOG.error("Error retrieving BPMN processes", e); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder() + .append("ErrorMessage{{ ") + .append(e.getMessage()) + .append(" }}") + .toString()); + } + + return response; + } +} diff --git a/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestCancelResource.java b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestCancelResource.java new file mode 100644 index 0000000..4012b69 --- /dev/null +++ b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestCancelResource.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.syncope.client.enduser.resources; + +import static org.apache.syncope.client.enduser.resources.BaseResource.LOG; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.apache.syncope.client.enduser.SyncopeEnduserSession; +import org.apache.syncope.client.enduser.annotations.Resource; +import org.apache.syncope.common.rest.api.service.UserRequestService; +import org.apache.wicket.request.IRequestParameters; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.request.resource.AbstractResource; +import org.apache.wicket.request.resource.IResource; +import org.apache.wicket.util.string.StringValue; + +@Resource(key = "userRequestCancel", path = "/api/flowable/userRequests/${executionId}") +public class UserRequestCancelResource extends BaseResource { + + private static final long serialVersionUID = 7273151109078469253L; + + @Override + protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) { + + ResourceResponse response = new AbstractResource.ResourceResponse(); + response.setContentType(MediaType.APPLICATION_JSON); + StringValue executionId; + try { + HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest(); + if (!xsrfCheck(request)) { + LOG.error("XSRF TOKEN does not match"); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), "XSRF TOKEN does not match"); + return response; + } + + if (!HttpMethod.DELETE.equals(request.getMethod())) { + throw new UnsupportedOperationException("Unsupported operation, only DELETE allowed"); + } + + PageParameters parameters = attributes.getParameters(); + executionId = parameters.get("executionId"); + IRequestParameters requestParameters = attributes.getRequest().getQueryParameters(); + StringValue reason = requestParameters.getParameterValue("reason"); + LOG.debug("Cancel Flowable User Request with execution id [{}] for user [{}] with reason [{}]", executionId, + SyncopeEnduserSession.get().getSelfTO().getUsername(), reason); + if (executionId.isEmpty()) { + throw new IllegalArgumentException("Empty executionId, please provide a value"); + } + + SyncopeEnduserSession.get().getService(UserRequestService.class).cancel(executionId.toString(), + reason.toString()); + + final String outcomeMessage = String.format( + "User Request with execution id [%s] successfully canceled for User [%s]", executionId. + toString(), SyncopeEnduserSession.get().getSelfTO().getUsername()); + + response.setWriteCallback(new AbstractResource.WriteCallback() { + + @Override + public void writeData(final IResource.Attributes attributes) throws IOException { + attributes.getResponse().write(outcomeMessage); + } + }); + + response.setContentType(MediaType.APPLICATION_JSON); + response.setTextEncoding(StandardCharsets.UTF_8.name()); + response.setStatusCode(Response.Status.OK.getStatusCode()); + } catch (Exception e) { + LOG.error("Error cancelling User Request for [{}]", SyncopeEnduserSession.get().getSelfTO().getUsername(), + e); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder() + .append("ErrorMessage{{ ") + .append(e.getMessage()) + .append(" }}") + .toString()); + } + + return response; + } +} diff --git a/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestFormClaimResource.java b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestFormClaimResource.java new file mode 100644 index 0000000..a342c7f --- /dev/null +++ b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestFormClaimResource.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.syncope.client.enduser.resources; + +import static org.apache.syncope.client.enduser.resources.BaseResource.LOG; +import static org.apache.syncope.client.enduser.resources.BaseResource.MAPPER; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.apache.syncope.client.enduser.SyncopeEnduserSession; +import org.apache.syncope.client.enduser.annotations.Resource; +import org.apache.syncope.common.lib.to.UserRequestForm; +import org.apache.syncope.common.rest.api.service.UserRequestService; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.request.resource.AbstractResource; +import org.apache.wicket.request.resource.IResource; +import org.apache.wicket.util.string.StringValue; + +@Resource(key = "userRequestCancelByUsername", path = "/api/flowable/userRequests/forms/${taskId}/claim") +public class UserRequestFormClaimResource extends BaseResource { + + private static final long serialVersionUID = 7273151109078469253L; + + @Override + protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) { + + ResourceResponse response = new AbstractResource.ResourceResponse(); + response.setContentType(MediaType.APPLICATION_JSON); + StringValue taskId; + try { + HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest(); + if (!xsrfCheck(request)) { + LOG.error("XSRF TOKEN does not match"); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), "XSRF TOKEN does not match"); + return response; + } + + PageParameters parameters = attributes.getParameters(); + taskId = parameters.get("taskId"); + LOG.debug("Claim Flowable User Request Form with task id [{}] for user [{}] with reason [{}]", taskId, + SyncopeEnduserSession.get().getSelfTO().getUsername()); + if (taskId.isEmpty()) { + throw new IllegalArgumentException("Empty taskId, please provide a value"); + } + UserRequestForm requestForm = SyncopeEnduserSession.get().getService(UserRequestService.class).claimForm( + taskId.toString()); + + response.setWriteCallback(new AbstractResource.WriteCallback() { + + @Override + public void writeData(final IResource.Attributes attributes) throws IOException { + attributes.getResponse().write(MAPPER.writeValueAsString(requestForm)); + } + }); + + response.setContentType(MediaType.APPLICATION_JSON); + response.setTextEncoding(StandardCharsets.UTF_8.name()); + response.setStatusCode(Response.Status.OK.getStatusCode()); + } catch (Exception e) { + LOG. + error("Error claiming User Request Form for [{}]", SyncopeEnduserSession.get().getSelfTO(). + getUsername(), e); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder() + .append("ErrorMessage{{ ") + .append(e.getMessage()) + .append(" }}") + .toString()); + } + + return response; + } +} diff --git a/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsFormsResource.java b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsFormsResource.java new file mode 100644 index 0000000..f303070 --- /dev/null +++ b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsFormsResource.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.syncope.client.enduser.resources; + +import static org.apache.syncope.client.enduser.resources.BaseResource.LOG; +import static org.apache.syncope.client.enduser.resources.BaseResource.MAPPER; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.FastDateFormat; +import org.apache.syncope.client.enduser.SyncopeEnduserSession; +import org.apache.syncope.client.enduser.annotations.Resource; +import org.apache.syncope.common.lib.to.PagedResult; +import org.apache.syncope.common.lib.to.UserRequestForm; +import org.apache.syncope.common.lib.types.UserRequestFormPropertyType; +import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery; +import org.apache.syncope.common.rest.api.service.UserRequestService; +import org.apache.wicket.request.IRequestParameters; +import org.apache.wicket.request.resource.AbstractResource; +import org.apache.wicket.request.resource.IResource; +import org.apache.wicket.util.string.StringValue; + +@Resource(key = "userRequestsForms", path = "/api/flowable/userRequests/forms") +public class UserRequestsFormsResource extends BaseResource { + + private static final long serialVersionUID = 7273151109078469253L; + + @Override + protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) { + + ResourceResponse response = new AbstractResource.ResourceResponse(); + response.setContentType(MediaType.APPLICATION_JSON); + response.setTextEncoding(StandardCharsets.UTF_8.name()); + StringValue username = StringValue.valueOf(SyncopeEnduserSession.get().getSelfTO().getUsername()); + try { + HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest(); + if (!xsrfCheck(request)) { + LOG.error("XSRF TOKEN does not match"); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), "XSRF TOKEN does not match"); + return response; + } + + switch (request.getMethod()) { + case HttpMethod.GET: + IRequestParameters requestParameters = attributes.getRequest().getQueryParameters(); + StringValue page = requestParameters.getParameterValue("page"); + StringValue size = requestParameters.getParameterValue("size"); + LOG.debug("List available Flowable User Requests Forms by user [{}]", username); + final PagedResult<UserRequestForm> userRequestForms = SyncopeEnduserSession.get(). + getService(UserRequestService.class).getForms( + new UserRequestFormQuery.Builder() + .user(username.isEmpty() + ? SyncopeEnduserSession.get().getSelfTO().getUsername() + : username.toString()) + .page(page.isEmpty() + ? 1 + : Integer.parseInt(page.toString())) + .size(size.isEmpty() + ? 10 + : Integer.parseInt(size.toString())).build()); + + // Date -> millis conversion for Date properties of the form + userRequestForms.getResult().stream().forEach(form + -> form.getProperties().stream() + .filter(prop -> UserRequestFormPropertyType.Date == prop.getType() + && StringUtils.isNotBlank(prop.getValue())) + .forEach(prop -> { + try { + prop.setValue(String.valueOf(FastDateFormat.getInstance(prop. + getDatePattern()).parse(prop.getValue()).getTime())); + } catch (ParseException e) { + LOG.error("Unable to parse date", e); + } + })); + + response.setWriteCallback(new AbstractResource.WriteCallback() { + + @Override + public void writeData(final IResource.Attributes attributes) throws IOException { + attributes.getResponse().write(MAPPER.writeValueAsString(userRequestForms)); + } + }); + break; + case HttpMethod.POST: + UserRequestForm requestForm = MAPPER. + readValue(request.getReader().readLine(), UserRequestForm.class); + if (requestForm == null) { + throw new IllegalArgumentException("Empty userRequestForm, please provide a valid one"); + } + + UserRequestService userRequestService = SyncopeEnduserSession.get().getService( + UserRequestService.class); + // 1. claim form as logged user + userRequestService.claimForm(requestForm.getTaskId()); + // millis -> Date conversion for Date properties of the form + requestForm.getProperties().stream() + .filter(prop -> UserRequestFormPropertyType.Date == prop.getType() + && StringUtils.isNotBlank(prop.getValue())) + .forEach(prop -> { + try { + prop.setValue(FastDateFormat.getInstance(prop.getDatePattern()).format(Long.valueOf( + prop.getValue()))); + } catch (NumberFormatException e) { + LOG.error("Unable to format date", e); + } + }); + // 2. Submit form + LOG.debug("Submit Flowable User Request Form for user [{}]", requestForm.getUsername()); + userRequestService.submitForm(requestForm); + + response.setStatusCode(Response.Status.NO_CONTENT.getStatusCode()); + response.setWriteCallback(new AbstractResource.WriteCallback() { + + @Override + public void writeData(final IResource.Attributes attributes) throws IOException { + // DO NOTHING + } + }); + break; + default: + LOG.error("Method [{}] not supported", request.getMethod()); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder() + .append("ErrorMessage{{ ") + .append("Method not supported") + .append(" }}") + .toString()); + break; + } + response.setContentType(MediaType.APPLICATION_JSON); + response.setTextEncoding(StandardCharsets.UTF_8.name()); + response.setStatusCode(Response.Status.OK.getStatusCode()); + } catch (Exception e) { + LOG.error("Error dealing with forms of user [{}]", username, e); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder() + .append("ErrorMessage{{ ") + .append(e.getMessage()) + .append(" }}") + .toString()); + } + + return response; + } +} diff --git a/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java new file mode 100644 index 0000000..812e408 --- /dev/null +++ b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.syncope.client.enduser.resources; + +import static org.apache.syncope.client.enduser.resources.BaseResource.LOG; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.apache.syncope.client.enduser.SyncopeEnduserSession; +import org.apache.syncope.client.enduser.annotations.Resource; +import org.apache.syncope.common.lib.to.PagedResult; +import org.apache.syncope.common.lib.to.UserRequest; +import org.apache.syncope.common.rest.api.beans.UserRequestQuery; +import org.apache.syncope.common.rest.api.service.UserRequestService; +import org.apache.wicket.request.IRequestParameters; +import org.apache.wicket.request.resource.AbstractResource; +import org.apache.wicket.request.resource.IResource; +import org.apache.wicket.util.string.StringValue; + +@Resource(key = "userRequests", path = "/api/flowable/userRequests") +public class UserRequestsResource extends BaseResource { + + private static final long serialVersionUID = 7273151109078469253L; + + @Override + protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) { + + ResourceResponse response = new AbstractResource.ResourceResponse(); + response.setContentType(MediaType.APPLICATION_JSON); + StringValue username = StringValue.valueOf(SyncopeEnduserSession.get().getSelfTO().getUsername()); + try { + HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest(); + if (!xsrfCheck(request)) { + LOG.error("XSRF TOKEN does not match"); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), "XSRF TOKEN does not match"); + return response; + } + + IRequestParameters requestParameters = attributes.getRequest().getQueryParameters(); + switch (request.getMethod()) { + case HttpMethod.DELETE: + StringValue executionId = requestParameters.getParameterValue("executionId"); + StringValue reason = requestParameters.getParameterValue("reason"); + LOG.debug("Cancel Flowable User Request with execution id [{}] for user [{}] with reason [{}]", + executionId, SyncopeEnduserSession.get().getSelfTO().getUsername(), reason); + if (executionId.isEmpty()) { + throw new IllegalArgumentException("Empty executionId, please provide a value"); + } + SyncopeEnduserSession.get().getService(UserRequestService.class).cancel(executionId.toString(), + reason.toString()); + response.setStatusCode(Response.Status.NO_CONTENT.getStatusCode()); + response.setWriteCallback(new AbstractResource.WriteCallback() { + + @Override + public void writeData(final IResource.Attributes attributes) throws IOException { + // DO NOTHING + } + }); + break; + + case HttpMethod.GET: + StringValue page = requestParameters.getParameterValue("page"); + StringValue size = requestParameters.getParameterValue("size"); + LOG.debug("List available Flowable User Requests for user [{}]", username); + final PagedResult<UserRequest> userRequests = SyncopeEnduserSession.get(). + getService(UserRequestService.class).list( + new UserRequestQuery.Builder() + .user(username.isEmpty() + ? SyncopeEnduserSession.get().getSelfTO().getUsername() + : username.toString()) + .page(page.isEmpty() + ? 1 + : Integer.parseInt( + page.toString())) + .size(size.isEmpty() + ? 10 + : Integer.parseInt( + size.toString())).build()); + response.setWriteCallback(new AbstractResource.WriteCallback() { + + @Override + public void writeData(final IResource.Attributes attributes) throws IOException { + attributes.getResponse().write(MAPPER.writeValueAsString(userRequests)); + } + }); + break; + default: + LOG.error("Method [{}] not supported", request.getMethod()); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder() + .append("ErrorMessage{{ ") + .append("Method not supported") + .append(" }}") + .toString()); + break; + } + response.setContentType(MediaType.APPLICATION_JSON); + response.setTextEncoding(StandardCharsets.UTF_8.name()); + response.setStatusCode(Response.Status.OK.getStatusCode()); + } catch (Exception e) { + LOG.error("Error retrieving user requests for [{}]", username, e); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder() + .append("ErrorMessage{{ ") + .append(e.getMessage()) + .append(" }}") + .toString()); + } + + return response; + } +} diff --git a/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java new file mode 100644 index 0000000..4c65768 --- /dev/null +++ b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.syncope.client.enduser.resources; + +import static org.apache.syncope.client.enduser.resources.BaseResource.LOG; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.apache.syncope.client.enduser.SyncopeEnduserSession; +import org.apache.syncope.client.enduser.annotations.Resource; +import org.apache.syncope.common.rest.api.service.UserRequestService; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.request.resource.AbstractResource; +import org.apache.wicket.request.resource.IResource; +import org.apache.wicket.util.string.StringValue; + +@Resource(key = "userRequests", path = "/api/flowable/userRequests/start/${bpmnProcess}") +public class UserRequestsStartResource extends BaseResource { + + private static final long serialVersionUID = 7273151109078469253L; + + @Override + protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) { + ResourceResponse response = new AbstractResource.ResourceResponse(); + response.setContentType(MediaType.APPLICATION_JSON); + StringValue bpmnProcess = null; + try { + HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest(); + if (!xsrfCheck(request)) { + LOG.error("XSRF TOKEN does not match"); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), "XSRF TOKEN does not match"); + return response; + } + + PageParameters parameters = attributes.getParameters(); + bpmnProcess = parameters.get("bpmnProcess"); + + LOG.debug("Start Flowable User Request from process [{}] for user [{}]", + bpmnProcess, SyncopeEnduserSession.get().getSelfTO().getUsername()); + if (bpmnProcess.isEmpty()) { + throw new IllegalArgumentException("Empty bpmnProcess, please provide a value"); + } + SyncopeEnduserSession.get().getService(UserRequestService.class).start(bpmnProcess.toString(), null); + response.setStatusCode(Response.Status.NO_CONTENT.getStatusCode()); + response.setWriteCallback(new AbstractResource.WriteCallback() { + + @Override + public void writeData(final IResource.Attributes attributes) throws IOException { + // DO NOTHING + } + }); + + response.setContentType(MediaType.APPLICATION_JSON); + response.setTextEncoding(StandardCharsets.UTF_8.name()); + response.setStatusCode(Response.Status.OK.getStatusCode()); + } catch (Exception e) { + LOG.error("Error starting user request from [{}]", bpmnProcess, e); + response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder() + .append("ErrorMessage{{ ") + .append(e.getMessage()) + .append(" }}") + .toString()); + } + + return response; + } +} diff --git a/fit/enduser-reference/pom.xml b/fit/enduser-reference/pom.xml index 9c752fe..48a7214 100644 --- a/fit/enduser-reference/pom.xml +++ b/fit/enduser-reference/pom.xml @@ -73,6 +73,12 @@ under the License. </dependency> <dependency> + <groupId>org.apache.syncope.ext.flowable</groupId> + <artifactId>syncope-ext-flowable-client-enduser</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> diff --git a/fit/enduser-reference/src/main/resources/customTemplate.json b/fit/enduser-reference/src/main/resources/customTemplate.json index e55ed4c..7223610 100644 --- a/fit/enduser-reference/src/main/resources/customTemplate.json +++ b/fit/enduser-reference/src/main/resources/customTemplate.json @@ -74,6 +74,9 @@ "resources": { "url": "/resources" }, + "userRequests": { + "url": "/user-requests" + }, "finish": { "url": "/finish" } diff --git a/fit/enduser-reference/src/test/resources/customTemplate.json b/fit/enduser-reference/src/test/resources/customTemplate.json index e55ed4c..12c7f3b 100644 --- a/fit/enduser-reference/src/test/resources/customTemplate.json +++ b/fit/enduser-reference/src/test/resources/customTemplate.json @@ -74,6 +74,12 @@ "resources": { "url": "/resources" }, + "userRequests": { + "url": "/user-requests" + }, + "userRequestForms": { + "url": "/user-request-forms" + }, "finish": { "url": "/finish" }