diff --git a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/form/FormIntentGenerator.java b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/form/FormIntentGenerator.java index 82d7fed1af4..117d1a16f84 100644 --- a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/form/FormIntentGenerator.java +++ b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/form/FormIntentGenerator.java @@ -61,7 +61,11 @@ * complete the current BPM user task: the Inbox/Process perspective opens the form with * {@code ?taskId=&processInstanceId=}, and the handler POSTs {@code COMPLETE} to * {@code /services/bpm/bpm-processes/tasks/} with the action name and the form model as - * process variables (so a downstream gateway can branch on the action). Forms opened outside a task + * process variables (so a downstream gateway can branch on the action). On success the handler + * closes its host via both {@code DialogHub.closeWindow()} and {@code window.close()} - the former + * closes the dialog when the form is opened from an entity view, the latter a standalone + * (script-opened) window; each is a harmless no-op where it does not apply, including the Inbox's + * inline iframe (which clears its own pane on its refresh cycle). Forms opened outside a task * report the missing {@code taskId} instead of failing silently. Business logic beyond completing * the task belongs in a hand-written form override under {@code custom/}. * @@ -146,6 +150,7 @@ private static String buildCode(FormIntent form) { const __taskParams = new URLSearchParams(window.location.search); const __taskId = __taskParams.get('taskId'); const __notifications = new NotificationHub(); + const __dialogs = new DialogHub(); function __completeTask(action) { if (!__taskId) { @@ -157,6 +162,7 @@ function __completeTask(action) { data: Object.assign({ action: action }, $scope.model || {}) }).then(() => { __notifications.show({ type: 'positive', title: 'Task submitted', description: 'The task was completed (' + action + ').' }); + __dialogs.closeWindow(); window.close(); }).catch((error) => { const message = error && error.data && error.data.message ? error.data.message : 'Unknown error'; diff --git a/components/resources/resources-dashboard/src/main/resources/META-INF/dirigible/dashboard/services/process-tasks.js b/components/resources/resources-dashboard/src/main/resources/META-INF/dirigible/dashboard/services/process-tasks.js new file mode 100644 index 00000000000..664705d81b0 --- /dev/null +++ b/components/resources/resources-dashboard/src/main/resources/META-INF/dirigible/dashboard/services/process-tasks.js @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2010-2026 Eclipse Dirigible contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * SPDX-FileCopyrightText: Eclipse Dirigible contributors + * SPDX-License-Identifier: EPL-2.0 + */ +/** + * Shared support for surfacing a record's BPM user tasks as in-context actions on generated entity + * views. A process-aware entity carries a system-managed ProcessId (the started process-instance id, + * written back by the intent process trigger); the current user's actionable inbox tasks are fetched + * once and bucketed by processInstanceId, so any view - list, manage, or a master-detail pane - can + * surface the tasks for a given record regardless of its layout. + * + * Usage in a generated view: depend on the 'ProcessTasks' module and drop + * next to the record's actions. + */ +angular.module('ProcessTasks', ['platformLocale']) + .factory('ProcessTasks', ['$http', 'LocaleService', function ($http, LocaleService) { + const Dialogs = new DialogHub(); + let byProcessId = {}; + let loadPromise = null; + + const bucket = (responses) => { + const map = {}; + const seen = new Set(); + const collect = (tasks, mine) => (tasks || []).forEach((task) => { + if (!task.processInstanceId || seen.has(task.id)) return; + seen.add(task.id); + task.mine = mine; + (map[task.processInstanceId] = map[task.processInstanceId] || []).push(task); + }); + collect(responses[0].data, true); + collect(responses[1].data, false); + return map; + }; + + const load = () => { + loadPromise = Promise.all([ + $http.get('/services/inbox/tasks?type=assignee', { params: { limit: 100 } }), + $http.get('/services/inbox/tasks?type=groups', { params: { limit: 100 } }) + ]).then((responses) => { + byProcessId = bucket(responses); + return byProcessId; + }, (error) => { + byProcessId = {}; + console.error('ProcessTasks: unable to load inbox tasks', error); + return byProcessId; + }); + return loadPromise; + }; + + const openForm = (task) => { + if (!task.formKey) { + Dialogs.showAlert({ + title: task.name, + message: LocaleService.t('dashboard.processTasks.noForm', {}, 'This task has no form to display.'), + type: AlertTypes.Information + }); + return; + } + const separator = task.formKey.indexOf('?') >= 0 ? '&' : '?'; + const formUrl = task.formKey + separator + 'taskId=' + encodeURIComponent(task.id) + '&processInstanceId=' + encodeURIComponent(task.processInstanceId); + // The generated task form completes the task and closes this window itself (DialogHub.closeWindow); + // we just re-fetch when it closes so the originating view's badge drops the completed task. + const closeTopic = 'dashboard.processTasks.window.' + task.id; + const closeListener = Dialogs.addMessageListener({ + topic: closeTopic, + handler: () => { + Dialogs.removeMessageListener(closeListener); + load(); + } + }); + Dialogs.showWindow({ + hasHeader: true, + title: task.name, + path: formUrl, + closeButton: true, + callbackTopic: closeTopic + }); + }; + + return { + /** Force a re-fetch of the current user's tasks (call after a list reload / record change). */ + refresh: () => load(), + /** Fetch once if not already loaded; subsequent calls reuse the in-flight / cached result. */ + ensureLoaded: () => loadPromise || load(), + /** The cached actionable tasks for a record, matched by entity.ProcessId === task.processInstanceId. */ + getTasks: (entity) => (entity && entity.ProcessId && byProcessId[entity.ProcessId]) || [], + /** Open a task's form; a candidate (not-yet-assigned) task is claimed for the user first. */ + openTask: (task) => { + if (task.mine) { + openForm(task); + return; + } + $http.post('/services/inbox/tasks/' + task.id, { action: 'CLAIM' }).then(() => { + task.mine = true; + openForm(task); + load(); + }, (error) => { + const message = error.data ? error.data.message : ''; + Dialogs.showAlert({ + title: task.name, + message: LocaleService.t('dashboard.processTasks.unableToClaim', { message: message }, `Unable to claim task: '${message}'`), + type: AlertTypes.Error + }); + console.error('ProcessTasks: unable to claim task', error); + }); + } + }; + }]) + .directive('entityProcessTasks', ['ProcessTasks', 'LocaleService', function (ProcessTasks, LocaleService) { + return { + restrict: 'E', + scope: { entity: '<' }, + template: ` + + + + + + + + +`, + link: (scope) => { + scope.ariaLabel = LocaleService.t('dashboard.processTasks.pending', {}, 'Pending tasks'); + scope.tasks = () => ProcessTasks.getTasks(scope.entity); + scope.openTask = (task) => ProcessTasks.openTask(task); + ProcessTasks.ensureLoaded().then(() => scope.$applyAsync()); + } + }; + }]); diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/controller.js.template index 81901e268f4..0aaa2a13041 100644 --- a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/controller.js.template +++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/controller.js.template @@ -1,9 +1,9 @@ #set($dollar = '$') -angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService']) +angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'#if($hasProcess), 'ProcessTasks'#end]) .config(['EntityServiceProvider', (EntityServiceProvider) => { EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller'; }]) - .controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService) => { + .controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService#if($hasProcess), ProcessTasks#end) => { const Dialogs = new DialogHub(); $scope.dataPage = 1; $scope.dataCount = 0; @@ -120,6 +120,9 @@ angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntitySer #end $scope.data = response.data; +#if($hasProcess) + ProcessTasks.refresh(); +#end }, (error) => { const message = error.data ? error.data.message : ''; Dialogs.showAlert({ diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/index.html.template index ca42f89ab0f..0014592b570 100644 --- a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/index.html.template +++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/index.html.template @@ -9,6 +9,9 @@ +#if($hasProcess) + +#end @@ -91,6 +94,9 @@ #end #end +#if($hasProcess) + +#end diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/controller.js.template index b8ecaa431d9..3ff847b52ed 100644 --- a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/controller.js.template +++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/controller.js.template @@ -1,9 +1,9 @@ #set($dollar = '$') -angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService']) +angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'#if($hasProcess), 'ProcessTasks'#end]) .config(['EntityServiceProvider', (EntityServiceProvider) => { EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller'; }]) - .controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService, ButtonStates) => { + .controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService, ButtonStates#if($hasProcess), ProcessTasks#end) => { const Dialogs = new DialogHub(); let translated = { yes: 'Yes', @@ -140,6 +140,9 @@ angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntitySer #end $scope.data = response.data; +#if($hasProcess) + ProcessTasks.refresh(); +#end }, (error) => { const message = error.data ? error.data.message : ''; Dialogs.showAlert({ diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/index.html.template index 41d6304554a..d10237b97f3 100644 --- a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/index.html.template +++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/index.html.template @@ -9,6 +9,9 @@ +#if($hasProcess) + +#end @@ -92,6 +95,9 @@ #end #end +#if($hasProcess) + +#end diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/controller.js.template index 6514041f993..bf25d3dbf6e 100644 --- a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/controller.js.template +++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/controller.js.template @@ -1,9 +1,9 @@ #set($dollar = '$') -angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService']) +angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'#if($hasProcess), 'ProcessTasks'#end]) .config(['EntityServiceProvider', (EntityServiceProvider) => { EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller'; }]) - .controller('PageController', ($scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService) => { + .controller('PageController', ($scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService#if($hasProcess), ProcessTasks#end) => { const Dialogs = new DialogHub(); //-----------------Custom Actions-------------------// Extensions.getWindows(['${projectName}-custom-action']).then((response) => { @@ -136,6 +136,9 @@ angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntitySer #end $scope.data = response.data; +#if($hasProcess) + ProcessTasks.refresh(); +#end }, (error) => { const message = error.data ? error.data.message : ''; Dialogs.showAlert({ diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/index.html.template index b371e016a1e..53895ee343c 100644 --- a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/index.html.template +++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/index.html.template @@ -9,6 +9,9 @@ +#if($hasProcess) + +#end @@ -94,6 +97,9 @@ #end #end +#if($hasProcess) + +#end diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/controller.js.template index 9edb2232f65..d68bb689b5a 100644 --- a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/controller.js.template +++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/controller.js.template @@ -1,5 +1,5 @@ #set($dollar = '$') -angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', ($scope, Extensions, LocaleService) => { +angular.module('page', ['blimpKit', 'platformView', 'platformLocale'#if($hasProcess), 'ProcessTasks'#end]).controller('PageController', ($scope, Extensions, LocaleService) => { const Dialogs = new DialogHub(); $scope.entity = {}; diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/index.html.template index 1c0f344529d..993dd67db7d 100644 --- a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/index.html.template +++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/index.html.template @@ -8,6 +8,9 @@ +#if($hasProcess) + +#end @@ -235,6 +238,15 @@ +#if($hasProcess) + + + + + + + +#end diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/controller.js.template index 00f1d1fd1d9..89ddff84888 100644 --- a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/controller.js.template +++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/controller.js.template @@ -1,9 +1,9 @@ #set($dollar = '$') -angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService']) +angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'#if($hasProcess), 'ProcessTasks'#end]) .config(['EntityServiceProvider', (EntityServiceProvider) => { EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller'; }]) - .controller('PageController', ($scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService, ButtonStates) => { + .controller('PageController', ($scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService, ButtonStates#if($hasProcess), ProcessTasks#end) => { const Dialogs = new DialogHub(); let translated = { yes: 'Yes', @@ -157,6 +157,9 @@ angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntitySer #end $scope.data = response.data; +#if($hasProcess) + ProcessTasks.refresh(); +#end }, (error) => { const message = error.data ? error.data.message : ''; Dialogs.showAlert({ diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/index.html.template index 82ead712f55..555c1de1afb 100644 --- a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/index.html.template +++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/index.html.template @@ -9,6 +9,9 @@ +#if($hasProcess) + +#end @@ -94,6 +97,9 @@ #end #end +#if($hasProcess) + +#end diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/controller.js.template index 929a0778448..3bfd655054c 100644 --- a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/controller.js.template +++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/controller.js.template @@ -1,5 +1,5 @@ #set($dollar = '$') -angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService']) +angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'#if($hasProcess), 'ProcessTasks'#end]) .config(["EntityServiceProvider", (EntityServiceProvider) => { EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller'; }]) diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/index.html.template index 42882bd555d..65b7f829979 100644 --- a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/index.html.template +++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/index.html.template @@ -10,6 +10,9 @@ +#if($hasProcess) + +#end @@ -576,6 +579,15 @@ +#if($hasProcess) + + + + + + + +#end diff --git a/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/parameterUtils.js b/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/parameterUtils.js index c365bb7344c..372416c3eef 100644 --- a/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/parameterUtils.js +++ b/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/parameterUtils.js @@ -71,6 +71,10 @@ export function process(model, parameters) { p.isReadOnlyProperty = p.isReadOnlyProperty === "true"; p.widgetIsMajor = p.widgetIsMajor === "true"; p.widgetLabel = p.widgetLabel ? p.widgetLabel : p.name; + + if (p.name === "ProcessId") { + e.hasProcess = true; + } p.widgetDropdownUrl = ""; p.widgetDropdownControllerUrl = "";