This is an automated email from the ASF dual-hosted git repository.

andreapatricelli pushed a commit to branch 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/2_1_X by this push:
     new acbd537  [SYNCOPE-1462] revisited UR management in enduser, fixed not 
refreshing UR list
acbd537 is described below

commit acbd537d5312171f760ba407f2af8ad5f22cfa97
Author: Andrea Patricelli <[email protected]>
AuthorDate: Thu Jun 6 12:19:05 2019 +0200

    [SYNCOPE-1462] revisited UR management in enduser, fixed not refreshing UR 
list
---
 .../META-INF/resources/app/css/editUser.css        |   8 +-
 .../resources/app/css/templates/dark/editUser.css  |   2 +-
 .../resources/META-INF/resources/app/index.html    |   2 +-
 .../resources/META-INF/resources/app/js/app.js     |  13 ---
 .../app/js/directives/autoCompleteTextField.js     |  47 ++++++++
 .../resources/app/js/directives/requestForms.js    | 110 ------------------
 .../resources/app/js/directives/requests.js        |  98 ++++++----------
 .../app/js/services/userRequestsService.js         |  13 +--
 .../resources/app/languages/de/static.json         |   4 +-
 .../resources/app/languages/en/static.json         |   2 +-
 .../resources/app/languages/it/static.json         |   2 +-
 .../resources/app/languages/ja/static.json         |   2 +-
 .../resources/app/views/autoCompleteTextField.html |  25 ++++
 .../META-INF/resources/app/views/requestForms.html |  61 ----------
 .../META-INF/resources/app/views/requests.html     | 129 ++++++++++++---------
 .../resources/app/views/user-request-forms.html    |  48 --------
 .../resources/app/views/user-requests.html         |   2 +-
 .../client/enduser/model/UserRequestWrapper.java   |  46 ++++++++
 .../enduser/resources/UserRequestsResource.java    |  62 +++++++---
 .../apache/syncope/common/lib/to/UserRequest.java  |  20 ++++
 .../core/flowable/api/UserRequestHandler.java      |   9 ++
 .../flowable/impl/FlowableUserRequestHandler.java  |  48 ++++++--
 .../syncope/core/logic/UserRequestLogic.java       |  40 ++++---
 .../rest/api/service/UserRequestService.java       |  14 +++
 .../rest/cxf/service/UserRequestServiceImpl.java   |   5 +
 25 files changed, 401 insertions(+), 411 deletions(-)

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 47d64d6..a0e86de 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
@@ -62,7 +62,7 @@ under the License.
   padding: 2%;
 }
 
-#attribute {
+#attribute, #form-submit {
   margin: auto;
   max-width:480px;
   padding: 10px;
@@ -379,6 +379,12 @@ div[role="tablist"] {
   background-color: #f5f5f5;
   border-color: #ddd;
 }
+
+.search-box {
+  padding-bottom: 5px !important;
+  margin: 3%
+}
+
 /* end default style
 ============================================================================= 
*/
 
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/css/templates/dark/editUser.css
 
b/client/enduser/src/main/resources/META-INF/resources/app/css/templates/dark/editUser.css
index dd0d351..1a34f70 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/css/templates/dark/editUser.css
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/css/templates/dark/editUser.css
@@ -92,4 +92,4 @@ span.k-dropdown,
 
 .card-header {
   background-color: #49553e;
-}
\ No newline at end of file
+}
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 7122b50..7f124f5 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
@@ -145,7 +145,6 @@ under the License.
   <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>
@@ -157,6 +156,7 @@ under the License.
   <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/autoCompleteTextField.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 f95093e..5b67897 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
@@ -237,16 +237,6 @@ app.config(['$stateProvider', '$urlRouterProvider', 
'$httpProvider', '$translate
                   }]
               }
             })
-            .state('update.userRequestForms', {
-              url: '/user-request-forms',
-              templateUrl: 'views/user-request-forms.html',
-              resolve: {
-                'authenticated': ['AuthService',
-                  function (AuthService) {
-                    return AuthService.islogged();
-                  }]
-              }
-            })
             /* </Extensions> */
             .state('update.finish', {
               url: '/finish',
@@ -457,9 +447,6 @@ app.controller('ApplicationController', ['$scope', 
'$rootScope', 'InfoService',
                     $scope.wizard.userRequests = {
                       "url": "/user-requests"
                     };
-                    $scope.wizard.userRequestForms = {
-                      "url": "/user-request-forms"
-                    };
                     /* </Extensions> */
                     $scope.wizardFirstStep = response.wizard.firstStep;
 
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/autoCompleteTextField.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/autoCompleteTextField.js
new file mode 100644
index 0000000..82ec6be
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/autoCompleteTextField.js
@@ -0,0 +1,47 @@
+/* 
+ * 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('autoCompleteTextField', function () {
+          return {
+            restrict: 'E',
+            templateUrl: 'views/autoCompleteTextField.html',
+            scope: {
+              values: "=",
+              selected: "="
+            },
+            controller: function ($scope) {
+              $scope.complete = function (string) {
+                var output = [];
+                angular.forEach($scope.values, function (selected) {
+                  if (selected.key.toLowerCase().indexOf(string.toLowerCase()) 
>= 0) {
+                    output.push(selected.key);
+                  }
+                });
+                $scope.filteredValues = output;
+              };
+
+              $scope.fillTextbox = function (selected) {
+                $scope.selected = selected;
+                $scope.filteredValues = null;
+              };
+            }
+          };
+        });
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
deleted file mode 100644
index 4512257..0000000
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requestForms.js
+++ /dev/null
@@ -1,110 +0,0 @@
-/* 
- * 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
index 64d15a5..ac4e1a6 100644
--- 
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
@@ -19,9 +19,8 @@
 'use strict';
 
 angular.module('self')
-        .directive('requests', ['UserRequestsService', 'BpmnProcessService', 
"$uibModal", "$document", '$filter',
-          '$rootScope',
-          function (UserRequestsService, BpmnProcessService, $uibModal, 
$document, $filter, $rootScope) {
+        .directive('requests', ['UserRequestsService', 'BpmnProcessService', 
'$rootScope',
+          function (UserRequestsService, BpmnProcessService, $rootScope) {
             return {
               restrict: 'E',
               templateUrl: 'views/requests.html',
@@ -46,18 +45,14 @@ angular.module('self')
                   // 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);
                     }
+                    // recalculate pages
+                    calculatePages();
                   });
                 };
                 // </Pagination>
@@ -80,18 +75,19 @@ angular.module('self')
 
                 var init = function () {
                   $scope.requests = [];
+                  $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;
+                  // init 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.selectedBpmnProcess = "";
                   $scope.bpmnProcesses = [];
                   BpmnProcessService.getBpmnProcesses().then(function 
(response) {
                     $scope.bpmnProcesses = response;
@@ -112,15 +108,15 @@ angular.module('self')
                 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);
+                $scope.cancel = function (requestWrapper, reason) {
+                  console.log("Cancel request ", 
requestWrapper.request.executionId, reason);
+                  
UserRequestsService.cancel(requestWrapper.request.executionId, 
reason).then(function (response) {
+                    var index = $scope.requests.result.indexOf(requestWrapper);
                     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");
+                      $scope.reloadPage($scope.query.page, $scope.query.size,
+                              "Process " + requestWrapper.request.executionId 
+ " successfully canceled");
                     }
                   }, function (response) {
                     var errorMessage;
@@ -129,49 +125,12 @@ angular.module('self')
                       errorMessage = response.split("ErrorMessage{{")[1];
                       errorMessage = errorMessage.split("}}")[0];
                     }
-                    console.error("Error canceling User Request: ", 
request.executionId, errorMessage);
+                    console.error("Error canceling User Request: ", 
requestWrapper.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];
+                $scope.startRequest = function () {
+                  var currentProc = $scope.selectedBpmnProcess;
                   UserRequestsService.start(currentProc).then(function 
(response) {
                     console.log("Process " + currentProc + " successfully 
started");
                     $scope.reloadPage($scope.query.page, $scope.query.size,
@@ -187,6 +146,25 @@ angular.module('self')
                     console.error("Error starting User Request: ", 
errorMessage);
                   });
                 };
+
+                $scope.submitForm = 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/services/userRequestsService.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/services/userRequestsService.js
index b887784..b7ed879 100644
--- 
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
@@ -27,18 +27,7 @@ angular.module('self')
             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 : ""))
+                      + (query.orderBy ? "&orderBy=" + query.orderby : "") + 
"&withForm=true")
                       .then(function (response) {
                         return response.data;
                       }, function (response) {
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 b685f6b..9d9eb38 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
@@ -57,10 +57,10 @@
   "SELECT_PROCESS": "Wählen Sie einen oder mehrere Prozesse aus, um die 
Anfragen zu starten",
   "PAGE_SIZE": "Seitengröße",
   "EXECUTION_ID": "Ausführungs-ID",
+  "TASK_ID": "Aufgaben-ID",
   "START_TIME": "Startzeit",
   "START": "Start",
   "own": "Besitzen",
-  "userRequests": "Anfragen",
-  "userRequestForms": "Formen"
+  "userRequests": "Anfragen"
 }
 
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 31574e4..93d74c9 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
@@ -46,7 +46,6 @@
   "virtualSchemas": "VirtualSchemas",
   "resources": "Resources",
   "userRequests": "Requests",
-  "userRequestForms": "Forms",
   "finish": "Finish",
   "RESOURCES_PLACEHOLDER": "Click to select resources...",
   "REALM": "Realm",
@@ -59,6 +58,7 @@
   "SELECT_PROCESS": "Select one (or many) processes to start request(s)",
   "PAGE_SIZE": "Page size",
   "EXECUTION_ID": "Execution id",
+  "TASK_ID": "Task 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 3194dfc..b922737 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
@@ -46,7 +46,6 @@
   "virtualSchemas": "VirtualSchemas",
   "resources": "Risorse",
   "userRequests": "Richieste",
-  "userRequestForms": "Form",
   "finish": "Fine",
   "RESOURCES_PLACEHOLDER": "Clicca per selezionare risorse...",
   "NEWUSER": "Nuovo utente",
@@ -59,6 +58,7 @@
   "SELECT_PROCESS": "Selezionare uno (o più) processi per far partire una 
richiesta(e)",
   "PAGE_SIZE": "Elementi per pagina",
   "EXECUTION_ID": "Id Esecuzione",
+  "TASK_ID": "Id Task",
   "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 51cd728..3d8d196 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
@@ -57,10 +57,10 @@
   "SELECT_PROCESS": "プロセスを1つ(または複数)選択して、要求を開始します。",
   "PAGE_SIZE": "ページサイズ",
   "EXECUTION_ID": "実行ID",
+  "TASK_ID": "タスクID",
   "START_TIME": "始まる時間",
   "START": "開始",
   "userRequests": "リクエスト",
-  "userRequestForms": "フォーム",
   "own": "自分"
 }
 
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/views/autoCompleteTextField.html
 
b/client/enduser/src/main/resources/META-INF/resources/app/views/autoCompleteTextField.html
new file mode 100644
index 0000000..c09e683
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/views/autoCompleteTextField.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+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>
+  <input type="text" name="selected" id="selected" ng-model="selected" 
ng-keyup="complete(selected)" class="form-control" />
+  <ul class="list-group">
+    <li class="list-group-item" ng-repeat="selected in filteredValues" 
ng-click="fillTextbox(selected)">{{selected}}</li>
+  </ul>
+</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
deleted file mode 100644
index aa44b2c..0000000
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/views/requestForms.html
+++ /dev/null
@@ -1,61 +0,0 @@
-<!--
-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
index 68c1da3..fc0f1f5 100644
--- 
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
@@ -16,61 +16,82 @@ 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 class="row panel panel-default card search-box">
+  <div class="col-md-10">
+    <auto-complete-text-field selected="selectedBpmnProcess" 
values="bpmnProcesses"></auto-complete-text-field>
   </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 class="col-md-2">
+    <button class="btn btn-secondary btn-default" type="button" 
ng-click="startRequest()">{{ 'START' | translate}}</button>
   </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 ng-repeat="requestWrapper in requests.result">
+  <uib-accordion ng-if="requests.result.length">
+    <div uib-accordion-group heading="{{requestWrapper.request.bpmnProcess| 
translate}}" 
+         class="breadcrumb-header panel panel-default">
+           <div style="margin-left: 3%; margin-right: 3%">
+             <table class="table">
+               <thead> 
+                 <tr> 
+                   <th>{{'EXECUTION_ID'| translate}}</th> 
+                   <th>{{'TASK_ID'| translate}}</th>
+                   <th>{{'START_TIME'| translate}}</th>
+                 </tr> 
+               </thead>
+
+               <tbody> 
+                 <tr>
+                   <td>{{requestWrapper.request.executionId}}</td> 
+                   <td>{{requestWrapper.request.taskId}}</td>
+                   <td>{{formatDate(requestWrapper.request.startTime)}}</td> 
+                 </tr>
+               </tbody>
+             </table>
+           </div>
+           <div ng-if="requestWrapper.request.hasForm && requestWrapper.form">
+             <div id="attribute" class="form-group" ng-repeat="property in 
requestWrapper.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" id="form-submit">
+               <button class="btn btn-secondary btn-default" type="button" 
ng-click="submitForm(requestWrapper.form)">{{ 'SUBMIT' | translate}}</button>
+             </div>
+           </div>
+           <div style="text-align: right; margin-top: 3%">
+             <a id="cancelRequest" class="btn btn-secondary btn-default" 
ng-click="cancel(requestWrapper)">
+               <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>
\ No newline at end of file
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
deleted file mode 100644
index 4971143..0000000
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/views/user-request-forms.html
+++ /dev/null
@@ -1,48 +0,0 @@
-<!--
-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
index 62c3e01..bc7aaa9 100644
--- 
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
@@ -16,7 +16,7 @@ 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">
+<div>
     <requests user="user"></requests>
 </div>
 
diff --git 
a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/model/UserRequestWrapper.java
 
b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/model/UserRequestWrapper.java
new file mode 100644
index 0000000..d96be15
--- /dev/null
+++ 
b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/model/UserRequestWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * 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.model;
+
+import org.apache.syncope.common.lib.BaseBean;
+import org.apache.syncope.common.lib.to.UserRequest;
+import org.apache.syncope.common.lib.to.UserRequestForm;
+
+public class UserRequestWrapper extends BaseBean {
+
+    private static final long serialVersionUID = -6221018606400361738L;
+
+    private final UserRequest request;
+
+    private final UserRequestForm form;
+
+    public UserRequestWrapper(final UserRequest request, final UserRequestForm 
form) {
+        this.request = request;
+        this.form = form;
+    }
+
+    public UserRequest getRequest() {
+        return request;
+    }
+
+    public UserRequestForm getForm() {
+        return form;
+    }
+
+}
diff --git 
a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java
 
b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java
index 812e408..6a25c5f 100644
--- 
a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java
+++ 
b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java
@@ -18,16 +18,20 @@
  */
 package org.apache.syncope.client.enduser.resources;
 
+import org.apache.syncope.client.enduser.model.UserRequestWrapper;
+
 import static org.apache.syncope.client.enduser.resources.BaseResource.LOG;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.stream.Collectors;
 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.BaseBean;
 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;
@@ -47,7 +51,6 @@ public class UserRequestsResource extends BaseResource {
 
         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)) {
@@ -81,21 +84,15 @@ public class UserRequestsResource extends BaseResource {
                 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());
+                    StringValue withForm = 
requestParameters.getParameterValue("withForm");
+                    LOG.debug("List available Flowable User Requests for user 
[{}]",
+                            
SyncopeEnduserSession.get().getSelfTO().getUsername());
+
+                    final PagedResult<? extends BaseBean> userRequests =
+                            withForm.toBoolean(false)
+                            ? fillWithForms(list(page, size))
+                            : list(page, size);
+
                     response.setWriteCallback(new 
AbstractResource.WriteCallback() {
 
                         @Override
@@ -117,7 +114,8 @@ public class UserRequestsResource extends BaseResource {
             response.setTextEncoding(StandardCharsets.UTF_8.name());
             response.setStatusCode(Response.Status.OK.getStatusCode());
         } catch (Exception e) {
-            LOG.error("Error retrieving user requests for [{}]", username, e);
+            LOG.error("Error retrieving user requests for [{}]", 
SyncopeEnduserSession.get().getSelfTO().getUsername(),
+                    e);
             response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new 
StringBuilder()
                     .append("ErrorMessage{{ ")
                     .append(e.getMessage())
@@ -127,4 +125,34 @@ public class UserRequestsResource extends BaseResource {
 
         return response;
     }
+
+    private static PagedResult<UserRequest> list(final StringValue page, final 
StringValue size)
+            throws NumberFormatException {
+        return SyncopeEnduserSession.get().getService(UserRequestService.class)
+                .list(new UserRequestQuery.Builder()
+                        
.user(SyncopeEnduserSession.get().getSelfTO().getUsername())
+                        .page(page.isEmpty()
+                                ? 1
+                                : Integer.parseInt(
+                                        page.toString()))
+                        .size(size.isEmpty()
+                                ? 10
+                                : Integer.parseInt(
+                                        size.toString())).build());
+    }
+
+    private PagedResult<UserRequestWrapper> fillWithForms(final 
PagedResult<UserRequest> reqsResult) {
+        PagedResult<UserRequestWrapper> result = new PagedResult<>();
+        result.getResult().addAll(reqsResult.getResult().stream()
+                .map(ur -> new UserRequestWrapper(ur, 
SyncopeEnduserSession.get().getService(UserRequestService.class)
+                
.getForm(SyncopeEnduserSession.get().getSelfTO().getUsername(), 
ur.getTaskId())))
+                .collect(Collectors.toList()));
+        result.setPage(reqsResult.getPage());
+        result.setSize(reqsResult.getSize());
+        result.setTotalCount(reqsResult.getTotalCount());
+        result.setPrev(reqsResult.getPrev());
+        result.setNext(reqsResult.getNext());
+
+        return result;
+    }
 }
diff --git 
a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequest.java
 
b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequest.java
index b0ce0a5..59f8129 100644
--- 
a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequest.java
+++ 
b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequest.java
@@ -38,6 +38,10 @@ public class UserRequest extends BaseBean {
     private String executionId;
 
     private String activityId;
+    
+    private String taskId;
+
+    private boolean hasForm;
 
     public String getBpmnProcess() {
         return bpmnProcess;
@@ -78,4 +82,20 @@ public class UserRequest extends BaseBean {
     public void setActivityId(final String activityId) {
         this.activityId = activityId;
     }
+    
+    public String getTaskId() {
+        return taskId;
+    }
+
+    public void setTaskId(final String taskId) {
+        this.taskId = taskId;
+    }
+
+    public boolean getHasForm() {
+        return hasForm;
+    }
+
+    public void setHasForm(final boolean hasForm) {
+        this.hasForm = hasForm;
+    }
 }
diff --git 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
index f7d7a35..73444f4 100644
--- 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
+++ 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
@@ -85,6 +85,15 @@ public interface UserRequestHandler {
     void cancelByUser(AnyDeletedEvent event);
 
     /**
+     * Get the form matching the provided task id.
+     *
+     * @param userKey user key
+     * @param taskId task id
+     * @return the form for the given task id
+     */
+    UserRequestForm getForm(String userKey, String taskId);
+    
+    /**
      * Get the forms matching the provided parameters.
      *
      * @param userKey user key (optional)
diff --git 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
index 86a6c03..06994a6 100644
--- 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
+++ 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
@@ -127,8 +127,11 @@ public class FlowableUserRequestHandler implements 
UserRequestHandler {
         userRequest.setStartTime(procInst.getStartTime());
         userRequest.setUsername(userDAO.find(split.getRight()).getUsername());
         userRequest.setExecutionId(procInst.getId());
-        userRequest.setActivityId(engine.getTaskService().createTaskQuery().
-                
processInstanceId(procInst.getProcessInstanceId()).singleResult().getTaskDefinitionKey());
+        final Task task = engine.getTaskService().createTaskQuery()
+                
.processInstanceId(procInst.getProcessInstanceId()).singleResult();
+        userRequest.setActivityId(task.getTaskDefinitionKey());
+        userRequest.setTaskId(task.getId());
+        userRequest.setHasForm(StringUtils.isNotBlank(task.getFormKey()));
         return userRequest;
     }
 
@@ -300,7 +303,9 @@ public class FlowableUserRequestHandler implements 
UserRequestHandler {
     }
 
     protected UserRequestForm getForm(final Task task) {
-        return FlowableUserRequestHandler.this.getForm(task, 
engine.getFormService().getTaskFormData(task.getId()));
+        return task == null
+                ? null
+                : FlowableUserRequestHandler.this.getForm(task, 
engine.getFormService().getTaskFormData(task.getId()));
     }
 
     protected UserRequestForm getForm(final Task task, final TaskFormData fd) {
@@ -450,6 +455,20 @@ public class FlowableUserRequestHandler implements 
UserRequestHandler {
         return formTO;
     }
 
+    @Override
+    public UserRequestForm getForm(final String userKey, final String taskId) {
+        TaskQuery query = 
engine.getTaskService().createTaskQuery().taskId(taskId);
+        if (userKey != null) {
+            
query.processInstanceBusinessKeyLike(FlowableRuntimeUtils.getProcBusinessKey("%",
 userKey));
+        }
+
+        String authUser = AuthContextUtils.getUsername();
+
+        return adminUser.equals(authUser)
+                ? getForm(getTask(taskId))
+                : 
getForm(query.taskCandidateOrAssigned(authUser).singleResult());
+    }
+
     @Transactional(readOnly = true)
     @Override
     public Pair<Integer, List<UserRequestForm>> getForms(
@@ -522,15 +541,7 @@ public class FlowableUserRequestHandler implements 
UserRequestHandler {
     }
 
     protected Pair<Task, TaskFormData> parseTask(final String taskId) {
-        Task task;
-        try {
-            task = 
engine.getTaskService().createTaskQuery().taskWithFormKey().taskId(taskId).singleResult();
-            if (task == null) {
-                throw new FlowableException("NULL result");
-            }
-        } catch (FlowableException e) {
-            throw new NotFoundException("Flowable Task " + taskId, e);
-        }
+        Task task = getTask(taskId);
 
         TaskFormData formData;
         try {
@@ -542,6 +553,19 @@ public class FlowableUserRequestHandler implements 
UserRequestHandler {
         return Pair.of(task, formData);
     }
 
+    protected Task getTask(final String taskId) throws NotFoundException {
+        Task task;
+        try {
+            task = 
engine.getTaskService().createTaskQuery().taskWithFormKey().taskId(taskId).singleResult();
+            if (task == null) {
+                throw new FlowableException("NULL result");
+            }
+        } catch (FlowableException e) {
+            throw new NotFoundException("Flowable Task " + taskId, e);
+        }
+        return task;
+    }
+
     @Override
     public UserRequestForm claimForm(final String taskId) {
         Pair<Task, TaskFormData> parsed = parseTask(taskId);
diff --git 
a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java
 
b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java
index cdef7bf..4b88eb8 100644
--- 
a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java
+++ 
b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java
@@ -153,27 +153,20 @@ public class UserRequestLogic extends 
AbstractTransactionalLogic<EntityTO> {
     }
 
     @PreAuthorize("isAuthenticated()")
+    public UserRequestForm getForm(final String userKey, final String taskId) {
+        evaluateKey(userKey);
+
+        return userRequestHandler.getForm(userKey, taskId);
+    }
+
+    @PreAuthorize("isAuthenticated()")
     @Transactional(readOnly = true)
     public Pair<Integer, List<UserRequestForm>> getForms(
             final String userKey,
             final int page,
             final int size,
             final List<OrderByClause> orderByClauses) {
-
-        if (userKey == null) {
-            securityChecks(null,
-                    FlowableEntitlement.USER_REQUEST_FORM_LIST,
-                    "Listing forms not allowed");
-        } else {
-            User user = userDAO.find(userKey);
-            if (user == null) {
-                throw new NotFoundException("User " + userKey);
-            }
-
-            securityChecks(user.getUsername(),
-                    FlowableEntitlement.USER_REQUEST_FORM_LIST,
-                    "Listing forms for user" + user.getUsername() + " not 
allowed");
-        }
+        evaluateKey(userKey);
 
         return userRequestHandler.getForms(userKey, page, size, 
orderByClauses);
     }
@@ -220,4 +213,21 @@ public class UserRequestLogic extends 
AbstractTransactionalLogic<EntityTO> {
 
         throw new UnresolvedReferenceException();
     }
+
+    private void evaluateKey(final String userKey) {
+        if (userKey == null) {
+            securityChecks(null,
+                    FlowableEntitlement.USER_REQUEST_FORM_LIST,
+                    "Listing forms not allowed");
+        } else {
+            User user = userDAO.find(userKey);
+            if (user == null) {
+                throw new NotFoundException("User " + userKey);
+            }
+
+            securityChecks(user.getUsername(),
+                    FlowableEntitlement.USER_REQUEST_FORM_LIST,
+                    "Listing forms for user" + user.getUsername() + " not 
allowed");
+        }
+    }
 }
diff --git 
a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java
 
b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java
index 7c2369b..dc55ad3 100644
--- 
a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java
+++ 
b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java
@@ -92,6 +92,20 @@ public interface UserRequestService extends JAXRSService {
             @QueryParam("reason") String reason);
 
     /**
+     * Returns a user request form matching the given task id.
+     *
+     * @param username username of the logged user
+     * @param taskId workflow task id
+     * @return the form for the given task id
+     */
+    @GET
+    @Path("forms/{username}/{taskId}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    UserRequestForm getForm(
+            @NotNull @PathParam("username") String username,
+            @NotNull @PathParam("taskId") String taskId);
+    
+    /**
      * Returns a list of user request forms matching the given query.
      *
      * @param query query conditions
diff --git 
a/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java
 
b/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java
index 580ca76..19c4f52 100644
--- 
a/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java
+++ 
b/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java
@@ -75,6 +75,11 @@ public class UserRequestServiceImpl extends 
AbstractServiceImpl implements UserR
     }
 
     @Override
+    public UserRequestForm getForm(final String username, final String taskId) 
{
+        return logic.getForm(getActualKey(userDAO, username), taskId);
+    }
+    
+    @Override
     public PagedResult<UserRequestForm> getForms(final UserRequestFormQuery 
query) {
         if (query.getUser() != null) {
             query.setUser(getActualKey(userDAO, query.getUser()));

Reply via email to