From 8db6a308c78b06e8e834a7f2affdc37602b9815e Mon Sep 17 00:00:00 2001 From: Javier Medina Date: Mon, 20 Apr 2026 10:59:20 +0100 Subject: [PATCH 01/10] feat(FilterModal): add variable management to task filters and enhance task display with variable details --- .../webapp/providers/TaskProvider.java | 46 +++++++++++++++- .../webapp/rest/model/FilterProperties.java | 3 ++ .../webapp/rest/model/FilterVariable.java | 29 ++++++++++ .../org/cibseven/webapp/rest/model/Task.java | 5 +- .../webapp/rest/model/TaskFiltering.java | 1 + frontend/src/assets/translations_de.json | 10 ++++ frontend/src/assets/translations_en.json | 10 ++++ frontend/src/assets/translations_es.json | 10 ++++ frontend/src/assets/translations_ru.json | 10 ++++ frontend/src/assets/translations_ua.json | 10 ++++ frontend/src/components/task/TasksContent.vue | 4 ++ frontend/src/components/task/TasksNavBar.vue | 40 ++++++++++++++ .../components/task/filter/FilterModal.vue | 53 ++++++++++++++++++- 13 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/FilterVariable.java diff --git a/cibseven-webclient-core/src/main/java/org/cibseven/webapp/providers/TaskProvider.java b/cibseven-webclient-core/src/main/java/org/cibseven/webapp/providers/TaskProvider.java index 050766076..26b01e997 100644 --- a/cibseven-webclient-core/src/main/java/org/cibseven/webapp/providers/TaskProvider.java +++ b/cibseven-webclient-core/src/main/java/org/cibseven/webapp/providers/TaskProvider.java @@ -19,11 +19,14 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import org.cibseven.webapp.auth.CIBUser; import org.cibseven.webapp.exception.SystemException; @@ -35,6 +38,7 @@ import org.cibseven.webapp.rest.model.TaskForm; import org.cibseven.webapp.rest.model.TaskHistory; import org.cibseven.webapp.rest.model.Variable; +import org.cibseven.webapp.rest.model.VariableInstance; import org.cibseven.webapp.providers.utils.URLUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; @@ -184,7 +188,12 @@ public Object form(String taskId, CIBUser user) { public Collection findTasksByFilter(TaskFiltering filters, String filterId, CIBUser user, Integer firstResult, Integer maxResults) { String url = getEngineRestUrl(user) + "/filter/" + filterId + "/list?firstResult=" + firstResult + "&maxResults=" + maxResults; try { - return Arrays.asList(((ResponseEntity) doPost(url, filters.json(), Task[].class, user)).getBody()); + List tasks = new ArrayList<>(Arrays.asList(((ResponseEntity) doPost(url, filters.json(), Task[].class, user)).getBody())); + List variableNames = filters.getVariableNames(); + if (variableNames != null && !variableNames.isEmpty()) { + enrichTasksWithVariables(tasks, variableNames, user); + } + return tasks; } catch (JsonProcessingException e) { SystemException se = new SystemException(e); log.info("Exception in getTasksFiltered(...):", se); @@ -192,6 +201,41 @@ public Collection findTasksByFilter(TaskFiltering filters, String filterId } } + private void enrichTasksWithVariables(List tasks, List variableNames, CIBUser user) { + String processInstanceIds = tasks.stream() + .map(Task::getProcessInstanceId) + .filter(id -> id != null && !id.isEmpty()) + .distinct() + .collect(Collectors.joining(",")); + + if (processInstanceIds.isEmpty()) return; + + String url = getEngineRestUrl(user) + "/variable-instance?processInstanceIdIn=" + + processInstanceIds + "&deserializeValues=false"; + try { + VariableInstance[] instances = ((ResponseEntity) + doGet(url, VariableInstance[].class, user, false)).getBody(); + if (instances == null) return; + + Map> varsByInstance = new HashMap<>(); + for (VariableInstance vi : instances) { + if (variableNames.contains(vi.getName()) && vi.getProcessInstanceId() != null) { + varsByInstance + .computeIfAbsent(vi.getProcessInstanceId(), k -> new HashMap<>()) + .put(vi.getName(), vi.getValue()); + } + } + + for (Task task : tasks) { + if (task.getProcessInstanceId() != null) { + task.setVariables(varsByInstance.getOrDefault(task.getProcessInstanceId(), new HashMap<>())); + } + } + } catch (Exception e) { + log.warn("Could not enrich tasks with variables: {}", e.getMessage()); + } + } + @Override public Integer findTasksCountByFilter(String filterId, CIBUser user, TaskFiltering filters) { String url = getEngineRestUrl(user) + "/filter/" + filterId + "/count"; diff --git a/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/FilterProperties.java b/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/FilterProperties.java index 79c662733..de63caf46 100644 --- a/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/FilterProperties.java +++ b/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/FilterProperties.java @@ -16,6 +16,8 @@ */ package org.cibseven.webapp.rest.model; +import java.util.List; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AllArgsConstructor; @@ -29,4 +31,5 @@ public class FilterProperties { private String description; private boolean refresh; private int priority; + private List variables; } diff --git a/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/FilterVariable.java b/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/FilterVariable.java new file mode 100644 index 000000000..e8758645b --- /dev/null +++ b/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/FilterVariable.java @@ -0,0 +1,29 @@ +/* + * Copyright CIB software GmbH and/or licensed to CIB software GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. CIB software licenses this file to you under the Apache License, + * Version 2.0; 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.cibseven.webapp.rest.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) +public class FilterVariable { + private String name; + private String label; +} diff --git a/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/Task.java b/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/Task.java index 3db660f7a..7e86821da 100644 --- a/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/Task.java +++ b/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/Task.java @@ -16,6 +16,8 @@ */ package org.cibseven.webapp.rest.model; +import java.util.Map; + import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -54,7 +56,8 @@ public class Task { @JsonProperty("taskDefinitionKey") @JsonAlias({"taskDefinitionId"}) String taskDefinitionKey; @JsonProperty("processDefinitionId") @JsonAlias({"processDefinitionKey"}) String processDefinitionId; @JsonProperty("processInstanceId") @JsonAlias({"processInstanceKey"}) String processInstanceId; - + Map variables; + public String json() throws JsonProcessingException { return new ObjectMapper().writeValueAsString(this); } diff --git a/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/TaskFiltering.java b/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/TaskFiltering.java index b3934539d..9bade2b4e 100644 --- a/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/TaskFiltering.java +++ b/cibseven-webclient-core/src/main/java/org/cibseven/webapp/rest/model/TaskFiltering.java @@ -35,6 +35,7 @@ public class TaskFiltering { List processVariables; List orQueries; Boolean likePatternIgnoreCase; + List variableNames; public String json() throws JsonProcessingException { return new ObjectMapper().writeValueAsString(this); diff --git a/frontend/src/assets/translations_de.json b/frontend/src/assets/translations_de.json index 15cf9f86f..888fd95f5 100644 --- a/frontend/src/assets/translations_de.json +++ b/frontend/src/assets/translations_de.json @@ -924,6 +924,16 @@ "legendExpression": "Mit \"*\" endende Schlüssel akzeptieren Ausdrücke als Wert. Z.B.: '${ currentUser() }' oder '${ currentUserGroups() }'", "legendMultiple": "Mit \"in\" endende Schlüssel akzeptieren mehrere durch Komma getrennte Werte. Z.B.: `SchlüsselC, SchlüsselA, SchlüsselB`", "create": "Filter erstellen", + "variablesTitle": "Variablen", + "variablesLegend": "Sie können Variablen definieren, die in der Aufgabenliste angezeigt werden.", + "showUndefinedVariable": "Undefinierte Variablen anzeigen", + "addVariable": "Variable hinzufügen", + "variable": { + "name": "Name", + "label": "Bezeichnung", + "namePlaceholder": "Variablenname", + "labelPlaceholder": "Lesbarer Name" + }, "operators": { "eq": "=", "neq": "≠", diff --git a/frontend/src/assets/translations_en.json b/frontend/src/assets/translations_en.json index 256ecb34e..7af0a335d 100644 --- a/frontend/src/assets/translations_en.json +++ b/frontend/src/assets/translations_en.json @@ -924,6 +924,16 @@ "legendExpression": "Keys ending with \"*\" accept expressions as value. E.g.: '${ currentUser() }' or '${ currentUserGroups() }'", "legendMultiple": "Keys ending with \"in\" accept multiple values separated by comma. E.g.: `keyC, keyA, keyB`", "create": "Create filter", + "variablesTitle": "Variables", + "variablesLegend": "You can define variables shown in the tasks list.", + "showUndefinedVariable": "Show undefined variables", + "addVariable": "Add variable", + "variable": { + "name": "Name", + "label": "Label", + "namePlaceholder": "Variable name", + "labelPlaceholder": "Readable label" + }, "operators": { "eq": "=", "neq": "≠", diff --git a/frontend/src/assets/translations_es.json b/frontend/src/assets/translations_es.json index 9ba421a4f..ddfbaf912 100644 --- a/frontend/src/assets/translations_es.json +++ b/frontend/src/assets/translations_es.json @@ -924,6 +924,16 @@ "legendExpression": "Las claves que terminan en \"*\" aceptan expresiones como valor. Por ejemplo: '${ currentUser() }' o '${ currentUserGroups() }'", "legendMultiple": "Las claves que terminan en \"in\" aceptan múltiples valores separados por coma. Por ejemplo: `claveC, claveA, claveB`", "create": "Crear filtro", + "variablesTitle": "Variables", + "variablesLegend": "Puede definir variables que se muestran en la lista de tareas.", + "showUndefinedVariable": "Mostrar variables no definidas", + "addVariable": "Añadir variable", + "variable": { + "name": "Nombre", + "label": "Etiqueta", + "namePlaceholder": "Nombre de variable", + "labelPlaceholder": "Etiqueta legible" + }, "operators": { "eq": "=", "neq": "≠", diff --git a/frontend/src/assets/translations_ru.json b/frontend/src/assets/translations_ru.json index 2598c0cf1..9032c1df9 100644 --- a/frontend/src/assets/translations_ru.json +++ b/frontend/src/assets/translations_ru.json @@ -924,6 +924,16 @@ "legendExpression": "Свойства с окончанием \"*\" в качестве значения могут иметь выражения, например, '${ currentUser() }' или '${ currentUserGroups() }'", "legendMultiple": "Свойства с окончанием \"in\" могут иметь несколько значений, например, список значений, разделённый запятыми, например, `keyC, keyA, keyB`", "create": "Создать фильтр", + "variablesTitle": "Переменные", + "variablesLegend": "Вы можете задать переменные, отображаемые в списке задач.", + "showUndefinedVariable": "Показывать неопределённые переменные", + "addVariable": "Добавить переменную", + "variable": { + "name": "Имя", + "label": "Метка", + "namePlaceholder": "Имя переменной", + "labelPlaceholder": "Читаемое название" + }, "operators": { "eq": "=", "neq": "≠", diff --git a/frontend/src/assets/translations_ua.json b/frontend/src/assets/translations_ua.json index c7c729b04..a54d3a37f 100644 --- a/frontend/src/assets/translations_ua.json +++ b/frontend/src/assets/translations_ua.json @@ -924,6 +924,16 @@ "legendExpression": "Властивості з закінченням \"*\" як значення можуть мати вирази, наприклад '${ currentUser() }' або '${ currentUserGroups() }'", "legendMultiple": "Властивості з закінченням \"in\" можуть мати кілька значень, наприклад список значень, розділений комами: `keyC, keyA, keyB`", "create": "Створити фільтр", + "variablesTitle": "Змінні", + "variablesLegend": "Ви можете задати змінні, які відображаються у списку завдань.", + "showUndefinedVariable": "Показувати невизначені змінні", + "addVariable": "Додати змінну", + "variable": { + "name": "Ім'я", + "label": "Мітка", + "namePlaceholder": "Ім'я змінної", + "labelPlaceholder": "Читабельна назва" + }, "operators": { "eq": "=", "neq": "≠", diff --git a/frontend/src/components/task/TasksContent.vue b/frontend/src/components/task/TasksContent.vue index 9bf6c8753..5f00cc1f2 100644 --- a/frontend/src/components/task/TasksContent.vue +++ b/frontend/src/components/task/TasksContent.vue @@ -275,6 +275,10 @@ export default { filters.orQueries.push(this.$store.getters.formatedCriteriaData) } } + const filterVariables = this.$store.state.filter.selected.properties?.variables + if (filterVariables && filterVariables.length > 0) { + filters.variableNames = filterVariables.map(v => v.name) + } TaskService.findTasksByFilter(this.$store.state.filter.selected.id, filters, { firstResult: firstResult, maxResults: maxResults }).then(result => { const tasks = this.tasksByPermissions(this.$root.config.permissions.displayTasks, result) diff --git a/frontend/src/components/task/TasksNavBar.vue b/frontend/src/components/task/TasksNavBar.vue index 9f16e753a..9ba20aaae 100644 --- a/frontend/src/components/task/TasksNavBar.vue +++ b/frontend/src/components/task/TasksNavBar.vue @@ -141,6 +141,23 @@ +
+
+
+
{{ filterVar.label }}:
+
+ {{ task.variables[filterVar.name] }} +
+
<undefined>
+
+
+
+ +
+
@@ -220,6 +237,7 @@ export default { pauseRefreshButton: false, advancedFilter: [], advancedFilterAux: null, + expandedTasks: {}, justSelectedFromList: false, pendingScrollToTaskId: null } @@ -273,6 +291,12 @@ export default { }, filteredFields() { return this.$root.config.taskSorting.fields.filter(item => this.showFields(item)) + }, + filterVariables: function() { + return this.$store.state.filter.selected.properties?.variables || [] + }, + showUndefinedVariable: function() { + return this.$store.state.filter.selected.properties?.showUndefinedVariable || false } }, created: function () { @@ -285,6 +309,15 @@ export default { methods: { ...mapActions('task', ['setSelectedAssignee']), formatDateForTooltips, + toggleTaskVariables: function(taskId) { + this.expandedTasks[taskId] = !this.expandedTasks[taskId] + }, + visibleFilterVariables: function(task) { + return this.filterVariables.filter(filterVar => + (task.variables && task.variables[filterVar.name] !== undefined && task.variables[filterVar.name] !== null) || + this.showUndefinedVariable + ) + }, loadAdvancedFilters: function() { this.advancedFilter = [] this.$root.config.taskFilter.advancedSearch.processVariables.forEach(pv => { @@ -536,6 +569,13 @@ export default {