From 23769434503318e26b399dfb0db5ca3edfd97a87 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Mon, 4 May 2026 08:08:28 +0200
Subject: [PATCH 01/49] [ETASK-23] Dynamic view configuration - update text
translations
---
.../menu/domain/configurations/TabbedCaseViewBody.java | 5 +++--
.../petriNets/engine-processes/menu/menu_item.xml | 8 +++++---
.../menu/tabbed_case_view_configuration.xml | 2 +-
3 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewBody.java
index 5f18b2920cb..da2fb5314d6 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewBody.java
@@ -2,6 +2,7 @@
import com.netgrif.application.engine.menu.domain.MenuItemView;
import com.netgrif.application.engine.menu.domain.ToDataSetOutcome;
+import com.netgrif.application.engine.petrinet.domain.I18nString;
import com.netgrif.application.engine.petrinet.domain.dataset.FieldType;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -15,7 +16,7 @@
@EqualsAndHashCode(callSuper = true)
public class TabbedCaseViewBody extends ViewBody {
private String viewSearchType = "fulltext_advanced";
- private String createCaseButtonTitle;
+ private I18nString createCaseButtonTitle;
private String createCaseButtonIcon = "add";
private boolean requireTitleInCreation = true;
private boolean showCreateCaseButton = true;
@@ -45,7 +46,7 @@ protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) {
outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP,
this.viewSearchType);
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_CREATE_CASE_BUTTON_TITLE, FieldType.TEXT,
+ outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_CREATE_CASE_BUTTON_TITLE, FieldType.I18N,
this.createCaseButtonTitle);
outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_CREATE_CASE_BUTTON_ICON, FieldType.TEXT,
this.createCaseButtonIcon);
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 8fe4ae4c9f7..f9f9fc7335b 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -468,7 +468,7 @@
tab_icon_preview
- Tab icon preview
+ Tab icon previewhtmltextarea
@@ -614,6 +614,7 @@
Bude zobrazený v menuIdentifikátor ikony v karteIdentifikátor Material ikony. Zoznam ikon s identifikátormi je dostupný online.
+ Náhľad ikony v karteZobraziť ikonu v karte?Názov položkyBude zobrazený v karte
@@ -670,6 +671,7 @@
Titel des EintragesWird im Menü angezeigtIkonen Identifikator der Registerkarte
+ Vorschau der Registerkarte IkoneZeige die Registerkarte Ikone an?Titel der RegisterkarteWird in der Registerkarte angezeigt
@@ -1436,11 +1438,11 @@
finish
-
+ delegate
-
+
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
index 3e1adad7aa7..cdb1d1c0ecb 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
@@ -229,7 +229,7 @@
fulltext_advanced
-
+
create_case_button_title"New case" button title
From a94fafd4257539468f56259ae0dfc49267e056f7 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Mon, 4 May 2026 09:16:19 +0200
Subject: [PATCH 02/49] [ETASK-23] Dynamic view configuration - fix null node -
add todo
---
.../menu/domain/configurations/TabbedCaseViewBody.java | 6 ++++--
.../application/engine/menu/services/MenuItemService.java | 2 ++
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewBody.java
index da2fb5314d6..661ff73eaa5 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewBody.java
@@ -46,8 +46,10 @@ protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) {
outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP,
this.viewSearchType);
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_CREATE_CASE_BUTTON_TITLE, FieldType.I18N,
- this.createCaseButtonTitle);
+ if (this.createCaseButtonTitle != null) {
+ outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_CREATE_CASE_BUTTON_TITLE, FieldType.I18N,
+ this.createCaseButtonTitle);
+ }
outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_CREATE_CASE_BUTTON_ICON, FieldType.TEXT,
this.createCaseButtonIcon);
outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_REQUIRE_TITLE_IN_CREATION, FieldType.BOOLEAN,
diff --git a/src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java
index 23f18825609..eceb876213f 100644
--- a/src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java
@@ -104,6 +104,8 @@ public Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableExce
throw new IllegalArgumentException(String.format("Menu item identifier %s is not unique!", sanitizedIdentifier));
}
+ // todo ETASK-23 validation
+
Case parentItemCase = getOrCreateFolderItem(body.getUri());
I18nString newName = body.getMenuName();
if (newName == null) {
From 42b8bb3658979b3c60d698545fb7802fca34bcf2 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Mon, 4 May 2026 12:02:58 +0200
Subject: [PATCH 03/49] [ETASK-23] Dynamic view configuration - update petri
net of menu_item.xml - fix handling of uri - update role permission in
menu_item.xml
---
.../engine/menu/domain/MenuItemConstants.java | 2 +-
.../engine/menu/services/MenuItemService.java | 5 +-
.../engine/menu/utils/MenuItemUtils.java | 2 +-
.../engine-processes/menu/menu_item.xml | 146 +++++++++++++++---
4 files changed, 130 insertions(+), 25 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
index d6ac1a37afc..0c1912a69fc 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
@@ -30,6 +30,6 @@ public class MenuItemConstants {
public static final String FIELD_VIEW_CONFIGURATION_FORM = "view_configuration_form";
public static final String TRANS_SETTINGS_ID = "item_settings";
- public static final String TRANS_INIT_ID = "initialize";
+ public static final String TRANS_INIT_ID = "system_initialize";
public static final String TRANS_SYNC_ID = "data_sync";
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java
index eceb876213f..05ae30fd936 100644
--- a/src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java
@@ -104,7 +104,7 @@ public Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableExce
throw new IllegalArgumentException(String.format("Menu item identifier %s is not unique!", sanitizedIdentifier));
}
- // todo ETASK-23 validation
+ // todo 23 validation
Case parentItemCase = getOrCreateFolderItem(body.getUri());
I18nString newName = body.getMenuName();
@@ -577,6 +577,9 @@ protected List updateNodeInChildrenFoldersRecursive(Case parentFolder) {
protected void resolveAndHandleNewNodePath(Case folderItem, String destUri) {
String newNodePath = resolveNewNodePath(folderItem, destUri);
+ if (newNodePath.startsWith("//")) {
+ newNodePath = newNodePath.replace("//", uriService.getUriSeparator());
+ }
UriNode newNode = uriService.getOrCreate(newNodePath, UriContentType.CASE);
folderItem.getDataField(MenuItemConstants.FIELD_NODE_PATH).setValue(newNode.getUriPath());
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java b/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java
index d1fcaf8e590..277de33d43a 100644
--- a/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java
+++ b/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java
@@ -64,7 +64,7 @@ public static String findTaskIdInCase(Case useCase, String transId) {
* */
public static boolean isCyclicNodePath(Case folderItem, String destUri) {
String oldNodePath = (String) folderItem.getFieldValue(MenuItemConstants.FIELD_NODE_PATH);
- return destUri.contains(oldNodePath);
+ return oldNodePath != null && destUri.contains(oldNodePath);
}
/**
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index f9f9fc7335b..9f934d81974 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -25,8 +25,6 @@
default
- false
- falsetrue
@@ -643,6 +641,9 @@
Použiť zobrazenie v taboch?Vybrať zobrazenieNastavenie zobrazenia
+ Vytvoriť menu položku
+ Zoradiť pod-položky
+ Vytvoriť menu položkuIkonevorschau
@@ -692,6 +693,9 @@
Möchten Sie die Ansicht mit Registerkarten verwenden?Wählen Sie einen AnsichtstypDie Ansichtskonfiguration
+ Menüeintrag erstellen
+ Untereintrage sortieren
+ Erstellen
@@ -699,22 +703,96 @@
initialize340220
-
+
hourglass_emptyadmintrue
- true
- true
- true
+
+ initialize_0
+ 4
+ grid
+
+ menu_item_identifier
+
+ editable
+ required
+
+
+ 0
+ 0
+ 1
+ 4
+ material
+ outline
+
+
+
+ move_dest_uri
+
+ editable
+ required
+
+
+ 0
+ 1
+ 1
+ 2
+ material
+ outline
+
+
+
+ move_dest_uri_new_node
+
+ editable
+
+
+ 2
+ 1
+ 1
+ 1
+ material
+ outline
+
+
+
+ move_add_node
+
+ editable
+
+
+ 3
+ 1
+ 1
+ 1
+ material
+ outline
+
+
+
+
+ finish
+
+
+ dest: f.move_dest_uri;
+
+ String newUri = dest.value.join("/")
+ newUri = newUri.replace("//","/")
+
+ changeMenuItem useCase uri { newUri }
+
+
+ Create
+ data_sync340
- 340
+ 112system
@@ -722,6 +800,12 @@
true
+
+ admin
+
+ true
+
+
@@ -735,9 +819,12 @@
admintrue
+
+
+
+ default
+ true
- true
- true
@@ -1178,9 +1265,6 @@
admintrue
- true
- true
- true
@@ -1262,9 +1346,6 @@
admintrue
- true
- true
- true
@@ -1320,16 +1401,13 @@
children_order580220
-
+
low_priorityautoadmintrue
- true
- true
- true
@@ -1368,9 +1446,6 @@
systemtrue
- true
- true
- true
@@ -1445,6 +1520,19 @@
+
+ system_initialize
+ 336
+ 304
+
+ computer
+
+ system
+
+ true
+
+
+
@@ -1507,4 +1595,18 @@
children_order1
+
+ a14
+ regular
+ uninitialized
+ system_initialize
+ 1
+
+
+ a15
+ regular
+ system_initialize
+ initialized
+ 1
+
\ No newline at end of file
From b5164e4127c5b788102f772fc7197502331d20f6 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Wed, 6 May 2026 12:23:27 +0200
Subject: [PATCH 04/49] [ETASK-23] Dynamic view configuration - refactor menu
item view forms - implement MenuController.getMenuItemData - implement
MenuItemServiceTest.getMenuItemDataTest
---
.../logic/action/ActionDelegate.groovy | 6 +-
.../startup/DefaultDashboardRunner.groovy | 2 +-
.../DashboardItemServiceImpl.java | 4 +-
.../DashboardManagementServiceImpl.java | 4 +-
.../MenuItemService.java | 48 +-
.../interfaces/DashboardItemService.java | 2 +-
.../DashboardManagementService.java | 2 +-
.../interfaces/IMenuItemService.java | 5 +-
.../engine/menu/utils/MenuItemUtils.java | 2 +-
.../engine/menu/web/MenuController.java | 45 +
.../responsebodies/MenuItemDataResponse.java | 23 +
.../engine/workflow/domain/Case.java | 2 +-
.../workflow/service/WorkflowService.java | 6 +-
.../engine-processes/menu/menu_item.xml | 802 ++++++++++--------
.../menu/tabbed_case_view_configuration.xml | 457 +++++++---
.../tabbed_single_task_view_configuration.xml | 60 +-
.../menu/tabbed_task_view_configuration.xml | 274 ++++--
.../menu/tabbed_ticket_view_configuration.xml | 101 ++-
.../engine/menu/MenuItemServiceTest.java | 156 ++++
19 files changed, 1388 insertions(+), 613 deletions(-)
rename src/main/java/com/netgrif/application/engine/menu/{services => service}/DashboardItemServiceImpl.java (97%)
rename src/main/java/com/netgrif/application/engine/menu/{services => service}/DashboardManagementServiceImpl.java (98%)
rename src/main/java/com/netgrif/application/engine/menu/{services => service}/MenuItemService.java (93%)
rename src/main/java/com/netgrif/application/engine/menu/{services => service}/interfaces/DashboardItemService.java (88%)
rename src/main/java/com/netgrif/application/engine/menu/{services => service}/interfaces/DashboardManagementService.java (89%)
rename src/main/java/com/netgrif/application/engine/menu/{services => service}/interfaces/IMenuItemService.java (93%)
create mode 100644 src/main/java/com/netgrif/application/engine/menu/web/MenuController.java
create mode 100644 src/main/java/com/netgrif/application/engine/menu/web/responsebodies/MenuItemDataResponse.java
create mode 100644 src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
index 26791594b3e..d4b527dcb6c 100644
--- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
+++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
@@ -31,9 +31,9 @@ import com.netgrif.application.engine.menu.domain.configurations.TabbedTaskViewB
import com.netgrif.application.engine.menu.domain.configurations.ViewBody
import com.netgrif.application.engine.menu.domain.dashboard.DashboardItemBody
import com.netgrif.application.engine.menu.domain.dashboard.DashboardManagementBody
-import com.netgrif.application.engine.menu.services.interfaces.DashboardItemService
-import com.netgrif.application.engine.menu.services.interfaces.DashboardManagementService
-import com.netgrif.application.engine.menu.services.interfaces.IMenuItemService
+import com.netgrif.application.engine.menu.service.interfaces.DashboardItemService
+import com.netgrif.application.engine.menu.service.interfaces.DashboardManagementService
+import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService
import com.netgrif.application.engine.orgstructure.groups.interfaces.INextGroupService
import com.netgrif.application.engine.pdf.generator.config.PdfResource
import com.netgrif.application.engine.pdf.generator.service.interfaces.IPdfGenerator
diff --git a/src/main/groovy/com/netgrif/application/engine/startup/DefaultDashboardRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/DefaultDashboardRunner.groovy
index 7e5475ef098..1e580755668 100644
--- a/src/main/groovy/com/netgrif/application/engine/startup/DefaultDashboardRunner.groovy
+++ b/src/main/groovy/com/netgrif/application/engine/startup/DefaultDashboardRunner.groovy
@@ -2,7 +2,7 @@ package com.netgrif.application.engine.startup
import com.netgrif.application.engine.menu.domain.dashboard.DashboardManagementBody
-import com.netgrif.application.engine.menu.services.interfaces.DashboardManagementService
+import com.netgrif.application.engine.menu.service.interfaces.DashboardManagementService
import com.netgrif.application.engine.petrinet.domain.I18nString
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
diff --git a/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java b/src/main/java/com/netgrif/application/engine/menu/service/DashboardItemServiceImpl.java
similarity index 97%
rename from src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
rename to src/main/java/com/netgrif/application/engine/menu/service/DashboardItemServiceImpl.java
index cd8b2c3a09c..20dbb8dbe18 100644
--- a/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/DashboardItemServiceImpl.java
@@ -1,4 +1,4 @@
-package com.netgrif.application.engine.menu.services;
+package com.netgrif.application.engine.menu.service;
import com.netgrif.application.engine.auth.domain.IUser;
import com.netgrif.application.engine.auth.domain.LoggedUser;
@@ -8,7 +8,7 @@
import com.netgrif.application.engine.menu.domain.ToDataSetOutcome;
import com.netgrif.application.engine.menu.domain.dashboard.DashboardItemBody;
import com.netgrif.application.engine.menu.domain.dashboard.DashboardItemConstants;
-import com.netgrif.application.engine.menu.services.interfaces.DashboardItemService;
+import com.netgrif.application.engine.menu.service.interfaces.DashboardItemService;
import com.netgrif.application.engine.menu.utils.MenuItemUtils;
import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException;
import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService;
diff --git a/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java b/src/main/java/com/netgrif/application/engine/menu/service/DashboardManagementServiceImpl.java
similarity index 98%
rename from src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
rename to src/main/java/com/netgrif/application/engine/menu/service/DashboardManagementServiceImpl.java
index c35ce0267f8..944270d33e5 100644
--- a/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/DashboardManagementServiceImpl.java
@@ -1,4 +1,4 @@
-package com.netgrif.application.engine.menu.services;
+package com.netgrif.application.engine.menu.service;
import com.netgrif.application.engine.auth.domain.IUser;
import com.netgrif.application.engine.auth.domain.LoggedUser;
@@ -9,7 +9,7 @@
import com.netgrif.application.engine.menu.domain.dashboard.DashboardItemConstants;
import com.netgrif.application.engine.menu.domain.dashboard.DashboardManagementBody;
import com.netgrif.application.engine.menu.domain.dashboard.DashboardManagementConstants;
-import com.netgrif.application.engine.menu.services.interfaces.DashboardManagementService;
+import com.netgrif.application.engine.menu.service.interfaces.DashboardManagementService;
import com.netgrif.application.engine.menu.utils.MenuItemUtils;
import com.netgrif.application.engine.petrinet.domain.I18nString;
import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException;
diff --git a/src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
similarity index 93%
rename from src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java
rename to src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
index 05ae30fd936..4c8f3d9ae65 100644
--- a/src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
@@ -1,4 +1,4 @@
-package com.netgrif.application.engine.menu.services;
+package com.netgrif.application.engine.menu.service;
import com.netgrif.application.engine.auth.domain.IUser;
import com.netgrif.application.engine.auth.domain.LoggedUser;
@@ -11,11 +11,12 @@
import com.netgrif.application.engine.menu.domain.ToDataSetOutcome;
import com.netgrif.application.engine.menu.domain.configurations.ViewBody;
import com.netgrif.application.engine.menu.domain.configurations.ViewConstants;
-import com.netgrif.application.engine.menu.services.interfaces.IMenuItemService;
+import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService;
import com.netgrif.application.engine.menu.utils.MenuItemUtils;
import com.netgrif.application.engine.petrinet.domain.I18nString;
import com.netgrif.application.engine.petrinet.domain.UriContentType;
import com.netgrif.application.engine.petrinet.domain.UriNode;
+import com.netgrif.application.engine.petrinet.domain.dataset.Field;
import com.netgrif.application.engine.petrinet.domain.dataset.FieldType;
import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException;
import com.netgrif.application.engine.petrinet.service.interfaces.IUriService;
@@ -23,6 +24,7 @@
import com.netgrif.application.engine.startup.FilterRunner;
import com.netgrif.application.engine.startup.ImportHelper;
import com.netgrif.application.engine.workflow.domain.Case;
+import com.netgrif.application.engine.workflow.domain.DataField;
import com.netgrif.application.engine.workflow.domain.Task;
import com.netgrif.application.engine.workflow.service.interfaces.IDataService;
import com.netgrif.application.engine.workflow.service.interfaces.ITaskService;
@@ -400,6 +402,48 @@ public Case removeChildItemFromParent(String folderId, Case childItem) {
return workflowService.save(parentFolder);
}
+ /**
+ * Retrieves menu item data along with its associated view configuration data. The method collects immediate
+ * data from the menu item case and recursively traverses through all associated view configuration cases,
+ * aggregating their immediate data into a single map.
+ *
+ * @param caseId identifier of the menu item case
+ * @return map where keys are process identifiers (with "_configuration" suffix removed for view cases) and
+ * values are lists of immediate data fields from the corresponding cases
+ */
+ @Override
+ public Map>> getMenuItemData(String caseId) {
+ Case menuItemCase = workflowService.findOne(caseId);
+ Map>> immediateDataMap = new HashMap<>();
+ immediateDataMap.put(menuItemCase.getProcessIdentifier(), menuItemCase.getImmediateData());
+ Optional viewCaseIdOpt = getNextViewCaseId(menuItemCase);
+ if (viewCaseIdOpt.isEmpty()) {
+ return immediateDataMap;
+ }
+
+ do {
+ Case viewCase = workflowService.findOne(viewCaseIdOpt.get());
+ immediateDataMap.put(viewCase.getProcessIdentifier().replace("_configuration", ""),
+ viewCase.getImmediateData());
+ viewCaseIdOpt = getNextViewCaseId(viewCase);
+ } while (viewCaseIdOpt.isPresent());
+
+ return immediateDataMap;
+ }
+
+ protected Optional getNextViewCaseId(Case parentCase) {
+ DataField viewConfigurationIdDataField = parentCase.getDataField(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID);
+ if (viewConfigurationIdDataField == null) {
+ return Optional.empty();
+ }
+ @SuppressWarnings("unchecked")
+ List viewConfigurationIdValue = (List) viewConfigurationIdDataField.getValue();
+ if (viewConfigurationIdValue == null || viewConfigurationIdValue.isEmpty()) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(viewConfigurationIdValue.get(0));
+ }
+
protected Case findCase(String processIdentifier, String query) {
CaseSearchRequest request = CaseSearchRequest.builder()
.process(Collections.singletonList(new CaseSearchRequest.PetriNet(processIdentifier)))
diff --git a/src/main/java/com/netgrif/application/engine/menu/services/interfaces/DashboardItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/DashboardItemService.java
similarity index 88%
rename from src/main/java/com/netgrif/application/engine/menu/services/interfaces/DashboardItemService.java
rename to src/main/java/com/netgrif/application/engine/menu/service/interfaces/DashboardItemService.java
index 6555ad2c04f..3dd698d53c1 100644
--- a/src/main/java/com/netgrif/application/engine/menu/services/interfaces/DashboardItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/DashboardItemService.java
@@ -1,4 +1,4 @@
-package com.netgrif.application.engine.menu.services.interfaces;
+package com.netgrif.application.engine.menu.service.interfaces;
import com.netgrif.application.engine.menu.domain.dashboard.DashboardItemBody;
import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException;
diff --git a/src/main/java/com/netgrif/application/engine/menu/services/interfaces/DashboardManagementService.java b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/DashboardManagementService.java
similarity index 89%
rename from src/main/java/com/netgrif/application/engine/menu/services/interfaces/DashboardManagementService.java
rename to src/main/java/com/netgrif/application/engine/menu/service/interfaces/DashboardManagementService.java
index 01efb79d055..e5765ddbf3b 100644
--- a/src/main/java/com/netgrif/application/engine/menu/services/interfaces/DashboardManagementService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/DashboardManagementService.java
@@ -1,4 +1,4 @@
-package com.netgrif.application.engine.menu.services.interfaces;
+package com.netgrif.application.engine.menu.service.interfaces;
import com.netgrif.application.engine.menu.domain.dashboard.DashboardManagementBody;
import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException;
diff --git a/src/main/java/com/netgrif/application/engine/menu/services/interfaces/IMenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java
similarity index 93%
rename from src/main/java/com/netgrif/application/engine/menu/services/interfaces/IMenuItemService.java
rename to src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java
index 9de2a7f2308..929f348cf10 100644
--- a/src/main/java/com/netgrif/application/engine/menu/services/interfaces/IMenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java
@@ -1,14 +1,16 @@
-package com.netgrif.application.engine.menu.services.interfaces;
+package com.netgrif.application.engine.menu.service.interfaces;
import com.netgrif.application.engine.menu.domain.FilterBody;
import com.netgrif.application.engine.menu.domain.MenuItemBody;
import com.netgrif.application.engine.menu.domain.MenuItemView;
import com.netgrif.application.engine.petrinet.domain.I18nString;
import com.netgrif.application.engine.petrinet.domain.UriNode;
+import com.netgrif.application.engine.petrinet.domain.dataset.Field;
import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException;
import com.netgrif.application.engine.workflow.domain.Case;
import com.netgrif.application.engine.petrinet.domain.dataset.MapOptionsField;
+import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -27,6 +29,7 @@ public interface IMenuItemService {
void moveItem(Case item, String destUri) throws TransitionNotExecutableException;
Case duplicateItem(Case originItem, I18nString newTitle, String newIdentifier) throws TransitionNotExecutableException;
Case removeChildItemFromParent(String folderId, Case childItem);
+ Map>> getMenuItemData(String caseId);
/**
* Gets all tabbed or non-tabbed views
diff --git a/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java b/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java
index 277de33d43a..8472e838599 100644
--- a/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java
+++ b/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java
@@ -3,7 +3,7 @@
import com.netgrif.application.engine.menu.domain.MenuItemConstants;
import com.netgrif.application.engine.workflow.domain.Case;
import com.netgrif.application.engine.workflow.domain.TaskPair;
-import com.netgrif.application.engine.menu.services.interfaces.IMenuItemService;
+import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService;
import java.text.Normalizer;
import java.util.List;
diff --git a/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java b/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java
new file mode 100644
index 00000000000..27ffee572e9
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java
@@ -0,0 +1,45 @@
+package com.netgrif.application.engine.menu.web;
+
+import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService;
+import com.netgrif.application.engine.menu.web.responsebodies.MenuItemDataResponse;
+import com.netgrif.application.engine.petrinet.domain.dataset.Field;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.MediaTypes;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@RestController
+@Tag(name = "Menu")
+@RequiredArgsConstructor
+@RequestMapping("/api/menu")
+public class MenuController {
+
+ private final IMenuItemService menuItemService;
+
+ @Operation(summary = "Get relevant data for the menu item", security = {@SecurityRequirement(name = "BasicAuth")})
+ @GetMapping(value = "/{encodedCaseId}", produces = MediaTypes.HAL_JSON_VALUE)
+ public EntityModel getMenuItemData(String encodedCaseId) {
+ try {
+ String caseId = new String(Base64.getDecoder().decode(encodedCaseId));
+ Map>> immediateDataMap = menuItemService.getMenuItemData(caseId);
+ return EntityModel.of(new MenuItemDataResponse(immediateDataMap));
+ } catch (Exception e) {
+ log.error("Getting menu item data failed", e);
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Getting menu item data failed", e);
+ }
+ }
+}
+
diff --git a/src/main/java/com/netgrif/application/engine/menu/web/responsebodies/MenuItemDataResponse.java b/src/main/java/com/netgrif/application/engine/menu/web/responsebodies/MenuItemDataResponse.java
new file mode 100644
index 00000000000..43f265a9547
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/web/responsebodies/MenuItemDataResponse.java
@@ -0,0 +1,23 @@
+package com.netgrif.application.engine.menu.web.responsebodies;
+
+import com.netgrif.application.engine.petrinet.domain.dataset.Field;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RequiredArgsConstructor
+public class MenuItemDataResponse {
+
+ /**
+ * Map containing menu item data where key is the view type and value is a list of immediate fields.
+ */
+ @Getter
+ private final Map>> data;
+
+ public MenuItemDataResponse() {
+ this(new HashMap<>());
+ }
+}
diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/Case.java b/src/main/java/com/netgrif/application/engine/workflow/domain/Case.java
index 1559cbe0017..cd0c8f24596 100644
--- a/src/main/java/com/netgrif/application/engine/workflow/domain/Case.java
+++ b/src/main/java/com/netgrif/application/engine/workflow/domain/Case.java
@@ -100,7 +100,7 @@ public class Case implements Serializable {
@Getter
@Setter
@Transient
- private List immediateData;
+ private List> immediateData;
@Getter
@Setter
diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java b/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java
index 472df01d5ae..6cd2c5e8826 100644
--- a/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java
+++ b/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java
@@ -505,11 +505,11 @@ public List getData(String caseId) {
}
private void setImmediateDataFieldsReadOnly(Case useCase) {
- List immediateData = new ArrayList<>();
+ List> immediateData = new ArrayList<>();
useCase.getImmediateDataFields().forEach(fieldId -> {
try {
- Field clone = fieldFactory.buildImmediateField(useCase, fieldId);
+ Field> clone = fieldFactory.buildImmediateField(useCase, fieldId);
immediateData.add(clone);
} catch (Exception e) {
log.error("Could not built immediate field [" + fieldId + "]");
@@ -526,7 +526,7 @@ protected Page setImmediateDataFields(Page cases) {
}
protected Case setImmediateDataFields(Case useCase) {
- List immediateData = new ArrayList<>();
+ List> immediateData = new ArrayList<>();
useCase.getImmediateDataFields().forEach(fieldId ->
immediateData.add(fieldFactory.buildImmediateField(useCase, fieldId))
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 9f934d81974..1f53c563974 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -591,6 +591,31 @@
])
+
+ role_divider
+
+
+ divider
+
+
+
+ advanced_content_form
+
+
+ task-list
+
+
+
+ get
+
+
+ advanced_content_form: f.advanced_content_form;
+ change advanced_content_form value { [useCase.tasks.find { taskPair -> taskPair.transition == "role_settings"}.task,
+ useCase.tasks.find { taskPair -> taskPair.transition == "view_settings"}.task] }
+
+
+
+
@@ -629,7 +654,6 @@
Zoznam povolených rolí, ktoré môžu zobraziť túto položkuZakázané rolyZoznam zakázaných rolí, ktoré nemôžu zobraziť túto položku
- VšeobecnéIdentifikátor položkyURI položkyNový uzol
@@ -660,7 +684,6 @@
Beispiel: "demo-tabbed-views"RollenZulässige Rollen
- AllgemeinIdentifikationsnummer des MenüeintragesMenüeintrag-URINeuer Knoten
@@ -800,12 +823,6 @@
true
-
- admin
-
- true
-
-
@@ -831,7 +848,6 @@
general_04grid
- Generalmenu_item_identifier
@@ -883,7 +899,7 @@
211
- 1
+ 2
material
outline
@@ -894,10 +910,10 @@
visible
- 3
- 1
+ 2
+ 21
- 1
+ 2
material
standard
@@ -908,52 +924,73 @@
editable
- 1
+ 021
- 1
+ 2
material
standard
-
-
- roles_management
- 5
- grid
- Roles
- processes_available
+ advanced_content_formeditable0
- 0
- 2
- 1
- 0
+ 3
+ 1
+ 4
material
outline
+
+
+ assign_0
+
+
+
+ finish_0
+
+
+
+
+
+ move_item
+ 580
+ 100
+
+ move_down
+ auto
+
+ admin
+
+ true
+
+
+
+ move
+ 4
+ grid
- roles_available
+ move_dest_urieditable
+ required
- 1
+ 00
- 2
- 1
- 0
+ 1
+ 2
material
outline
- add_allowed_roles
+ move_dest_uri_new_nodeeditable
@@ -962,12 +999,12 @@
011
- 0
material
+ outline
- allowed_roles
+ move_add_nodeeditable
@@ -976,90 +1013,439 @@
011
- 0
material
outline
+
+
+ finish
+
+
+ dest: f.move_dest_uri;
+
+ if (dest.value == null || dest.value == []) {
+ throw new IllegalArgumentException("URI must not be empty!")
+ }
+
+ String newUri = dest.value.join("/")
+ newUri = newUri.replace("//","/")
+
+ changeMenuItem useCase uri { newUri }
+
+
+ Move
+
+
+
+
+ duplicate_item
+ 580
+ 340
+
+ content_copy
+ auto
+
+ admin
+
+ true
+
+
+
+ duplicate
+ 4
+ grid
- remove_allowed_roles
+ duplicate_new_titleeditable
+ required
- 4
+ 001
- 1
- 0
+ 4
material
+ outline
- add_banned_roles
+ duplicate_view_identifiereditable
+ required
- 2
+ 011
- 1
- 0
+ 4
material
+ outline
+
+
+ finish
+
+
+ identifier: f.duplicate_view_identifier,
+ title: f.duplicate_new_title;
+
+ duplicateMenuItem(useCase, title.value, identifier.value)
+
+
+ Duplicate
+
+
+
+
+ children_order
+ 580
+ 220
+
+ low_priority
+ auto
+
+ admin
+
+ true
+
+
+
+ children_order_0
+ 4
+ grid
- banned_roles
+ childItemFormseditable
+
+ forms: f.childItemForms,
+ ids: f.childItemIds;
+
+ def orderedTaskIds = ids.value?.collect { id -> workflowService.findOne(id).tasks.find { it.transition == "row_for_ordering" }.task }
+ change forms value { orderedTaskIds }
+
- 3
- 1
+ 0
+ 01
- 1
- 0
+ 4
+ material
+ outline
+
+
+
+
+
+ row_for_ordering
+ 741
+ 219
+
+
+ system
+
+ true
+
+
+
+ row_for_ordering_0
+ 6
+ grid
+
+ menu_item_identifier
+
+ visible
+
+
+ 0
+ 0
+ 1
+ 2
material
outline
- remove_banned_roles
+ menu_name_as_visible
+
+ visible
+
+
+ 2
+ 0
+ 1
+ 2
+ material
+ outline
+
+
+
+ order_downeditable4
- 1
+ 011
- 0
+ 1
material
+ outline
-
-
- configuration_view
- 4
- grid
- View configuration
- use_custom_view
+ order_upeditable
- 0
+ 5011
+ 1
material
outline
-
- 0
-
+
+
+
+ finish
+
+
+
+ delegate
+
+
+
+
+ system_initialize
+ 336
+ 304
+
+ computer
+
+ system
+
+ true
+
+
+
+
+ role_settings
+ 400
+ 48
+
+ person
+ auto
+
+ admin
+
+ true
+
+
+
+ default
+
+ true
+
+
+
+ roles_management
+ 3
+ grid
+
+ processes_available
+
+ editable
+
+
+ 0
+ 0
+ 2
+ 1
+ 0
+ material
+ outline
+
+
+
+ roles_available
+
+ editable
+
+
+ 1
+ 0
+ 2
+ 1
+ 0
+ material
+ outline
+
+
+
+ add_allowed_roles
+
+ editable
+
+
+ 2
+ 0
+ 1
+ 1
+ 0
+ material
+
+
+
+ add_banned_roles
+
+ editable
+
+
+ 2
+ 1
+ 1
+ 1
+ 0
+ material
+
+
+
+ role_divider
+
+ visible
+
+
+ 0
+ 2
+ 1
+ 3
+ 0
+ material
+
+
+
+ allowed_roles
+
+ editable
+
+
+ 0
+ 3
+ 1
+ 2
+ 0
+ material
+ outline
+
+
+
+ remove_allowed_roles
+
+ editable
+
+
+ 2
+ 3
+ 1
+ 1
+ 0
+ material
+
+
+
+ banned_roles
+
+ editable
+
+
+ 0
+ 4
+ 1
+ 2
+ 0
+ material
+ outline
+
+
+
+ remove_banned_roles
+
+ editable
+
+
+ 2
+ 4
+ 1
+ 1
+ 0
+ material
+
+
+
+
+ assign
+
+
+
+ cancel
+
+
+
+ finish_0
+
+
+
+ delegate
+
+
+
+
+ view_settings
+ 528
+ 48
+
+ image
+ auto
+
+ admin
+
+ true
+
+
+
+ default
+
+ true
+
+
+
+ configuration_view
+ 4
+ grid
+
+ use_custom_view
+
+ editable
+
+
+ 0
+ 0
+ 1
+ 1
+ material
+ outline
+
+
+ 0
+
trans: t.this,
useTabIcon: f.use_tab_icon,
@@ -1252,267 +1638,16 @@
-
-
-
- move_item
- 580
- 100
-
- move_down
- auto
-
- admin
-
- true
-
-
-
- move
- 4
- grid
-
- move_dest_uri
-
- editable
- required
-
-
- 0
- 0
- 1
- 2
- material
- outline
-
-
-
- move_dest_uri_new_node
-
- editable
-
-
- 2
- 0
- 1
- 1
- material
- outline
-
-
-
- move_add_node
-
- editable
-
-
- 3
- 0
- 1
- 1
- material
- outline
-
-
-
-
- finish
-
-
- dest: f.move_dest_uri;
-
- if (dest.value == null || dest.value == []) {
- throw new IllegalArgumentException("URI must not be empty!")
- }
-
- String newUri = dest.value.join("/")
- newUri = newUri.replace("//","/")
-
- changeMenuItem useCase uri { newUri }
-
-
- Move
+
+ assign
+
-
-
-
- duplicate_item
- 580
- 340
-
- content_copy
- auto
-
- admin
-
- true
-
-
-
- duplicate
- 4
- grid
-
- duplicate_new_title
-
- editable
- required
-
-
- 0
- 0
- 1
- 4
- material
- outline
-
-
-
- duplicate_view_identifier
-
- editable
- required
-
-
- 0
- 1
- 1
- 4
- material
- outline
-
-
-
-
- finish
-
-
- identifier: f.duplicate_view_identifier,
- title: f.duplicate_new_title;
-
- duplicateMenuItem(useCase, title.value, identifier.value)
-
-
- Duplicate
+
+ cancel
+
-
-
-
- children_order
- 580
- 220
-
- low_priority
- auto
-
- admin
-
- true
-
-
-
- children_order_0
- 4
- grid
-
- childItemForms
-
- editable
-
- forms: f.childItemForms,
- ids: f.childItemIds;
-
- def orderedTaskIds = ids.value?.collect { id -> workflowService.findOne(id).tasks.find { it.transition == "row_for_ordering" }.task }
- change forms value { orderedTaskIds }
-
-
-
- 0
- 0
- 1
- 4
- material
- outline
-
-
-
-
-
- row_for_ordering
- 741
- 219
-
-
- system
-
- true
-
-
-
- row_for_ordering_0
- 6
- grid
-
- menu_item_identifier
-
- visible
-
-
- 0
- 0
- 1
- 2
- material
- outline
-
-
-
- menu_name_as_visible
-
- visible
-
-
- 2
- 0
- 1
- 2
- material
- outline
-
-
-
- order_down
-
- editable
-
-
- 4
- 0
- 1
- 1
- 1
- material
- outline
-
-
-
- order_up
-
- editable
-
-
- 5
- 0
- 1
- 1
- 1
- material
- outline
-
-
-
- finish
+ finish_0
@@ -1520,19 +1655,6 @@
-
- system_initialize
- 336
- 304
-
- computer
-
- system
-
- true
-
-
-
@@ -1609,4 +1731,18 @@
initialized1
+
+ a16
+ read
+ initialized
+ view_settings
+ 1
+
+
+ a17
+ read
+ initialized
+ role_settings
+ 1
+
\ No newline at end of file
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
index cdb1d1c0ecb..39740f21fce 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
@@ -25,8 +25,6 @@
default
- false
- falsetrue
@@ -147,7 +145,7 @@
autocomplete_dynamic
- trans: t.settings,
+ trans: t.filter_settings,
filterAutocomplete: f.this,
filter_case: f.filter_case,
update_filter: f.update_filter,
@@ -156,7 +154,7 @@
updateFilterAutocompleteOptions(filterAutocomplete, previewTaskRef, filter_case, update_filter, trans)
- trans: t.settings,
+ trans: t.filter_settings,
filterAutocomplete: f.this,
filter_case: f.filter_case,
update_filter: f.update_filter,
@@ -173,7 +171,7 @@
raised
- trans: t.settings,
+ trans: t.filter_settings,
update_filter: f.update_filter,
contains_filter: f.contains_filter,
filter_case: f.filter_case,
@@ -392,6 +390,26 @@
view_configuration_form
+
+ advanced_content_form
+
+
+
+ task-list
+
+
+ get
+
+
+ advanced_content_form: f.advanced_content_form;
+ change advanced_content_form value { [useCase.tasks.find { taskPair -> taskPair.transition == "header_settings"}.task,
+ useCase.tasks.find { taskPair -> taskPair.transition == "create_case_btn_settings"}.task,
+ useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task,
+ useCase.tasks.find { taskPair -> taskPair.transition == "next_view_settings"}.task] }
+
+
+
+
@@ -420,14 +438,12 @@
Povoliť tabuľkový mód pre hlavičky?Použiť vlastné predvolené hlavičky?Nastavenie položky
- FilterSúčasný filterZobrazenie prípadovVšeobecnéPoužiť predvolené hlavičkyVybrať zobrazenieNastavenie
- Asociované zobrazenieSchaltflächentitel "Neuer Fall"
@@ -446,7 +462,6 @@
Erlaube Änderung des Kopfzeilenmodus?Erlaube Tabellenmodus?Eigene Kopfzeilen verwenden?
- FilterAktueller FilterAllgemeinAktualisiere die Ansicht mit dem ausgewählten Filter
@@ -462,7 +477,6 @@
Benutzerdefinierte Standardheader verwenden?Wählen Sie einen AnsichtstypEinstellungen
- zugehörige Ansicht
@@ -471,12 +485,24 @@
208hourglass_empty
+
+ admin
+
+ true
+
+ data_sync368328
+
+ system
+
+ true
+
+ settings
@@ -488,35 +514,94 @@
admintrue
+
+
+
+ default
+ true
- true
- true
- form_title
+ view_dataGroup4grid
- view_header
+ view_search_type
- visible
+ editable
+ required001
+ 2
+ material
+ outline
+
+
+
+ show_more_menu
+
+ editable
+
+
+ 2
+ 0
+ 1
+ 2
+ material
+ outline
+
+
+
+ advanced_content_form
+
+ editable
+
+
+ 0
+ 1
+ 14
material
outline
+
+ assign_0
+
+
+
+ finish_0
+
+
+
+
+ filter_settings
+ 432
+ 48
+
+ filter_alt
+ auto
+
+ admin
+
+ true
+
+
+
+ default
+
+ true
+
+
- filter_update
+ filter_04grid
- Filterfilter_autocomplete_selection
@@ -588,48 +673,151 @@
+
+ assign
+
+
+
+ cancel
+
+
+
+ finish_0
+
+
+
+ delegate
+
+
+
+
+ next_view_settings
+ 560
+ 48
+
+ queue_play_next
+ auto
+
+ admin
+
+ true
+
+
+
+ default
+
+ true
+
+
- view_dataGroup
+ associated_view_04grid
- view_search_type
+ view_configuration_typeeditable
- required001
- 2
+ 4
+ 0
material
outline
+
+ 0
+
+
+ view_configuration_type: f.view_configuration_type,
+ view_configuration_form: f.view_configuration_form,
+ view_configuration_id: f.view_configuration_id;
+
+ if (view_configuration_id.value != null && !view_configuration_id.value.isEmpty()) {
+ workflowService.deleteCase(view_configuration_id.value[0])
+ }
+
+ if (view_configuration_type.value == null || view_configuration_type.value == "") {
+ change view_configuration_id value { [] }
+ change view_configuration_form value { [] }
+ return
+ }
+
+ def configurationCase = createCase(view_configuration_type.value + "_configuration")
+ def initTask = assignTask("initialize", configurationCase)
+ finishTask(initTask)
+ configurationCase = workflowService.findOne(configurationCase.stringId)
+ change view_configuration_id value { [configurationCase.stringId] }
+ change view_configuration_form value { [configurationCase.tasks.find { it.transition == "settings" }.task] }
+
+
+
- show_create_case_button
+ view_configuration_formeditable
- required
- 2
- 0
+ 0
+ 11
- 1
+ 4
+ 0
material
outline
+
+
+ assign
+
+
+
+ cancel
+
+
+
+ finish_0
+
+
+
+ delegate
+
+
+
+
+ create_case_btn_settings
+ 656
+ 48
+
+ add_box
+ auto
+
+ admin
+
+ true
+
+
+
+ default
+
+ true
+
+
+
+ create_case_btn_0
+ 3
+ grid
- show_more_menu
+ show_create_case_buttoneditable
- required
- 3
+ 0011
@@ -638,27 +826,27 @@
- create_case_button_title
+ require_title_in_creationeditable
- 0
- 1
+ 1
+ 0110
material
- outline
+ standard
- create_case_button_icon
+ create_case_button_titleeditable
- 1
+ 0111
@@ -668,27 +856,27 @@
- create_case_button_icon_preview
+ create_case_button_icon
- visible
+ editable
- 2
+ 11110
material
- standard
+ outline
- require_title_in_creation
+ create_case_button_icon_preview
- editable
+ visible
- 3
+ 2111
@@ -706,16 +894,52 @@
021
- 4
+ 30
material
outline
+
+ assign
+
+
+
+ cancel
+
+
+
+ finish_0
+
+
+
+ delegate
+
+
+
+
+ header_settings
+ 336
+ 48
+
+ view_column
+ auto
+
+ admin
+
+ true
+
+
+
+ default
+
+ true
+
+
- view_headers
- 5
+ header_0
+ 3gridis_header_mode_changeable
@@ -746,10 +970,17 @@
- allow_header_table_mode
+ use_case_default_headerseditable
- required
+
+ trans: t.this,
+ use: f.use_case_default_headers,
+ headers: f.default_headers;
+
+ make headers,editable on trans when { use.value }
+ make headers,visible on trans when { !use.value }
+ 1
@@ -762,31 +993,14 @@
- headers_mode
+ default_headerseditable
- required201
- 2
- 0
- material
- outline
-
-
-
- headers_default_mode
-
- editable
- required
-
-
- 4
- 0
- 110
material
@@ -794,17 +1008,10 @@
- use_case_default_headers
+ allow_header_table_modeeditable
-
- trans: t.this,
- use: f.use_case_default_headers,
- headers: f.default_headers;
-
- make headers,editable on trans when { use.value }
- make headers,visible on trans when { !use.value }
-
+ required0
@@ -817,84 +1024,54 @@
- default_headers
+ headers_modeeditable
+ required111
- 4
- 0
- material
- outline
-
-
-
-
- associated_view
- 4
- grid
- Associated view
-
- view_configuration_type
-
- editable
-
-
- 0
- 0
- 1
- 4
+ 10
material
outline
-
- 0
-
-
- view_configuration_type: f.view_configuration_type,
- view_configuration_form: f.view_configuration_form,
- view_configuration_id: f.view_configuration_id;
-
- if (view_configuration_id.value != null && !view_configuration_id.value.isEmpty()) {
- workflowService.deleteCase(view_configuration_id.value[0])
- }
-
- if (view_configuration_type.value == null || view_configuration_type.value == "") {
- change view_configuration_id value { [] }
- change view_configuration_form value { [] }
- return
- }
-
- def configurationCase = createCase(view_configuration_type.value + "_configuration")
- def initTask = assignTask("initialize", configurationCase)
- finishTask(initTask)
- configurationCase = workflowService.findOne(configurationCase.stringId)
- change view_configuration_id value { [configurationCase.stringId] }
- change view_configuration_form value { [configurationCase.tasks.find { it.transition == "settings" }.task] }
-
-
-
- view_configuration_form
+ headers_default_modeeditable
+ required
- 0
+ 211
- 4
+ 10
material
outline
+
+ assign
+
+
+
+ cancel
+
+
+
+ finish_0
+
+
+
+ delegate
+
+ initialized
@@ -933,4 +1110,32 @@
initialized1
+
+ a4
+ read
+ initialized
+ filter_settings
+ 1
+
+
+ a5
+ read
+ initialized
+ next_view_settings
+ 1
+
+
+ a6
+ read
+ initialized
+ header_settings
+ 1
+
+
+ a7
+ read
+ initialized
+ create_case_btn_settings
+ 1
+
\ No newline at end of file
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml
index 10c34b150e8..22f4853e17f 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml
@@ -25,8 +25,6 @@
default
- false
- falsetrue
@@ -171,14 +169,6 @@
-
- view_header
-
- Single task view
-
- divider
-
-
transition_idTransition id
@@ -193,8 +183,6 @@
Súčasný filterVybrať zobrazenieNastavenie
- Asociované zobrazenie
- Zobrazenie jednej úlohyID prechodu
@@ -204,8 +192,6 @@
Aktualisiere die Ansicht mit dem ausgewählten FilterWählen Sie einen AnsichtstypEinstellungen
- zugehörige Ansicht
- EinzelaufgabenansichtÜbergangs-ID
@@ -215,12 +201,24 @@
208hourglass_empty
+
+ admin
+
+ true
+
+ data_sync368328
+
+ system
+
+ true
+
+ settings
@@ -232,30 +230,14 @@
admintrue
+
+
+
+ default
+ true
- true
- true
-
- form_title
- 4
- grid
-
- view_header
-
- visible
-
-
- 0
- 0
- 1
- 4
- material
- outline
-
-
- view_dataGroup4
@@ -275,6 +257,14 @@
+
+ assign_0
+
+
+
+ finish_0
+
+ initialized
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml
index 66caae1268f..abc71d160a9 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml
@@ -25,8 +25,6 @@
default
- false
- falsetrue
@@ -125,7 +123,7 @@
autocomplete_dynamic
- trans: t.settings,
+ trans: t.filter_settings,
filterAutocomplete: f.this,
filter_case: f.filter_case,
update_filter: f.update_filter,
@@ -134,7 +132,7 @@
updateFilterAutocompleteOptions(filterAutocomplete, previewTaskRef, filter_case, update_filter, trans)
- trans: t.settings,
+ trans: t.filter_settings,
filterAutocomplete: f.this,
filter_case: f.filter_case,
update_filter: f.update_filter,
@@ -151,7 +149,7 @@
raised
- trans: t.settings,
+ trans: t.filter_settings,
update_filter: f.update_filter,
contains_filter: f.contains_filter,
filter_case: f.filter_case,
@@ -175,7 +173,7 @@
filter_case
- trans: t.settings,
+ trans: t.filter_settings,
filterHeader: f.filter_header,
removeButton: f.remove_filter,
contains_filter: f.contains_filter,
@@ -224,14 +222,6 @@
-
- view_header
-
- Task view
-
- divider
-
-
view_search_typeSearch type for task view
@@ -315,6 +305,24 @@
Show more menu for task item?true
+
+ advanced_content_form
+
+
+
+ task-list
+
+
+ get
+
+
+ advanced_content_form: f.advanced_content_form;
+ change advanced_content_form value { [useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task,
+ useCase.tasks.find { taskPair -> taskPair.transition == "header_settings"}.task] }
+
+
+
+
@@ -337,9 +345,7 @@
Napríklad: "meta-title,meta-user"Zobrazovať menu pre úlohovú položku?Nastavenie položky
- FilterSúčasný filter
- Zobrazenie úlohVšeobecnéVymazať filter
@@ -357,7 +363,6 @@
Eigene Kopfzeilen verwenden?Anzuzeigende Attributmenge auswählenBeispiel: "meta-title,meta-user"
- FilterAktueller FilterAllgemeinAktualisiere die Ansicht mit dem ausgewählten Filter
@@ -366,7 +371,6 @@
Suchmodus im Aufgabenansicht"Erweiterte Optionen" Taste bei einzelnen Aufgaben anzeigenMenüeintrageinstellungen
- AufgabenansichtFilter entfernen
@@ -376,12 +380,24 @@
208hourglass_empty
+
+ admin
+
+ true
+
+ data_sync368328
+
+ system
+
+ true
+
+ settings
@@ -393,35 +409,94 @@
admintrue
+
+
+
+ default
+ true
- true
- true
- form_title
+ view_dataGroup4grid
- view_header
+ view_search_type
- visible
+ editable
+ required001
+ 3
+ material
+ outline
+
+
+
+ show_more_menu
+
+ editable
+
+
+ 3
+ 0
+ 1
+ 1
+ material
+ outline
+
+
+
+ advanced_content_form
+
+ editable
+
+
+ 0
+ 1
+ 14
material
outline
+
+ assign_0
+
+
+
+ finish_0
+
+
+
+
+ filter_settings
+ 400
+ 48
+
+ filter_alt
+ auto
+
+ admin
+
+ true
+
+
+
+ default
+
+ true
+
+ filter_update4grid
- Filterfilter_autocomplete_selection
@@ -526,50 +601,50 @@
-
- view_dataGroup
- 4
- grid
-
- view_search_type
-
- editable
- required
-
-
- 0
- 0
- 1
- 3
- material
- outline
-
-
-
- show_more_menu
-
- editable
- required
-
-
- 3
- 0
- 1
- 1
- material
- outline
-
-
-
+
+ assign
+
+
+
+ cancel
+
+
+
+ finish_0
+
+
+
+ delegate
+
+
+
+
+ header_settings
+ 592
+ 48
+
+ view_column
+ auto
+
+ admin
+
+ true
+
+
+
+ default
+
+ true
+
+ view_headers
- 5
+ 3gridis_header_mode_changeableeditable
- required
trans: t.this,
isChangeable: f.is_header_mode_changeable,
@@ -594,44 +669,51 @@
- allow_header_table_mode
+ use_default_headerseditable
- required
+
+ trans: t.this,
+ use: f.use_default_headers,
+ headers: f.default_headers;
+
+ make headers,editable on trans when { use.value }
+ make headers,visible on trans when { !use.value }
+ 1011
+ 0
material
outline
- headers_mode
+ default_headerseditable
- required201
- 2
+ 1
+ 0
material
outline
- headers_default_mode
+ allow_header_table_modeeditable
- required
- 4
- 0
+ 0
+ 111
material
@@ -639,44 +721,52 @@
- use_default_headers
+ headers_modeeditable
-
- trans: t.this,
- use: f.use_default_headers,
- headers: f.default_headers;
-
- make headers,editable on trans when { use.value }
- make headers,visible on trans when { !use.value }
-
+ required
- 0
+ 1111
- 0
material
outline
- default_headers
+ headers_default_modeeditable
+ required
- 1
+ 211
- 4
- 0
+ 1
material
outline
+
+ assign
+
+
+
+ cancel
+
+
+
+ finish_0
+
+
+
+ delegate
+
+ initialized
@@ -715,4 +805,18 @@
initialized1
+
+ a4
+ read
+ initialized
+ filter_settings
+ 1
+
+
+ a5
+ read
+ initialized
+ header_settings
+ 1
+
\ No newline at end of file
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
index b68407bc78a..66f770c7188 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
@@ -25,8 +25,6 @@
default
- false
- falsetrue
@@ -222,13 +220,22 @@
-
- view_header
+
+ advanced_content_form
- Ticket view
- divider
+
+ task-list
+
+ get
+
+
+ advanced_content_form: f.advanced_content_form;
+ change advanced_content_form value { [useCase.tasks.find { taskPair -> taskPair.transition == "next_view_settings"}.task] }
+
+
+
@@ -240,8 +247,6 @@
Súčasný filterVybrať zobrazenieNastavenie
- Asociované zobrazenie
- Tiketové zobrazenieNeue Filter auswählen
@@ -250,8 +255,6 @@
Aktualisiere die Ansicht mit dem ausgewählten FilterWählen Sie einen AnsichtstypEinstellungen
- zugehörige Ansicht
- Ticketansicht
@@ -260,12 +263,24 @@
208hourglass_empty
+
+ admin
+
+ true
+
+ data_sync368328
+
+ system
+
+ true
+
+ settings
@@ -277,35 +292,66 @@
admintrue
+
+
+
+ default
+ true
- true
- true
- form_title
+ settings_04grid
- view_header
+ advanced_content_form
- visible
+ editable0014
+ 0
material
outline
+
+ assign_0
+
+
+
+ finish_0
+
+
+
+
+ next_view_settings
+ 400
+ 48
+
+ queue_play_next
+ auto
+
+ admin
+
+ true
+
+
+
+ default
+
+ true
+
+ associated_view4grid
- Associated viewview_configuration_type
@@ -364,6 +410,22 @@
+
+ assign
+
+
+
+ cancel
+
+
+
+ finish_0
+
+
+
+ delegate
+
+ initialized
@@ -402,4 +464,11 @@
initialized1
+
+ a4
+ read
+ initialized
+ next_view_settings
+ 1
+
\ No newline at end of file
diff --git a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
new file mode 100644
index 00000000000..cd965650498
--- /dev/null
+++ b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
@@ -0,0 +1,156 @@
+package com.netgrif.application.engine.menu;
+
+import com.netgrif.application.engine.TestHelper;
+import com.netgrif.application.engine.menu.domain.FilterBody;
+import com.netgrif.application.engine.menu.domain.MenuItemBody;
+import com.netgrif.application.engine.menu.domain.configurations.TabbedCaseViewBody;
+import com.netgrif.application.engine.menu.domain.configurations.TabbedTaskViewBody;
+import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService;
+import com.netgrif.application.engine.petrinet.domain.I18nString;
+import com.netgrif.application.engine.petrinet.domain.dataset.Field;
+import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException;
+import com.netgrif.application.engine.workflow.domain.Case;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+@ActiveProfiles({"test"})
+@ExtendWith(SpringExtension.class)
+public class MenuItemServiceTest {
+
+ @Autowired
+ private TestHelper testHelper;
+
+ @Autowired
+ private IMenuItemService menuItemService;
+
+ @BeforeEach
+ public void beforeEach() {
+ testHelper.truncateDbs();
+ }
+
+ @Test
+ public void createFilterTest() {
+ // todo
+ }
+
+ @Test
+ public void updateFilterTest() {
+ // todo
+ }
+
+ @Test
+ public void createMenuItemTest() {
+ // todo
+ }
+
+ @Test
+ public void updateMenuItemTest() {
+ // todo
+ }
+
+ @Test
+ public void createOrUpdateMenuItemTest() {
+ // todo
+ }
+
+ @Test
+ public void createOrIgnoreMenuItemTest() {
+ // todo
+ }
+
+ @Test
+ public void findMenuItemByIdentifierTest() {
+ // todo
+ }
+
+ @Test
+ public void findMenuItemByUriAndNameTest() {
+ // todo
+ }
+
+ @Test
+ public void findFolderCaseTest() {
+ // todo
+ }
+
+ @Test
+ public void existsMenuItemTest() {
+ // todo
+ }
+
+ @Test
+ public void moveItemTest() {
+ // todo
+ }
+
+ @Test
+ public void duplicateItemTest() {
+ // todo
+ }
+
+ @Test
+ public void removeChildItemFromParentTest() {
+ // todo
+ }
+
+ @Test
+ public void getMenuItemDataTest() throws TransitionNotExecutableException {
+ assertThrows(IllegalArgumentException.class, () -> menuItemService.getMenuItemData("wrongCaseId"));
+
+ Case menuItemCase = createDefaultMenuItem("my_menu_item",
+ new I18nString("This is name", Map.of("sk", "Toto je nazov")));
+
+ Map>> resultMap = menuItemService.getMenuItemData(menuItemCase.getStringId());
+ assertEquals(3, resultMap.size());
+ assertTrue(resultMap.containsKey("menu_item"));
+ assertTrue(resultMap.containsKey("tabbed_case_view"));
+ assertTrue(resultMap.containsKey("tabbed_task_view"));
+ }
+
+ @Test
+ public void getAvailableViewsAsOptionsByIsPrimaryTest() {
+ // todo
+ }
+
+ @Test
+ public void getAvailableViewsAsOptionsByViewIdentifierTest() {
+ // todo
+ }
+
+ private Case createDefaultMenuItem(String identifier, I18nString name) throws TransitionNotExecutableException {
+ FilterBody filterBody = new FilterBody();
+ filterBody.setTitle(new I18nString("My case view filter"));
+ filterBody.setQuery("processIdentifier:process1");
+ filterBody.setType("Case");
+ filterBody.setAllowedNets(List.of("process1"));
+ filterBody.setIcon("home");
+ filterBody.setVisibility("private");
+
+ TabbedCaseViewBody caseView = new TabbedCaseViewBody();
+ caseView.setFilterBody(filterBody);
+ caseView.setRequireTitleInCreation(false);
+ caseView.setChainedView(new TabbedTaskViewBody());
+
+ MenuItemBody menuItemBody = new MenuItemBody();
+ menuItemBody.setUri("/");
+ menuItemBody.setIdentifier(identifier);
+ menuItemBody.setMenuIcon("home");
+ menuItemBody.setMenuName(name);
+ menuItemBody.setTabIcon("folder");
+ menuItemBody.setTabName(name);
+ menuItemBody.setView(caseView);
+
+ return menuItemService.createMenuItem(menuItemBody);
+ }
+}
From 21fd5c38bfcf7240da38bd893a7dd862d79b29e0 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Thu, 7 May 2026 11:41:20 +0200
Subject: [PATCH 05/49] [ETASK-23] Dynamic view configuration - fix resolving
menu item in the menu - add todos
---
.../engine/menu/domain/MenuItemBody.java | 2 +
.../engine/menu/domain/MenuItemConstants.java | 2 +
.../menu/domain/configurations/ViewBody.java | 2 +
.../domain/configurations/ViewConstants.java | 2 +
.../engine/menu/service/MenuItemService.java | 37 +---
.../service/interfaces/IMenuItemService.java | 5 +-
.../engine/menu/web/MenuController.java | 14 +-
.../responsebodies/MenuItemDataResponse.java | 10 +-
.../engine-processes/menu/menu_item.xml | 174 +++++++++++++++++-
.../menu/tabbed_case_view_configuration.xml | 149 +++++++++++++++
.../menu/tabbed_task_view_configuration.xml | 106 +++++++++++
.../menu/tabbed_ticket_view_configuration.xml | 35 ++++
.../engine/menu/MenuItemServiceTest.java | 24 ++-
13 files changed, 507 insertions(+), 55 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java
index 9637f9ddeb8..5af588b2fa3 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java
@@ -181,6 +181,8 @@ public ToDataSetOutcome toDataSet(String parentId, String nodePath, Case viewCas
List.of(viewCase.getStringId()));
String taskId = MenuItemUtils.findTaskIdInCase(viewCase, ViewConstants.TRANS_SETTINGS_ID);
outcome.putDataSetEntry(MenuItemConstants.FIELD_VIEW_CONFIGURATION_FORM, FieldType.TASK_REF, List.of(taskId));
+ String allDataTaskId = MenuItemUtils.findTaskIdInCase(viewCase, ViewConstants.TRANS_ALL_MENU_DATA_ID);
+ outcome.putDataSetEntry(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM, FieldType.TASK_REF, List.of(allDataTaskId));
}
return outcome;
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
index 0c1912a69fc..320c0f08ebf 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
@@ -28,8 +28,10 @@ public class MenuItemConstants {
public static final String FIELD_IS_AUTO_SELECT = "is_auto_select";
public static final String FIELD_VIEW_CONFIGURATION_ID = "view_configuration_id";
public static final String FIELD_VIEW_CONFIGURATION_FORM = "view_configuration_form";
+ public static final String FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM = "view_configuration_all_data_form";
public static final String TRANS_SETTINGS_ID = "item_settings";
public static final String TRANS_INIT_ID = "system_initialize";
public static final String TRANS_SYNC_ID = "data_sync";
+ public static final String TRANS_ALL_MENU_DATA = "all_menu_data";
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewBody.java
index d636b6e60b1..7496fa45998 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewBody.java
@@ -73,6 +73,8 @@ public ToDataSetOutcome toDataSet(Case associatedViewCase, Case filterCase) {
List.of(associatedViewCase.getStringId()));
String taskId = MenuItemUtils.findTaskIdInCase(associatedViewCase, ViewConstants.TRANS_SETTINGS_ID);
outcome.putDataSetEntry(ViewConstants.FIELD_VIEW_CONFIGURATION_FORM, FieldType.TASK_REF, List.of(taskId));
+ String allDataTaskId = MenuItemUtils.findTaskIdInCase(associatedViewCase, ViewConstants.TRANS_ALL_MENU_DATA_ID);
+ outcome.putDataSetEntry(ViewConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM, FieldType.TASK_REF, List.of(allDataTaskId));
}
if (filterCase != null) {
outcome.putDataSetEntry(ViewConstants.FIELD_VIEW_FILTER_CASE, FieldType.CASE_REF, List.of(filterCase.getStringId()));
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewConstants.java
index 74c38803f06..c0a904aa9b5 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewConstants.java
@@ -7,10 +7,12 @@ public class ViewConstants {
public static final String FIELD_CONFIGURATION_TYPE = "view_configuration_type";
public static final String FIELD_VIEW_CONFIGURATION_ID = "view_configuration_id";
public static final String FIELD_VIEW_CONFIGURATION_FORM = "view_configuration_form";
+ public static final String FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM = "view_configuration_all_data_form";
public static final String FIELD_VIEW_CONTAINS_FILTER = "contains_filter";
public static final String FIELD_VIEW_FILTER_CASE = "filter_case";
public static final String TRANS_INIT_ID = "initialize";
public static final String TRANS_SETTINGS_ID = "settings";
+ public static final String TRANS_ALL_MENU_DATA_ID = "all_menu_data";
public static final String TRANS_SYNC_ID = "data_sync";
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
index 4c8f3d9ae65..708c05eb4a8 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
@@ -13,10 +13,10 @@
import com.netgrif.application.engine.menu.domain.configurations.ViewConstants;
import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService;
import com.netgrif.application.engine.menu.utils.MenuItemUtils;
+import com.netgrif.application.engine.petrinet.domain.DataGroup;
import com.netgrif.application.engine.petrinet.domain.I18nString;
import com.netgrif.application.engine.petrinet.domain.UriContentType;
import com.netgrif.application.engine.petrinet.domain.UriNode;
-import com.netgrif.application.engine.petrinet.domain.dataset.Field;
import com.netgrif.application.engine.petrinet.domain.dataset.FieldType;
import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException;
import com.netgrif.application.engine.petrinet.service.interfaces.IUriService;
@@ -24,7 +24,6 @@
import com.netgrif.application.engine.startup.FilterRunner;
import com.netgrif.application.engine.startup.ImportHelper;
import com.netgrif.application.engine.workflow.domain.Case;
-import com.netgrif.application.engine.workflow.domain.DataField;
import com.netgrif.application.engine.workflow.domain.Task;
import com.netgrif.application.engine.workflow.service.interfaces.IDataService;
import com.netgrif.application.engine.workflow.service.interfaces.ITaskService;
@@ -406,42 +405,16 @@ public Case removeChildItemFromParent(String folderId, Case childItem) {
* Retrieves menu item data along with its associated view configuration data. The method collects immediate
* data from the menu item case and recursively traverses through all associated view configuration cases,
* aggregating their immediate data into a single map.
- *
+ * todo 23 doc
* @param caseId identifier of the menu item case
* @return map where keys are process identifiers (with "_configuration" suffix removed for view cases) and
* values are lists of immediate data fields from the corresponding cases
*/
@Override
- public Map>> getMenuItemData(String caseId) {
+ public List getMenuItemData(String caseId, Locale locale) {
Case menuItemCase = workflowService.findOne(caseId);
- Map>> immediateDataMap = new HashMap<>();
- immediateDataMap.put(menuItemCase.getProcessIdentifier(), menuItemCase.getImmediateData());
- Optional viewCaseIdOpt = getNextViewCaseId(menuItemCase);
- if (viewCaseIdOpt.isEmpty()) {
- return immediateDataMap;
- }
-
- do {
- Case viewCase = workflowService.findOne(viewCaseIdOpt.get());
- immediateDataMap.put(viewCase.getProcessIdentifier().replace("_configuration", ""),
- viewCase.getImmediateData());
- viewCaseIdOpt = getNextViewCaseId(viewCase);
- } while (viewCaseIdOpt.isPresent());
-
- return immediateDataMap;
- }
-
- protected Optional getNextViewCaseId(Case parentCase) {
- DataField viewConfigurationIdDataField = parentCase.getDataField(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID);
- if (viewConfigurationIdDataField == null) {
- return Optional.empty();
- }
- @SuppressWarnings("unchecked")
- List viewConfigurationIdValue = (List) viewConfigurationIdDataField.getValue();
- if (viewConfigurationIdValue == null || viewConfigurationIdValue.isEmpty()) {
- return Optional.empty();
- }
- return Optional.ofNullable(viewConfigurationIdValue.get(0));
+ String taskId = MenuItemUtils.findTaskIdInCase(menuItemCase, MenuItemConstants.TRANS_ALL_MENU_DATA);
+ return dataService.getDataGroups(taskId, locale).getData();
}
protected Case findCase(String processIdentifier, String query) {
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java
index 929f348cf10..90cd699331b 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java
@@ -3,14 +3,15 @@
import com.netgrif.application.engine.menu.domain.FilterBody;
import com.netgrif.application.engine.menu.domain.MenuItemBody;
import com.netgrif.application.engine.menu.domain.MenuItemView;
+import com.netgrif.application.engine.petrinet.domain.DataGroup;
import com.netgrif.application.engine.petrinet.domain.I18nString;
import com.netgrif.application.engine.petrinet.domain.UriNode;
-import com.netgrif.application.engine.petrinet.domain.dataset.Field;
import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException;
import com.netgrif.application.engine.workflow.domain.Case;
import com.netgrif.application.engine.petrinet.domain.dataset.MapOptionsField;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
@@ -29,7 +30,7 @@ public interface IMenuItemService {
void moveItem(Case item, String destUri) throws TransitionNotExecutableException;
Case duplicateItem(Case originItem, I18nString newTitle, String newIdentifier) throws TransitionNotExecutableException;
Case removeChildItemFromParent(String folderId, Case childItem);
- Map>> getMenuItemData(String caseId);
+ List getMenuItemData(String caseId, Locale locale);
/**
* Gets all tabbed or non-tabbed views
diff --git a/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java b/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java
index 27ffee572e9..32e32f3fdde 100644
--- a/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java
+++ b/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java
@@ -2,7 +2,7 @@
import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService;
import com.netgrif.application.engine.menu.web.responsebodies.MenuItemDataResponse;
-import com.netgrif.application.engine.petrinet.domain.dataset.Field;
+import com.netgrif.application.engine.petrinet.domain.DataGroup;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -12,13 +12,14 @@
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import java.util.Base64;
import java.util.List;
-import java.util.Map;
+import java.util.Locale;
@Slf4j
@RestController
@@ -29,17 +30,20 @@ public class MenuController {
private final IMenuItemService menuItemService;
+ // todo 23 menu item authorization
@Operation(summary = "Get relevant data for the menu item", security = {@SecurityRequirement(name = "BasicAuth")})
@GetMapping(value = "/{encodedCaseId}", produces = MediaTypes.HAL_JSON_VALUE)
- public EntityModel getMenuItemData(String encodedCaseId) {
+ public EntityModel getMenuItemData(@PathVariable("encodedCaseId") String encodedCaseId, Locale locale) {
try {
String caseId = new String(Base64.getDecoder().decode(encodedCaseId));
- Map>> immediateDataMap = menuItemService.getMenuItemData(caseId);
- return EntityModel.of(new MenuItemDataResponse(immediateDataMap));
+ List dataGroups = menuItemService.getMenuItemData(caseId, locale);
+ return EntityModel.of(new MenuItemDataResponse(dataGroups));
} catch (Exception e) {
log.error("Getting menu item data failed", e);
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Getting menu item data failed", e);
}
}
+
+ // todo 23 search with authorization
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/web/responsebodies/MenuItemDataResponse.java b/src/main/java/com/netgrif/application/engine/menu/web/responsebodies/MenuItemDataResponse.java
index 43f265a9547..78e7851e573 100644
--- a/src/main/java/com/netgrif/application/engine/menu/web/responsebodies/MenuItemDataResponse.java
+++ b/src/main/java/com/netgrif/application/engine/menu/web/responsebodies/MenuItemDataResponse.java
@@ -1,12 +1,10 @@
package com.netgrif.application.engine.menu.web.responsebodies;
-import com.netgrif.application.engine.petrinet.domain.dataset.Field;
+import com.netgrif.application.engine.petrinet.domain.DataGroup;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
@RequiredArgsConstructor
public class MenuItemDataResponse {
@@ -15,9 +13,5 @@ public class MenuItemDataResponse {
* Map containing menu item data where key is the view type and value is a list of immediate fields.
*/
@Getter
- private final Map>> data;
-
- public MenuItemDataResponse() {
- this(new HashMap<>());
- }
+ private final List data;
}
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 1f53c563974..d9e702304d2 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -380,7 +380,11 @@
processes: f.this;
- change processes options { return configurableMenuService.getNetsByAuthorAsMapOptions(loggedUser(), org.springframework.context.i18n.LocaleContextHolder.locale) }
+ try {
+ change processes options { return configurableMenuService.getNetsByAuthorAsMapOptions(loggedUser(), org.springframework.context.i18n.LocaleContextHolder.locale) }
+ } catch (Exception e) {
+ log.error("Cannot resolve processes: ", e)
+ }
processes: f.this,
@@ -509,6 +513,10 @@
view_configuration_form
+
+ view_configuration_all_data_form
+
+
order_down
@@ -727,6 +735,7 @@
340220
+
hourglass_emptyadmin
@@ -1601,6 +1610,7 @@
view_configuration_type: f.view_configuration_type,
view_configuration_form: f.view_configuration_form,
+ view_configuration_all_data_form: f.view_configuration_all_data_form,
view_configuration_id: f.view_configuration_id;
if (view_configuration_id.value != null && !view_configuration_id.value.isEmpty()) {
@@ -1610,6 +1620,7 @@
if (view_configuration_type.value == null || view_configuration_type.value == "") {
change view_configuration_id value { [] }
change view_configuration_form value { [] }
+ change view_configuration_all_data_form value { [] }
return
}
@@ -1619,6 +1630,7 @@
configurationCase = workflowService.findOne(configurationCase.stringId)
change view_configuration_id value { [configurationCase.stringId] }
change view_configuration_form value { [configurationCase.tasks.find { it.transition == "settings" }.task] }
+ change view_configuration_all_data_form value { [configurationCase.tasks.find { it.transition == "all_menu_data" }.task] }
@@ -1655,6 +1667,166 @@
+
+ all_menu_data
+ 10
+ 10
+
+
+ system
+
+ true
+
+
+
+ all_menu_data_0
+ flow
+
+ menu_item_identifier
+
+ visible
+
+
+
+ nodePath
+
+ visible
+
+
+
+ menu_name
+
+ visible
+
+
+
+ menu_icon
+
+ visible
+
+
+
+ menu_icon_preview
+
+ visible
+
+
+
+ hasChildren
+
+ visible
+
+
+
+ is_auto_select
+
+ visible
+
+
+
+ processes_available
+
+ visible
+
+
+
+ roles_available
+
+ visible
+
+
+
+ add_allowed_roles
+
+ visible
+
+
+
+ allowed_roles
+
+ visible
+
+
+
+ remove_allowed_roles
+
+ visible
+
+
+
+ add_banned_roles
+
+ visible
+
+
+
+ banned_roles
+
+ visible
+
+
+
+ remove_banned_roles
+
+ visible
+
+
+
+ use_custom_view
+
+ visible
+
+
+
+ custom_view_selector
+
+ visible
+
+
+
+ use_tabbed_view
+
+ visible
+
+
+
+ tab_name
+
+ visible
+
+
+
+ use_tab_icon
+
+ visible
+
+
+
+ tab_icon
+
+ visible
+
+
+
+ tab_icon_preview
+
+ visible
+
+
+
+ view_configuration_type
+
+ visible
+
+
+
+ view_configuration_all_data_form
+
+ visible
+
+
+
+
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
index 39740f21fce..37ce910b0e3 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
@@ -390,6 +390,10 @@
view_configuration_form
+
+ view_configuration_all_data_form
+
+
advanced_content_form
@@ -733,6 +737,7 @@
view_configuration_type: f.view_configuration_type,
view_configuration_form: f.view_configuration_form,
+ view_configuration_all_data_form: f.view_configuration_all_data_form,
view_configuration_id: f.view_configuration_id;
if (view_configuration_id.value != null && !view_configuration_id.value.isEmpty()) {
@@ -742,6 +747,7 @@
if (view_configuration_type.value == null || view_configuration_type.value == "") {
change view_configuration_id value { [] }
change view_configuration_form value { [] }
+ change view_configuration_all_data_form value { [] }
return
}
@@ -751,6 +757,7 @@
configurationCase = workflowService.findOne(configurationCase.stringId)
change view_configuration_id value { [configurationCase.stringId] }
change view_configuration_form value { [configurationCase.tasks.find { it.transition == "settings" }.task] }
+ change view_configuration_all_data_form value { [configurationCase.tasks.find { it.transition == "all_menu_data" }.task] }
@@ -1073,6 +1080,148 @@
+
+ all_menu_data
+ 10
+ 10
+
+
+ system
+
+ true
+
+
+
+ all_menu_data_0
+ flow
+
+ filter_autocomplete_selection
+
+ visible
+
+
+
+ update_filter
+
+ visible
+
+
+
+ selected_filter_preview
+
+ visible
+
+
+
+ filter_header
+
+ visible
+
+
+
+ current_filter_preview
+
+ visible
+
+
+
+ view_search_type
+
+ visible
+
+
+
+ show_create_case_button
+
+ visible
+
+
+
+ show_more_menu
+
+ visible
+
+
+
+ create_case_button_title
+
+ visible
+
+
+
+ create_case_button_icon
+
+ visible
+
+
+
+ create_case_button_icon_preview
+
+ visible
+
+
+
+ require_title_in_creation
+
+ visible
+
+
+
+ banned_nets_in_creation
+
+ visible
+
+
+
+ is_header_mode_changeable
+
+ visible
+
+
+
+ allow_header_table_mode
+
+ visible
+
+
+
+ headers_mode
+
+ visible
+
+
+
+ headers_default_mode
+
+ visible
+
+
+
+ use_case_default_headers
+
+ visible
+
+
+
+ default_headers
+
+ visible
+
+
+
+ view_configuration_type
+
+ visible
+
+
+
+ view_configuration_all_data_form
+
+ visible
+
+
+
+ initialized496
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml
index abc71d160a9..9ad685ae90a 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml
@@ -768,6 +768,112 @@
+
+ all_menu_data
+ 10
+ 10
+
+
+ system
+
+ true
+
+
+
+ all_menu_data_0
+ flow
+
+ filter_autocomplete_selection
+
+ visible
+
+
+
+ update_filter
+
+ visible
+
+
+
+ selected_filter_preview
+
+ visible
+
+
+
+ filter_header
+
+ visible
+
+
+
+ current_filter_preview
+
+ visible
+
+
+
+ merge_filters
+
+ visible
+
+
+
+ remove_filter
+
+ visible
+
+
+
+ view_search_type
+
+ visible
+
+
+
+ show_more_menu
+
+ visible
+
+
+
+ is_header_mode_changeable
+
+ visible
+
+
+
+ allow_header_table_mode
+
+ visible
+
+
+
+ headers_mode
+
+ visible
+
+
+
+ headers_default_mode
+
+ visible
+
+
+
+ use_default_headers
+
+ visible
+
+
+
+ default_headers
+
+ visible
+
+
+
+ initialized496
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
index 66f770c7188..35dc6a471d5 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
@@ -217,6 +217,10 @@
view_configuration_form
+
+ view_configuration_all_data_form
+
+
@@ -372,6 +376,7 @@
view_configuration_type: f.view_configuration_type,
view_configuration_form: f.view_configuration_form,
+ view_configuration_all_data_form: f.view_configuration_all_data_form,
view_configuration_id: f.view_configuration_id;
if (view_configuration_id.value != null && !view_configuration_id.value.isEmpty()) {
@@ -381,6 +386,7 @@
if (view_configuration_type.value == null || view_configuration_type.value == "") {
change view_configuration_id value { [] }
change view_configuration_form value { [] }
+ change view_configuration_all_data_form value { [] }
return
}
@@ -390,6 +396,7 @@
change view_configuration_id value { [configurationCase.stringId] }
configurationCase = workflowService.findOne(configurationCase.stringId)
change view_configuration_form value { [configurationCase.tasks.find { it.transition == "settings" }.task] }
+ change view_configuration_all_data_form value { [configurationCase.tasks.find { it.transition == "all_menu_data" }.task] }
@@ -427,6 +434,34 @@
+
+ all_menu_data
+ 10
+ 10
+
+
+ system
+
+ true
+
+
+
+ all_menu_data_0
+ flow
+
+ view_configuration_type
+
+ visible
+
+
+
+ view_configuration_all_data_form
+
+ visible
+
+
+
+ initialized496
diff --git a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
index cd965650498..0f711caf38c 100644
--- a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
+++ b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
@@ -7,18 +7,22 @@
import com.netgrif.application.engine.menu.domain.configurations.TabbedTaskViewBody;
import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService;
import com.netgrif.application.engine.petrinet.domain.I18nString;
-import com.netgrif.application.engine.petrinet.domain.dataset.Field;
import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException;
+import com.netgrif.application.engine.startup.SuperCreator;
import com.netgrif.application.engine.workflow.domain.Case;
+import com.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes.GetDataGroupsEventOutcome;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
@@ -33,6 +37,9 @@ public class MenuItemServiceTest {
@Autowired
private IMenuItemService menuItemService;
+
+ @Autowired
+ private SuperCreator superCreator;
@BeforeEach
public void beforeEach() {
@@ -106,16 +113,15 @@ public void removeChildItemFromParentTest() {
@Test
public void getMenuItemDataTest() throws TransitionNotExecutableException {
- assertThrows(IllegalArgumentException.class, () -> menuItemService.getMenuItemData("wrongCaseId"));
+ // todo 23 fix test
+ assertThrows(IllegalArgumentException.class, () -> menuItemService.getMenuItemData("wrongCaseId", Locale.getDefault()));
Case menuItemCase = createDefaultMenuItem("my_menu_item",
new I18nString("This is name", Map.of("sk", "Toto je nazov")));
- Map>> resultMap = menuItemService.getMenuItemData(menuItemCase.getStringId());
- assertEquals(3, resultMap.size());
- assertTrue(resultMap.containsKey("menu_item"));
- assertTrue(resultMap.containsKey("tabbed_case_view"));
- assertTrue(resultMap.containsKey("tabbed_task_view"));
+ login();
+ GetDataGroupsEventOutcome result = menuItemService.getMenuItemData(menuItemCase.getStringId(), Locale.getDefault());
+ assertTrue(result != null);
}
@Test
@@ -153,4 +159,8 @@ private Case createDefaultMenuItem(String identifier, I18nString name) throws Tr
return menuItemService.createMenuItem(menuItemBody);
}
+
+ private void login() {
+ SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(superCreator.getLoggedSuper(), null));
+ }
}
From 341ea391c58fde41533c059a7ce5004b9cef0034 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Mon, 11 May 2026 09:17:50 +0200
Subject: [PATCH 06/49] [ETASK-23] Dynamic view configuration - fix changing
menu item by ActionDelegate - fix menu_item.manageBehaviorOfTabFields - fix
menu_item move_add_node set action - improve menu_item initialization when
creating by forms - fix next view selection in ticket view
---
.../logic/action/ActionDelegate.groovy | 10 ++++-----
.../engine-processes/menu/menu_item.xml | 7 +++++-
.../tabbed_single_task_view_configuration.xml | 22 +++++++++++++++++++
3 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
index d4b527dcb6c..9b8b0c338bd 100644
--- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
+++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
@@ -1899,31 +1899,31 @@ class ActionDelegate {
title : { cl ->
def value = cl()
I18nString newName = (value instanceof I18nString) ? value : new I18nString(value as String)
- setData(MenuItemConstants.TRANS_SETTINGS_ID, item, [
+ setData(MenuItemConstants.TRANS_SYNC_ID, item, [
(MenuItemConstants.FIELD_MENU_NAME): ["type": "i18n", "value": newName]
])
},
menuIcon : { cl ->
def value = cl()
- setData(MenuItemConstants.TRANS_SETTINGS_ID, item, [
+ setData(MenuItemConstants.TRANS_SYNC_ID, item, [
(MenuItemConstants.FIELD_MENU_ICON): ["type": "text", "value": value]
])
},
tabIcon : { cl ->
def value = cl()
- setData(MenuItemConstants.TRANS_SETTINGS_ID, item, [
+ setData(MenuItemConstants.TRANS_SYNC_ID, item, [
(MenuItemConstants.FIELD_TAB_ICON): ["type": "text", "value": value]
])
},
useCustomView : { cl ->
def value = cl()
- setData(MenuItemConstants.TRANS_SETTINGS_ID, item, [
+ setData(MenuItemConstants.TRANS_SYNC_ID, item, [
(MenuItemConstants.FIELD_USE_CUSTOM_VIEW): ["type": "boolean", "value": value]
])
},
customViewSelector: { cl ->
def value = cl()
- setData(MenuItemConstants.TRANS_SETTINGS_ID, item, [
+ setData(MenuItemConstants.TRANS_SYNC_ID, item, [
(MenuItemConstants.FIELD_CUSTOM_VIEW_SELECTOR): ["type": "text", "value": value]
])
}]
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index d9e702304d2..fc9bdfddbc6 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -93,7 +93,7 @@
}
- { boolean useTabIcon, boolean useTabbedView, String transId = "item_settings" ->
+ { boolean useTabIcon, boolean useTabbedView, String transId = "view_settings" ->
def settingsTrans = useCase.petriNet.transitions[transId]
make [useCase.getField("use_tab_icon"), useCase.getField("tab_icon"), useCase.getField("tab_name")],
@@ -193,6 +193,7 @@
prefixUri = prefixUri.replace("//","/")
String newUri = prefixUri + uriService.getUriSeparator() + newNodeName.value
+ newUri = newUri.replace("//","/")
def newNode = uriService.getOrCreate(newUri, com.netgrif.application.engine.petrinet.domain.UriContentType.CASE)
change selectedUri value { splitUriPath(newNode.uriPath) }
@@ -737,6 +738,7 @@
hourglass_empty
+ autoadmin
@@ -810,12 +812,15 @@
finish
+ name: f.menu_name,
+ identifier: f.menu_item_identifier,
dest: f.move_dest_uri;
String newUri = dest.value.join("/")
newUri = newUri.replace("//","/")
changeMenuItem useCase uri { newUri }
+ change name value { new com.netgrif.application.engine.petrinet.domain.I18nString(identifier.value) }
Create
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml
index 22f4853e17f..2c869531c05 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml
@@ -266,6 +266,28 @@
+
+ all_menu_data
+ 10
+ 10
+
+
+ system
+
+ true
+
+
+
+ all_menu_data_0
+ flow
+
+ transition_id
+
+ visible
+
+
+
+ initialized496
From 400bf4307fd2575e757cd779d434eeb8dbbf9ae3 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Mon, 11 May 2026 13:15:17 +0200
Subject: [PATCH 07/49] [ETASK-23] Dynamic view configuration - make single
task view as a primary view - update init values in menu_item.xml - update
tabbed_single_task_view_configuration configurations
---
.../engine/menu/domain/MenuItemView.java | 2 +-
.../engine-processes/menu/menu_item.xml | 14 +-
.../menu/tabbed_case_view_configuration.xml | 2 +
.../tabbed_single_task_view_configuration.xml | 235 ++++++++++++++++--
.../menu/tabbed_task_view_configuration.xml | 10 +-
.../menu/tabbed_ticket_view_configuration.xml | 106 +-------
6 files changed, 239 insertions(+), 130 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java
index 1bdf5a43d54..6a1ba78b206 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java
@@ -23,7 +23,7 @@ public enum MenuItemView {
"de", "Ticketansicht mit Registerkarten")), "tabbed_ticket_view",
List.of("tabbed_single_task_view"), true, true),
TABBED_SINGLE_TASK_VIEW(new I18nString("Tabbed single task view", Map.of("sk", "Zobrazenie jednej úlohy v taboch",
- "de", "Einzelaufgabenansicht mit Registerkarten")), "tabbed_single_task_view", List.of(), true, false);
+ "de", "Einzelaufgabenansicht mit Registerkarten")), "tabbed_single_task_view", List.of(), true, true);
private final I18nString name;
private final String identifier;
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index fc9bdfddbc6..5c1d8f85c3a 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -448,6 +448,7 @@
use_tabbed_viewDo you want to use view with tabs?
+ true
@@ -490,7 +491,7 @@
view_configuration_typePick view type
- menuItemService.getAvailableViewsAsOptions(false, true)
+ menuItemService.getAvailableViewsAsOptions(true, true)
use_tabbed_view: f.use_tabbed_view,
@@ -508,6 +509,7 @@
tabbed_case_view_configurationtabbed_task_view_configurationtabbed_ticket_view_configuration
+ tabbed_single_task_view_configuration
@@ -1532,7 +1534,7 @@
tab_name
- hidden
+ editable2
@@ -1546,7 +1548,7 @@
use_tab_icon
- hidden
+ editable3
@@ -1571,7 +1573,7 @@
tab_icon
- hidden
+ editable0
@@ -1585,7 +1587,7 @@
tab_icon_preview
- hidden
+ visible1
@@ -1686,6 +1688,8 @@
all_menu_data_0flow
+ start
+ truemenu_item_identifier
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
index 37ce910b0e3..14626ff6bab 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
@@ -1094,6 +1094,8 @@
all_menu_data_0flow
+ start
+ truefilter_autocomplete_selection
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml
index 2c869531c05..6a346161c40 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml
@@ -53,7 +53,7 @@
} else {
change filterAutocomplete options {
def findAllPredicate = { filterCase -> !selectedFilterRef.value.contains(filterCase.stringId)
- && filterCase.dataSet["filter_type"].value == "Case" }
+ && filterCase.dataSet["filter_type"].value == "Task" }
return findFilters(filterAutocomplete.value != null ? filterAutocomplete.value : "")
.findAll(findAllPredicate)
.collectEntries({filterCase -> [filterCase.stringId, filterCase.title]})
@@ -70,7 +70,7 @@
- current_filter_preview
+ current_task_filter_preview
@@ -97,7 +97,7 @@
autocomplete_dynamic
- trans: t.settings,
+ trans: t.filter_settings,
filterAutocomplete: f.this,
filter_case: f.filter_case,
update_filter: f.update_filter,
@@ -106,7 +106,7 @@
updateFilterAutocompleteOptions(filterAutocomplete, previewTaskRef, filter_case, update_filter, trans)
- trans: t.settings,
+ trans: t.filter_settings,
filterAutocomplete: f.this,
filter_case: f.filter_case,
update_filter: f.update_filter,
@@ -123,7 +123,7 @@
raised
- trans: t.settings,
+ trans: t.filter_settings,
update_filter: f.update_filter,
contains_filter: f.contains_filter,
filter_case: f.filter_case,
@@ -147,7 +147,7 @@
filter_case
- filterTaskRef: f.current_filter_preview,
+ filterTaskRef: f.current_task_filter_preview,
contains_filter: f.contains_filter,
filterCaseRef: f.filter_case;
@@ -167,13 +167,33 @@
filter
-
-
-
- transition_id
- Transition id
+
+ advanced_content_form
+
+
+
+ task-list
+
+
+ get
+
+
+ advanced_content_form: f.advanced_content_form;
+ change advanced_content_form value { [useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task] }
+
+
+
+
+
+ show_page_header
+ Show page header
+ true
+
+
+ show_page_footer
+ Show page footer
+ false
-
@@ -183,7 +203,6 @@
Súčasný filterVybrať zobrazenieNastavenie
- ID prechoduNeue Filter auswählen
@@ -192,7 +211,6 @@
Aktualisiere die Ansicht mit dem ausgewählten FilterWählen Sie einen AnsichtstypEinstellungen
- Übergangs-ID
@@ -226,6 +244,7 @@
112settings
+
admin
@@ -243,7 +262,7 @@
4grid
- transition_id
+ show_page_headereditable
@@ -251,6 +270,34 @@
001
+ 2
+ material
+ outline
+
+
+
+ show_page_footer
+
+ editable
+
+
+ 2
+ 0
+ 1
+ 2
+ material
+ outline
+
+
+
+ advanced_content_form
+
+ editable
+
+
+ 0
+ 1
+ 14
material
outline
@@ -266,6 +313,117 @@
+
+ filter_settings
+ 432
+ 48
+
+ filter_alt
+ auto
+
+ admin
+
+ true
+
+
+
+ default
+
+ true
+
+
+
+ filter_0
+ 4
+ grid
+
+ filter_autocomplete_selection
+
+ editable
+
+
+ 0
+ 0
+ 1
+ 3
+ material
+ outline
+
+
+
+ update_filter
+
+ visible
+
+
+ 3
+ 0
+ 1
+ 1
+ material
+ standard
+
+
+
+ selected_filter_preview
+
+ visible
+
+
+ 0
+ 1
+ 1
+ 4
+ material
+ standard
+
+
+
+ filter_header
+
+ visible
+
+
+ 0
+ 2
+ 1
+ 4
+ material
+ outline
+
+
+
+ current_task_filter_preview
+
+ visible
+
+
+ 0
+ 3
+ 1
+ 4
+ material
+ standard
+
+
+
+
+ assign
+
+
+
+ cancel
+
+
+
+ finish_0
+
+
+
+ delegate
+
+
+ all_menu_data10
@@ -280,8 +438,46 @@
all_menu_data_0flow
+ start
+ true
+
+ show_page_header
+
+ visible
+
+
+
+ show_page_footer
+
+ visible
+
+
+
+ filter_autocomplete_selection
+
+ visible
+
+
+
+ update_filter
+
+ visible
+
+
+
+ selected_filter_preview
+
+ visible
+
+
- transition_id
+ filter_header
+
+ visible
+
+
+
+ current_task_filter_previewvisible
@@ -325,4 +521,11 @@
initialized1
+
+ a4
+ read
+ initialized
+ filter_settings
+ 1
+
\ No newline at end of file
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml
index 9ad685ae90a..e89d84d1448 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml
@@ -96,7 +96,7 @@
- current_filter_preview
+ current_task_filter_preview
@@ -178,7 +178,7 @@
removeButton: f.remove_filter,
contains_filter: f.contains_filter,
mergeFilters: f.merge_filters,
- filterTaskRef: f.current_filter_preview,
+ filterTaskRef: f.current_task_filter_preview,
filterCaseRef: f.filter_case;
if (filterCaseRef.value == null || filterCaseRef.value == []) {
@@ -559,7 +559,7 @@
- current_filter_preview
+ current_task_filter_previewvisible
@@ -782,6 +782,8 @@
all_menu_data_0flow
+ start
+ truefilter_autocomplete_selection
@@ -807,7 +809,7 @@
- current_filter_preview
+ current_task_filter_previewvisible
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
index 35dc6a471d5..32fc9e33a2b 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
@@ -87,110 +87,6 @@
}
-
-
- selected_filter_preview
-
-
-
- current_filter_preview
-
-
-
- filter_header
-
- Current filter
-
- divider
-
-
-
- new_filter_id
-
-
-
- contains_filter
-
- false
-
-
- filter_autocomplete_selection
- Select new filter
-
- autocomplete_dynamic
-
-
- trans: t.settings,
- filterAutocomplete: f.this,
- filter_case: f.filter_case,
- update_filter: f.update_filter,
- previewTaskRef: f.selected_filter_preview;
-
- updateFilterAutocompleteOptions(filterAutocomplete, previewTaskRef, filter_case, update_filter, trans)
-
-
- trans: t.settings,
- filterAutocomplete: f.this,
- filter_case: f.filter_case,
- update_filter: f.update_filter,
- previewTaskRef: f.selected_filter_preview;
-
- updateFilterAutocompleteOptions(filterAutocomplete, previewTaskRef, filter_case, update_filter, trans)
-
-
-
- update_filter
-
- Update view with selected filter
-
- raised
-
-
- trans: t.settings,
- update_filter: f.update_filter,
- contains_filter: f.contains_filter,
- filter_case: f.filter_case,
- filterAutocomplete: f.filter_autocomplete_selection;
-
- boolean containsFilter = filterAutocomplete.value != null && filterAutocomplete.value != ""
- if (containsFilter) {
- def filterCase = findCase({it._id.eq(filterAutocomplete.value)})
- if (filterCase.dataSet["filter_type"].value != "Case") {
- throw new IllegalArgumentException("Filter is of wrong type. Only filter of Case type allowed.")
- }
- }
-
- change contains_filter value { containsFilter }
- change filter_case value { [filterAutocomplete.value] }
- change filterAutocomplete value { "" }
- make update_filter,visible on trans when { true }
-
-
-
- filter_case
-
-
- filterTaskRef: f.current_filter_preview,
- contains_filter: f.contains_filter,
- filterCaseRef: f.filter_case;
-
- if (filterCaseRef.value == null || filterCaseRef.value == []) {
- change filterTaskRef value { [] }
- change contains_filter value { false }
- return
- }
-
- def filterCase = findCase({it._id.eq(filterCaseRef.value[0])})
- change filterTaskRef value {
- return [findTask({it.caseId.eq(filterCase.stringId).and(it.transitionId.eq("view_filter"))}).stringId]
- }
- change contains_filter value { true }
-
-
- filter
-
-
-
view_configuration_type
@@ -448,6 +344,8 @@
all_menu_data_0flow
+ start
+ trueview_configuration_type
From f537cb1f448e0b51cd087f3c2448aee7802bc325 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Mon, 11 May 2026 14:40:45 +0200
Subject: [PATCH 08/49] [ETASK-23] Dynamic view configuration - make single
task view as untabbed too
---
.../engine/menu/domain/MenuItemView.java | 30 +++++++++++--------
...kViewBody.java => SingleTaskViewBody.java} | 10 ++++---
.../SingleTaskViewConstants.java | 6 ++++
.../TabbedSingleTaskViewConstants.java | 5 ----
.../engine-processes/menu/menu_item.xml | 2 +-
...xml => single_task_view_configuration.xml} | 4 +--
.../menu/tabbed_ticket_view_configuration.xml | 2 +-
.../resources/dashboard_management_test.xml | 2 +-
8 files changed, 34 insertions(+), 27 deletions(-)
rename src/main/java/com/netgrif/application/engine/menu/domain/configurations/{TabbedSingleTaskViewBody.java => SingleTaskViewBody.java} (61%)
create mode 100644 src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewConstants.java
delete mode 100644 src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedSingleTaskViewConstants.java
rename src/main/resources/petriNets/engine-processes/menu/{tabbed_single_task_view_configuration.xml => single_task_view_configuration.xml} (99%)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java
index 6a1ba78b206..ddffbb2dcb2 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java
@@ -14,16 +14,18 @@
* */
@Getter
public enum MenuItemView {
- TABBED_CASE_VIEW(new I18nString("Tabbed case view", Map.of("sk", "Zobrazenie prípadov v taboch",
- "de", "Fallansicht mit Registerkarten")), "tabbed_case_view", List.of("tabbed_task_view"),
- true, true),
- TABBED_TASK_VIEW(new I18nString("Tabbed task view", Map.of("sk", "Zobrazenie úloh v taboch",
- "de", "Aufgabenansicht mit Registerkarten")), "tabbed_task_view", List.of(), true, true),
- TABBED_TICKET_VIEW(new I18nString("Tabbed ticket view", Map.of("sk", "Tiketové zobrazenie v taboch",
- "de", "Ticketansicht mit Registerkarten")), "tabbed_ticket_view",
- List.of("tabbed_single_task_view"), true, true),
- TABBED_SINGLE_TASK_VIEW(new I18nString("Tabbed single task view", Map.of("sk", "Zobrazenie jednej úlohy v taboch",
- "de", "Einzelaufgabenansicht mit Registerkarten")), "tabbed_single_task_view", List.of(), true, true);
+ TABBED_CASE_VIEW(new I18nString("Tabbed case view",
+ Map.of("sk", "Zobrazenie prípadov v taboch", "de", "Fallansicht mit Registerkarten")),
+ "tabbed_case_view", List.of("tabbed_task_view"), true, false, true),
+ TABBED_TASK_VIEW(new I18nString("Tabbed task view",
+ Map.of("sk", "Zobrazenie úloh v taboch", "de", "Aufgabenansicht mit Registerkarten")),
+ "tabbed_task_view", List.of(), true, false, true),
+ TABBED_TICKET_VIEW(new I18nString("Tabbed ticket view",
+ Map.of("sk", "Tiketové zobrazenie v taboch", "de", "Ticketansicht mit Registerkarten")),
+ "tabbed_ticket_view", List.of("tabbed_single_task_view"), true, false, true),
+ SINGLE_TASK_VIEW(new I18nString("Single task view",
+ Map.of("sk", "Zobrazenie jednej úlohy", "de", "Einzelaufgabenansicht")),
+ "single_task_view", List.of(), true, true, true);
private final I18nString name;
private final String identifier;
@@ -32,17 +34,19 @@ public enum MenuItemView {
* */
private final List allowedAssociatedViews;
private final boolean isTabbed;
+ private final boolean isUntabbed;
/**
* if false, the view cannot be used as first configuration of the menu_item, but can be used as secondary
* (associated to another view)
* */
private final boolean isPrimary;
- MenuItemView(I18nString name, String identifier, List allowedAssociatedViews, boolean isTabbed, boolean isPrimary) {
+ MenuItemView(I18nString name, String identifier, List allowedAssociatedViews, boolean isTabbed, boolean isUntabbed, boolean isPrimary) {
this.name = name;
this.identifier = identifier;
this.allowedAssociatedViews = allowedAssociatedViews;
this.isTabbed = isTabbed;
+ this.isUntabbed = isUntabbed;
this.isPrimary = isPrimary;
}
@@ -68,7 +72,7 @@ public static MenuItemView fromIdentifier(String identifier) {
* */
public static List findAllByIsTabbedAndIsPrimary(boolean isTabbed, boolean isPrimary) {
return Arrays.stream(MenuItemView.values())
- .filter(view -> view.isTabbed == isTabbed && view.isPrimary == isPrimary)
+ .filter(view -> (view.isTabbed == isTabbed || view.isUntabbed != isTabbed) && view.isPrimary == isPrimary)
.collect(Collectors.toList());
}
@@ -83,7 +87,7 @@ public static List findAllByIsTabbedAndIsPrimary(boolean isTabbed,
public static List findAllByIsTabbedAndParentIdentifier(boolean isTabbed, String parentIdentifier) {
MenuItemView parentView = fromIdentifier(parentIdentifier);
return Arrays.stream(MenuItemView.values())
- .filter(view -> view.isTabbed == isTabbed
+ .filter(view -> (view.isTabbed == isTabbed || view.isUntabbed != isTabbed)
&& parentView.getAllowedAssociatedViews().contains(view.identifier))
.collect(Collectors.toList());
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedSingleTaskViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewBody.java
similarity index 61%
rename from src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedSingleTaskViewBody.java
rename to src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewBody.java
index fa58689c976..06633508bed 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedSingleTaskViewBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewBody.java
@@ -10,8 +10,9 @@
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
-public class TabbedSingleTaskViewBody extends ViewBody {
- private String transitionId;
+public class SingleTaskViewBody extends ViewBody {
+ private boolean showPageHeader = true;
+ private boolean showPageFooter = false;
@Override
public ViewBody getAssociatedViewBody() {
@@ -20,12 +21,13 @@ public ViewBody getAssociatedViewBody() {
@Override
public MenuItemView getViewType() {
- return MenuItemView.TABBED_SINGLE_TASK_VIEW;
+ return MenuItemView.SINGLE_TASK_VIEW;
}
@Override
protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) {
- outcome.putDataSetEntry(TabbedSingleTaskViewConstants.FIELD_TRANSITION_ID, FieldType.TEXT, this.transitionId);
+ outcome.putDataSetEntry(SingleTaskViewConstants.FIELD_SHOW_PAGE_HEADER, FieldType.BOOLEAN, this.showPageHeader);
+ outcome.putDataSetEntry(SingleTaskViewConstants.FIELD_SHOW_PAGE_FOOTER, FieldType.BOOLEAN, this.showPageFooter);
return outcome;
}
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewConstants.java
new file mode 100644
index 00000000000..2782e50e0dc
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewConstants.java
@@ -0,0 +1,6 @@
+package com.netgrif.application.engine.menu.domain.configurations;
+
+public class SingleTaskViewConstants extends ViewConstants {
+ public static final String FIELD_SHOW_PAGE_HEADER = "show_page_header";
+ public static final String FIELD_SHOW_PAGE_FOOTER = "show_page_footer";
+}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedSingleTaskViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedSingleTaskViewConstants.java
deleted file mode 100644
index c0d6949b328..00000000000
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedSingleTaskViewConstants.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.netgrif.application.engine.menu.domain.configurations;
-
-public class TabbedSingleTaskViewConstants extends ViewConstants {
- public static final String FIELD_TRANSITION_ID = "transition_id";
-}
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 5c1d8f85c3a..e29a594cd6c 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -509,7 +509,7 @@
tabbed_case_view_configurationtabbed_task_view_configurationtabbed_ticket_view_configuration
- tabbed_single_task_view_configuration
+ single_task_view_configuration
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml
similarity index 99%
rename from src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml
rename to src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml
index 6a346161c40..c951d9b2a10 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml
@@ -1,7 +1,7 @@
- tabbed_single_task_view_configuration
+ single_task_view_configurationTST
- Tabbed single task view configuration
+ Single task view configurationcheck_box_outline_blanktruefalse
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
index 32fc9e33a2b..07b4c103e61 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
@@ -106,7 +106,7 @@
view_configuration_id
- tabbed_single_task_view_configuration
+ single_task_view_configuration
diff --git a/src/test/resources/dashboard_management_test.xml b/src/test/resources/dashboard_management_test.xml
index d3f1ae15906..199b8a68b98 100644
--- a/src/test/resources/dashboard_management_test.xml
+++ b/src/test/resources/dashboard_management_test.xml
@@ -27,7 +27,7 @@
filterBody.getTitle(),
filterBody.getIcon()
)
- def singleTaskViewBody = new com.netgrif.application.engine.menu.domain.configurations.TabbedSingleTaskViewBody()
+ def singleTaskViewBody = new com.netgrif.application.engine.menu.domain.configurations.SingleTaskViewBody()
def ticketViewBody = new com.netgrif.application.engine.menu.domain.configurations.TabbedTicketViewBody()
ticketViewBody.setChainedView(singleTaskViewBody)
menuItemBody.setAutoSelect(true)
From 4126f045a9a6b123d1caf8a4e3045694bb6144c7 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Tue, 12 May 2026 09:23:52 +0200
Subject: [PATCH 09/49] [ETASK-23] Dynamic view configuration - make task view
as untabbed too
---
.../logic/action/ActionDelegate.groovy | 6 ++---
.../engine/menu/domain/MenuItemView.java | 10 ++++-----
...bedTaskViewBody.java => TaskViewBody.java} | 22 +++++++++----------
...wConstants.java => TaskViewConstants.java} | 4 ++--
.../engine-processes/menu/menu_item.xml | 2 +-
.../menu/tabbed_case_view_configuration.xml | 2 +-
...ration.xml => task_view_configuration.xml} | 4 ++--
.../engine/action/MenuItemApiTest.groovy | 18 +++++++--------
.../engine/menu/MenuItemServiceTest.java | 4 ++--
9 files changed, 36 insertions(+), 36 deletions(-)
rename src/main/java/com/netgrif/application/engine/menu/domain/configurations/{TabbedTaskViewBody.java => TaskViewBody.java} (63%)
rename src/main/java/com/netgrif/application/engine/menu/domain/configurations/{TabbedTaskViewConstants.java => TaskViewConstants.java} (85%)
rename src/main/resources/petriNets/engine-processes/menu/{tabbed_task_view_configuration.xml => task_view_configuration.xml} (99%)
diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
index 9b8b0c338bd..d19a3ddbe83 100644
--- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
+++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
@@ -27,7 +27,7 @@ import com.netgrif.application.engine.menu.domain.FilterBody
import com.netgrif.application.engine.menu.domain.MenuItemBody
import com.netgrif.application.engine.menu.domain.MenuItemConstants
import com.netgrif.application.engine.menu.domain.configurations.TabbedCaseViewBody
-import com.netgrif.application.engine.menu.domain.configurations.TabbedTaskViewBody
+import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody
import com.netgrif.application.engine.menu.domain.configurations.ViewBody
import com.netgrif.application.engine.menu.domain.dashboard.DashboardItemBody
import com.netgrif.application.engine.menu.domain.dashboard.DashboardManagementBody
@@ -2118,13 +2118,13 @@ class ActionDelegate {
caseView.setDefaultHeaders(caseDefaultHeaders)
caseView.setRequireTitleInCreation(true)
- ViewBody taskView = new TabbedTaskViewBody()
+ ViewBody taskView = new TaskViewBody()
taskView.setDefaultHeaders(taskDefaultHeaders)
caseView.setChainedView(taskView)
return caseView
} else if (filterBody.getType() == "Task") {
- ViewBody taskView = new TabbedTaskViewBody()
+ ViewBody taskView = new TaskViewBody()
taskView.setFilterBody(filterBody)
taskView.setDefaultHeaders(taskDefaultHeaders)
return taskView
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java
index ddffbb2dcb2..8ecbab35c10 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java
@@ -16,13 +16,13 @@
public enum MenuItemView {
TABBED_CASE_VIEW(new I18nString("Tabbed case view",
Map.of("sk", "Zobrazenie prípadov v taboch", "de", "Fallansicht mit Registerkarten")),
- "tabbed_case_view", List.of("tabbed_task_view"), true, false, true),
- TABBED_TASK_VIEW(new I18nString("Tabbed task view",
- Map.of("sk", "Zobrazenie úloh v taboch", "de", "Aufgabenansicht mit Registerkarten")),
- "tabbed_task_view", List.of(), true, false, true),
+ "tabbed_case_view", List.of("task_view"), true, false, true),
+ TASK_VIEW(new I18nString("Task view",
+ Map.of("sk", "Zobrazenie úloh", "de", "Aufgabenansicht")),
+ "task_view", List.of(), true, true, true),
TABBED_TICKET_VIEW(new I18nString("Tabbed ticket view",
Map.of("sk", "Tiketové zobrazenie v taboch", "de", "Ticketansicht mit Registerkarten")),
- "tabbed_ticket_view", List.of("tabbed_single_task_view"), true, false, true),
+ "tabbed_ticket_view", List.of("single_task_view"), true, false, true),
SINGLE_TASK_VIEW(new I18nString("Single task view",
Map.of("sk", "Zobrazenie jednej úlohy", "de", "Einzelaufgabenansicht")),
"single_task_view", List.of(), true, true, true);
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTaskViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java
similarity index 63%
rename from src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTaskViewBody.java
rename to src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java
index 0fabe6940fe..9e572485b3b 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTaskViewBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java
@@ -14,7 +14,7 @@
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
-public class TabbedTaskViewBody extends ViewBody {
+public class TaskViewBody extends ViewBody {
private Case filter;
private boolean mergeFilters = true;
private String viewSearchType = "fulltext_advanced";
@@ -33,29 +33,29 @@ public ViewBody getAssociatedViewBody() {
@Override
public MenuItemView getViewType() {
- return MenuItemView.TABBED_TASK_VIEW;
+ return MenuItemView.TASK_VIEW;
}
@Override
protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) {
- outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_MERGE_FILTERS, FieldType.BOOLEAN,
+ outcome.putDataSetEntry(TaskViewConstants.FIELD_MERGE_FILTERS, FieldType.BOOLEAN,
this.mergeFilters);
- outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP,
+ outcome.putDataSetEntry(TaskViewConstants.FIELD_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP,
this.viewSearchType);
- outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_HEADERS_MODE, FieldType.MULTICHOICE_MAP,
+ outcome.putDataSetEntry(TaskViewConstants.FIELD_HEADERS_MODE, FieldType.MULTICHOICE_MAP,
this.headersMode == null ? new ArrayList<>() : this.headersMode);
- outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP,
+ outcome.putDataSetEntry(TaskViewConstants.FIELD_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP,
this.headersDefaultMode);
- outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN,
+ outcome.putDataSetEntry(TaskViewConstants.FIELD_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN,
this.isHeaderModeChangeable);
- outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_ALLOW_HEADER_TABLE_MODE, FieldType.BOOLEAN,
+ outcome.putDataSetEntry(TaskViewConstants.FIELD_ALLOW_HEADER_TABLE_MODE, FieldType.BOOLEAN,
this.allowHeaderTableMode);
- outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_USE_DEFAULT_HEADERS, FieldType.BOOLEAN,
+ outcome.putDataSetEntry(TaskViewConstants.FIELD_USE_DEFAULT_HEADERS, FieldType.BOOLEAN,
this.useDefaultHeaders);
- outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_DEFAULT_HEADERS, FieldType.TEXT,
+ outcome.putDataSetEntry(TaskViewConstants.FIELD_DEFAULT_HEADERS, FieldType.TEXT,
this.defaultHeaders != null ? String.join(",", this.defaultHeaders) : null);
- outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_SHOW_MORE_MENU, FieldType.BOOLEAN,
+ outcome.putDataSetEntry(TaskViewConstants.FIELD_SHOW_MORE_MENU, FieldType.BOOLEAN,
this.showMoreMenu);
return outcome;
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTaskViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java
similarity index 85%
rename from src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTaskViewConstants.java
rename to src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java
index f895558fa77..afc745c463b 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTaskViewConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java
@@ -1,9 +1,9 @@
package com.netgrif.application.engine.menu.domain.configurations;
/**
- * Here are declared constants of process tabbed_task_view_configuration.xml.
+ * Here are declared constants of process task_view_configuration.xml.
*/
-public class TabbedTaskViewConstants extends ViewConstants {
+public class TaskViewConstants extends ViewConstants {
public static final String FIELD_MERGE_FILTERS = "merge_filters";
public static final String FIELD_VIEW_SEARCH_TYPE = "view_search_type";
public static final String FIELD_DEFAULT_HEADERS = "default_headers";
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index e29a594cd6c..2699eff5789 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -507,7 +507,7 @@
tabbed_case_view_configuration
- tabbed_task_view_configuration
+ task_view_configurationtabbed_ticket_view_configurationsingle_task_view_configuration
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
index 14626ff6bab..f8d9736f507 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
@@ -383,7 +383,7 @@
view_configuration_id
- tabbed_task_view_configuration
+ task_view_configuration
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
similarity index 99%
rename from src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml
rename to src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
index e89d84d1448..e09b34d8674 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
@@ -1,7 +1,7 @@
- tabbed_task_view_configuration
+ task_view_configurationTTV
- Tabbed task view configuration
+ Task view configurationcheck_box_outline_blanktruefalse
diff --git a/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy b/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy
index e3ae062e5e0..89d93af0054 100644
--- a/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy
+++ b/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy
@@ -7,7 +7,7 @@ import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchReques
import com.netgrif.application.engine.menu.domain.MenuItemConstants
import com.netgrif.application.engine.menu.domain.MenuItemView
import com.netgrif.application.engine.menu.domain.configurations.TabbedCaseViewConstants
-import com.netgrif.application.engine.menu.domain.configurations.TabbedTaskViewConstants
+import com.netgrif.application.engine.menu.domain.configurations.TaskViewConstants
import com.netgrif.application.engine.menu.utils.MenuItemUtils
import com.netgrif.application.engine.orgstructure.groups.interfaces.INextGroupService
import com.netgrif.application.engine.petrinet.domain.I18nString
@@ -104,14 +104,14 @@ class MenuItemApiTest {
assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_VIEW_CONTAINS_FILTER].value == true
assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_VIEW_FILTER_CASE].value[0] == filter.stringId
assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_DEFAULT_HEADERS].value == "meta-title,meta-title"
- assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_CONFIGURATION_TYPE].value == MenuItemView.TABBED_TASK_VIEW.identifier
+ assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_CONFIGURATION_TYPE].value == MenuItemView.TASK_VIEW.identifier
String tabbedTaskViewId = MenuItemUtils.getCaseIdFromCaseRef(tabbedCaseView, TabbedCaseViewConstants.FIELD_VIEW_CONFIGURATION_ID)
assert tabbedTaskViewId != null
Case tabbedTaskView = workflowService.findOne(tabbedTaskViewId)
- assert tabbedTaskView.dataSet[TabbedTaskViewConstants.FIELD_VIEW_CONTAINS_FILTER].value == false
- assert tabbedTaskView.dataSet[TabbedTaskViewConstants.FIELD_VIEW_FILTER_CASE].value == []
- assert tabbedTaskView.dataSet[TabbedTaskViewConstants.FIELD_DEFAULT_HEADERS].value == "meta-title,meta-title"
+ assert tabbedTaskView.dataSet[TaskViewConstants.FIELD_VIEW_CONTAINS_FILTER].value == false
+ assert tabbedTaskView.dataSet[TaskViewConstants.FIELD_VIEW_FILTER_CASE].value == []
+ assert tabbedTaskView.dataSet[TaskViewConstants.FIELD_DEFAULT_HEADERS].value == "meta-title,meta-title"
Case testFolder = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue.keyword:\"/netgrif/test\"", PageRequest.of(0, 1))[0]
Case netgrifFolder = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue.keyword:\"/netgrif\"", PageRequest.of(0, 1))[0]
@@ -170,14 +170,14 @@ class MenuItemApiTest {
assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_VIEW_CONTAINS_FILTER].value == true
assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_VIEW_FILTER_CASE].value[0] == filter.stringId
assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_DEFAULT_HEADERS].value == "meta-title,meta-title,meta-title"
- assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_CONFIGURATION_TYPE].value == MenuItemView.TABBED_TASK_VIEW.identifier
+ assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_CONFIGURATION_TYPE].value == MenuItemView.TASK_VIEW.identifier
String tabbedTaskViewId = MenuItemUtils.getCaseIdFromCaseRef(tabbedCaseView, TabbedCaseViewConstants.FIELD_VIEW_CONFIGURATION_ID)
assert tabbedTaskViewId != null && tabbedTaskViewId.equals(tabbedTaskViewIdBeforeChange)
Case tabbedTaskView = workflowService.findOne(tabbedTaskViewId)
- assert tabbedTaskView.dataSet[TabbedTaskViewConstants.FIELD_VIEW_CONTAINS_FILTER].value == false
- assert tabbedTaskView.dataSet[TabbedTaskViewConstants.FIELD_VIEW_FILTER_CASE].value == []
- assert tabbedTaskView.dataSet[TabbedTaskViewConstants.FIELD_DEFAULT_HEADERS].value == "meta-title,meta-title,meta-title"
+ assert tabbedTaskView.dataSet[TaskViewConstants.FIELD_VIEW_CONTAINS_FILTER].value == false
+ assert tabbedTaskView.dataSet[TaskViewConstants.FIELD_VIEW_FILTER_CASE].value == []
+ assert tabbedTaskView.dataSet[TaskViewConstants.FIELD_DEFAULT_HEADERS].value == "meta-title,meta-title,meta-title"
}
@Test
diff --git a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
index 0f711caf38c..dffd8056049 100644
--- a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
+++ b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
@@ -4,7 +4,7 @@
import com.netgrif.application.engine.menu.domain.FilterBody;
import com.netgrif.application.engine.menu.domain.MenuItemBody;
import com.netgrif.application.engine.menu.domain.configurations.TabbedCaseViewBody;
-import com.netgrif.application.engine.menu.domain.configurations.TabbedTaskViewBody;
+import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody;
import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService;
import com.netgrif.application.engine.petrinet.domain.I18nString;
import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException;
@@ -146,7 +146,7 @@ private Case createDefaultMenuItem(String identifier, I18nString name) throws Tr
TabbedCaseViewBody caseView = new TabbedCaseViewBody();
caseView.setFilterBody(filterBody);
caseView.setRequireTitleInCreation(false);
- caseView.setChainedView(new TabbedTaskViewBody());
+ caseView.setChainedView(new TaskViewBody());
MenuItemBody menuItemBody = new MenuItemBody();
menuItemBody.setUri("/");
From 64b92f03b8a8199148a1ade60cd3dedfb1b7823a Mon Sep 17 00:00:00 2001
From: chvostek
Date: Tue, 12 May 2026 11:09:18 +0200
Subject: [PATCH 10/49] [ETASK-23] Dynamic view configuration - make case view
as untabbed too
---
.../logic/action/ActionDelegate.groovy | 4 +-
.../engine/menu/domain/MenuItemView.java | 6 +-
...bedCaseViewBody.java => CaseViewBody.java} | 30 +++++-----
...wConstants.java => CaseViewConstants.java} | 4 +-
.../engine/menu/service/MenuItemService.java | 27 +++++----
...ration.xml => case_view_configuration.xml} | 60 +++++++++++++++----
.../engine-processes/menu/menu_item.xml | 7 ++-
.../engine/action/MenuItemApiTest.groovy | 30 +++++-----
.../engine/menu/MenuItemServiceTest.java | 4 +-
9 files changed, 110 insertions(+), 62 deletions(-)
rename src/main/java/com/netgrif/application/engine/menu/domain/configurations/{TabbedCaseViewBody.java => CaseViewBody.java} (60%)
rename src/main/java/com/netgrif/application/engine/menu/domain/configurations/{TabbedCaseViewConstants.java => CaseViewConstants.java} (90%)
rename src/main/resources/petriNets/engine-processes/menu/{tabbed_case_view_configuration.xml => case_view_configuration.xml} (96%)
diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
index d19a3ddbe83..e1bc462a28c 100644
--- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
+++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
@@ -26,7 +26,7 @@ import com.netgrif.application.engine.mail.interfaces.IMailService
import com.netgrif.application.engine.menu.domain.FilterBody
import com.netgrif.application.engine.menu.domain.MenuItemBody
import com.netgrif.application.engine.menu.domain.MenuItemConstants
-import com.netgrif.application.engine.menu.domain.configurations.TabbedCaseViewBody
+import com.netgrif.application.engine.menu.domain.configurations.CaseViewBody
import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody
import com.netgrif.application.engine.menu.domain.configurations.ViewBody
import com.netgrif.application.engine.menu.domain.dashboard.DashboardItemBody
@@ -2113,7 +2113,7 @@ class ActionDelegate {
protected ViewBody createLegacyMenuItemViews(FilterBody filterBody, List caseDefaultHeaders = null,
List taskDefaultHeaders = null) {
if (filterBody.getType() == "Case") {
- ViewBody caseView = new TabbedCaseViewBody()
+ ViewBody caseView = new CaseViewBody()
caseView.setFilterBody(filterBody)
caseView.setDefaultHeaders(caseDefaultHeaders)
caseView.setRequireTitleInCreation(true)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java
index 8ecbab35c10..5061dc31a6f 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java
@@ -14,9 +14,9 @@
* */
@Getter
public enum MenuItemView {
- TABBED_CASE_VIEW(new I18nString("Tabbed case view",
- Map.of("sk", "Zobrazenie prípadov v taboch", "de", "Fallansicht mit Registerkarten")),
- "tabbed_case_view", List.of("task_view"), true, false, true),
+ CASE_VIEW(new I18nString("Case view",
+ Map.of("sk", "Zobrazenie prípadov", "de", "Fallansicht")),
+ "case_view", List.of("task_view"), true, true, true),
TASK_VIEW(new I18nString("Task view",
Map.of("sk", "Zobrazenie úloh", "de", "Aufgabenansicht")),
"task_view", List.of(), true, true, true),
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
similarity index 60%
rename from src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewBody.java
rename to src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
index 661ff73eaa5..baf76eff3ba 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
@@ -14,7 +14,7 @@
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
-public class TabbedCaseViewBody extends ViewBody {
+public class CaseViewBody extends ViewBody {
private String viewSearchType = "fulltext_advanced";
private I18nString createCaseButtonTitle;
private String createCaseButtonIcon = "add";
@@ -38,39 +38,39 @@ public ViewBody getAssociatedViewBody() {
@Override
public MenuItemView getViewType() {
- return MenuItemView.TABBED_CASE_VIEW;
+ return MenuItemView.CASE_VIEW;
}
@Override
protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) {
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP,
this.viewSearchType);
if (this.createCaseButtonTitle != null) {
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_CREATE_CASE_BUTTON_TITLE, FieldType.I18N,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_CREATE_CASE_BUTTON_TITLE, FieldType.I18N,
this.createCaseButtonTitle);
}
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_CREATE_CASE_BUTTON_ICON, FieldType.TEXT,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_CREATE_CASE_BUTTON_ICON, FieldType.TEXT,
this.createCaseButtonIcon);
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_REQUIRE_TITLE_IN_CREATION, FieldType.BOOLEAN,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_REQUIRE_TITLE_IN_CREATION, FieldType.BOOLEAN,
this.requireTitleInCreation);
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_SHOW_CREATE_CASE_BUTTON, FieldType.BOOLEAN,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_SHOW_CREATE_CASE_BUTTON, FieldType.BOOLEAN,
this.showCreateCaseButton);
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_BANNED_NETS_IN_CREATION, FieldType.TEXT,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_BANNED_NETS_IN_CREATION, FieldType.TEXT,
this.bannedNetsInCreation);
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_SHOW_MORE_MENU, FieldType.BOOLEAN,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_SHOW_MORE_MENU, FieldType.BOOLEAN,
this.showMoreMenu);
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_ALLOW_HEADER_TABLE_MODE, FieldType.BOOLEAN,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_ALLOW_HEADER_TABLE_MODE, FieldType.BOOLEAN,
this.allowHeaderTableMode);
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_HEADERS_MODE, FieldType.MULTICHOICE_MAP,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_HEADERS_MODE, FieldType.MULTICHOICE_MAP,
this.headersMode == null ? new ArrayList<>() : this.headersMode);
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP,
this.headersDefaultMode);
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_DEFAULT_HEADERS, FieldType.TEXT,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_DEFAULT_HEADERS, FieldType.TEXT,
this.defaultHeaders != null ? String.join(",", this.defaultHeaders) : null);
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN,
this.isHeaderModeChangeable);
- outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_USE_CASE_DEFAULT_HEADERS, FieldType.BOOLEAN,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_USE_CASE_DEFAULT_HEADERS, FieldType.BOOLEAN,
this.useDefaultHeaders);
return outcome;
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java
similarity index 90%
rename from src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewConstants.java
rename to src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java
index b5c8ef2361b..b26ce065485 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java
@@ -1,9 +1,9 @@
package com.netgrif.application.engine.menu.domain.configurations;
/**
- * Here are declared constants of process tabbed_case_view_configuration.xml.
+ * Here are declared constants of process case_view_configuration.xml.
*/
-public class TabbedCaseViewConstants extends ViewConstants {
+public class CaseViewConstants extends ViewConstants {
public static final String FIELD_NEW_FILTER_ID = "new_filter_id";
public static final String FIELD_DEFAULT_HEADERS = "default_headers";
public static final String FIELD_REQUIRE_TITLE_IN_CREATION = "require_title_in_creation";
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
index 708c05eb4a8..27a5f80c6c7 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
@@ -124,7 +124,7 @@ public Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableExce
Case viewCase = null;
if (body.hasView()) {
- viewCase = createView(body.getView());
+ viewCase = createView(body.getView(), body.isUseTabbedView());
}
ToDataSetOutcome dataSetOutcome = body.toDataSet(parentItemCase.getStringId(), nodePath, viewCase);
menuItemCase = setDataWithExecute(menuItemCase, MenuItemConstants.TRANS_INIT_ID, dataSetOutcome.getDataSet());
@@ -150,7 +150,7 @@ public Case updateMenuItem(Case itemCase, MenuItemBody body) throws TransitionNo
}
Case viewCase = findView(itemCase);
- viewCase = handleView(viewCase, body.getView());
+ viewCase = handleView(viewCase, body.getView(), body.isUseTabbedView());
ToDataSetOutcome dataSetOutcome = body.toDataSet(viewCase);
itemCase = setData(itemCase, MenuItemConstants.TRANS_SYNC_ID, dataSetOutcome.getDataSet());
log.debug("Updated menu item case [{}] with identifier [{}].", itemCase.getStringId(), body.getIdentifier());
@@ -475,30 +475,30 @@ protected Case findCaseInCaseRef(Case useCase, String caseRefId) {
}
}
- protected Case handleView(Case existingViewCase, ViewBody body) throws TransitionNotExecutableException {
+ protected Case handleView(Case existingViewCase, ViewBody body, boolean isTabbed) throws TransitionNotExecutableException {
if (mustUpdateView(existingViewCase, body)) {
- return updateView(existingViewCase, body);
+ return updateView(existingViewCase, body, isTabbed);
} else if (mustCreateView(existingViewCase, body)) {
- return createView(body);
+ return createView(body, isTabbed);
} else if (mustRemoveView(existingViewCase, body)) {
removeView(existingViewCase);
return null;
} else if (mustRemoveAndCreateView(existingViewCase, body)) {
removeView(existingViewCase);
- return createView(body);
+ return createView(body, isTabbed);
} else {
return null;
}
}
- protected Case createView(ViewBody body) throws TransitionNotExecutableException {
+ protected Case createView(ViewBody body, boolean isTabbed) throws TransitionNotExecutableException {
IUser loggedUser = userService.getLoggedOrSystem();
Case viewCase = createCase(body.getViewProcessIdentifier(), body.getViewProcessIdentifier(),
- loggedUser.transformToLoggedUser());
+ loggedUser.transformToLoggedUser(), isTabbed);
Case associatedViewCase = null;
if (body.hasAssociatedView()) {
- associatedViewCase = createView(body.getAssociatedViewBody());
+ associatedViewCase = createView(body.getAssociatedViewBody(), isTabbed);
}
Case filterCase = null;
if (body.getFilterBody() != null) {
@@ -516,12 +516,12 @@ protected Case createView(ViewBody body) throws TransitionNotExecutableException
return viewCase;
}
- protected Case updateView(Case viewCase, ViewBody body) throws TransitionNotExecutableException {
+ protected Case updateView(Case viewCase, ViewBody body, boolean isTabbed) throws TransitionNotExecutableException {
Case filterCase = findFilter(viewCase);
filterCase = handleFilter(filterCase, body.getFilterBody());
Case associatedViewCase = findView(viewCase);
- associatedViewCase = handleView(associatedViewCase, body.getAssociatedViewBody());
+ associatedViewCase = handleView(associatedViewCase, body.getAssociatedViewBody(), isTabbed);
ToDataSetOutcome outcome = body.toDataSet(associatedViewCase, filterCase);
viewCase = setData(viewCase, ViewConstants.TRANS_SYNC_ID, outcome.getDataSet());
@@ -699,6 +699,11 @@ protected Case createCase(String identifier, String title, LoggedUser loggedUser
return workflowService.createCaseByIdentifier(identifier, title, "", loggedUser).getCase();
}
+ protected Case createCase(String identifier, String title, LoggedUser loggedUser, boolean isTabbed) {
+ return workflowService.createCaseByIdentifier(identifier, title, "", loggedUser,
+ Map.of("is_tabbed", String.valueOf(isTabbed))).getCase();
+ }
+
protected Case setData(Case useCase, String transId, Map> dataSet) {
String taskId = MenuItemUtils.findTaskIdInCase(useCase, transId);
return setData(taskId, dataSet);
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
similarity index 96%
rename from src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
rename to src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
index f8d9736f507..a088abb4cff 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
@@ -1,7 +1,7 @@
- tabbed_case_view_configuration
- TCV
- Tabbed case view configuration
+ case_view_configuration
+ CVC
+ Case view configurationcheck_box_outline_blanktruefalse
@@ -29,10 +29,19 @@
+
+ view_create
+
+
+ is_tabbed_var_arc: f.is_tabbed_var_arc;
+ change is_tabbed_var_arc value { Boolean.valueOf(params["is_tabbed"]) ? 1 : 0 }
+
+
+ view_delete
-
+
removeViewCase()
@@ -406,14 +415,22 @@
advanced_content_form: f.advanced_content_form;
- change advanced_content_form value { [useCase.tasks.find { taskPair -> taskPair.transition == "header_settings"}.task,
- useCase.tasks.find { taskPair -> taskPair.transition == "create_case_btn_settings"}.task,
- useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task,
- useCase.tasks.find { taskPair -> taskPair.transition == "next_view_settings"}.task] }
+ String nextViewTaskId = useCase.tasks.find { taskPair -> taskPair.transition == "next_view_settings"}?.task
+ def taskIds = [useCase.tasks.find { taskPair -> taskPair.transition == "header_settings"}.task,
+ useCase.tasks.find { taskPair -> taskPair.transition == "create_case_btn_settings"}.task,
+ useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task]
+ if (nextViewTaskId != null) {
+ taskIds.add(nextViewTaskId)
+ }
+ change advanced_content_form value { taskIds }
+
+ is_tabbed_var_arc
+ Is tabbed
+
@@ -696,7 +713,7 @@
next_view_settings
- 560
+ 33648queue_play_next
@@ -927,7 +944,7 @@
header_settings
- 336
+ 56048view_column
@@ -1240,6 +1257,14 @@
1false
+
+ is_tabbed
+ 240
+ 48
+
+ 0
+ false
+ a1read
@@ -1289,4 +1314,19 @@
create_case_btn_settings1
+
+ a8
+ regular
+ initialize
+ is_tabbed
+ 1
+ is_tabbed_var_arc
+
+
+ a9
+ read
+ is_tabbed
+ next_view_settings
+ 1
+
\ No newline at end of file
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 2699eff5789..140c69c6379 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -506,7 +506,7 @@
view_configuration_id
- tabbed_case_view_configuration
+ case_view_configurationtask_view_configurationtabbed_ticket_view_configurationsingle_task_view_configuration
@@ -1615,6 +1615,7 @@
0
+ use_tabbed_view: f.use_tabbed_view,
view_configuration_type: f.view_configuration_type,
view_configuration_form: f.view_configuration_form,
view_configuration_all_data_form: f.view_configuration_all_data_form,
@@ -1631,7 +1632,9 @@
return
}
- def configurationCase = createCase(view_configuration_type.value + "_configuration")
+ def configurationCase = createCase(view_configuration_type.value + "_configuration", null, "",
+ userService.loggedOrSystem, org.springframework.context.i18n.LocaleContextHolder.getLocale(),
+ ["is_tabbed":String.valueOf(use_tabbed_view.value)])
def initTask = assignTask("initialize", configurationCase)
finishTask(initTask)
configurationCase = workflowService.findOne(configurationCase.stringId)
diff --git a/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy b/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy
index 89d93af0054..3c591ddd80d 100644
--- a/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy
+++ b/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy
@@ -6,7 +6,7 @@ import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseSer
import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest
import com.netgrif.application.engine.menu.domain.MenuItemConstants
import com.netgrif.application.engine.menu.domain.MenuItemView
-import com.netgrif.application.engine.menu.domain.configurations.TabbedCaseViewConstants
+import com.netgrif.application.engine.menu.domain.configurations.CaseViewConstants
import com.netgrif.application.engine.menu.domain.configurations.TaskViewConstants
import com.netgrif.application.engine.menu.utils.MenuItemUtils
import com.netgrif.application.engine.orgstructure.groups.interfaces.INextGroupService
@@ -91,7 +91,7 @@ class MenuItemApiTest {
assert item.dataSet[MenuItemConstants.FIELD_BANNED_ROLES].options.containsKey("role_2:filter_api_test")
assert item.dataSet[MenuItemConstants.FIELD_ALLOWED_ROLES].options.containsKey("role_1:filter_api_test")
assert item.dataSet[MenuItemConstants.FIELD_USE_TABBED_VIEW].value == true
- assert item.dataSet[MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE].value == MenuItemView.TABBED_CASE_VIEW.identifier
+ assert item.dataSet[MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE].value == MenuItemView.CASE_VIEW.identifier
assert filter.dataSet["filter"].filterMetadata["filterType"] == "Case"
assert filter.dataSet["filter"].allowedNets == ["filter", "menu_item"]
@@ -101,12 +101,12 @@ class MenuItemApiTest {
String tabbedCaseViewId = MenuItemUtils.getCaseIdFromCaseRef(item, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID)
assert tabbedCaseViewId != null
Case tabbedCaseView = workflowService.findOne(tabbedCaseViewId)
- assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_VIEW_CONTAINS_FILTER].value == true
- assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_VIEW_FILTER_CASE].value[0] == filter.stringId
- assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_DEFAULT_HEADERS].value == "meta-title,meta-title"
- assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_CONFIGURATION_TYPE].value == MenuItemView.TASK_VIEW.identifier
+ assert tabbedCaseView.dataSet[CaseViewConstants.FIELD_VIEW_CONTAINS_FILTER].value == true
+ assert tabbedCaseView.dataSet[CaseViewConstants.FIELD_VIEW_FILTER_CASE].value[0] == filter.stringId
+ assert tabbedCaseView.dataSet[CaseViewConstants.FIELD_DEFAULT_HEADERS].value == "meta-title,meta-title"
+ assert tabbedCaseView.dataSet[CaseViewConstants.FIELD_CONFIGURATION_TYPE].value == MenuItemView.TASK_VIEW.identifier
- String tabbedTaskViewId = MenuItemUtils.getCaseIdFromCaseRef(tabbedCaseView, TabbedCaseViewConstants.FIELD_VIEW_CONFIGURATION_ID)
+ String tabbedTaskViewId = MenuItemUtils.getCaseIdFromCaseRef(tabbedCaseView, CaseViewConstants.FIELD_VIEW_CONFIGURATION_ID)
assert tabbedTaskViewId != null
Case tabbedTaskView = workflowService.findOne(tabbedTaskViewId)
assert tabbedTaskView.dataSet[TaskViewConstants.FIELD_VIEW_CONTAINS_FILTER].value == false
@@ -139,7 +139,7 @@ class MenuItemApiTest {
Case item = getMenuItem(caze)
String tabbedCaseViewIdBeforeChange = MenuItemUtils.getCaseIdFromCaseRef(item, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID)
Case tabbedCaseViewBeforeChange = workflowService.findOne(tabbedCaseViewIdBeforeChange)
- String tabbedTaskViewIdBeforeChange = MenuItemUtils.getCaseIdFromCaseRef(tabbedCaseViewBeforeChange, TabbedCaseViewConstants.FIELD_VIEW_CONFIGURATION_ID)
+ String tabbedTaskViewIdBeforeChange = MenuItemUtils.getCaseIdFromCaseRef(tabbedCaseViewBeforeChange, CaseViewConstants.FIELD_VIEW_CONFIGURATION_ID)
def newUri = uriService.getOrCreate("/netgrif/test_new", UriContentType.DEFAULT)
caze = setData(caze, [
@@ -157,7 +157,7 @@ class MenuItemApiTest {
assert item.dataSet[MenuItemConstants.FIELD_MENU_NAME].value.toString() == "CHANGED FILTER"
assert item.dataSet[MenuItemConstants.FIELD_ALLOWED_ROLES].options.entrySet()[0].key.contains("role_2")
assert item.dataSet[MenuItemConstants.FIELD_USE_TABBED_VIEW].value == true
- assert item.dataSet[MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE].value == MenuItemView.TABBED_CASE_VIEW.identifier
+ assert item.dataSet[MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE].value == MenuItemView.CASE_VIEW.identifier
assert item.uriNodeId == newUri.stringId
assert filter.dataSet["filter"].allowedNets == ["filter"]
@@ -167,12 +167,12 @@ class MenuItemApiTest {
String tabbedCaseViewId = MenuItemUtils.getCaseIdFromCaseRef(item, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID)
assert tabbedCaseViewId != null && tabbedCaseViewId.equals(tabbedCaseViewIdBeforeChange)
Case tabbedCaseView = workflowService.findOne(tabbedCaseViewId)
- assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_VIEW_CONTAINS_FILTER].value == true
- assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_VIEW_FILTER_CASE].value[0] == filter.stringId
- assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_DEFAULT_HEADERS].value == "meta-title,meta-title,meta-title"
- assert tabbedCaseView.dataSet[TabbedCaseViewConstants.FIELD_CONFIGURATION_TYPE].value == MenuItemView.TASK_VIEW.identifier
+ assert tabbedCaseView.dataSet[CaseViewConstants.FIELD_VIEW_CONTAINS_FILTER].value == true
+ assert tabbedCaseView.dataSet[CaseViewConstants.FIELD_VIEW_FILTER_CASE].value[0] == filter.stringId
+ assert tabbedCaseView.dataSet[CaseViewConstants.FIELD_DEFAULT_HEADERS].value == "meta-title,meta-title,meta-title"
+ assert tabbedCaseView.dataSet[CaseViewConstants.FIELD_CONFIGURATION_TYPE].value == MenuItemView.TASK_VIEW.identifier
- String tabbedTaskViewId = MenuItemUtils.getCaseIdFromCaseRef(tabbedCaseView, TabbedCaseViewConstants.FIELD_VIEW_CONFIGURATION_ID)
+ String tabbedTaskViewId = MenuItemUtils.getCaseIdFromCaseRef(tabbedCaseView, CaseViewConstants.FIELD_VIEW_CONFIGURATION_ID)
assert tabbedTaskViewId != null && tabbedTaskViewId.equals(tabbedTaskViewIdBeforeChange)
Case tabbedTaskView = workflowService.findOne(tabbedTaskViewId)
assert tabbedTaskView.dataSet[TaskViewConstants.FIELD_VIEW_CONTAINS_FILTER].value == false
@@ -354,7 +354,7 @@ class MenuItemApiTest {
String tabbedCaseViewId = MenuItemUtils.getCaseIdFromCaseRef(leafItemCase, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID)
assert tabbedCaseViewId != null
Case tabbedCaseView = workflowService.findOne(tabbedCaseViewId)
- String tabbedTaskViewId = MenuItemUtils.getCaseIdFromCaseRef(tabbedCaseView, TabbedCaseViewConstants.FIELD_VIEW_CONFIGURATION_ID)
+ String tabbedTaskViewId = MenuItemUtils.getCaseIdFromCaseRef(tabbedCaseView, CaseViewConstants.FIELD_VIEW_CONFIGURATION_ID)
assert tabbedTaskViewId != null
workflowService.deleteCase(testFolder)
diff --git a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
index dffd8056049..c1df0ebdf78 100644
--- a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
+++ b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
@@ -3,7 +3,7 @@
import com.netgrif.application.engine.TestHelper;
import com.netgrif.application.engine.menu.domain.FilterBody;
import com.netgrif.application.engine.menu.domain.MenuItemBody;
-import com.netgrif.application.engine.menu.domain.configurations.TabbedCaseViewBody;
+import com.netgrif.application.engine.menu.domain.configurations.CaseViewBody;
import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody;
import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService;
import com.netgrif.application.engine.petrinet.domain.I18nString;
@@ -143,7 +143,7 @@ private Case createDefaultMenuItem(String identifier, I18nString name) throws Tr
filterBody.setIcon("home");
filterBody.setVisibility("private");
- TabbedCaseViewBody caseView = new TabbedCaseViewBody();
+ CaseViewBody caseView = new CaseViewBody();
caseView.setFilterBody(filterBody);
caseView.setRequireTitleInCreation(false);
caseView.setChainedView(new TaskViewBody());
From fe4f7437f4be84ff9b5151ffa85ccaddf7e81c83 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Tue, 12 May 2026 15:50:10 +0200
Subject: [PATCH 11/49] [ETASK-23] Dynamic view configuration - fixes
---
.../menu/domain/configurations/CaseViewBody.java | 2 +-
.../domain/configurations/CaseViewConstants.java | 2 +-
.../menu/case_view_configuration.xml | 12 +++++-------
3 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
index baf76eff3ba..3bb18185d18 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
@@ -70,7 +70,7 @@ protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) {
this.defaultHeaders != null ? String.join(",", this.defaultHeaders) : null);
outcome.putDataSetEntry(CaseViewConstants.FIELD_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN,
this.isHeaderModeChangeable);
- outcome.putDataSetEntry(CaseViewConstants.FIELD_USE_CASE_DEFAULT_HEADERS, FieldType.BOOLEAN,
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_USE_DEFAULT_HEADERS, FieldType.BOOLEAN,
this.useDefaultHeaders);
return outcome;
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java
index b26ce065485..ecadc80da20 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java
@@ -17,5 +17,5 @@ public class CaseViewConstants extends ViewConstants {
public static final String FIELD_HEADERS_MODE = "headers_mode";
public static final String FIELD_HEADERS_DEFAULT_MODE = "headers_default_mode";
public static final String FIELD_IS_HEADER_MODE_CHANGEABLE = "is_header_mode_changeable";
- public static final String FIELD_USE_CASE_DEFAULT_HEADERS = "use_case_default_headers";
+ public static final String FIELD_USE_DEFAULT_HEADERS = "use_default_headers";
}
diff --git a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
index a088abb4cff..273ea0822ae 100644
--- a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
@@ -357,8 +357,8 @@
true
- use_case_default_headers
- Use custom default headers?
+ use_default_headers
+ Use custom default headers?true
@@ -462,7 +462,6 @@
Súčasný filterZobrazenie prípadovVšeobecné
- Použiť predvolené hlavičkyVybrať zobrazenieNastavenie
@@ -495,7 +494,6 @@
"Erweiterte Optionen" Taste bei einzelnen Fällen anzeigenMenüeintrageinstellungenFallansicht
- Benutzerdefinierte Standardheader verwenden?Wählen Sie einen AnsichtstypEinstellungen
@@ -994,12 +992,12 @@
- use_case_default_headers
+ use_default_headerseditable
trans: t.this,
- use: f.use_case_default_headers,
+ use: f.use_default_headers,
headers: f.default_headers;
make headers,editable on trans when { use.value }
@@ -1216,7 +1214,7 @@
- use_case_default_headers
+ use_default_headersvisible
From dd2ec2ed86a3defef9e10459f408bbd186aa550f Mon Sep 17 00:00:00 2001
From: chvostek
Date: Wed, 13 May 2026 09:44:44 +0200
Subject: [PATCH 12/49] [ETASK-23] Dynamic view configuration - fix field ids
---
.../configurations/CaseViewConstants.java | 16 +--
.../configurations/TaskViewConstants.java | 16 +--
.../menu/case_view_configuration.xml | 114 +++++++++---------
.../menu/task_view_configuration.xml | 114 +++++++++---------
4 files changed, 130 insertions(+), 130 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java
index ecadc80da20..99ec5c2f2fa 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java
@@ -5,17 +5,17 @@
*/
public class CaseViewConstants extends ViewConstants {
public static final String FIELD_NEW_FILTER_ID = "new_filter_id";
- public static final String FIELD_DEFAULT_HEADERS = "default_headers";
+ public static final String FIELD_DEFAULT_HEADERS = "case_default_headers";
public static final String FIELD_REQUIRE_TITLE_IN_CREATION = "require_title_in_creation";
- public static final String FIELD_VIEW_SEARCH_TYPE = "view_search_type";
+ public static final String FIELD_VIEW_SEARCH_TYPE = "case_view_search_type";
public static final String FIELD_CREATE_CASE_BUTTON_TITLE = "create_case_button_title";
public static final String FIELD_CREATE_CASE_BUTTON_ICON = "create_case_button_icon";
public static final String FIELD_BANNED_NETS_IN_CREATION = "banned_nets_in_creation";
public static final String FIELD_SHOW_CREATE_CASE_BUTTON = "show_create_case_button";
- public static final String FIELD_SHOW_MORE_MENU = "show_more_menu";
- public static final String FIELD_ALLOW_HEADER_TABLE_MODE = "allow_header_table_mode";
- public static final String FIELD_HEADERS_MODE = "headers_mode";
- public static final String FIELD_HEADERS_DEFAULT_MODE = "headers_default_mode";
- public static final String FIELD_IS_HEADER_MODE_CHANGEABLE = "is_header_mode_changeable";
- public static final String FIELD_USE_DEFAULT_HEADERS = "use_default_headers";
+ public static final String FIELD_SHOW_MORE_MENU = "case_show_more_menu";
+ public static final String FIELD_ALLOW_HEADER_TABLE_MODE = "case_allow_header_table_mode";
+ public static final String FIELD_HEADERS_MODE = "case_headers_mode";
+ public static final String FIELD_HEADERS_DEFAULT_MODE = "case_headers_default_mode";
+ public static final String FIELD_IS_HEADER_MODE_CHANGEABLE = "case_is_header_mode_changeable";
+ public static final String FIELD_USE_DEFAULT_HEADERS = "use_case_default_headers";
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java
index afc745c463b..32e9b716f67 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java
@@ -5,12 +5,12 @@
*/
public class TaskViewConstants extends ViewConstants {
public static final String FIELD_MERGE_FILTERS = "merge_filters";
- public static final String FIELD_VIEW_SEARCH_TYPE = "view_search_type";
- public static final String FIELD_DEFAULT_HEADERS = "default_headers";
- public static final String FIELD_HEADERS_MODE = "headers_mode";
- public static final String FIELD_HEADERS_DEFAULT_MODE = "headers_default_mode";
- public static final String FIELD_IS_HEADER_MODE_CHANGEABLE = "is_header_mode_changeable";
- public static final String FIELD_ALLOW_HEADER_TABLE_MODE = "allow_header_table_mode";
- public static final String FIELD_USE_DEFAULT_HEADERS = "use_default_headers";
- public static final String FIELD_SHOW_MORE_MENU = "show_more_menu";
+ public static final String FIELD_VIEW_SEARCH_TYPE = "task_view_search_type";
+ public static final String FIELD_DEFAULT_HEADERS = "task_default_headers";
+ public static final String FIELD_HEADERS_MODE = "task_headers_mode";
+ public static final String FIELD_HEADERS_DEFAULT_MODE = "task_headers_default_mode";
+ public static final String FIELD_IS_HEADER_MODE_CHANGEABLE = "task_is_header_mode_changeable";
+ public static final String FIELD_ALLOW_HEADER_TABLE_MODE = "task_allow_header_table_mode";
+ public static final String FIELD_USE_DEFAULT_HEADERS = "use_task_default_headers";
+ public static final String FIELD_SHOW_MORE_MENU = "task_show_more_menu";
}
diff --git a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
index 273ea0822ae..a017c3fce7e 100644
--- a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
@@ -227,8 +227,8 @@
- view_search_type
- Search type for case view
+ case_view_search_type
+ Search type for case view
@@ -299,18 +299,18 @@
- show_more_menu
- Show more menu for case item?
+ case_show_more_menu
+ Show more menu for case item?false
- allow_header_table_mode
- Allow table mode for headers?
+ case_allow_header_table_mode
+ Allow table mode for headers?true
- headers_mode
- Header mode
+ case_headers_mode
+ Header mode
@@ -318,23 +318,23 @@
sort,edit,search
- headersMode: f.headers_mode,
- defaultMode: f.headers_default_mode,
+ headersMode: f.case_headers_mode,
+ defaultMode: f.case_headers_default_mode,
holder: f.headers_options_holder;
updateOptionsBasedOnValue(defaultMode, headersMode, holder)
- headersMode: f.headers_mode,
- defaultMode: f.headers_default_mode,
+ headersMode: f.case_headers_mode,
+ defaultMode: f.case_headers_default_mode,
holder: f.headers_options_holder;
updateOptionsBasedOnValue(defaultMode, headersMode, holder)
- headers_default_mode
- Default header mode
+ case_headers_default_mode
+ Default header mode
@@ -352,18 +352,18 @@
- is_header_mode_changeable
- Can header mode be changed?
+ case_is_header_mode_changeable
+ Can header mode be changed?true
- use_default_headers
- Use custom default headers?
+ use_case_default_headers
+ Use custom default headers?true
- default_headers
- Set default headers
+ case_default_headers
+ Set default headersExample: "meta-title,meta-visualId"
defaultHeaders: f.this;
@@ -437,11 +437,11 @@
Názov tlačidla "Nová inštancia"Identifikátor ikony tlačidla "Nová inštancia"Náhľad ikony
- Predvolené hlavičky
+ Predvolené hlavičkyNapríklad: "meta-title,meta-visualId"Zvoľte nový filterAktualizovať zobrazenie s vybraným filtrom
- Typ vyhľadávania prípadov
+ Typ vyhľadávania prípadovSkrytéFulltextFulltext a rozšírené
@@ -449,15 +449,15 @@
Zakázané siete pri vytváraníUveďte identifikátory procesov oddelené čiarkou. Napríklad: mynet1,mynet2Zobraziť tlačidlo na vytvorenie prípadu?
- Zobrazovať menu pre prípadovú položku?
+ Zobrazovať menu pre prípadovú položku?ZoraďovanieVyhľadávanieUpravovanie
- Mód hlavičiek
- Predvolený mód hlavičiek
- Môže byť mód hlavičiek zmenený?
- Povoliť tabuľkový mód pre hlavičky?
- Použiť vlastné predvolené hlavičky?
+ Mód hlavičiek
+ Predvolený mód hlavičiek
+ Môže byť mód hlavičiek zmenený?
+ Povoliť tabuľkový mód pre hlavičky?
+ Použiť vlastné predvolené hlavičky?Nastavenie položkySúčasný filterZobrazenie prípadov
@@ -469,7 +469,7 @@
Schaltflächentitel "Neuer Fall"Ikone IDIkonevorschau
- Anzuzeigende Attributmenge auswählen
+ Anzuzeigende Attributmenge auswählenNeue Filter auswählenBeispiel: "meta-title,meta-visualId"Versteckt
@@ -477,21 +477,21 @@
SortierenSuchenBearbeiten
- Kopfzeilenmodus
- Standardkopfzeilenmodus
- Erlaube Änderung des Kopfzeilenmodus?
- Erlaube Tabellenmodus?
- Eigene Kopfzeilen verwenden?
+ Kopfzeilenmodus
+ Standardkopfzeilenmodus
+ Erlaube Änderung des Kopfzeilenmodus?
+ Erlaube Tabellenmodus?
+ Eigene Kopfzeilen verwenden?Aktueller FilterAllgemeinAktualisiere die Ansicht mit dem ausgewählten Filter
- Suchmodus im Fallansicht
+ Suchmodus im FallansichtEinfacher und erweiterter SuchmodusErforde den Titel beim erzeugen von Fällen?Ausgeschlossene ProzesseTrenne die Prozessidentifikatoren mit einer Komma. z.B.: netz1,netz2Schaltfläche „Fall erstellen“ anzeigen?
- "Erweiterte Optionen" Taste bei einzelnen Fällen anzeigen
+ "Erweiterte Optionen" Taste bei einzelnen Fällen anzeigenMenüeintrageinstellungenFallansichtWählen Sie einen Ansichtstyp
@@ -546,7 +546,7 @@
4grid
- view_search_type
+ case_view_search_typeeditablerequired
@@ -561,7 +561,7 @@
- show_more_menu
+ case_show_more_menueditable
@@ -964,15 +964,15 @@
3grid
- is_header_mode_changeable
+ case_is_header_mode_changeableeditablerequired
trans: t.this,
- isChangeable: f.is_header_mode_changeable,
- mode: f.headers_mode,
- defaultMode: f.headers_default_mode;
+ isChangeable: f.case_is_header_mode_changeable,
+ mode: f.case_headers_mode,
+ defaultMode: f.case_headers_default_mode;
make [mode, defaultMode], editable on trans when { isChangeable.value }
make [mode, defaultMode], required on trans when { isChangeable.value }
@@ -992,13 +992,13 @@
- use_default_headers
+ use_case_default_headerseditable
trans: t.this,
- use: f.use_default_headers,
- headers: f.default_headers;
+ use: f.use_case_default_headers,
+ headers: f.case_default_headers;
make headers,editable on trans when { use.value }
make headers,visible on trans when { !use.value }
@@ -1015,7 +1015,7 @@
- default_headers
+ case_default_headerseditable
@@ -1030,7 +1030,7 @@
- allow_header_table_mode
+ case_allow_header_table_modeeditablerequired
@@ -1046,7 +1046,7 @@
- headers_mode
+ case_headers_modeeditablerequired
@@ -1062,7 +1062,7 @@
- headers_default_mode
+ case_headers_default_modeeditablerequired
@@ -1142,7 +1142,7 @@
- view_search_type
+ case_view_search_typevisible
@@ -1154,7 +1154,7 @@
- show_more_menu
+ case_show_more_menuvisible
@@ -1190,37 +1190,37 @@
- is_header_mode_changeable
+ case_is_header_mode_changeablevisible
- allow_header_table_mode
+ case_allow_header_table_modevisible
- headers_mode
+ case_headers_modevisible
- headers_default_mode
+ case_headers_default_modevisible
- use_default_headers
+ use_case_default_headersvisible
- default_headers
+ case_default_headersvisible
diff --git a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
index e09b34d8674..c9f25059274 100644
--- a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
@@ -223,8 +223,8 @@
- view_search_type
- Search type for task view
+ task_view_search_type
+ Search type for task view
@@ -233,31 +233,31 @@
fulltext_advanced
- headers_mode
- Header mode
+ task_headers_mode
+ Header modesort,edit
- headersMode: f.headers_mode,
- defaultMode: f.headers_default_mode,
+ headersMode: f.task_headers_mode,
+ defaultMode: f.task_headers_default_mode,
holder: f.headers_options_holder;
updateOptionsBasedOnValue(defaultMode, headersMode, holder)
- headersMode: f.headers_mode,
- defaultMode: f.headers_default_mode,
+ headersMode: f.task_headers_mode,
+ defaultMode: f.task_headers_default_mode,
holder: f.headers_options_holder;
updateOptionsBasedOnValue(defaultMode, headersMode, holder)
- headers_default_mode
- Default header mode
+ task_headers_default_mode
+ Default header mode
@@ -273,23 +273,23 @@
- is_header_mode_changeable
- Can header mode be changed?
+ task_is_header_mode_changeable
+ Can header mode be changed?true
- allow_header_table_mode
- Allow table mode for headers?
+ task_allow_header_table_mode
+ Allow table mode for headers?true
- use_default_headers
- Use custom default headers?
+ use_task_default_headers
+ Use custom default headers?true
- default_headers
- Set default headers
+ task_default_headers
+ Set default headersExample: "meta-title,meta-user"
defaultHeaders: f.this;
@@ -301,8 +301,8 @@
- show_more_menu
- Show more menu for task item?
+ task_show_more_menu
+ Show more menu for task item?true
@@ -335,15 +335,15 @@
VyhľadávanieUpravovanieZjednotiť filter so základným filtrom?
- Typ vyhľadávania úloh
- Mód hlavičiek
- Predvolený mód hlavičiek
- Môže byť mód hlavičiek zmenený?
- Povoliť tabuľkový mód pre hlavičky?
- Použiť vlastné predvolené hlavičky?
- Predvolené hlavičky
+ Typ vyhľadávania úloh
+ Mód hlavičiek
+ Predvolený mód hlavičiek
+ Môže byť mód hlavičiek zmenený?
+ Povoliť tabuľkový mód pre hlavičky?
+ Použiť vlastné predvolené hlavičky?
+ Predvolené hlavičkyNapríklad: "meta-title,meta-user"
- Zobrazovať menu pre úlohovú položku?
+ Zobrazovať menu pre úlohovú položku?Nastavenie položkySúčasný filterVšeobecné
@@ -356,20 +356,20 @@
SortierenSuchenBearbeiten
- Kopfzeilenmodus
- Standardkopfzeilenmodus
- Erlaube Änderung des Kopfzeilenmodus?
- Erlaube Tabellenmodus?
- Eigene Kopfzeilen verwenden?
- Anzuzeigende Attributmenge auswählen
+ Kopfzeilenmodus
+ Standardkopfzeilenmodus
+ Erlaube Änderung des Kopfzeilenmodus?
+ Erlaube Tabellenmodus?
+ Eigene Kopfzeilen verwenden?
+ Anzuzeigende Attributmenge auswählenBeispiel: "meta-title,meta-user"Aktueller FilterAllgemeinAktualisiere die Ansicht mit dem ausgewählten FilterEinfacher und erweiterter SuchmodusMit dem Basisfilter kombinieren?
- Suchmodus im Aufgabenansicht
- "Erweiterte Optionen" Taste bei einzelnen Aufgaben anzeigen
+ Suchmodus im Aufgabenansicht
+ "Erweiterte Optionen" Taste bei einzelnen Aufgaben anzeigenMenüeintrageinstellungenFilter entfernen
@@ -422,7 +422,7 @@
4grid
- view_search_type
+ task_view_search_typeeditablerequired
@@ -437,7 +437,7 @@
- show_more_menu
+ task_show_more_menueditable
@@ -642,14 +642,14 @@
3grid
- is_header_mode_changeable
+ task_is_header_mode_changeableeditable
trans: t.this,
- isChangeable: f.is_header_mode_changeable,
- mode: f.headers_mode,
- defaultMode: f.headers_default_mode;
+ isChangeable: f.task_is_header_mode_changeable,
+ mode: f.task_headers_mode,
+ defaultMode: f.task_headers_default_mode;
make [mode, defaultMode], editable on trans when { isChangeable.value }
make [mode, defaultMode], required on trans when { isChangeable.value }
@@ -669,13 +669,13 @@
- use_default_headers
+ use_task_default_headerseditable
trans: t.this,
- use: f.use_default_headers,
- headers: f.default_headers;
+ use: f.use_task_default_headers,
+ headers: f.task_default_headers;
make headers,editable on trans when { use.value }
make headers,visible on trans when { !use.value }
@@ -692,7 +692,7 @@
- default_headers
+ task_default_headerseditable
@@ -707,7 +707,7 @@
- allow_header_table_mode
+ task_allow_header_table_modeeditable
@@ -721,7 +721,7 @@
- headers_mode
+ task_headers_modeeditablerequired
@@ -736,7 +736,7 @@
- headers_default_mode
+ task_headers_default_modeeditablerequired
@@ -827,49 +827,49 @@
- view_search_type
+ task_view_search_typevisible
- show_more_menu
+ task_show_more_menuvisible
- is_header_mode_changeable
+ task_is_header_mode_changeablevisible
- allow_header_table_mode
+ task_allow_header_table_modevisible
- headers_mode
+ task_headers_modevisible
- headers_default_mode
+ task_headers_default_modevisible
- use_default_headers
+ use_task_default_headersvisible
- default_headers
+ task_default_headersvisible
From 08ae9b02f4aa00ff30c195b09b77adbdf8e260d5 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Thu, 14 May 2026 08:36:29 +0200
Subject: [PATCH 13/49] [ETASK-23] Dynamic view configuration - introduce empty
content configuration - fix null node
---
.../engine/menu/domain/MenuItemBody.java | 4 +-
.../domain/configurations/CaseViewBody.java | 8 ++
.../configurations/CaseViewConstants.java | 2 +
.../domain/configurations/TaskViewBody.java | 9 ++
.../configurations/TaskViewConstants.java | 2 +
.../menu/case_view_configuration.xml | 107 +++++++++++++++++-
.../engine-processes/menu/menu_item.xml | 47 ++++----
.../menu/task_view_configuration.xml | 105 ++++++++++++++++-
8 files changed, 250 insertions(+), 34 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java
index 5af588b2fa3..15f73c3e8c0 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java
@@ -160,7 +160,9 @@ public ToDataSetOutcome toDataSet(String parentId, String nodePath, Case viewCas
outcome.putDataSetEntry(MenuItemConstants.FIELD_MENU_NAME, FieldType.I18N, this.menuName);
outcome.putDataSetEntry(MenuItemConstants.FIELD_MENU_ICON, FieldType.TEXT, this.menuIcon);
outcome.putDataSetEntry(MenuItemConstants.FIELD_USE_TABBED_VIEW, FieldType.BOOLEAN, this.useTabbedView);
- outcome.putDataSetEntry(MenuItemConstants.FIELD_TAB_NAME, FieldType.I18N, this.tabName);
+ if (this.tabName != null) {
+ outcome.putDataSetEntry(MenuItemConstants.FIELD_TAB_NAME, FieldType.I18N, this.tabName);
+ }
outcome.putDataSetEntry(MenuItemConstants.FIELD_TAB_ICON, FieldType.TEXT, this.tabIcon);
if (this.identifier != null) {
outcome.putDataSetEntry(MenuItemConstants.FIELD_IDENTIFIER, FieldType.TEXT, this.getIdentifier());
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
index 3bb18185d18..7be6230497a 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
@@ -28,6 +28,8 @@ public class CaseViewBody extends ViewBody {
private List defaultHeaders;
private boolean isHeaderModeChangeable = true;
private boolean useDefaultHeaders = true;
+ private I18nString emptyContentText;
+ private String emptyContentIcon;
private ViewBody chainedView;
@@ -72,6 +74,12 @@ protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) {
this.isHeaderModeChangeable);
outcome.putDataSetEntry(CaseViewConstants.FIELD_USE_DEFAULT_HEADERS, FieldType.BOOLEAN,
this.useDefaultHeaders);
+ if (this.emptyContentText != null) {
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_EMPTY_CONTENT_TEXT, FieldType.I18N,
+ this.emptyContentText);
+ }
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_EMPTY_CONTENT_ICON, FieldType.TEXT,
+ this.emptyContentIcon);
return outcome;
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java
index 99ec5c2f2fa..721cce1b851 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java
@@ -18,4 +18,6 @@ public class CaseViewConstants extends ViewConstants {
public static final String FIELD_HEADERS_DEFAULT_MODE = "case_headers_default_mode";
public static final String FIELD_IS_HEADER_MODE_CHANGEABLE = "case_is_header_mode_changeable";
public static final String FIELD_USE_DEFAULT_HEADERS = "use_case_default_headers";
+ public static final String FIELD_EMPTY_CONTENT_TEXT = "case_empty_content_text";
+ public static final String FIELD_EMPTY_CONTENT_ICON = "case_empty_content_icon";
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java
index 9e572485b3b..7ad90df3469 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java
@@ -2,6 +2,7 @@
import com.netgrif.application.engine.menu.domain.MenuItemView;
import com.netgrif.application.engine.menu.domain.ToDataSetOutcome;
+import com.netgrif.application.engine.petrinet.domain.I18nString;
import com.netgrif.application.engine.petrinet.domain.dataset.FieldType;
import com.netgrif.application.engine.workflow.domain.Case;
import lombok.Data;
@@ -25,6 +26,8 @@ public class TaskViewBody extends ViewBody {
private boolean useDefaultHeaders = true;
private List defaultHeaders;
private boolean showMoreMenu = true;
+ private I18nString emptyContentText;
+ private String emptyContentIcon;
@Override
public ViewBody getAssociatedViewBody() {
@@ -57,6 +60,12 @@ protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) {
this.defaultHeaders != null ? String.join(",", this.defaultHeaders) : null);
outcome.putDataSetEntry(TaskViewConstants.FIELD_SHOW_MORE_MENU, FieldType.BOOLEAN,
this.showMoreMenu);
+ if (this.emptyContentText != null) {
+ outcome.putDataSetEntry(TaskViewConstants.FIELD_EMPTY_CONTENT_TEXT, FieldType.I18N,
+ this.emptyContentText);
+ }
+ outcome.putDataSetEntry(TaskViewConstants.FIELD_EMPTY_CONTENT_ICON, FieldType.TEXT,
+ this.emptyContentIcon);
return outcome;
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java
index 32e9b716f67..771f27726ce 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java
@@ -13,4 +13,6 @@ public class TaskViewConstants extends ViewConstants {
public static final String FIELD_ALLOW_HEADER_TABLE_MODE = "task_allow_header_table_mode";
public static final String FIELD_USE_DEFAULT_HEADERS = "use_task_default_headers";
public static final String FIELD_SHOW_MORE_MENU = "task_show_more_menu";
+ public static final String FIELD_EMPTY_CONTENT_TEXT = "task_empty_content_text";
+ public static final String FIELD_EMPTY_CONTENT_ICON = "task_empty_content_icon";
}
diff --git a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
index a017c3fce7e..f8a30347617 100644
--- a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
@@ -431,6 +431,41 @@
is_tabbed_var_arcIs tabbed
+
+ case_empty_content_text
+ Empty content text
+ If empty, default text will be used
+
+
+ case_empty_content_icon
+ Empty content icon
+ If empty, default icon will be used
+
+ 0
+
+
+ icon: f.this,
+ iconPreview: f.case_empty_content_icon_preview;
+
+ if (icon.value == "") {
+ change iconPreview value {"""]]>}
+ return
+ }
+
+ change iconPreview value {
+ """]]> + icon.value + """]]>
+ }
+
+
+
+
+
+ case_empty_content_icon_preview
+ Empty content icon preview
+
+ htmltextarea
+
+
@@ -543,7 +578,7 @@
view_dataGroup
- 4
+ 3gridcase_view_search_type
@@ -555,7 +590,7 @@
001
- 2
+ 1
material
outline
@@ -566,14 +601,56 @@
editable
- 2
+ 101
- 2
+ 1
+ material
+ outline
+
+
+
+ case_empty_content_text
+
+ editable
+
+
+ 0
+ 1
+ 1
+ 1
+ material
+ outline
+
+
+
+ case_empty_content_icon
+
+ editable
+
+
+ 1
+ 1
+ 1
+ 1
material
outline
+
+ case_empty_content_icon_preview
+
+ visible
+
+
+ 2
+ 1
+ 1
+ 1
+ material
+ standard
+
+ advanced_content_form
@@ -581,9 +658,9 @@
0
- 1
+ 21
- 4
+ 3
material
outline
@@ -1159,6 +1236,24 @@
visible
+
+ case_empty_content_text
+
+ visible
+
+
+
+ case_empty_content_icon
+
+ visible
+
+
+
+ case_empty_content_icon_preview
+
+ visible
+
+ create_case_button_title
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 140c69c6379..8b35a669b23 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -486,6 +486,9 @@
tab_nameName of the itemWill be shown in tab
+
+ new com.netgrif.application.engine.petrinet.domain.I18nString("View", [sk: "Zobrazenie", de: "Die Ansicht"])
+ view_configuration_type
@@ -1444,7 +1447,7 @@
configuration_view
- 4
+ 3griduse_custom_view
@@ -1497,7 +1500,7 @@
101
- 3
+ 2
material
outline
@@ -1531,27 +1534,13 @@
-
- tab_name
-
- editable
-
-
- 2
- 0
- 1
- 1
- material
- outline
-
- use_tab_iconeditable
- 3
+ 2011
@@ -1571,7 +1560,7 @@
- tab_icon
+ tab_nameeditable
@@ -1584,16 +1573,30 @@
outline
+
+ tab_icon
+
+ editable
+
+
+ 1
+ 1
+ 1
+ 1
+ material
+ outline
+
+ tab_icon_previewvisible
- 1
+ 211
- 2
+ 1
material
standard
@@ -1607,7 +1610,7 @@
021
- 4
+ 3
material
outline
@@ -1654,7 +1657,7 @@
031
- 4
+ 3
material
outline
diff --git a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
index c9f25059274..b8298bfa552 100644
--- a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
@@ -323,6 +323,41 @@
+
+ task_empty_content_text
+ Empty content text
+ If empty, default text will be used
+
+
+ task_empty_content_icon
+ Empty content icon
+ If empty, default icon will be used
+
+ 0
+
+
+ icon: f.this,
+ iconPreview: f.task_empty_content_icon_preview;
+
+ if (icon.value == "") {
+ change iconPreview value {"""]]>}
+ return
+ }
+
+ change iconPreview value {
+ """]]> + icon.value + """]]>
+ }
+
+
+
+
+
+ task_empty_content_icon_preview
+ Empty content icon preview
+
+ htmltextarea
+
+
@@ -419,7 +454,7 @@
view_dataGroup
- 4
+ 3gridtask_view_search_type
@@ -431,7 +466,7 @@
001
- 3
+ 1
material
outline
@@ -442,7 +477,7 @@
editable
- 3
+ 1011
@@ -451,7 +486,7 @@
- advanced_content_form
+ task_empty_content_texteditable
@@ -459,7 +494,49 @@
011
- 4
+ 1
+ material
+ outline
+
+
+
+ task_empty_content_icon
+
+ editable
+
+
+ 1
+ 1
+ 1
+ 1
+ material
+ outline
+
+
+
+ task_empty_content_icon_preview
+
+ visible
+
+
+ 2
+ 1
+ 1
+ 1
+ material
+ standard
+
+
+
+ advanced_content_form
+
+ editable
+
+
+ 0
+ 2
+ 1
+ 3
material
outline
@@ -838,6 +915,24 @@
visible
+
+ task_empty_content_text
+
+ visible
+
+
+
+ task_empty_content_icon
+
+ visible
+
+
+
+ task_empty_content_icon_preview
+
+ visible
+
+ task_is_header_mode_changeable
From 8e6f3db68fd7007d1ccbcdd19d9766b4b68a674d Mon Sep 17 00:00:00 2001
From: chvostek
Date: Thu, 14 May 2026 14:58:50 +0200
Subject: [PATCH 14/49] [ETASK-23] Dynamic view configuration - fix test -
update doc
---
.../engine/menu/service/MenuItemService.java | 10 ++++------
.../application/engine/menu/MenuItemServiceTest.java | 7 +++----
2 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
index 27a5f80c6c7..4fc58a05749 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
@@ -402,13 +402,11 @@ public Case removeChildItemFromParent(String folderId, Case childItem) {
}
/**
- * Retrieves menu item data along with its associated view configuration data. The method collects immediate
- * data from the menu item case and recursively traverses through all associated view configuration cases,
- * aggregating their immediate data into a single map.
- * todo 23 doc
+ * Retrieves menu item data groups for the specified case and locale.
+ *
* @param caseId identifier of the menu item case
- * @return map where keys are process identifiers (with "_configuration" suffix removed for view cases) and
- * values are lists of immediate data fields from the corresponding cases
+ * @param locale locale to use for retrieving localized data
+ * @return list of data groups from the menu item case
*/
@Override
public List getMenuItemData(String caseId, Locale locale) {
diff --git a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
index c1df0ebdf78..a6c4d428352 100644
--- a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
+++ b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
@@ -6,11 +6,11 @@
import com.netgrif.application.engine.menu.domain.configurations.CaseViewBody;
import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody;
import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService;
+import com.netgrif.application.engine.petrinet.domain.DataGroup;
import com.netgrif.application.engine.petrinet.domain.I18nString;
import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException;
import com.netgrif.application.engine.startup.SuperCreator;
import com.netgrif.application.engine.workflow.domain.Case;
-import com.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes.GetDataGroupsEventOutcome;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -113,15 +113,14 @@ public void removeChildItemFromParentTest() {
@Test
public void getMenuItemDataTest() throws TransitionNotExecutableException {
- // todo 23 fix test
assertThrows(IllegalArgumentException.class, () -> menuItemService.getMenuItemData("wrongCaseId", Locale.getDefault()));
Case menuItemCase = createDefaultMenuItem("my_menu_item",
new I18nString("This is name", Map.of("sk", "Toto je nazov")));
login();
- GetDataGroupsEventOutcome result = menuItemService.getMenuItemData(menuItemCase.getStringId(), Locale.getDefault());
- assertTrue(result != null);
+ List result = menuItemService.getMenuItemData(menuItemCase.getStringId(), Locale.getDefault());
+ assertTrue(result != null && !result.isEmpty());
}
@Test
From bfc90b15cd4824306aa1eaa9b765b7d3fa6441a6 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Fri, 15 May 2026 14:33:05 +0200
Subject: [PATCH 15/49] [ETASK-23] Dynamic view configuration - begin updating
filter
---
.../petriNets/engine-processes/filter.xml | 14 ++--
.../menu/case_view_configuration.xml | 81 +++++++++----------
2 files changed, 44 insertions(+), 51 deletions(-)
diff --git a/src/main/resources/petriNets/engine-processes/filter.xml b/src/main/resources/petriNets/engine-processes/filter.xml
index 0be99e1940f..3e721c26fc5 100644
--- a/src/main/resources/petriNets/engine-processes/filter.xml
+++ b/src/main/resources/petriNets/engine-processes/filter.xml
@@ -121,27 +121,29 @@
i18n_filter_name
-
+ filter_case_id
-
+ menu_identifier
-
+ and_me
+ ANDand_view
+ ANDtaskref_and_parent
-
+ trimmed_origin_view_id
@@ -172,7 +174,7 @@
missing_nets_translation
-
+
@@ -181,7 +183,7 @@
my_full_filter
-
+ allowed_nets
diff --git a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
index f8a30347617..b93951a6913 100644
--- a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
@@ -224,6 +224,39 @@
filter
+
+ available_filter_cases
+
+
+ caseref
+ meta-title,meta-author,filter-visibility
+ filter
+ true
+ true
+ true
+ { "query": "processIdentifier:filter AND (author:<<me>> OR dataSet.visibility.keyValue:public) AND dataSet.filter_type.keyValue:Case" }
+
+
+ 0
+
+
+ available_filter_cases: f.this,
+ current_filter_preview: f.current_filter_preview;
+
+ if (available_filter_cases.value == null || available_filter_cases.value == "") {
+ change current_filter_preview value { [] }
+ return
+ }
+ change current_filter_preview value {
+ return [findTask({it.caseId.eq(available_filter_cases.value).and(it.transitionId.eq("view_filter"))}).stringId]
+ }
+
+
+
+
+ filter
+
+
@@ -699,7 +732,7 @@
4grid
- filter_autocomplete_selection
+ available_filter_caseseditable
@@ -707,48 +740,6 @@
001
- 3
- material
- outline
-
-
-
- update_filter
-
- visible
-
-
- 3
- 0
- 1
- 1
- material
- standard
-
-
-
- selected_filter_preview
-
- visible
-
-
- 0
- 1
- 1
- 4
- material
- standard
-
-
-
- filter_header
-
- visible
-
-
- 0
- 2
- 14
material
outline
@@ -761,7 +752,7 @@
0
- 3
+ 114
material
@@ -1189,7 +1180,7 @@
starttrue
- filter_autocomplete_selection
+ available_filter_casesvisible
From dfea0b87e42feb030c2e8badec1db1c2dbb52a0d Mon Sep 17 00:00:00 2001
From: chvostek
Date: Fri, 15 May 2026 15:58:10 +0200
Subject: [PATCH 16/49] [ETASK-23] Dynamic view configuration - introduce menu
item configuration templates
---
.../engine/menu/domain/templates/README.md | 48 +++++++++++++++++
.../templates/SimpleCaseViewTemplate.java | 47 +++++++++++++++++
.../templates/SimpleTaskViewTemplate.java | 47 +++++++++++++++++
.../templates/SingleTaskViewTemplate.java | 48 +++++++++++++++++
.../templates/TabbedCaseViewTemplate.java | 52 +++++++++++++++++++
.../templates/TabbedTaskViewTemplate.java | 47 +++++++++++++++++
.../templates/TabbedTicketViewTemplate.java | 47 +++++++++++++++++
.../menu/domain/templates/Template.java | 10 ++++
.../engine/menu/service/MenuItemService.java | 6 +++
.../menu/service/MenuItemTemplateHolder.java | 52 +++++++++++++++++++
.../service/interfaces/IMenuItemService.java | 1 +
.../engine-processes/menu/menu_item.xml | 30 +++++++++++
12 files changed, 435 insertions(+)
create mode 100644 src/main/java/com/netgrif/application/engine/menu/domain/templates/README.md
create mode 100644 src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java
create mode 100644 src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java
create mode 100644 src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java
create mode 100644 src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java
create mode 100644 src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java
create mode 100644 src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java
create mode 100644 src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java
create mode 100644 src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/README.md b/src/main/java/com/netgrif/application/engine/menu/domain/templates/README.md
new file mode 100644
index 00000000000..7dd8af166fa
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/README.md
@@ -0,0 +1,48 @@
+# Menu Item Template Creation Guide
+
+## Overview
+
+Templates provide predefined configurations for menu items that can be reused throughout the application. Each template
+encapsulates a complete menu item structure with its associated views and filters.
+
+## Template Structure
+
+A template must implement the `Template` interface with three core components:
+
+- **Identifier**: A unique string constant that identifies the template
+- **Name**: An internationalized string (I18nString) providing the display name in multiple languages
+- **Template Body**: A MenuItemBody instance containing the complete menu item configuration
+
+## Creation Steps
+
+1. **Create Template Class**
+ - Implement the `Template` interface
+ - Define a unique public static IDENTIFIER constant
+ - Create a private static I18nString NAME with translations
+ - Declare a private static MenuItemBody TEMPLATE field
+
+2. **Build Template Configuration**
+ - Implement a private static `buildTemplate()` method
+ - Instantiate MenuItemBody and configure its properties
+ - Create and configure the appropriate view body (CaseViewBody, TaskViewBody, ...)
+ - Set up FilterBody for data filtering requirements
+ - Configure chained views if needed (e.g., TaskViewBody within CaseViewBody)
+ - Wire all components together through setter methods
+
+3. **Implement Interface Methods**
+ - Return IDENTIFIER from `getIdentifier()`
+ - Return NAME from `getName()`
+ - Return TEMPLATE from `getTemplate()`
+
+4. **Register Template**
+ - Add the template to the `MenuItemTemplateHolder.templates` map
+ - Use the template's IDENTIFIER as the key
+ - Instantiate the template class as the value
+
+## Best Practices
+
+- Keep identifiers lowercase with underscores for consistency
+- Provide translations for all supported languages in the NAME field
+- Build template configuration statically to ensure immutability
+- Ensure all required view and filter configurations are properly initialized
+- Use descriptive identifiers that reflect the template's purpose
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java
new file mode 100644
index 00000000000..1fc40d4e94f
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java
@@ -0,0 +1,47 @@
+package com.netgrif.application.engine.menu.domain.templates;
+
+import com.netgrif.application.engine.menu.domain.FilterBody;
+import com.netgrif.application.engine.menu.domain.MenuItemBody;
+import com.netgrif.application.engine.menu.domain.configurations.CaseViewBody;
+import com.netgrif.application.engine.petrinet.domain.I18nString;
+
+import java.util.Map;
+
+public class SimpleCaseViewTemplate implements Template {
+
+ public static final String IDENTIFIER = "simple_case_view";
+
+ private static final I18nString NAME = new I18nString("Simple case view",
+ Map.of("sk", "", "de", "")); // todo 23 translate
+
+ private static final MenuItemBody TEMPLATE = buildTemplate();
+
+ private static MenuItemBody buildTemplate() {
+ MenuItemBody menuItemBody = new MenuItemBody();
+ // todo 23 menu item body data
+ // is tabbed
+
+ CaseViewBody caseViewBody = new CaseViewBody();
+ // todo 23 task view body data
+
+ FilterBody filterBody = new FilterBody();
+ // todo 23 filter body data
+
+ caseViewBody.setFilterBody(filterBody);
+ menuItemBody.setView(caseViewBody);
+
+ return menuItemBody;
+ }
+
+ public String getIdentifier() {
+ return IDENTIFIER;
+ }
+
+ public I18nString getName() {
+ return NAME;
+ }
+
+ public MenuItemBody getTemplate() {
+ return TEMPLATE;
+ }
+}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java
new file mode 100644
index 00000000000..98df69c9bfb
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java
@@ -0,0 +1,47 @@
+package com.netgrif.application.engine.menu.domain.templates;
+
+import com.netgrif.application.engine.menu.domain.FilterBody;
+import com.netgrif.application.engine.menu.domain.MenuItemBody;
+import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody;
+import com.netgrif.application.engine.petrinet.domain.I18nString;
+
+import java.util.Map;
+
+public class SimpleTaskViewTemplate implements Template {
+
+ public static final String IDENTIFIER = "simple_task_view";
+
+ private static final I18nString NAME = new I18nString("Simple task view",
+ Map.of("sk", "", "de", "")); // todo 23 translate
+
+ private static final MenuItemBody TEMPLATE = buildTemplate();
+
+ private static MenuItemBody buildTemplate() {
+ MenuItemBody menuItemBody = new MenuItemBody();
+ // todo 23 menu item body data
+ // is tabbed
+
+ TaskViewBody taskViewBody = new TaskViewBody();
+ // todo 23 task view body data
+
+ FilterBody filterBody = new FilterBody();
+ // todo 23 filter body data
+
+ taskViewBody.setFilterBody(filterBody);
+ menuItemBody.setView(taskViewBody);
+
+ return menuItemBody;
+ }
+
+ public String getIdentifier() {
+ return IDENTIFIER;
+ }
+
+ public I18nString getName() {
+ return NAME;
+ }
+
+ public MenuItemBody getTemplate() {
+ return TEMPLATE;
+ }
+}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java
new file mode 100644
index 00000000000..893b505b740
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java
@@ -0,0 +1,48 @@
+package com.netgrif.application.engine.menu.domain.templates;
+
+import com.netgrif.application.engine.menu.domain.FilterBody;
+import com.netgrif.application.engine.menu.domain.MenuItemBody;
+import com.netgrif.application.engine.menu.domain.configurations.SingleTaskViewBody;
+import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody;
+import com.netgrif.application.engine.petrinet.domain.I18nString;
+
+import java.util.Map;
+
+public class SingleTaskViewTemplate implements Template {
+
+ public static final String IDENTIFIER = "single_task_view";
+
+ private static final I18nString NAME = new I18nString("Single task view",
+ Map.of("sk", "", "de", "")); // todo 23 translate
+
+ private static final MenuItemBody TEMPLATE = buildTemplate();
+
+ private static MenuItemBody buildTemplate() {
+ MenuItemBody menuItemBody = new MenuItemBody();
+ // todo 23 menu item body data
+ // is tabbed
+
+ SingleTaskViewBody singleTaskViewBody = new SingleTaskViewBody();
+ // todo 23 task view body data
+
+ FilterBody filterBody = new FilterBody();
+ // todo 23 filter body data
+
+ singleTaskViewBody.setFilterBody(filterBody);
+ menuItemBody.setView(singleTaskViewBody);
+
+ return menuItemBody;
+ }
+
+ public String getIdentifier() {
+ return IDENTIFIER;
+ }
+
+ public I18nString getName() {
+ return NAME;
+ }
+
+ public MenuItemBody getTemplate() {
+ return TEMPLATE;
+ }
+}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java
new file mode 100644
index 00000000000..88656826c20
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java
@@ -0,0 +1,52 @@
+package com.netgrif.application.engine.menu.domain.templates;
+
+import com.netgrif.application.engine.menu.domain.FilterBody;
+import com.netgrif.application.engine.menu.domain.MenuItemBody;
+import com.netgrif.application.engine.menu.domain.configurations.CaseViewBody;
+import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody;
+import com.netgrif.application.engine.petrinet.domain.I18nString;
+
+import java.util.Map;
+
+public class TabbedCaseViewTemplate implements Template {
+
+ public static final String IDENTIFIER = "tabbed_case_view";
+
+ private static final I18nString NAME = new I18nString("Tabbed case view",
+ Map.of("sk", "", "de", "")); // todo 23 translate
+
+ private static final MenuItemBody TEMPLATE = buildTemplate();
+
+ private static MenuItemBody buildTemplate() {
+ MenuItemBody menuItemBody = new MenuItemBody();
+ // todo 23 menu item body data
+ // is tabbed
+
+ CaseViewBody caseViewBody = new CaseViewBody();
+ // todo 23 case view body data
+
+ FilterBody filterBody = new FilterBody();
+ // todo 23 filter body data
+
+ TaskViewBody taskViewBody = new TaskViewBody();
+ // todo 23 task view body data
+
+ caseViewBody.setFilterBody(filterBody);
+ caseViewBody.setChainedView(taskViewBody);
+ menuItemBody.setView(caseViewBody);
+
+ return menuItemBody;
+ }
+
+ public String getIdentifier() {
+ return IDENTIFIER;
+ }
+
+ public I18nString getName() {
+ return NAME;
+ }
+
+ public MenuItemBody getTemplate() {
+ return TEMPLATE;
+ }
+}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java
new file mode 100644
index 00000000000..38be03aaae9
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java
@@ -0,0 +1,47 @@
+package com.netgrif.application.engine.menu.domain.templates;
+
+import com.netgrif.application.engine.menu.domain.FilterBody;
+import com.netgrif.application.engine.menu.domain.MenuItemBody;
+import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody;
+import com.netgrif.application.engine.petrinet.domain.I18nString;
+
+import java.util.Map;
+
+public class TabbedTaskViewTemplate implements Template {
+
+ public static final String IDENTIFIER = "tabbed_task_view";
+
+ private static final I18nString NAME = new I18nString("Tabbed task view",
+ Map.of("sk", "", "de", "")); // todo 23 translate
+
+ private static final MenuItemBody TEMPLATE = buildTemplate();
+
+ private static MenuItemBody buildTemplate() {
+ MenuItemBody menuItemBody = new MenuItemBody();
+ // todo 23 menu item body data
+ // is tabbed
+
+ TaskViewBody taskViewBody = new TaskViewBody();
+ // todo 23 task view body data
+
+ FilterBody filterBody = new FilterBody();
+ // todo 23 filter body data
+
+ taskViewBody.setFilterBody(filterBody);
+ menuItemBody.setView(taskViewBody);
+
+ return menuItemBody;
+ }
+
+ public String getIdentifier() {
+ return IDENTIFIER;
+ }
+
+ public I18nString getName() {
+ return NAME;
+ }
+
+ public MenuItemBody getTemplate() {
+ return TEMPLATE;
+ }
+}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java
new file mode 100644
index 00000000000..3b1eb03e775
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java
@@ -0,0 +1,47 @@
+package com.netgrif.application.engine.menu.domain.templates;
+
+import com.netgrif.application.engine.menu.domain.MenuItemBody;
+import com.netgrif.application.engine.menu.domain.configurations.SingleTaskViewBody;
+import com.netgrif.application.engine.menu.domain.configurations.TabbedTicketViewBody;
+import com.netgrif.application.engine.petrinet.domain.I18nString;
+
+import java.util.Map;
+
+public class TabbedTicketViewTemplate implements Template {
+
+ public static final String IDENTIFIER = "tabbed_ticket_view";
+
+ private static final I18nString NAME = new I18nString("Tabbed ticket view",
+ Map.of("sk", "", "de", "")); // todo 23 translate
+
+ private static final MenuItemBody TEMPLATE = buildTemplate();
+
+ private static MenuItemBody buildTemplate() {
+ MenuItemBody menuItemBody = new MenuItemBody();
+ // todo 23 menu item body data
+ // is tabbed
+
+ TabbedTicketViewBody tabbedTicketViewBody = new TabbedTicketViewBody();
+ // todo 23 case view body data
+
+ SingleTaskViewBody singleTaskViewBody = new SingleTaskViewBody();
+ // todo 23 task view body data
+
+ tabbedTicketViewBody.setChainedView(singleTaskViewBody);
+ menuItemBody.setView(tabbedTicketViewBody);
+
+ return menuItemBody;
+ }
+
+ public String getIdentifier() {
+ return IDENTIFIER;
+ }
+
+ public I18nString getName() {
+ return NAME;
+ }
+
+ public MenuItemBody getTemplate() {
+ return TEMPLATE;
+ }
+}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java
new file mode 100644
index 00000000000..ecbc61f3ae3
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java
@@ -0,0 +1,10 @@
+package com.netgrif.application.engine.menu.domain.templates;
+
+import com.netgrif.application.engine.menu.domain.MenuItemBody;
+import com.netgrif.application.engine.petrinet.domain.I18nString;
+
+public interface Template {
+ String getIdentifier();
+ I18nString getName();
+ MenuItemBody getTemplate();
+}
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
index 4fc58a05749..acf42be17d8 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
@@ -415,6 +415,12 @@ public List getMenuItemData(String caseId, Locale locale) {
return dataService.getDataGroups(taskId, locale).getData();
}
+ // todo 23 doc
+ @Override
+ public void handleConfigurationTemplate(Case menuItemCase) {
+ // todo 23
+ }
+
protected Case findCase(String processIdentifier, String query) {
CaseSearchRequest request = CaseSearchRequest.builder()
.process(Collections.singletonList(new CaseSearchRequest.PetriNet(processIdentifier)))
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java
new file mode 100644
index 00000000000..10070526211
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java
@@ -0,0 +1,52 @@
+package com.netgrif.application.engine.menu.service;
+
+import com.netgrif.application.engine.menu.domain.templates.*;
+import com.netgrif.application.engine.petrinet.domain.I18nString;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Holder class that manages predefined menu item templates.
+ *
+ * This class provides a central registry for menu item templates that can be used
+ * throughout the application. Templates are identified by unique string identifiers
+ * and can be retrieved or transformed into user-selectable options.
+ *
+ */
+public class MenuItemTemplateHolder {
+ /**
+ * Map of available menu item templates indexed by their unique identifiers.
+ *
+ * Each entry maps a template identifier to its corresponding Template instance.
+ * This map is immutable and initialized with predefined templates.
+ * The key is the template identifier (String), and the value is the Template instance.
+ *
+ */
+ public static Map templates = Map.of(
+ TabbedCaseViewTemplate.IDENTIFIER, new TabbedCaseViewTemplate(),
+ TabbedTaskViewTemplate.IDENTIFIER, new TabbedTaskViewTemplate(),
+ SimpleCaseViewTemplate.IDENTIFIER, new SimpleCaseViewTemplate(),
+ SimpleTaskViewTemplate.IDENTIFIER, new SimpleTaskViewTemplate(),
+ SingleTaskViewTemplate.IDENTIFIER, new SingleTaskViewTemplate(),
+ TabbedTicketViewTemplate.IDENTIFIER, new TabbedTicketViewTemplate()
+ );
+
+ /**
+ * Transforms the available templates into a map of selectable options.
+ *
+ * This method converts the templates map into a format suitable for displaying
+ * as user-selectable options, where the key is the template identifier and the
+ * value is the internationalized name of the template.
+ *
+ *
+ * @return a map where keys are template identifiers and values are internationalized template names
+ */
+ public static Map transformToOptions() {
+ return templates.entrySet().stream()
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> entry.getValue().getName()
+ ));
+ }
+}
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java
index 90cd699331b..5f0ef140ddf 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java
@@ -31,6 +31,7 @@ public interface IMenuItemService {
Case duplicateItem(Case originItem, I18nString newTitle, String newIdentifier) throws TransitionNotExecutableException;
Case removeChildItemFromParent(String folderId, Case childItem);
List getMenuItemData(String caseId, Locale locale);
+ void handleConfigurationTemplate(Case menuItemCase);
/**
* Gets all tabbed or non-tabbed views
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 8b35a669b23..01ebb9eae98 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -630,6 +630,13 @@
+
+ configuration_templates
+ Pick a configuration template
+
+ com.netgrif.application.engine.menu.service.MenuItemTemplateHolder.transformToOptions()
+
+
@@ -812,6 +819,20 @@
outline
+
+ configuration_templates
+
+ editable
+
+
+ 0
+ 2
+ 1
+ 4
+ material
+ outline
+
+ finish
@@ -827,6 +848,15 @@
changeMenuItem useCase uri { newUri }
change name value { new com.netgrif.application.engine.petrinet.domain.I18nString(identifier.value) }
+
+ configuration_templates: f.configuration_templates;
+
+ if (configuration_templates.value == null || configuration_templates.value == "") {
+ return
+ }
+
+ menuItemService.handleConfigurationTemplate(configuration_templates.value)
+ Create
From 0d40b4b5689bbe3452d8a0be5191c2ec8c667b76 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Mon, 18 May 2026 11:33:44 +0200
Subject: [PATCH 17/49] [ETASK-23] Dynamic view configuration - finish
implementation of template configurations
---
.../domain/ConfigurationTemplateOutcome.java | 20 +++++++++
.../engine/menu/domain/MenuItemBody.java | 32 ++++++++++++--
.../engine/menu/domain/MenuItemConstants.java | 1 +
.../templates/SimpleCaseViewTemplate.java | 11 +----
.../templates/SimpleTaskViewTemplate.java | 11 +----
.../templates/SingleTaskViewTemplate.java | 12 +----
.../templates/TabbedCaseViewTemplate.java | 17 ++-----
.../templates/TabbedTaskViewTemplate.java | 12 ++---
.../templates/TabbedTicketViewTemplate.java | 7 +--
.../menu/domain/templates/Template.java | 25 +++++++++++
.../engine/menu/service/MenuItemService.java | 44 ++++++++++++++++---
.../menu/service/MenuItemTemplateHolder.java | 18 +++++++-
.../service/interfaces/IMenuItemService.java | 3 +-
.../engine-processes/menu/menu_item.xml | 9 ++--
14 files changed, 148 insertions(+), 74 deletions(-)
create mode 100644 src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java b/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java
new file mode 100644
index 00000000000..e31d6bbdbea
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java
@@ -0,0 +1,20 @@
+package com.netgrif.application.engine.menu.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ConfigurationTemplateOutcome {
+ // todo 23 doc
+ public final Map mapping;
+
+ public ConfigurationTemplateOutcome() {
+ this.mapping = new HashMap<>();
+ }
+
+ public ConfigurationTemplateOutcome(ToDataSetOutcome toDataSetOutcome) {
+ this();
+ toDataSetOutcome.getDataSet()
+ .forEach((fieldId, fieldMap) -> this.mapping.put(fieldId, fieldMap.get("value")));
+ }
+
+}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java
index 15f73c3e8c0..bb19500b411 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java
@@ -2,6 +2,7 @@
import com.netgrif.application.engine.menu.domain.configurations.ViewBody;
import com.netgrif.application.engine.menu.domain.configurations.ViewConstants;
+import com.netgrif.application.engine.menu.domain.templates.Template;
import com.netgrif.application.engine.menu.utils.MenuItemUtils;
import com.netgrif.application.engine.petrinet.domain.I18nString;
import com.netgrif.application.engine.petrinet.domain.dataset.FieldType;
@@ -31,7 +32,7 @@ public class MenuItemBody {
private String customViewSelector;
private boolean isAutoSelect = false;
- private boolean useTabbedView;
+ private boolean useTabbedView = true;
private String tabIcon;
private boolean useTabIcon = true;
private I18nString tabName;
@@ -108,8 +109,14 @@ public void setTabName(String name) {
}
public void setView(ViewBody viewBody) {
- this.view = viewBody;
- this.useTabbedView = viewBody == null || viewBody.getViewType().isTabbed();
+ if (viewBody != null) {
+ this.view = viewBody;
+ MenuItemView viewType = viewBody.getViewType();
+ if (viewType.isTabbed() != viewType.isUntabbed()) {
+ // if isTabbed == isUntabbed we cannot determine the result value
+ this.useTabbedView = viewType.isTabbed();
+ }
+ }
}
/**
@@ -176,6 +183,24 @@ public ToDataSetOutcome toDataSet(String parentId, String nodePath, Case viewCas
outcome.putDataSetEntryOptions(MenuItemConstants.FIELD_ALLOWED_ROLES, FieldType.MULTICHOICE_MAP, this.allowedRoles);
outcome.putDataSetEntryOptions(MenuItemConstants.FIELD_BANNED_ROLES, FieldType.MULTICHOICE_MAP, this.bannedRoles);
+ outcome = toDataSetWithView(viewCase, outcome);
+
+ return outcome;
+ }
+
+ /**
+ * Transforms minimal attributes into dataSet for view configuration by template.
+ *
+ * @param viewCase case instance of view
+ * @return {@link ToDataSetOutcome} object with dataSet containing only view-related fields
+ */
+ public ToDataSetOutcome toDataSetByConfigTemplate(Case viewCase) {
+ ToDataSetOutcome outcome = new ToDataSetOutcome();
+ outcome.putDataSetEntry(MenuItemConstants.FIELD_USE_TABBED_VIEW, FieldType.BOOLEAN, this.useTabbedView);
+ return toDataSetWithView(viewCase, outcome);
+ }
+
+ protected ToDataSetOutcome toDataSetWithView(Case viewCase, ToDataSetOutcome outcome) {
if (viewCase != null) {
outcome.putDataSetEntry(MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE, FieldType.ENUMERATION_MAP,
this.view.getViewType().getIdentifier());
@@ -186,7 +211,6 @@ public ToDataSetOutcome toDataSet(String parentId, String nodePath, Case viewCas
String allDataTaskId = MenuItemUtils.findTaskIdInCase(viewCase, ViewConstants.TRANS_ALL_MENU_DATA_ID);
outcome.putDataSetEntry(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM, FieldType.TASK_REF, List.of(allDataTaskId));
}
-
return outcome;
}
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
index 320c0f08ebf..a8405551449 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
@@ -29,6 +29,7 @@ public class MenuItemConstants {
public static final String FIELD_VIEW_CONFIGURATION_ID = "view_configuration_id";
public static final String FIELD_VIEW_CONFIGURATION_FORM = "view_configuration_form";
public static final String FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM = "view_configuration_all_data_form";
+ public static final String FIELD_CONFIGURATION_TEMPLATES = "configuration_templates";
public static final String TRANS_SETTINGS_ID = "item_settings";
public static final String TRANS_INIT_ID = "system_initialize";
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java
index 1fc40d4e94f..a23b243f7eb 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java
@@ -1,6 +1,5 @@
package com.netgrif.application.engine.menu.domain.templates;
-import com.netgrif.application.engine.menu.domain.FilterBody;
import com.netgrif.application.engine.menu.domain.MenuItemBody;
import com.netgrif.application.engine.menu.domain.configurations.CaseViewBody;
import com.netgrif.application.engine.petrinet.domain.I18nString;
@@ -18,16 +17,10 @@ public class SimpleCaseViewTemplate implements Template {
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
- // todo 23 menu item body data
- // is tabbed
+ menuItemBody.setUseTabbedView(false);
CaseViewBody caseViewBody = new CaseViewBody();
- // todo 23 task view body data
-
- FilterBody filterBody = new FilterBody();
- // todo 23 filter body data
-
- caseViewBody.setFilterBody(filterBody);
+ caseViewBody.setFilterBody(Template.defaultCaseFilterBody(NAME));
menuItemBody.setView(caseViewBody);
return menuItemBody;
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java
index 98df69c9bfb..aa4815c2f6e 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java
@@ -1,6 +1,5 @@
package com.netgrif.application.engine.menu.domain.templates;
-import com.netgrif.application.engine.menu.domain.FilterBody;
import com.netgrif.application.engine.menu.domain.MenuItemBody;
import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody;
import com.netgrif.application.engine.petrinet.domain.I18nString;
@@ -18,16 +17,10 @@ public class SimpleTaskViewTemplate implements Template {
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
- // todo 23 menu item body data
- // is tabbed
+ menuItemBody.setUseTabbedView(false);
TaskViewBody taskViewBody = new TaskViewBody();
- // todo 23 task view body data
-
- FilterBody filterBody = new FilterBody();
- // todo 23 filter body data
-
- taskViewBody.setFilterBody(filterBody);
+ taskViewBody.setFilterBody(Template.defaultTaskFilterBody(NAME));
menuItemBody.setView(taskViewBody);
return menuItemBody;
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java
index 893b505b740..98ea5f60190 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java
@@ -1,9 +1,7 @@
package com.netgrif.application.engine.menu.domain.templates;
-import com.netgrif.application.engine.menu.domain.FilterBody;
import com.netgrif.application.engine.menu.domain.MenuItemBody;
import com.netgrif.application.engine.menu.domain.configurations.SingleTaskViewBody;
-import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody;
import com.netgrif.application.engine.petrinet.domain.I18nString;
import java.util.Map;
@@ -19,16 +17,10 @@ public class SingleTaskViewTemplate implements Template {
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
- // todo 23 menu item body data
- // is tabbed
+ menuItemBody.setUseTabbedView(false);
SingleTaskViewBody singleTaskViewBody = new SingleTaskViewBody();
- // todo 23 task view body data
-
- FilterBody filterBody = new FilterBody();
- // todo 23 filter body data
-
- singleTaskViewBody.setFilterBody(filterBody);
+ singleTaskViewBody.setFilterBody(Template.defaultTaskFilterBody(NAME));
menuItemBody.setView(singleTaskViewBody);
return menuItemBody;
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java
index 88656826c20..609384ad046 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java
@@ -1,6 +1,5 @@
package com.netgrif.application.engine.menu.domain.templates;
-import com.netgrif.application.engine.menu.domain.FilterBody;
import com.netgrif.application.engine.menu.domain.MenuItemBody;
import com.netgrif.application.engine.menu.domain.configurations.CaseViewBody;
import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody;
@@ -13,26 +12,18 @@ public class TabbedCaseViewTemplate implements Template {
public static final String IDENTIFIER = "tabbed_case_view";
private static final I18nString NAME = new I18nString("Tabbed case view",
- Map.of("sk", "", "de", "")); // todo 23 translate
+ Map.of("sk", "Zobrazenie prípadov v záložkách", "de", "Registerkartenansicht für Fälle"));
private static final MenuItemBody TEMPLATE = buildTemplate();
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
- // todo 23 menu item body data
- // is tabbed
+ menuItemBody.setUseTabbedView(true);
CaseViewBody caseViewBody = new CaseViewBody();
- // todo 23 case view body data
- FilterBody filterBody = new FilterBody();
- // todo 23 filter body data
-
- TaskViewBody taskViewBody = new TaskViewBody();
- // todo 23 task view body data
-
- caseViewBody.setFilterBody(filterBody);
- caseViewBody.setChainedView(taskViewBody);
+ caseViewBody.setFilterBody(Template.defaultCaseFilterBody(NAME));
+ caseViewBody.setChainedView(new TaskViewBody());
menuItemBody.setView(caseViewBody);
return menuItemBody;
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java
index 38be03aaae9..47877bf8c1c 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java
@@ -1,6 +1,5 @@
package com.netgrif.application.engine.menu.domain.templates;
-import com.netgrif.application.engine.menu.domain.FilterBody;
import com.netgrif.application.engine.menu.domain.MenuItemBody;
import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody;
import com.netgrif.application.engine.petrinet.domain.I18nString;
@@ -12,22 +11,17 @@ public class TabbedTaskViewTemplate implements Template {
public static final String IDENTIFIER = "tabbed_task_view";
private static final I18nString NAME = new I18nString("Tabbed task view",
- Map.of("sk", "", "de", "")); // todo 23 translate
+ Map.of("sk", "Zobrazenie úloh v záložkách", "de", "Aufgabenansicht in Registerkarten"));
private static final MenuItemBody TEMPLATE = buildTemplate();
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
- // todo 23 menu item body data
- // is tabbed
+ menuItemBody.setUseTabbedView(true);
TaskViewBody taskViewBody = new TaskViewBody();
- // todo 23 task view body data
- FilterBody filterBody = new FilterBody();
- // todo 23 filter body data
-
- taskViewBody.setFilterBody(filterBody);
+ taskViewBody.setFilterBody(Template.defaultTaskFilterBody(NAME));
menuItemBody.setView(taskViewBody);
return menuItemBody;
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java
index 3b1eb03e775..347b0d96cdb 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java
@@ -18,15 +18,12 @@ public class TabbedTicketViewTemplate implements Template {
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
- // todo 23 menu item body data
- // is tabbed
+ menuItemBody.setUseTabbedView(true);
TabbedTicketViewBody tabbedTicketViewBody = new TabbedTicketViewBody();
- // todo 23 case view body data
SingleTaskViewBody singleTaskViewBody = new SingleTaskViewBody();
- // todo 23 task view body data
-
+ singleTaskViewBody.setFilterBody(Template.defaultTaskFilterBody(NAME));
tabbedTicketViewBody.setChainedView(singleTaskViewBody);
menuItemBody.setView(tabbedTicketViewBody);
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java
index ecbc61f3ae3..c6c2a462842 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java
@@ -1,10 +1,35 @@
package com.netgrif.application.engine.menu.domain.templates;
+import com.netgrif.application.engine.menu.domain.FilterBody;
import com.netgrif.application.engine.menu.domain.MenuItemBody;
import com.netgrif.application.engine.petrinet.domain.I18nString;
+import java.util.List;
+
public interface Template {
String getIdentifier();
I18nString getName();
MenuItemBody getTemplate();
+
+ static FilterBody defaultTaskFilterBody(I18nString name) {
+ FilterBody filterBody = new FilterBody();
+ filterBody.setIcon("filter");
+ filterBody.setType("Task");
+ filterBody.setVisibility("private");
+ filterBody.setTitle(name);
+ filterBody.setQuery("*");
+ filterBody.setAllowedNets(List.of());
+ return filterBody;
+ }
+
+ static FilterBody defaultCaseFilterBody(I18nString name) {
+ FilterBody filterBody = new FilterBody();
+ filterBody.setIcon("filter");
+ filterBody.setType("Case");
+ filterBody.setVisibility("private");
+ filterBody.setTitle(name);
+ filterBody.setQuery("*");
+ filterBody.setAllowedNets(List.of());
+ return filterBody;
+ }
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
index acf42be17d8..f7626296cfd 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
@@ -5,12 +5,10 @@
import com.netgrif.application.engine.auth.service.interfaces.IUserService;
import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService;
import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest;
-import com.netgrif.application.engine.menu.domain.FilterBody;
-import com.netgrif.application.engine.menu.domain.MenuItemBody;
-import com.netgrif.application.engine.menu.domain.MenuItemConstants;
-import com.netgrif.application.engine.menu.domain.ToDataSetOutcome;
+import com.netgrif.application.engine.menu.domain.*;
import com.netgrif.application.engine.menu.domain.configurations.ViewBody;
import com.netgrif.application.engine.menu.domain.configurations.ViewConstants;
+import com.netgrif.application.engine.menu.domain.templates.Template;
import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService;
import com.netgrif.application.engine.menu.utils.MenuItemUtils;
import com.netgrif.application.engine.petrinet.domain.DataGroup;
@@ -415,10 +413,42 @@ public List getMenuItemData(String caseId, Locale locale) {
return dataService.getDataGroups(taskId, locale).getData();
}
- // todo 23 doc
+ /**
+ * Handles the application of a configuration template to a menu item case.
+ *
+ * This method retrieves the selected configuration template from the menu item case,
+ * loads the corresponding template definition, and applies it by creating or updating
+ * the associated view configuration. If no template is selected, the method returns
+ * without making any changes.
+ *
+ *
+ * @param menuItemCase the menu item case to which the configuration template should be applied
+ * @return a ConfigurationTemplateOutcome containing the dataSet outcome from applying the template,
+ * or an empty outcome if no template was selected
+ * @throws TransitionNotExecutableException if the workflow transition required for applying the template configuration cannot be executed
+ * @throws IllegalArgumentException if the selected template identifier does not correspond to any registered template
+ */
@Override
- public void handleConfigurationTemplate(Case menuItemCase) {
- // todo 23
+ public ConfigurationTemplateOutcome handleConfigurationTemplate(Case menuItemCase) throws TransitionNotExecutableException {
+ String selectedTemplate = (String) menuItemCase.getFieldValue(MenuItemConstants.FIELD_CONFIGURATION_TEMPLATES);
+ if (selectedTemplate == null || selectedTemplate.isEmpty()) {
+ return new ConfigurationTemplateOutcome();
+ }
+
+ String menuItemIdentifier = (String) menuItemCase.getFieldValue(MenuItemConstants.FIELD_IDENTIFIER);
+ log.debug("Handling configuration template selection for menu item: [{}, {}] and configuration template: {}",
+ menuItemCase.getStringId(), menuItemIdentifier, selectedTemplate);
+ Optional templateOpt = MenuItemTemplateHolder.get(selectedTemplate);
+ if (templateOpt.isEmpty()) {
+ throw new IllegalArgumentException(String.format("No configuration template found with name: %s", selectedTemplate));
+ }
+
+ MenuItemBody menuItemBody = templateOpt.get().getTemplate();
+ Case viewCase = createView(menuItemBody.getView(), menuItemBody.isUseTabbedView());
+ ToDataSetOutcome dataSetOutcome = menuItemBody.toDataSetByConfigTemplate(viewCase);
+ log.debug("For menu item: [{}. {}] was used configuration template: {}", menuItemCase.getStringId(),
+ menuItemIdentifier, selectedTemplate);
+ return new ConfigurationTemplateOutcome(dataSetOutcome);
}
protected Case findCase(String processIdentifier, String query) {
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java
index 10070526211..29232fbb82a 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java
@@ -4,6 +4,7 @@
import com.netgrif.application.engine.petrinet.domain.I18nString;
import java.util.Map;
+import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -23,7 +24,7 @@ public class MenuItemTemplateHolder {
* The key is the template identifier (String), and the value is the Template instance.
*
*/
- public static Map templates = Map.of(
+ private final static Map templates = Map.of(
TabbedCaseViewTemplate.IDENTIFIER, new TabbedCaseViewTemplate(),
TabbedTaskViewTemplate.IDENTIFIER, new TabbedTaskViewTemplate(),
SimpleCaseViewTemplate.IDENTIFIER, new SimpleCaseViewTemplate(),
@@ -32,6 +33,21 @@ SingleTaskViewTemplate.IDENTIFIER, new SingleTaskViewTemplate(),
TabbedTicketViewTemplate.IDENTIFIER, new TabbedTicketViewTemplate()
);
+
+ /**
+ * Retrieves a template by its unique identifier.
+ *
+ * This method looks up and returns the Template instance associated with the
+ * provided identifier from the templates registry.
+ *
+ *
+ * @param identifier the unique identifier of the template to retrieve
+ * @return an Optional containing the Template instance if found, or empty Optional if no template exists for the given identifier
+ */
+ public static Optional get(String identifier) {
+ return Optional.ofNullable(templates.get(identifier));
+ }
+
/**
* Transforms the available templates into a map of selectable options.
*
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java
index 5f0ef140ddf..bf3404cb10b 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java
@@ -1,5 +1,6 @@
package com.netgrif.application.engine.menu.service.interfaces;
+import com.netgrif.application.engine.menu.domain.ConfigurationTemplateOutcome;
import com.netgrif.application.engine.menu.domain.FilterBody;
import com.netgrif.application.engine.menu.domain.MenuItemBody;
import com.netgrif.application.engine.menu.domain.MenuItemView;
@@ -31,7 +32,7 @@ public interface IMenuItemService {
Case duplicateItem(Case originItem, I18nString newTitle, String newIdentifier) throws TransitionNotExecutableException;
Case removeChildItemFromParent(String folderId, Case childItem);
List getMenuItemData(String caseId, Locale locale);
- void handleConfigurationTemplate(Case menuItemCase);
+ ConfigurationTemplateOutcome handleConfigurationTemplate(Case menuItemCase) throws TransitionNotExecutableException;
/**
* Gets all tabbed or non-tabbed views
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 01ebb9eae98..deb25683429 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -849,13 +849,10 @@
change name value { new com.netgrif.application.engine.petrinet.domain.I18nString(identifier.value) }
- configuration_templates: f.configuration_templates;
-
- if (configuration_templates.value == null || configuration_templates.value == "") {
- return
+ def outcome = menuItemService.handleConfigurationTemplate(useCase)
+ outcome.mapping.each { fieldId, value ->
+ change useCase.getField(fieldId) value { value }
}
-
- menuItemService.handleConfigurationTemplate(configuration_templates.value)
Create
From a8f8645be5e4172deddc15919ee040c1a11aaa84 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Mon, 18 May 2026 11:54:54 +0200
Subject: [PATCH 18/49] [ETASK-23] Dynamic view configuration - resolve todos -
fix behavior in menu_item.xml
---
.../menu/domain/ConfigurationTemplateOutcome.java | 4 +++-
.../domain/templates/SimpleCaseViewTemplate.java | 2 +-
.../domain/templates/SimpleTaskViewTemplate.java | 2 +-
.../domain/templates/SingleTaskViewTemplate.java | 2 +-
.../domain/templates/TabbedTicketViewTemplate.java | 2 +-
.../engine/menu/web/MenuController.java | 4 ++--
.../petriNets/engine-processes/menu/menu_item.xml | 14 +++++++++++---
7 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java b/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java
index e31d6bbdbea..d85be58223f 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java
@@ -4,7 +4,9 @@
import java.util.Map;
public class ConfigurationTemplateOutcome {
- // todo 23 doc
+ /**
+ * Map of field data where the key is field ID and the value is field value.
+ */
public final Map mapping;
public ConfigurationTemplateOutcome() {
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java
index a23b243f7eb..1cfc62302df 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java
@@ -11,7 +11,7 @@ public class SimpleCaseViewTemplate implements Template {
public static final String IDENTIFIER = "simple_case_view";
private static final I18nString NAME = new I18nString("Simple case view",
- Map.of("sk", "", "de", "")); // todo 23 translate
+ Map.of("sk", "Zobrazenie prípadov", "de", "Fallansicht"));
private static final MenuItemBody TEMPLATE = buildTemplate();
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java
index aa4815c2f6e..36f1c77a948 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java
@@ -11,7 +11,7 @@ public class SimpleTaskViewTemplate implements Template {
public static final String IDENTIFIER = "simple_task_view";
private static final I18nString NAME = new I18nString("Simple task view",
- Map.of("sk", "", "de", "")); // todo 23 translate
+ Map.of("sk", "Zobrazenie úloh", "de", "Aufgabenansicht"));
private static final MenuItemBody TEMPLATE = buildTemplate();
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java
index 98ea5f60190..835dd50da3b 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java
@@ -11,7 +11,7 @@ public class SingleTaskViewTemplate implements Template {
public static final String IDENTIFIER = "single_task_view";
private static final I18nString NAME = new I18nString("Single task view",
- Map.of("sk", "", "de", "")); // todo 23 translate
+ Map.of("sk", "Zobrazenie jednej úlohy", "de", "Anzeige einer Aufgabe"));
private static final MenuItemBody TEMPLATE = buildTemplate();
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java
index 347b0d96cdb..8eb59a20568 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java
@@ -12,7 +12,7 @@ public class TabbedTicketViewTemplate implements Template {
public static final String IDENTIFIER = "tabbed_ticket_view";
private static final I18nString NAME = new I18nString("Tabbed ticket view",
- Map.of("sk", "", "de", "")); // todo 23 translate
+ Map.of("sk", "Tiketové zobrazenie", "de", "Ticketansicht"));
private static final MenuItemBody TEMPLATE = buildTemplate();
diff --git a/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java b/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java
index 32e32f3fdde..99ffa224aaa 100644
--- a/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java
+++ b/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java
@@ -30,7 +30,7 @@ public class MenuController {
private final IMenuItemService menuItemService;
- // todo 23 menu item authorization
+ // todo menu item authorization
@Operation(summary = "Get relevant data for the menu item", security = {@SecurityRequirement(name = "BasicAuth")})
@GetMapping(value = "/{encodedCaseId}", produces = MediaTypes.HAL_JSON_VALUE)
public EntityModel getMenuItemData(@PathVariable("encodedCaseId") String encodedCaseId, Locale locale) {
@@ -44,6 +44,6 @@ public EntityModel getMenuItemData(@PathVariable("encodedC
}
}
- // todo 23 search with authorization
+ // todo search with authorization
}
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index deb25683429..666f7cba23f 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -449,6 +449,17 @@
use_tabbed_viewDo you want to use view with tabs?true
+
+ 0
+
+
+ use_tabbed_view: f.use_tabbed_view,
+ use_tab_icon: f.use_tab_icon;
+
+ manageBehaviorOfTabFields(use_tab_icon.value, use_tabbed_view.value)
+
+
+
@@ -1550,11 +1561,8 @@
use_tabbed_view: f.use_tabbed_view,
- use_tab_icon: f.use_tab_icon,
view_configuration_type: f.view_configuration_type;
- manageBehaviorOfTabFields(use_tab_icon.value, use_tabbed_view.value)
-
change view_configuration_type value { null }
change view_configuration_type options { menuItemService.getAvailableViewsAsOptions(use_tabbed_view.value, true) }
From fb86d8f4f8c1036961df7f562c78bebffbe0a94d Mon Sep 17 00:00:00 2001
From: chvostek
Date: Mon, 18 May 2026 15:49:41 +0200
Subject: [PATCH 19/49] [ETASK-23] Dynamic view configuration - add menu item
body validation - add tags in menu_item.xml transitions for better filtering
---
.../engine/menu/service/MenuItemService.java | 18 ++++++++++++++++--
.../engine-processes/menu/menu_item.xml | 16 +++++++++++++++-
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
index f7626296cfd..6ef36992247 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
@@ -95,6 +95,8 @@ public Case updateFilter(Case filterCase, FilterBody body) {
* */
@Override
public Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableException {
+ validateMenuItemBody(body);
+
log.debug("Creation of menu item case with identifier [{}] started.", body.getIdentifier());
IUser loggedUser = userService.getLoggedOrSystem();
String sanitizedIdentifier = MenuItemUtils.sanitize(body.getIdentifier());
@@ -103,8 +105,6 @@ public Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableExce
throw new IllegalArgumentException(String.format("Menu item identifier %s is not unique!", sanitizedIdentifier));
}
- // todo 23 validation
-
Case parentItemCase = getOrCreateFolderItem(body.getUri());
I18nString newName = body.getMenuName();
if (newName == null) {
@@ -140,6 +140,8 @@ public Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableExce
* */
@Override
public Case updateMenuItem(Case itemCase, MenuItemBody body) throws TransitionNotExecutableException {
+ validateMenuItemBody(body);
+
log.debug("Update of menu item case with identifier [{}] started.", body.getIdentifier());
String actualUriNodeId = uriService.findByUri(body.getUri()).getStringId();
if (!itemCase.getUriNodeId().equals(actualUriNodeId)) {
@@ -451,6 +453,18 @@ public ConfigurationTemplateOutcome handleConfigurationTemplate(Case menuItemCas
return new ConfigurationTemplateOutcome(dataSetOutcome);
}
+ protected void validateMenuItemBody(MenuItemBody menuItemBody) {
+ if (menuItemBody == null) {
+ throw new IllegalArgumentException("Input data cannot be null");
+ }
+ if (menuItemBody.getIdentifier() == null) {
+ throw new IllegalArgumentException("Identifier cannot be null");
+ }
+ if (menuItemBody.getUri() == null || menuItemBody.getUri().isBlank()) {
+ throw new IllegalArgumentException("Uri cannot be null");
+ }
+ }
+
protected Case findCase(String processIdentifier, String query) {
CaseSearchRequest request = CaseSearchRequest.builder()
.process(Collections.singletonList(new CaseSearchRequest.PetriNet(processIdentifier)))
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 666f7cba23f..49236ce8d14 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -759,7 +759,9 @@
340220
-
+
+ true
+ hourglass_emptyauto
@@ -887,6 +889,9 @@
460100
+
+ true
+ settingsauto
@@ -1019,6 +1024,9 @@
580100
+
+ true
+ move_downauto
@@ -1100,6 +1108,9 @@
580340
+
+ true
+ content_copyauto
@@ -1162,6 +1173,9 @@
580220
+
+ true
+ low_priorityauto
From 460835a8b260145682ff2666e96ea83111535ded Mon Sep 17 00:00:00 2001
From: chvostek
Date: Tue, 19 May 2026 16:11:14 +0200
Subject: [PATCH 20/49] [ETASK-23] Dynamic view configuration - change default
headers field type to string collection - introduce CustomViewTemplate -
update default filter bodies to exclude allowed nets
---
.../engine/menu/domain/FilterBody.java | 12 +-
.../domain/configurations/CaseViewBody.java | 6 +-
.../domain/configurations/TaskViewBody.java | 6 +-
.../domain/templates/CustomViewTemplate.java | 37 ++++
.../menu/domain/templates/Template.java | 2 -
.../menu/service/MenuItemTemplateHolder.java | 3 +-
.../engine/workflow/service/DataService.java | 6 +-
.../menu/case_view_configuration.xml | 94 ++++++--
.../engine-processes/menu/menu_item.xml | 202 +++++++-----------
.../menu/task_view_configuration.xml | 16 +-
10 files changed, 211 insertions(+), 173 deletions(-)
create mode 100644 src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java
index de048064e33..8735b9fcf1b 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java
@@ -62,12 +62,12 @@ public ToDataSetOutcome toDataSet() {
if (metadata == null) {
metadata = getDefaultMetadata(this.type);
}
- outcome.getDataSet().put(DefaultFiltersRunner.FILTER_FIELD_ID, Map.of(
- "type", "filter",
- "value", this.query,
- "allowedNets", this.allowedNets,
- "filterMetadata", metadata
- ));
+ Map dataSetValues = new HashMap<>();
+ dataSetValues.put("type", "filter");
+ dataSetValues.put("value", this.query);
+ dataSetValues.put("filterMetadata", metadata);
+ dataSetValues.put("allowedNets", this.allowedNets);
+ outcome.getDataSet().put(DefaultFiltersRunner.FILTER_FIELD_ID, dataSetValues);
return outcome;
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
index 7be6230497a..2e7edb283c4 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
@@ -68,8 +68,10 @@ protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) {
this.headersMode == null ? new ArrayList<>() : this.headersMode);
outcome.putDataSetEntry(CaseViewConstants.FIELD_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP,
this.headersDefaultMode);
- outcome.putDataSetEntry(CaseViewConstants.FIELD_DEFAULT_HEADERS, FieldType.TEXT,
- this.defaultHeaders != null ? String.join(",", this.defaultHeaders) : null);
+ if (this.defaultHeaders != null) {
+ outcome.putDataSetEntry(CaseViewConstants.FIELD_DEFAULT_HEADERS, FieldType.STRING_COLLECTION,
+ this.defaultHeaders);
+ }
outcome.putDataSetEntry(CaseViewConstants.FIELD_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN,
this.isHeaderModeChangeable);
outcome.putDataSetEntry(CaseViewConstants.FIELD_USE_DEFAULT_HEADERS, FieldType.BOOLEAN,
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java
index 7ad90df3469..c0e40cc6b92 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java
@@ -56,8 +56,10 @@ protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) {
this.allowHeaderTableMode);
outcome.putDataSetEntry(TaskViewConstants.FIELD_USE_DEFAULT_HEADERS, FieldType.BOOLEAN,
this.useDefaultHeaders);
- outcome.putDataSetEntry(TaskViewConstants.FIELD_DEFAULT_HEADERS, FieldType.TEXT,
- this.defaultHeaders != null ? String.join(",", this.defaultHeaders) : null);
+ if (this.defaultHeaders != null) {
+ outcome.putDataSetEntry(TaskViewConstants.FIELD_DEFAULT_HEADERS, FieldType.STRING_COLLECTION,
+ this.defaultHeaders);
+ }
outcome.putDataSetEntry(TaskViewConstants.FIELD_SHOW_MORE_MENU, FieldType.BOOLEAN,
this.showMoreMenu);
if (this.emptyContentText != null) {
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java
new file mode 100644
index 00000000000..95d2e7f6b45
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java
@@ -0,0 +1,37 @@
+package com.netgrif.application.engine.menu.domain.templates;
+
+import com.netgrif.application.engine.menu.domain.MenuItemBody;
+import com.netgrif.application.engine.menu.domain.configurations.SingleTaskViewBody;
+import com.netgrif.application.engine.menu.domain.configurations.TabbedTicketViewBody;
+import com.netgrif.application.engine.petrinet.domain.I18nString;
+
+import java.util.Map;
+
+public class CustomViewTemplate implements Template {
+ public static final String IDENTIFIER = "none";
+
+ private static final I18nString NAME = new I18nString("Custom view",
+ Map.of("sk", "Vlastné zobrazenie", "de", "Benutzerdefinierte Ansicht"));
+
+ private static final MenuItemBody TEMPLATE = buildTemplate();
+
+ private static MenuItemBody buildTemplate() {
+ MenuItemBody menuItemBody = new MenuItemBody();
+ menuItemBody.setUseTabbedView(false);
+ menuItemBody.setUseCustomView(true);
+
+ return menuItemBody;
+ }
+
+ public String getIdentifier() {
+ return IDENTIFIER;
+ }
+
+ public I18nString getName() {
+ return NAME;
+ }
+
+ public MenuItemBody getTemplate() {
+ return TEMPLATE;
+ }
+}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java
index c6c2a462842..f4e61b80a87 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java
@@ -18,7 +18,6 @@ static FilterBody defaultTaskFilterBody(I18nString name) {
filterBody.setVisibility("private");
filterBody.setTitle(name);
filterBody.setQuery("*");
- filterBody.setAllowedNets(List.of());
return filterBody;
}
@@ -29,7 +28,6 @@ static FilterBody defaultCaseFilterBody(I18nString name) {
filterBody.setVisibility("private");
filterBody.setTitle(name);
filterBody.setQuery("*");
- filterBody.setAllowedNets(List.of());
return filterBody;
}
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java
index 29232fbb82a..ef862551927 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java
@@ -30,7 +30,8 @@ TabbedTaskViewTemplate.IDENTIFIER, new TabbedTaskViewTemplate(),
SimpleCaseViewTemplate.IDENTIFIER, new SimpleCaseViewTemplate(),
SimpleTaskViewTemplate.IDENTIFIER, new SimpleTaskViewTemplate(),
SingleTaskViewTemplate.IDENTIFIER, new SingleTaskViewTemplate(),
- TabbedTicketViewTemplate.IDENTIFIER, new TabbedTicketViewTemplate()
+ TabbedTicketViewTemplate.IDENTIFIER, new TabbedTicketViewTemplate(),
+ CustomViewTemplate.IDENTIFIER, new CustomViewTemplate()
);
diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/DataService.java b/src/main/java/com/netgrif/application/engine/workflow/service/DataService.java
index 70a6903f40a..11c7271f6a0 100644
--- a/src/main/java/com/netgrif/application/engine/workflow/service/DataService.java
+++ b/src/main/java/com/netgrif/application/engine/workflow/service/DataService.java
@@ -6,6 +6,7 @@
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.netgrif.application.engine.auth.domain.IUser;
import com.netgrif.application.engine.auth.service.interfaces.IUserService;
@@ -981,10 +982,11 @@ private List parseListStringAllowedNets(ObjectNode node) {
}
private List parseListString(ObjectNode node, String attributeKey) {
- ArrayNode arrayNode = (ArrayNode) node.get(attributeKey);
- if (arrayNode == null) {
+ Object objectNode = node.get(attributeKey);
+ if (objectNode == null || objectNode instanceof NullNode) {
return null;
}
+ ArrayNode arrayNode = (ArrayNode) objectNode;
ArrayList list = new ArrayList<>();
arrayNode.forEach(string -> list.add(string.asText()));
return list;
diff --git a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
index b93951a6913..3ab0ef11da9 100644
--- a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
@@ -394,22 +394,14 @@
Use custom default headers?true
-
+
case_default_headersSet default headers
- Example: "meta-title,meta-visualId"
-
- defaultHeaders: f.this;
-
- String trimmed = defaultHeaders.value?.replaceAll("\\s","")
- if (defaultHeaders.value != trimmed) {
- change defaultHeaders value { trimmed }
- }
-
+ Example: "meta-title", "meta-visualId"view_configuration_type
- Pick view type
+ View typemenuItemService.getAvailableViewsAsOptions(true, useCase.processIdentifier)
@@ -448,7 +440,7 @@
advanced_content_form: f.advanced_content_form;
- String nextViewTaskId = useCase.tasks.find { taskPair -> taskPair.transition == "next_view_settings"}?.task
+ String nextViewTaskId = useCase.tasks.find { taskPair -> taskPair.transition == "open_case_settings"}?.task
def taskIds = [useCase.tasks.find { taskPair -> taskPair.transition == "header_settings"}.task,
useCase.tasks.find { taskPair -> taskPair.transition == "create_case_btn_settings"}.task,
useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task]
@@ -506,7 +498,7 @@
Identifikátor ikony tlačidla "Nová inštancia"Náhľad ikonyPredvolené hlavičky
- Napríklad: "meta-title,meta-visualId"
+ Napríklad: "meta-title", "meta-visualId"Zvoľte nový filterAktualizovať zobrazenie s vybraným filtromTyp vyhľadávania prípadov
@@ -530,7 +522,7 @@
Súčasný filterZobrazenie prípadovVšeobecné
- Vybrať zobrazenie
+ Typ zobrazeniaNastavenie
@@ -539,7 +531,7 @@
IkonevorschauAnzuzeigende Attributmenge auswählenNeue Filter auswählen
- Beispiel: "meta-title,meta-visualId"
+ Beispiel: "meta-title", "meta-visualId"VerstecktEinfacher SuchmodusSortieren
@@ -562,7 +554,7 @@
"Erweiterte Optionen" Taste bei einzelnen Fällen anzeigenMenüeintrageinstellungenFallansicht
- Wählen Sie einen Ansichtstyp
+ Der AnsichtstypEinstellungen
@@ -778,10 +770,10 @@
- next_view_settings
+ open_case_settings33648
-
+
queue_play_nextauto
@@ -1325,6 +1317,32 @@
+
+ change_to_tabbed
+ 240
+ 144
+
+
+ system
+
+ true
+
+
+
+
+
+
+ change_to_untabbed
+ 112
+ 48
+
+
+ system
+
+ true
+
+
+ initialized496
@@ -1349,6 +1367,14 @@
0false
+
+ is_untabbed
+ 112
+ 144
+
+ 0
+ false
+ a1read
@@ -1381,7 +1407,7 @@
a5readinitialized
- next_view_settings
+ open_case_settings1
@@ -1410,7 +1436,35 @@
a9readis_tabbed
- next_view_settings
+ open_case_settings
+ 1
+
+
+ a10
+ regular
+ is_tabbed
+ change_to_untabbed
+ 1
+
+
+ a11
+ regular
+ change_to_untabbed
+ is_untabbed
+ 1
+
+
+ a12
+ regular
+ change_to_tabbed
+ is_tabbed
+ 1
+
+
+ a14
+ regular
+ is_untabbed
+ change_to_tabbed1
\ No newline at end of file
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 49236ce8d14..5183f6289ab 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -290,7 +290,7 @@
menu_name_as_visibleName of the item
- Is shown in the menu
+ Will be shown in the menuautocomplete
@@ -503,7 +503,7 @@
view_configuration_type
- Pick view type
+ View typemenuItemService.getAvailableViewsAsOptions(true, true)
@@ -643,7 +643,7 @@
configuration_templates
- Pick a configuration template
+ Configuration templatecom.netgrif.application.engine.menu.service.MenuItemTemplateHolder.transformToOptions()
@@ -695,7 +695,7 @@
Automatické zvolenie zobrazeniaPo automatickom zvolení sa dané zobrazenie používateľovi otvoríPoužiť zobrazenie v taboch?
- Vybrať zobrazenie
+ Typ zobrazeniaNastavenie zobrazeniaVytvoriť menu položkuZoradiť pod-položky
@@ -746,7 +746,7 @@
Automatische AnzeigeauswahlWenn ausgewählt, wird die Ansicht automatisch geöffnetMöchten Sie die Ansicht mit Registerkarten verwenden?
- Wählen Sie einen Ansichtstyp
+ Der AnsichtstypDie AnsichtskonfigurationMenüeintrag erstellenUntereintrage sortieren
@@ -775,7 +775,7 @@
4grid
- menu_item_identifier
+ menu_nameeditablerequired
@@ -784,7 +784,44 @@
001
- 4
+ 2
+ material
+ outline
+
+
+ 0
+
+
+ menu_item_identifier: f.menu_item_identifier,
+ menu_name: f.menu_name;
+
+ if (menu_item_identifier.value != null && !menu_item_identifier.value.isEmpty()
+ || menu_name.value == null) {
+ return
+ }
+
+ String defaultName = menu_name.value.defaultValue
+ if (defaultName == null || defaultName.isEmpty()) {
+ defaultName = menu_name.value.translations?.values()?.find()
+ }
+
+ String identifier = com.netgrif.application.engine.menu.utils.MenuItemUtils.sanitize(defaultName)
+ change menu_item_identifier value { identifier }
+
+
+
+
+
+ menu_item_identifier
+
+ editable
+ required
+
+
+ 2
+ 0
+ 1
+ 2
material
outline
@@ -836,6 +873,7 @@
configuration_templateseditable
+ required0
@@ -859,7 +897,6 @@
newUri = newUri.replace("//","/")
changeMenuItem useCase uri { newUri }
- change name value { new com.netgrif.application.engine.petrinet.domain.I18nString(identifier.value) }
def outcome = menuItemService.handleConfigurationTemplate(useCase)
@@ -939,9 +976,9 @@
- menu_name
+ configuration_templates
- editable
+ visible0
@@ -953,7 +990,7 @@
- menu_icon
+ menu_nameeditable
@@ -966,6 +1003,20 @@
outline
+
+ menu_icon
+
+ editable
+
+
+ 0
+ 2
+ 1
+ 2
+ material
+ outline
+
+ menu_icon_preview
@@ -987,7 +1038,7 @@
0
- 2
+ 312
material
@@ -1001,7 +1052,7 @@
0
- 3
+ 414
material
@@ -1502,9 +1553,9 @@
3grid
- use_custom_view
+ view_configuration_type
- editable
+ visible0
@@ -1514,39 +1565,11 @@
material
outline
-
- 0
-
-
- trans: t.this,
- useTabIcon: f.use_tab_icon,
- use_tabbed_view: f.use_tabbed_view,
- view_configuration_form: f.view_configuration_form,
- view_configuration_type: f.view_configuration_type,
- tabIconPreview: f.tab_icon_preview,
- tabName: f.tab_name,
- tabIcon: f.tab_icon,
- use: f.use_custom_view,
- selector: f.custom_view_selector;
-
- if (use.value) {
- make selector, editable on trans when { true }
- make [useTabIcon, tabIconPreview, tabName, tabIcon, use_tabbed_view, view_configuration_form,
- view_configuration_type], hidden on trans when { true }
- // todo remove configuration or keep it?
- } else {
- manageBehaviorOfTabFields(useTabIcon.value, use_tabbed_view.value)
- make [use_tabbed_view, view_configuration_form, view_configuration_type], editable on trans when { true }
- make selector, hidden on trans when { true }
- }
-
-
-
- custom_view_selector
+ tab_name
- hidden
+ editable1
@@ -1558,30 +1581,18 @@
- use_tabbed_view
+ custom_view_selector
- editable
+ hidden
- 1
+ 001
- 1
+ 3
material
- standard
+ outline
-
- 0
-
-
- use_tabbed_view: f.use_tabbed_view,
- view_configuration_type: f.view_configuration_type;
-
- change view_configuration_type value { null }
- change view_configuration_type options { menuItemService.getAvailableViewsAsOptions(use_tabbed_view.value, true) }
-
-
- use_tab_icon
@@ -1589,8 +1600,8 @@
editable
- 2
- 0
+ 0
+ 1110
@@ -1608,20 +1619,6 @@
-
- tab_name
-
- editable
-
-
- 0
- 1
- 1
- 1
- material
- outline
-
- tab_icon
@@ -1650,53 +1647,6 @@
standard
-
- view_configuration_type
-
- editable
-
-
- 0
- 2
- 1
- 3
- material
- outline
-
-
- 0
-
-
- use_tabbed_view: f.use_tabbed_view,
- view_configuration_type: f.view_configuration_type,
- view_configuration_form: f.view_configuration_form,
- view_configuration_all_data_form: f.view_configuration_all_data_form,
- view_configuration_id: f.view_configuration_id;
-
- if (view_configuration_id.value != null && !view_configuration_id.value.isEmpty()) {
- workflowService.deleteCase(view_configuration_id.value[0])
- }
-
- if (view_configuration_type.value == null || view_configuration_type.value == "") {
- change view_configuration_id value { [] }
- change view_configuration_form value { [] }
- change view_configuration_all_data_form value { [] }
- return
- }
-
- def configurationCase = createCase(view_configuration_type.value + "_configuration", null, "",
- userService.loggedOrSystem, org.springframework.context.i18n.LocaleContextHolder.getLocale(),
- ["is_tabbed":String.valueOf(use_tabbed_view.value)])
- def initTask = assignTask("initialize", configurationCase)
- finishTask(initTask)
- configurationCase = workflowService.findOne(configurationCase.stringId)
- change view_configuration_id value { [configurationCase.stringId] }
- change view_configuration_form value { [configurationCase.tasks.find { it.transition == "settings" }.task] }
- change view_configuration_all_data_form value { [configurationCase.tasks.find { it.transition == "all_menu_data" }.task] }
-
-
-
- view_configuration_form
@@ -1706,7 +1656,7 @@
031
- 3
+ 2
material
outline
diff --git a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
index b8298bfa552..5ffa5aa3732 100644
--- a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
@@ -287,18 +287,10 @@
Use custom default headers?true
-
+
task_default_headersSet default headers
- Example: "meta-title,meta-user"
-
- defaultHeaders: f.this;
-
- String trimmed = defaultHeaders.value?.replaceAll("\\s","")
- if (defaultHeaders.value != trimmed) {
- change defaultHeaders value { trimmed }
- }
-
+ Example: "meta-title", "meta-user"task_show_more_menu
@@ -377,7 +369,7 @@
Povoliť tabuľkový mód pre hlavičky?Použiť vlastné predvolené hlavičky?Predvolené hlavičky
- Napríklad: "meta-title,meta-user"
+ Napríklad: "meta-title", "meta-user"Zobrazovať menu pre úlohovú položku?Nastavenie položkySúčasný filter
@@ -397,7 +389,7 @@
Erlaube Tabellenmodus?Eigene Kopfzeilen verwenden?Anzuzeigende Attributmenge auswählen
- Beispiel: "meta-title,meta-user"
+ Beispiel: "meta-title", "meta-user"Aktueller FilterAllgemeinAktualisiere die Ansicht mit dem ausgewählten Filter
From 150a3afbab0e2118e53a7956e44460063c57acd5 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Wed, 20 May 2026 08:55:46 +0200
Subject: [PATCH 21/49] [ETASK-23] Dynamic view configuration - update
CustomViewTemplate - introduce allAllowedNets metadata for filter - fix
behavior in menu_item.xml
---
.../engine/menu/domain/FilterBody.java | 5 ++-
.../domain/templates/CustomViewTemplate.java | 4 +-
.../engine-processes/menu/menu_item.xml | 39 +++++++++++++++++++
3 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java
index 8735b9fcf1b..cd96a400206 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java
@@ -35,7 +35,7 @@ public FilterBody(Case filterCase) {
*
* @return metadata containing filter type as map
* */
- public static Map getDefaultMetadata(String type) {
+ public static Map getDefaultMetadata(String type, boolean allAllowedNets) {
Map resultMap = new HashMap<>();
resultMap.put("searchCategories", List.of());
@@ -43,6 +43,7 @@ public static Map getDefaultMetadata(String type) {
resultMap.put("filterType", type);
resultMap.put("defaultSearchCategories", true);
resultMap.put("inheritAllowedNets", false);
+ resultMap.put("allAllowedNets", allAllowedNets);
return resultMap;
}
@@ -60,7 +61,7 @@ public ToDataSetOutcome toDataSet() {
outcome.putDataSetEntry(DefaultFiltersRunner.FILTER_I18N_TITLE_FIELD_ID, FieldType.I18N, this.title);
Map metadata = this.metadata;
if (metadata == null) {
- metadata = getDefaultMetadata(this.type);
+ metadata = getDefaultMetadata(this.type, this.allowedNets == null);
}
Map dataSetValues = new HashMap<>();
dataSetValues.put("type", "filter");
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java
index 95d2e7f6b45..538198a993f 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java
@@ -1,14 +1,12 @@
package com.netgrif.application.engine.menu.domain.templates;
import com.netgrif.application.engine.menu.domain.MenuItemBody;
-import com.netgrif.application.engine.menu.domain.configurations.SingleTaskViewBody;
-import com.netgrif.application.engine.menu.domain.configurations.TabbedTicketViewBody;
import com.netgrif.application.engine.petrinet.domain.I18nString;
import java.util.Map;
public class CustomViewTemplate implements Template {
- public static final String IDENTIFIER = "none";
+ public static final String IDENTIFIER = "custom_view";
private static final I18nString NAME = new I18nString("Custom view",
Map.of("sk", "Vlastné zobrazenie", "de", "Benutzerdefinierte Ansicht"));
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 5183f6289ab..1044e970802 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -898,6 +898,23 @@
changeMenuItem useCase uri { newUri }
+
+ trans: t.view_settings,
+ useTabIcon: f.use_tab_icon,
+ view_configuration_form: f.view_configuration_form,
+ view_configuration_type: f.view_configuration_type,
+ tabIconPreview: f.tab_icon_preview,
+ tabName: f.tab_name,
+ tabIcon: f.tab_icon,
+ use_custom_view: f.use_custom_view,
+ selector: f.custom_view_selector;
+
+ if (use_custom_view.value) {
+ make selector, editable on trans when { true }
+ make [useTabIcon, tabIconPreview, tabName, tabIcon, view_configuration_form,
+ view_configuration_type], hidden on trans when { true }
+ }
+
def outcome = menuItemService.handleConfigurationTemplate(useCase)
outcome.mapping.each { fieldId, value ->
@@ -1357,6 +1374,28 @@
true
+
+ finish
+
+
+ trans: t.view_settings,
+ useTabIcon: f.use_tab_icon,
+ view_configuration_form: f.view_configuration_form,
+ view_configuration_type: f.view_configuration_type,
+ tabIconPreview: f.tab_icon_preview,
+ tabName: f.tab_name,
+ tabIcon: f.tab_icon,
+ use_custom_view: f.use_custom_view,
+ selector: f.custom_view_selector;
+
+ if (use_custom_view.value) {
+ make selector, editable on trans when { true }
+ make [useTabIcon, tabIconPreview, tabName, tabIcon, view_configuration_form,
+ view_configuration_type], hidden on trans when { true }
+ }
+
+
+ role_settings
From 0081969b3df4c19533f1fef7dd5637cec5dd8e61 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Wed, 20 May 2026 10:39:53 +0200
Subject: [PATCH 22/49] [ETASK-23] Dynamic view configuration - update
templates configuration - fix custom view creation via template - rework
settings parts UX
---
.../engine/menu/domain/MenuItemBody.java | 3 +
.../domain/templates/CustomViewTemplate.java | 1 +
.../templates/SimpleCaseViewTemplate.java | 1 +
.../templates/SimpleTaskViewTemplate.java | 1 +
.../templates/SingleTaskViewTemplate.java | 1 +
.../templates/TabbedCaseViewTemplate.java | 1 +
.../templates/TabbedTaskViewTemplate.java | 1 +
.../templates/TabbedTicketViewTemplate.java | 1 +
.../engine/menu/service/MenuItemService.java | 5 +-
.../menu/case_view_configuration.xml | 69 ++++++-------------
.../engine-processes/menu/menu_item.xml | 47 +++++++++----
.../menu/single_task_view_configuration.xml | 23 ++++---
.../menu/tabbed_ticket_view_configuration.xml | 61 +++++-----------
.../menu/task_view_configuration.xml | 25 ++++---
14 files changed, 116 insertions(+), 124 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java
index bb19500b411..51787157310 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java
@@ -23,6 +23,7 @@
public class MenuItemBody {
private String uri;
private String identifier;
+ private String configurationTemplateIdentifier;
private String menuIcon = "filter_none";
private I18nString menuName;
@@ -182,6 +183,7 @@ public ToDataSetOutcome toDataSet(String parentId, String nodePath, Case viewCas
outcome.putDataSetEntry(MenuItemConstants.FIELD_IS_AUTO_SELECT, FieldType.BOOLEAN, this.isAutoSelect);
outcome.putDataSetEntryOptions(MenuItemConstants.FIELD_ALLOWED_ROLES, FieldType.MULTICHOICE_MAP, this.allowedRoles);
outcome.putDataSetEntryOptions(MenuItemConstants.FIELD_BANNED_ROLES, FieldType.MULTICHOICE_MAP, this.bannedRoles);
+ outcome.putDataSetEntry(MenuItemConstants.FIELD_CONFIGURATION_TEMPLATES, FieldType.ENUMERATION_MAP, this.configurationTemplateIdentifier);
outcome = toDataSetWithView(viewCase, outcome);
@@ -197,6 +199,7 @@ public ToDataSetOutcome toDataSet(String parentId, String nodePath, Case viewCas
public ToDataSetOutcome toDataSetByConfigTemplate(Case viewCase) {
ToDataSetOutcome outcome = new ToDataSetOutcome();
outcome.putDataSetEntry(MenuItemConstants.FIELD_USE_TABBED_VIEW, FieldType.BOOLEAN, this.useTabbedView);
+ outcome.putDataSetEntry(MenuItemConstants.FIELD_USE_CUSTOM_VIEW, FieldType.BOOLEAN, this.useCustomView);
return toDataSetWithView(viewCase, outcome);
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java
index 538198a993f..213d3f829ca 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java
@@ -15,6 +15,7 @@ public class CustomViewTemplate implements Template {
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
+ menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER);
menuItemBody.setUseTabbedView(false);
menuItemBody.setUseCustomView(true);
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java
index 1cfc62302df..1bb984dba48 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java
@@ -17,6 +17,7 @@ public class SimpleCaseViewTemplate implements Template {
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
+ menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER);
menuItemBody.setUseTabbedView(false);
CaseViewBody caseViewBody = new CaseViewBody();
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java
index 36f1c77a948..b7bdf81bfb0 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java
@@ -17,6 +17,7 @@ public class SimpleTaskViewTemplate implements Template {
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
+ menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER);
menuItemBody.setUseTabbedView(false);
TaskViewBody taskViewBody = new TaskViewBody();
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java
index 835dd50da3b..648a185b7ac 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java
@@ -17,6 +17,7 @@ public class SingleTaskViewTemplate implements Template {
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
+ menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER);
menuItemBody.setUseTabbedView(false);
SingleTaskViewBody singleTaskViewBody = new SingleTaskViewBody();
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java
index 609384ad046..36da1030d11 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java
@@ -18,6 +18,7 @@ public class TabbedCaseViewTemplate implements Template {
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
+ menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER);
menuItemBody.setUseTabbedView(true);
CaseViewBody caseViewBody = new CaseViewBody();
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java
index 47877bf8c1c..60eb0733c7d 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java
@@ -17,6 +17,7 @@ public class TabbedTaskViewTemplate implements Template {
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
+ menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER);
menuItemBody.setUseTabbedView(true);
TaskViewBody taskViewBody = new TaskViewBody();
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java
index 8eb59a20568..84af0717833 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java
@@ -18,6 +18,7 @@ public class TabbedTicketViewTemplate implements Template {
private static MenuItemBody buildTemplate() {
MenuItemBody menuItemBody = new MenuItemBody();
+ menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER);
menuItemBody.setUseTabbedView(true);
TabbedTicketViewBody tabbedTicketViewBody = new TabbedTicketViewBody();
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
index 6ef36992247..fdcb7c86717 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
@@ -446,7 +446,10 @@ public ConfigurationTemplateOutcome handleConfigurationTemplate(Case menuItemCas
}
MenuItemBody menuItemBody = templateOpt.get().getTemplate();
- Case viewCase = createView(menuItemBody.getView(), menuItemBody.isUseTabbedView());
+ Case viewCase = null;
+ if (menuItemBody.hasView()) {
+ viewCase = createView(menuItemBody.getView(), menuItemBody.isUseTabbedView());
+ }
ToDataSetOutcome dataSetOutcome = menuItemBody.toDataSetByConfigTemplate(viewCase);
log.debug("For menu item: [{}. {}] was used configuration template: {}", menuItemCase.getStringId(),
menuItemIdentifier, selectedTemplate);
diff --git a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
index 3ab0ef11da9..d6e60adec9f 100644
--- a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
@@ -56,6 +56,19 @@
Admin
+
+ { ->
+ def advancedContentFormField = useCase.getField("advanced_content_form")
+ def taskIds = [useCase.tasks.find { taskPair -> taskPair.transition == "header_settings"}.task,
+ useCase.tasks.find { taskPair -> taskPair.transition == "create_case_btn_settings"}.task,
+ useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task]
+ String nextViewTaskId = useCase.tasks.find { taskPair -> taskPair.transition == "open_case_settings"}?.task
+ if (nextViewTaskId != null) {
+ taskIds.add(nextViewTaskId)
+ }
+ change advancedContentFormField value { taskIds }
+ }
+
{
com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField filterAutocomplete,
@@ -435,22 +448,6 @@
task-list
-
- get
-
-
- advanced_content_form: f.advanced_content_form;
- String nextViewTaskId = useCase.tasks.find { taskPair -> taskPair.transition == "open_case_settings"}?.task
- def taskIds = [useCase.tasks.find { taskPair -> taskPair.transition == "header_settings"}.task,
- useCase.tasks.find { taskPair -> taskPair.transition == "create_case_btn_settings"}.task,
- useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task]
- if (nextViewTaskId != null) {
- taskIds.add(nextViewTaskId)
- }
- change advanced_content_form value { taskIds }
-
-
- is_tabbed_var_arc
@@ -570,6 +567,14 @@
true
+
+ finish
+
+
+ initializeAdvancedForm()
+
+
+ data_sync
@@ -795,7 +800,7 @@
view_configuration_type
- editable
+ visible0
@@ -806,36 +811,6 @@
material
outline
-
- 0
-
-
- view_configuration_type: f.view_configuration_type,
- view_configuration_form: f.view_configuration_form,
- view_configuration_all_data_form: f.view_configuration_all_data_form,
- view_configuration_id: f.view_configuration_id;
-
- if (view_configuration_id.value != null && !view_configuration_id.value.isEmpty()) {
- workflowService.deleteCase(view_configuration_id.value[0])
- }
-
- if (view_configuration_type.value == null || view_configuration_type.value == "") {
- change view_configuration_id value { [] }
- change view_configuration_form value { [] }
- change view_configuration_all_data_form value { [] }
- return
- }
-
- def configurationCase = createCase(view_configuration_type.value + "_configuration")
- def initTask = assignTask("initialize", configurationCase)
- finishTask(initTask)
- configurationCase = workflowService.findOne(configurationCase.stringId)
- change view_configuration_id value { [configurationCase.stringId] }
- change view_configuration_form value { [configurationCase.tasks.find { it.transition == "settings" }.task] }
- change view_configuration_all_data_form value { [configurationCase.tasks.find { it.transition == "all_menu_data" }.task] }
-
-
- view_configuration_form
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 1044e970802..02ba2a2e066 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -48,6 +48,25 @@
adminAdmin
+
+ { ->
+ def advancedContentFormField = useCase.getField("advanced_content_form")
+ def taskIds = [useCase.tasks.find { taskPair -> taskPair.transition == "role_settings"}.task,
+ useCase.tasks.find { taskPair -> taskPair.transition == "view_settings"}.task]
+
+ def viewConfigurationIdDataField = useCase.getDataField("view_configuration_id")
+ if (viewConfigurationIdDataField.value != null && !viewConfigurationIdDataField.value.isEmpty()) {
+ def viewCase = workflowService.findOne(viewConfigurationIdDataField.value[0])
+ def taskIdsFromView = viewCase.getFieldValue("advanced_content_form")
+ if (taskIdsFromView != null) {
+ taskIds.addAll(taskIdsFromView)
+ make ["advanced_content_form"], hidden on (viewCase.tasks.findAll { it.transition == "settings" }?.task as List) when { true }
+ }
+ }
+
+ change advancedContentFormField value { taskIds }
+ }
+
{ ->
@@ -630,16 +649,6 @@
task-list
-
- get
-
-
- advanced_content_form: f.advanced_content_form;
- change advanced_content_form value { [useCase.tasks.find { taskPair -> taskPair.transition == "role_settings"}.task,
- useCase.tasks.find { taskPair -> taskPair.transition == "view_settings"}.task] }
-
-
- configuration_templates
@@ -888,6 +897,12 @@
finish
+
+ def outcome = menuItemService.handleConfigurationTemplate(useCase)
+ outcome.mapping.each { fieldId, value ->
+ change useCase.getField(fieldId) value { value }
+ }
+
name: f.menu_name,
identifier: f.menu_item_identifier,
@@ -915,11 +930,10 @@
view_configuration_type], hidden on trans when { true }
}
+
+
- def outcome = menuItemService.handleConfigurationTemplate(useCase)
- outcome.mapping.each { fieldId, value ->
- change useCase.getField(fieldId) value { value }
- }
+ initializeAdvancedForm()
Create
@@ -1395,6 +1409,11 @@
}
+
+
+ initializeAdvancedForm()
+
+
diff --git a/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml
index c951d9b2a10..07bf4098f50 100644
--- a/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml
@@ -37,6 +37,12 @@
Admin
+
+ { ->
+ def advancedContentFormField = useCase.getField("advanced_content_form")
+ change advancedContentFormField value { [useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task] }
+ }
+
{
com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField filterAutocomplete,
@@ -174,15 +180,6 @@
task-list
-
- get
-
-
- advanced_content_form: f.advanced_content_form;
- change advanced_content_form value { [useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task] }
-
-
- show_page_header
@@ -225,6 +222,14 @@
true
+
+ finish
+
+
+ initializeAdvancedForm()
+
+
+ data_sync
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
index 07b4c103e61..769e0c9788c 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
@@ -47,6 +47,12 @@
Admin
+
+ { ->
+ def advancedContentFormField = useCase.getField("advanced_content_form")
+ change advancedContentFormField value { [useCase.tasks.find { taskPair -> taskPair.transition == "next_view_settings"}.task] }
+ }
+
{
com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField filterAutocomplete,
@@ -90,7 +96,7 @@
view_configuration_type
- Pick view type
+ View typemenuItemService.getAvailableViewsAsOptions(true, useCase.processIdentifier)
@@ -127,15 +133,6 @@
task-list
-
- get
-
-
- advanced_content_form: f.advanced_content_form;
- change advanced_content_form value { [useCase.tasks.find { taskPair -> taskPair.transition == "next_view_settings"}.task] }
-
-
-
@@ -145,7 +142,7 @@
Aktualizovať zobrazenie s vybraným filtromFilterSúčasný filter
- Vybrať zobrazenie
+ ZobrazenieNastavenie
@@ -153,7 +150,7 @@
FilterAktueller FilterAktualisiere die Ansicht mit dem ausgewählten Filter
- Wählen Sie einen Ansichtstyp
+ Der AnsichtstypEinstellungen
@@ -169,6 +166,14 @@
true
+
+ finish
+
+
+ initializeAdvancedForm()
+
+
+ data_sync
@@ -255,7 +260,7 @@
view_configuration_type
- editable
+ visible0
@@ -266,36 +271,6 @@
material
outline
-
- 0
-
-
- view_configuration_type: f.view_configuration_type,
- view_configuration_form: f.view_configuration_form,
- view_configuration_all_data_form: f.view_configuration_all_data_form,
- view_configuration_id: f.view_configuration_id;
-
- if (view_configuration_id.value != null && !view_configuration_id.value.isEmpty()) {
- workflowService.deleteCase(view_configuration_id.value[0])
- }
-
- if (view_configuration_type.value == null || view_configuration_type.value == "") {
- change view_configuration_id value { [] }
- change view_configuration_form value { [] }
- change view_configuration_all_data_form value { [] }
- return
- }
-
- def configurationCase = createCase(view_configuration_type.value + "_configuration")
- def initTask = assignTask("initialize", configurationCase)
- finishTask(initTask)
- change view_configuration_id value { [configurationCase.stringId] }
- configurationCase = workflowService.findOne(configurationCase.stringId)
- change view_configuration_form value { [configurationCase.tasks.find { it.transition == "settings" }.task] }
- change view_configuration_all_data_form value { [configurationCase.tasks.find { it.transition == "all_menu_data" }.task] }
-
-
- view_configuration_form
diff --git a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
index 5ffa5aa3732..a867954ff63 100644
--- a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
@@ -37,6 +37,13 @@
Admin
+
+ { ->
+ def advancedContentFormField = useCase.getField("advanced_content_form")
+ change advancedContentFormField value { [useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task,
+ useCase.tasks.find { taskPair -> taskPair.transition == "header_settings"}.task] }
+ }
+
{
com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField filterAutocomplete,
@@ -304,16 +311,6 @@
task-list
-
- get
-
-
- advanced_content_form: f.advanced_content_form;
- change advanced_content_form value { [useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task,
- useCase.tasks.find { taskPair -> taskPair.transition == "header_settings"}.task] }
-
-
- task_empty_content_text
@@ -413,6 +410,14 @@
true
+
+ finish
+
+
+ initializeAdvancedForm()
+
+
+ data_sync
From fabf8d86c39f0596c797a48721d0e90ae5b008ea Mon Sep 17 00:00:00 2001
From: chvostek
Date: Wed, 20 May 2026 12:19:23 +0200
Subject: [PATCH 23/49] [ETASK-23] Dynamic view configuration - change menu
item process identifier constant - add duplicity check on create item
transition
---
.../engine/menu/domain/MenuItemConstants.java | 2 ++
.../engine/menu/service/MenuItemService.java | 22 +++++++++----------
.../engine-processes/menu/menu_item.xml | 9 ++++++++
.../engine/action/MenuItemApiTest.groovy | 16 +++++++-------
4 files changed, 30 insertions(+), 19 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
index a8405551449..9ce6aef7081 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
@@ -35,4 +35,6 @@ public class MenuItemConstants {
public static final String TRANS_INIT_ID = "system_initialize";
public static final String TRANS_SYNC_ID = "data_sync";
public static final String TRANS_ALL_MENU_DATA = "all_menu_data";
+
+ public static final String PROCESS_IDENTIFIER = "menu_item";
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
index fdcb7c86717..3b3f43d618e 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
@@ -110,7 +110,7 @@ public Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableExce
if (newName == null) {
newName = new I18nString(body.getIdentifier());
}
- Case menuItemCase = createCase(FilterRunner.MENU_NET_IDENTIFIER, newName.getDefaultValue(),
+ Case menuItemCase = createCase(MenuItemConstants.PROCESS_IDENTIFIER, newName.getDefaultValue(),
loggedUser.transformToLoggedUser());
menuItemCase.setUriNodeId(uriService.findByUri(body.getUri()).getStringId());
menuItemCase = workflowService.save(menuItemCase);
@@ -205,8 +205,8 @@ public Case createOrIgnoreMenuItem(MenuItemBody body) throws TransitionNotExecut
@Override
public Case findMenuItem(String identifier) {
String query = String.format("processIdentifier:%s AND dataSet.%s.textValue.keyword:\"%s\"",
- FilterRunner.MENU_NET_IDENTIFIER, MenuItemConstants.FIELD_IDENTIFIER, identifier);
- return findCase(FilterRunner.MENU_NET_IDENTIFIER, query);
+ MenuItemConstants.PROCESS_IDENTIFIER, MenuItemConstants.FIELD_IDENTIFIER, identifier);
+ return findCase(MenuItemConstants.PROCESS_IDENTIFIER, query);
}
/**
@@ -221,8 +221,8 @@ public Case findMenuItem(String identifier) {
public Case findMenuItem(String uri, String name) {
UriNode uriNode = uriService.findByUri(uri);
String query = String.format("processIdentifier:%s AND title.keyword:\"%s\" AND uriNodeId:\"%s\"",
- FilterRunner.MENU_NET_IDENTIFIER, name, uriNode.getStringId());
- return findCase(FilterRunner.MENU_NET_IDENTIFIER, query);
+ MenuItemConstants.PROCESS_IDENTIFIER, name, uriNode.getStringId());
+ return findCase(MenuItemConstants.PROCESS_IDENTIFIER, query);
}
/**
@@ -235,8 +235,8 @@ public Case findMenuItem(String uri, String name) {
@Override
public Case findFolderCase(UriNode node) {
String query = String.format("processIdentifier:%s AND dataSet.%s.textValue.keyword:\"%s\"",
- FilterRunner.MENU_NET_IDENTIFIER, MenuItemConstants.FIELD_NODE_PATH, node.getUriPath());
- return findCase(FilterRunner.MENU_NET_IDENTIFIER, query);
+ MenuItemConstants.PROCESS_IDENTIFIER, MenuItemConstants.FIELD_NODE_PATH, node.getUriPath());
+ return findCase(MenuItemConstants.PROCESS_IDENTIFIER, query);
}
/**
@@ -249,8 +249,8 @@ public Case findFolderCase(UriNode node) {
@Override
public boolean existsMenuItem(String identifier) {
String query = String.format("processIdentifier:%s AND dataSet.%s.textValue.keyword:\"%s\"",
- FilterRunner.MENU_NET_IDENTIFIER, MenuItemConstants.FIELD_IDENTIFIER, identifier);
- return countCases(FilterRunner.MENU_NET_IDENTIFIER, query) > 0;
+ MenuItemConstants.PROCESS_IDENTIFIER, MenuItemConstants.FIELD_IDENTIFIER, identifier);
+ return countCases(MenuItemConstants.PROCESS_IDENTIFIER, query) > 0;
}
/**
@@ -339,7 +339,7 @@ public Case duplicateItem(Case originItem, I18nString newTitle, String newIdenti
duplicatedViewCase = duplicateView(originViewCase);
}
- Case duplicated = createCase(FilterRunner.MENU_NET_IDENTIFIER, newTitle.getDefaultValue(),
+ Case duplicated = createCase(MenuItemConstants.PROCESS_IDENTIFIER, newTitle.getDefaultValue(),
userService.getLoggedOrSystem().transformToLoggedUser());
duplicated.setUriNodeId(originItem.getUriNodeId());
duplicated.setDataSet(originItem.getDataSet());
@@ -684,7 +684,7 @@ protected Case getOrCreateFolderRecursive(UriNode node, MenuItemBody body, Case
return folderCase;
}
- folderCase = createCase(FilterRunner.MENU_NET_IDENTIFIER, body.getMenuName().getDefaultValue(),
+ folderCase = createCase(MenuItemConstants.PROCESS_IDENTIFIER, body.getMenuName().getDefaultValue(),
loggedUser.transformToLoggedUser());
folderCase.setUriNodeId(node.getParentId());
folderCase = workflowService.save(folderCase);
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 02ba2a2e066..bcca6ee0711 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -897,6 +897,15 @@
finish
+
+ identifier: f.menu_item_identifier;
+ String query = String.format("processIdentifier:%s AND dataSet.%s.textValue.keyword:\"%s\"",
+ com.netgrif.application.engine.menu.domain.MenuItemConstants.PROCESS_IDENTIFIER,
+ com.netgrif.application.engine.menu.domain.MenuItemConstants.FIELD_IDENTIFIER, identifier.value)
+ if (countCasesElastic(query) > 1) {
+ throw new IllegalArgumentException("Such menu item already exists. Please, use different identifier")
+ }
+
def outcome = menuItemService.handleConfigurationTemplate(useCase)
outcome.mapping.each { fieldId, value ->
diff --git a/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy b/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy
index 3c591ddd80d..01f4166931a 100644
--- a/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy
+++ b/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy
@@ -113,11 +113,11 @@ class MenuItemApiTest {
assert tabbedTaskView.dataSet[TaskViewConstants.FIELD_VIEW_FILTER_CASE].value == []
assert tabbedTaskView.dataSet[TaskViewConstants.FIELD_DEFAULT_HEADERS].value == "meta-title,meta-title"
- Case testFolder = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue.keyword:\"/netgrif/test\"", PageRequest.of(0, 1))[0]
- Case netgrifFolder = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue.keyword:\"/netgrif\"", PageRequest.of(0, 1))[0]
+ Case testFolder = findCasesElastic("processIdentifier:$MenuItemConstants.PROCESS_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue.keyword:\"/netgrif/test\"", PageRequest.of(0, 1))[0]
+ Case netgrifFolder = findCasesElastic("processIdentifier:$MenuItemConstants.PROCESS_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue.keyword:\"/netgrif\"", PageRequest.of(0, 1))[0]
UriNode testNode = uriService.findByUri("/netgrif")
UriNode netgrifNode = uriService.getRoot()
- Case rootFolder = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue.keyword:\"/\"", PageRequest.of(0, 1))[0]
+ Case rootFolder = findCasesElastic("processIdentifier:$MenuItemConstants.PROCESS_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue.keyword:\"/\"", PageRequest.of(0, 1))[0]
assert testFolder != null && testNode != null
assert testFolder.uriNodeId == testNode.stringId
@@ -212,7 +212,7 @@ class MenuItemApiTest {
Thread.sleep(2000)
UriNode node = uriService.findByUri("/netgrif2")
- Case folderCase = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue:\"/netgrif2\"", PageRequest.of(0, 1))[0]
+ Case folderCase = findCasesElastic("processIdentifier:$MenuItemConstants.PROCESS_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue:\"/netgrif2\"", PageRequest.of(0, 1))[0]
assert viewCase.uriNodeId == node.stringId
ArrayList childIds = folderCase.dataSet[MenuItemConstants.FIELD_CHILD_ITEM_IDS].value as ArrayList
@@ -237,11 +237,11 @@ class MenuItemApiTest {
])
Thread.sleep(2000)
- folderCase = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue:\"/netgrif/test3\"", PageRequest.of(0, 1))[0]
- Case folderCase2 = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue:\"/netgrif\"", PageRequest.of(0, 1))[0]
+ folderCase = findCasesElastic("processIdentifier:$MenuItemConstants.PROCESS_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue:\"/netgrif/test3\"", PageRequest.of(0, 1))[0]
+ Case folderCase2 = findCasesElastic("processIdentifier:$MenuItemConstants.PROCESS_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue:\"/netgrif\"", PageRequest.of(0, 1))[0]
assert folderCase != null && folderCase.dataSet[MenuItemConstants.FIELD_PARENT_ID].value == [folderCase2.stringId]
- folderCase = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue:\"/netgrif/test3/netgrif2\"", PageRequest.of(0, 1))[0]
+ folderCase = findCasesElastic("processIdentifier:$MenuItemConstants.PROCESS_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue:\"/netgrif/test3/netgrif2\"", PageRequest.of(0, 1))[0]
assert folderCase != null
node = uriService.findByUri("/netgrif/test3")
assert node != null
@@ -344,7 +344,7 @@ class MenuItemApiTest {
Case leafItemCase = getMenuItem(apiCase)
sleep(2000)
- Case testFolder = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue:\"/netgrif/test\"", PageRequest.of(0, 1))[0]
+ Case testFolder = findCasesElastic("processIdentifier:$MenuItemConstants.PROCESS_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue:\"/netgrif/test\"", PageRequest.of(0, 1))[0]
String netgrifFolderId = (testFolder.dataSet[MenuItemConstants.FIELD_PARENT_ID].value as ArrayList)[0]
Case netgrifFolder = workflowService.findOne(netgrifFolderId)
From 4d05301ce862b6badd71aabea4cb5ff64e78ae17 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Wed, 20 May 2026 14:52:44 +0200
Subject: [PATCH 24/49] [ETASK-23] Dynamic view configuration - implement menu
item data index creation and usage
---
.../engine/startup/MongoDbRunner.groovy | 29 ++++++++++++--
.../engine/menu/domain/MenuItemConstants.java | 3 ++
.../engine/menu/service/MenuItemService.java | 30 +++++++++-----
.../engine-processes/menu/menu_item.xml | 39 +++++++++++++------
4 files changed, 76 insertions(+), 25 deletions(-)
diff --git a/src/main/groovy/com/netgrif/application/engine/startup/MongoDbRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/MongoDbRunner.groovy
index 4fac0dc985e..bdfd9e709ee 100644
--- a/src/main/groovy/com/netgrif/application/engine/startup/MongoDbRunner.groovy
+++ b/src/main/groovy/com/netgrif/application/engine/startup/MongoDbRunner.groovy
@@ -1,5 +1,7 @@
package com.netgrif.application.engine.startup
+import com.netgrif.application.engine.menu.domain.MenuItemConstants
+import com.netgrif.application.engine.workflow.domain.Case
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
@@ -7,6 +9,8 @@ import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Profile
import org.springframework.data.mapping.context.MappingContext
import org.springframework.data.mongodb.core.MongoTemplate
+import org.springframework.data.mongodb.core.index.CompoundIndexDefinition
+import org.springframework.data.mongodb.core.index.IndexDefinition
import org.springframework.data.mongodb.core.index.IndexOperations
import org.springframework.data.mongodb.core.index.IndexResolver
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver
@@ -48,23 +52,40 @@ class MongoDbRunner extends AbstractOrderedCommandLineRunner {
if (host != null && port != null)
log.info("Dropping Mongo database ${host}:${port}/${name}")
else if (uri != null)
- log.info("Droppiung Mongo database ${uri}")
+ log.info("Dropping Mongo database ${uri}")
mongoTemplate.getDb().drop()
}
if (resolveIndexesOnStartup) {
+ log.info("Ensuring Mongo indexes")
resolveIndexes()
}
}
- void resolveIndexes() {
+ private void resolveIndexes() {
MappingContext extends MongoPersistentEntity>, MongoPersistentProperty> mappingContext = mongoTemplate.getConverter().getMappingContext()
IndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext)
mappingContext.getPersistentEntities()
.stream()
.filter(it -> it.isAnnotationPresent(Document.class))
.forEach(it -> {
- IndexOperations indexOps = mongoTemplate.indexOps(it.getType());
- resolver.resolveIndexFor(it.getType()).forEach(indexOps::ensureIndex);
+ IndexOperations indexOps = mongoTemplate.indexOps(it.getType())
+ resolver.resolveIndexFor(it.getType()).forEach(indexOps::ensureIndex)
})
+ customMenuItemDataIndexes().each { indexKey, indexName ->
+ org.bson.Document keys = new org.bson.Document()
+ .append("processIdentifier", 1)
+ .append(indexKey, 1)
+ IndexDefinition index = new CompoundIndexDefinition(keys)
+ .named(indexName)
+ .background()
+ mongoTemplate.indexOps(Case.class).ensureIndex(index)
+ }
+ }
+
+ private static Map customMenuItemDataIndexes() {
+ return Map.of(
+ "dataSet.${MenuItemConstants.FIELD_IDENTIFIER}.value" as String, MenuItemConstants.IDENTIFIER_INDEX_NAME,
+ "dataSet.${MenuItemConstants.FIELD_NODE_PATH}.value" as String, MenuItemConstants.NODE_PATH_INDEX_NAME
+ )
}
}
\ No newline at end of file
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
index 9ce6aef7081..a6870e3d5ff 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java
@@ -37,4 +37,7 @@ public class MenuItemConstants {
public static final String TRANS_ALL_MENU_DATA = "all_menu_data";
public static final String PROCESS_IDENTIFIER = "menu_item";
+
+ public static final String IDENTIFIER_INDEX_NAME = "menuItemIdentifierIdx";
+ public static final String NODE_PATH_INDEX_NAME = "menuItemNodePathIdx";
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
index 3b3f43d618e..dba913d53ca 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
@@ -29,6 +29,9 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.*;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import java.util.*;
@@ -43,6 +46,7 @@ public class MenuItemService implements IMenuItemService {
protected final IUserService userService;
protected final IUriService uriService;
protected final IElasticCaseService elasticCaseService;
+ protected final MongoTemplate mongoTemplate;
protected static final String DEFAULT_FOLDER_ICON = "folder";
@@ -204,9 +208,14 @@ public Case createOrIgnoreMenuItem(MenuItemBody body) throws TransitionNotExecut
* */
@Override
public Case findMenuItem(String identifier) {
- String query = String.format("processIdentifier:%s AND dataSet.%s.textValue.keyword:\"%s\"",
- MenuItemConstants.PROCESS_IDENTIFIER, MenuItemConstants.FIELD_IDENTIFIER, identifier);
- return findCase(MenuItemConstants.PROCESS_IDENTIFIER, query);
+ Query query = Query.query(
+ Criteria.where("processIdentifier").is(MenuItemConstants.PROCESS_IDENTIFIER)
+ .and(String.format("dataSet.%s.value", MenuItemConstants.FIELD_IDENTIFIER)).is(identifier)
+ );
+ query.withHint(MenuItemConstants.IDENTIFIER_INDEX_NAME);
+ List caseAsList = mongoTemplate.find(query, Case.class);
+ Optional caseOptional = caseAsList.stream().findFirst();
+ return caseOptional.orElse(null);
}
/**
@@ -234,9 +243,14 @@ public Case findMenuItem(String uri, String name) {
* */
@Override
public Case findFolderCase(UriNode node) {
- String query = String.format("processIdentifier:%s AND dataSet.%s.textValue.keyword:\"%s\"",
- MenuItemConstants.PROCESS_IDENTIFIER, MenuItemConstants.FIELD_NODE_PATH, node.getUriPath());
- return findCase(MenuItemConstants.PROCESS_IDENTIFIER, query);
+ Query query = Query.query(
+ Criteria.where("processIdentifier").is(MenuItemConstants.PROCESS_IDENTIFIER)
+ .and(String.format("dataSet.%s.value", MenuItemConstants.FIELD_NODE_PATH)).is(node.getUriPath())
+ );
+ query.withHint(MenuItemConstants.NODE_PATH_INDEX_NAME);
+ List caseAsList = mongoTemplate.find(query, Case.class);
+ Optional caseOptional = caseAsList.stream().findFirst();
+ return caseOptional.orElse(null);
}
/**
@@ -248,9 +262,7 @@ public Case findFolderCase(UriNode node) {
* */
@Override
public boolean existsMenuItem(String identifier) {
- String query = String.format("processIdentifier:%s AND dataSet.%s.textValue.keyword:\"%s\"",
- MenuItemConstants.PROCESS_IDENTIFIER, MenuItemConstants.FIELD_IDENTIFIER, identifier);
- return countCases(MenuItemConstants.PROCESS_IDENTIFIER, query) > 0;
+ return findMenuItem(identifier) != null;
}
/**
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index bcca6ee0711..0977dd0267f 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -259,6 +259,10 @@
menu_item_identifierMenu item identifier
+
+ menu_item_identifier_on_create
+ Menu item identifier
+
nodePathItem URI
@@ -801,7 +805,7 @@
0
- menu_item_identifier: f.menu_item_identifier,
+ menu_item_identifier: f.menu_item_identifier_on_create,
menu_name: f.menu_name;
if (menu_item_identifier.value != null && !menu_item_identifier.value.isEmpty()
@@ -821,7 +825,7 @@
- menu_item_identifier
+ menu_item_identifier_on_createeditablerequired
@@ -834,6 +838,23 @@
material
outline
+
+ 0
+
+
+ menu_item_identifier: f.menu_item_identifier_on_create;
+
+ if (menuItemService.existsMenuItem(menu_item_identifier.value)) {
+ change menu_item_identifier validations { validation('''regex $.^''',
+ new com.netgrif.application.engine.petrinet.domain.I18nString("Such menu item already exists",
+ [sk: "Taká položka menu už existuje", de: "Ein solcher Menüeintrag existiert bereits"]))
+ }
+ } else {
+ change menu_item_identifier validations { null }
+ }
+
+
+ move_dest_uri
@@ -897,15 +918,6 @@
finish
-
- identifier: f.menu_item_identifier;
- String query = String.format("processIdentifier:%s AND dataSet.%s.textValue.keyword:\"%s\"",
- com.netgrif.application.engine.menu.domain.MenuItemConstants.PROCESS_IDENTIFIER,
- com.netgrif.application.engine.menu.domain.MenuItemConstants.FIELD_IDENTIFIER, identifier.value)
- if (countCasesElastic(query) > 1) {
- throw new IllegalArgumentException("Such menu item already exists. Please, use different identifier")
- }
-
def outcome = menuItemService.handleConfigurationTemplate(useCase)
outcome.mapping.each { fieldId, value ->
@@ -914,8 +926,11 @@
name: f.menu_name,
- identifier: f.menu_item_identifier,
+ menu_item_identifier: f.menu_item_identifier,
+ menu_item_identifier_on_create: f.menu_item_identifier_on_create;
dest: f.move_dest_uri;
+
+ change menu_item_identifier value { menu_item_identifier_on_create.value }
String newUri = dest.value.join("/")
newUri = newUri.replace("//","/")
From f42843e150c3c4c53948512f6e2c5b724c3eea39 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Wed, 20 May 2026 16:59:51 +0200
Subject: [PATCH 25/49] [ETASK-23] Dynamic view configuration - menu item
identifier and uri fixes - refactor MenuItemService.updateMenuItem - fix
MenuItemService.findMenuItem - improve identifier behavior on creation task
---
.../engine/menu/service/MenuItemService.java | 115 +++---------------
.../engine/menu/utils/MenuItemUtils.java | 28 +++++
.../engine-processes/menu/menu_item.xml | 4 +
3 files changed, 51 insertions(+), 96 deletions(-)
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
index dba913d53ca..519071e3893 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
@@ -103,10 +103,9 @@ public Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableExce
log.debug("Creation of menu item case with identifier [{}] started.", body.getIdentifier());
IUser loggedUser = userService.getLoggedOrSystem();
- String sanitizedIdentifier = MenuItemUtils.sanitize(body.getIdentifier());
- if (existsMenuItem(sanitizedIdentifier)) {
- throw new IllegalArgumentException(String.format("Menu item identifier %s is not unique!", sanitizedIdentifier));
+ if (existsMenuItem(body.getIdentifier())) {
+ throw new IllegalArgumentException(String.format("Menu item identifier %s is not unique!", body.getIdentifier()));
}
Case parentItemCase = getOrCreateFolderItem(body.getUri());
@@ -121,7 +120,7 @@ public Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableExce
parentItemCase = appendChildCaseIdAndSave(parentItemCase, menuItemCase.getStringId());
- String nodePath = createNodePath(body.getUri(), sanitizedIdentifier);
+ String nodePath = createNodePath(body.getUri(), body.getIdentifier());
uriService.getOrCreate(nodePath, UriContentType.CASE);
Case viewCase = null;
@@ -135,28 +134,20 @@ public Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableExce
}
/**
- * Updates menu item case and it's configuration cases
+ * Updates menu item case and it's configuration cases (recreates)
*
* @param itemCase menu item case to be updated
* @param body data used for update
*
- * @return updated menu item case (configuration cases are updated, but not returned)
+ * @return recreated menu item case (configuration cases are recreated, but not returned)
* */
@Override
public Case updateMenuItem(Case itemCase, MenuItemBody body) throws TransitionNotExecutableException {
validateMenuItemBody(body);
log.debug("Update of menu item case with identifier [{}] started.", body.getIdentifier());
- String actualUriNodeId = uriService.findByUri(body.getUri()).getStringId();
- if (!itemCase.getUriNodeId().equals(actualUriNodeId)) {
- itemCase.setUriNodeId(actualUriNodeId);
- itemCase = workflowService.save(itemCase);
- }
-
- Case viewCase = findView(itemCase);
- viewCase = handleView(viewCase, body.getView(), body.isUseTabbedView());
- ToDataSetOutcome dataSetOutcome = body.toDataSet(viewCase);
- itemCase = setData(itemCase, MenuItemConstants.TRANS_SYNC_ID, dataSetOutcome.getDataSet());
+ workflowService.deleteCase(itemCase);
+ itemCase = createMenuItem(body);
log.debug("Updated menu item case [{}] with identifier [{}].", itemCase.getStringId(), body.getIdentifier());
return itemCase;
}
@@ -215,7 +206,7 @@ public Case findMenuItem(String identifier) {
query.withHint(MenuItemConstants.IDENTIFIER_INDEX_NAME);
List caseAsList = mongoTemplate.find(query, Case.class);
Optional caseOptional = caseAsList.stream().findFirst();
- return caseOptional.orElse(null);
+ return caseOptional.map(aCase -> workflowService.findOne(aCase.getStringId())).orElse(null);
}
/**
@@ -250,7 +241,7 @@ public Case findFolderCase(UriNode node) {
query.withHint(MenuItemConstants.NODE_PATH_INDEX_NAME);
List caseAsList = mongoTemplate.find(query, Case.class);
Optional caseOptional = caseAsList.stream().findFirst();
- return caseOptional.orElse(null);
+ return caseOptional.map(aCase -> workflowService.findOne(aCase.getStringId())).orElse(null);
}
/**
@@ -468,15 +459,21 @@ public ConfigurationTemplateOutcome handleConfigurationTemplate(Case menuItemCas
return new ConfigurationTemplateOutcome(dataSetOutcome);
}
- protected void validateMenuItemBody(MenuItemBody menuItemBody) {
- if (menuItemBody == null) {
+ protected void validateMenuItemBody(MenuItemBody body) {
+ if (body == null) {
throw new IllegalArgumentException("Input data cannot be null");
}
- if (menuItemBody.getIdentifier() == null) {
+ if (body.getIdentifier() == null) {
throw new IllegalArgumentException("Identifier cannot be null");
}
- if (menuItemBody.getUri() == null || menuItemBody.getUri().isBlank()) {
+ if (body.getUri() == null || body.getUri().isBlank()) {
throw new IllegalArgumentException("Uri cannot be null");
+ } else {
+ body.setUri(MenuItemUtils.sanitizeUriSegments(body.getUri(), uriService));
+ List uriSegments = List.of(body.getUri().split(uriService.getUriSeparator()));
+ if (uriSegments.contains(body.getIdentifier())) {
+ throw new IllegalArgumentException("Uri cannot contain this identifier");
+ }
}
}
@@ -521,10 +518,6 @@ protected Case duplicateView(Case viewCase) throws TransitionNotExecutableExcept
return setDataWithExecute(duplicatedViewCase, MenuItemConstants.TRANS_INIT_ID, dataSet);
}
- protected Case findView(Case itemOrViewCase) {
- return findCaseInCaseRef(itemOrViewCase, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID);
- }
-
protected Case findFilter(Case viewCase) {
return findCaseInCaseRef(viewCase, ViewConstants.FIELD_VIEW_FILTER_CASE);
}
@@ -538,22 +531,6 @@ protected Case findCaseInCaseRef(Case useCase, String caseRefId) {
}
}
- protected Case handleView(Case existingViewCase, ViewBody body, boolean isTabbed) throws TransitionNotExecutableException {
- if (mustUpdateView(existingViewCase, body)) {
- return updateView(existingViewCase, body, isTabbed);
- } else if (mustCreateView(existingViewCase, body)) {
- return createView(body, isTabbed);
- } else if (mustRemoveView(existingViewCase, body)) {
- removeView(existingViewCase);
- return null;
- } else if (mustRemoveAndCreateView(existingViewCase, body)) {
- removeView(existingViewCase);
- return createView(body, isTabbed);
- } else {
- return null;
- }
- }
-
protected Case createView(ViewBody body, boolean isTabbed) throws TransitionNotExecutableException {
IUser loggedUser = userService.getLoggedOrSystem();
Case viewCase = createCase(body.getViewProcessIdentifier(), body.getViewProcessIdentifier(),
@@ -579,60 +556,6 @@ protected Case createView(ViewBody body, boolean isTabbed) throws TransitionNotE
return viewCase;
}
- protected Case updateView(Case viewCase, ViewBody body, boolean isTabbed) throws TransitionNotExecutableException {
- Case filterCase = findFilter(viewCase);
- filterCase = handleFilter(filterCase, body.getFilterBody());
-
- Case associatedViewCase = findView(viewCase);
- associatedViewCase = handleView(associatedViewCase, body.getAssociatedViewBody(), isTabbed);
-
- ToDataSetOutcome outcome = body.toDataSet(associatedViewCase, filterCase);
- viewCase = setData(viewCase, ViewConstants.TRANS_SYNC_ID, outcome.getDataSet());
-
- log.trace("Updated configuration view case [{}] of identifier [{}]", viewCase.getStringId(),
- body.getViewProcessIdentifier());
- return viewCase;
- }
-
- protected void removeView(Case viewCase) {
- workflowService.deleteCase(viewCase);
- log.trace("Removed configuration view case [{}].", viewCase.getStringId());
- }
-
- protected Case handleFilter(Case filterCase, FilterBody body) throws TransitionNotExecutableException {
- if (mustCreateFilter(filterCase, body)) {
- return createFilter(body);
- } else if (mustUpdateFilter(filterCase, body)) {
- return updateFilter(filterCase, body);
- } else {
- return filterCase;
- }
- }
-
- protected boolean mustUpdateView(Case useCase, ViewBody body) {
- return body != null && useCase != null && useCase.getProcessIdentifier().equals(body.getViewProcessIdentifier());
- }
-
- protected boolean mustRemoveAndCreateView(Case useCase, ViewBody body) {
- return body != null && useCase != null && !useCase.getProcessIdentifier().equals(body.getViewProcessIdentifier());
- }
-
- protected boolean mustRemoveView(Case useCase, ViewBody body) {
- return body == null && useCase != null;
- }
-
- protected boolean mustCreateView(Case useCase, ViewBody body) {
- return body != null && useCase == null;
- }
-
- protected boolean mustCreateFilter(Case filterCase, FilterBody body) {
- return filterCase == null && body != null;
- }
-
- protected boolean mustUpdateFilter(Case filterCase, FilterBody body) {
- return filterCase != null && body != null;
- }
-
protected List updateNodeInChildrenFoldersRecursive(Case parentFolder) {
List childItemIds = MenuItemUtils.getCaseIdsFromCaseRef(parentFolder, MenuItemConstants.FIELD_CHILD_ITEM_IDS);
if (childItemIds == null || childItemIds.isEmpty()) {
diff --git a/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java b/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java
index 8472e838599..b7b3107a8b6 100644
--- a/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java
+++ b/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java
@@ -1,6 +1,7 @@
package com.netgrif.application.engine.menu.utils;
import com.netgrif.application.engine.menu.domain.MenuItemConstants;
+import com.netgrif.application.engine.petrinet.service.interfaces.IUriService;
import com.netgrif.application.engine.workflow.domain.Case;
import com.netgrif.application.engine.workflow.domain.TaskPair;
import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService;
@@ -29,6 +30,33 @@ public static String sanitize(String input) {
.toLowerCase();
}
+ /**
+ * Sanitizes each segment of a URI path by removing diacritical marks, replacing special characters with
+ * delimiters, and converting to lowercase. The URI is split by the separator defined in the provided
+ * {@link IUriService}, each segment is sanitized individually, and then the segments are concatenated back
+ * together.
+ *
+ * @param uri the URI string to be sanitized
+ * @param uriService the service providing the URI separator configuration
+ *
+ * @return sanitized URI string with all segments processed, or null if input URI is null
+ * */
+ public static String sanitizeUriSegments(String uri, IUriService uriService) {
+ if (uri == null) {
+ return null;
+ }
+ String[] uriSegments = uri.split(uriService.getUriSeparator());
+ if (uriSegments.length == 0) {
+ return uriService.getRoot().getUriPath();
+ }
+ StringBuilder sanitizedUriBuilder = new StringBuilder();
+ for (String uriSegment : uriSegments) {
+ sanitizedUriBuilder.append(uriService.getUriSeparator());
+ sanitizedUriBuilder.append(MenuItemUtils.sanitize(uriSegment));
+ }
+ return sanitizedUriBuilder.toString().replaceAll("//", uriService.getUriSeparator());
+ }
+
/**
* Finds task id in the provided case instance by transition id
*
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 0977dd0267f..a76182a6c89 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -851,6 +851,10 @@
}
} else {
change menu_item_identifier validations { null }
+ String sanitizedIdentifier = com.netgrif.application.engine.menu.utils.MenuItemUtils.sanitize(menu_item_identifier.value)
+ if (menu_item_identifier.value != sanitizedIdentifier) {
+ change menu_item_identifier value { sanitizedIdentifier }
+ }
}
From ca60ccf7170270d97ed2fccdb27ed884585991e4 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Fri, 22 May 2026 11:45:49 +0200
Subject: [PATCH 26/49] [ETASK-23] Dynamic view configuration - remove filter
case - remove deprecated methods in ActionDelegate - update menu
configurations for direct use of filter field
---
docs/_sidebar.md | 1 -
docs/search/filter.md | 2 +
docs/search/filter_import_export.md | 172 ---
.../logic/action/ActionDelegate.groovy | 720 +----------
.../startup/DefaultFiltersRunner.groovy | 314 -----
.../engine/startup/FilterRunner.groovy | 59 -
.../engine/startup/MenuRunner.groovy | 39 +
.../engine/startup/RunnerController.groovy | 3 +-
.../engine/auth/service/UserService.java | 6 -
.../engine/menu/domain/FilterBody.java | 21 +-
.../domain/configurations/CaseViewBody.java | 5 +
.../configurations/CaseViewConstants.java | 2 +-
.../configurations/SingleTaskViewBody.java | 5 +
.../SingleTaskViewConstants.java | 1 +
.../configurations/TabbedTicketViewBody.java | 5 +
.../domain/configurations/TaskViewBody.java | 6 +-
.../configurations/TaskViewConstants.java | 1 +
.../menu/domain/configurations/ViewBody.java | 10 +-
.../domain/configurations/ViewConstants.java | 2 -
.../domain/templates/CustomViewTemplate.java | 7 +-
.../templates/SimpleCaseViewTemplate.java | 9 +-
.../templates/SimpleTaskViewTemplate.java | 9 +-
.../templates/SingleTaskViewTemplate.java | 9 +-
.../templates/TabbedCaseViewTemplate.java | 9 +-
.../templates/TabbedTaskViewTemplate.java | 9 +-
.../templates/TabbedTicketViewTemplate.java | 9 +-
.../menu/domain/templates/Template.java | 6 +-
.../engine/menu/service/MenuItemService.java | 63 +-
.../service/interfaces/IMenuItemService.java | 2 -
.../service/FilterImportExportService.java | 380 ------
.../service/MenuImportExportService.java | 383 ------
.../service/UserFilterSearchService.java | 49 -
.../IFilterImportExportService.java | 34 -
.../interfaces/IMenuImportExportService.java | 33 -
.../interfaces/IUserFilterSearchService.java | 9 -
.../engine-processes/export_filters.xml | 171 ---
.../petriNets/engine-processes/filter.xml | 1096 -----------------
.../engine-processes/import_filters.xml | 182 ---
.../menu/case_view_configuration.xml | 215 +---
.../engine-processes/menu/menu_item.xml | 2 +-
.../menu/single_task_view_configuration.xml | 225 +---
.../menu/tabbed_ticket_view_configuration.xml | 34 -
.../menu/task_view_configuration.xml | 246 +---
.../petriNets/engine-processes/org_group.xml | 41 -
.../preference_filter_item.xml | 983 ---------------
.../application/engine/TestHelper.groovy | 2 +-
.../engine/action/ActionDelegateTest.groovy | 13 +-
.../engine/action/FilterApiTest.groovy | 184 ---
.../engine/action/MenuItemApiTest.groovy | 4 +-
.../filters/FilterImportExportTest.groovy | 546 --------
.../engine/menu/MenuImportExportTest.groovy | 14 +-
.../engine/menu/MenuItemServiceTest.java | 1 -
52 files changed, 141 insertions(+), 6212 deletions(-)
delete mode 100644 docs/search/filter_import_export.md
delete mode 100644 src/main/groovy/com/netgrif/application/engine/startup/DefaultFiltersRunner.groovy
delete mode 100644 src/main/groovy/com/netgrif/application/engine/startup/FilterRunner.groovy
create mode 100644 src/main/groovy/com/netgrif/application/engine/startup/MenuRunner.groovy
delete mode 100644 src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java
delete mode 100644 src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java
delete mode 100644 src/main/java/com/netgrif/application/engine/workflow/service/UserFilterSearchService.java
delete mode 100644 src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFilterImportExportService.java
delete mode 100644 src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IMenuImportExportService.java
delete mode 100644 src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IUserFilterSearchService.java
delete mode 100644 src/main/resources/petriNets/engine-processes/export_filters.xml
delete mode 100644 src/main/resources/petriNets/engine-processes/filter.xml
delete mode 100644 src/main/resources/petriNets/engine-processes/import_filters.xml
delete mode 100644 src/main/resources/petriNets/engine-processes/preference_filter_item.xml
delete mode 100644 src/test/groovy/com/netgrif/application/engine/action/FilterApiTest.groovy
delete mode 100644 src/test/groovy/com/netgrif/application/engine/filters/FilterImportExportTest.groovy
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 0bd5aacf956..d4ef204c9a9 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -8,7 +8,6 @@
* [Search]()
* [Elastic](search/elastic_mapping.md)
* [Filter](search/filter.md)
- * [Import / Export](search/filter_import_export.md)
* [Permissions]()
* [User list](roles/userlist.md)
* [Permissions](roles/permissions.md)
diff --git a/docs/search/filter.md b/docs/search/filter.md
index 0d9924921b2..454ce9f2cbd 100644
--- a/docs/search/filter.md
+++ b/docs/search/filter.md
@@ -42,6 +42,8 @@ field component.
## Filter process
+[//]: # (todo 23)
+
The engine filter process is located under `resources/petriNets/engine-processes/filter.xml`

diff --git a/docs/search/filter_import_export.md b/docs/search/filter_import_export.md
deleted file mode 100644
index cd98e559025..00000000000
--- a/docs/search/filter_import_export.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# Filter import/export
-
-## User guide
-
-This guide is aimed at application users that wish to know, how to import/export selected filters from/to application.
-If you are developer that wishes to know more about the implementation and various ways the system can be interacted
-with, you can find this information in the Developer guide of filter import/export.
-
-### Overview
-
-Every user in the application have option to import/export filters. For every user in application case for import
-filters and also case for export filters exists as shown on the next image.
-
-
-
-
-
-
-
-### Processes
-
-#### Export filters
-
-After user opens **Export filters USERNAME** case, task to export filters is displayed. The task consists of three
-fields, from which only two are initially displayed to user.
-
-
-
-
-
-
-
-First field is multi choice field, which allows user to select multiple filters, which he wants to export. Filters that
-are displayed in this multi choice are all **public** filters in the application (filters which can be created by other
-users) and also all **private** filters of currently logged user. After user selects filters which he wants to export
-and clicks on the **Export filters** button, the third field is displayed.
-
-
-
-This field holds exported xml file, which contains exported filters in the custom xml format. User can download this
-file by clicking on its name. This file can be later used to import filters into the application. Users should never
-create or modify these files on their own, because of the complexity of filters format inside xml file.
-
-#### Import filters
-
-After user opens **Import filters USERNAME** case, task to import filters is displayed. The task consists of two fields,
-which are initially displayed to user and after these two fields there is one section for every imported filter.
-
-
-
-
-
-
-
-First field is file field, where user should upload xml file, that contains exported filters. After user uploads this
-xml file and clicks on **Import filters** button, application validate uploaded file against schema, which doesn't allow
-upload of files in incorrect format. If format of uploaded file is correct, application initialize creation of filters
-and displays these filters under initially displayed fields.
-
-
-
-Each section after horizontal line represents one imported filter. User can change two properties for each imported
-filter. First one is filter name and the second one is visibility of the filter. User can also see type of the filter (
-if the filter is for tasks or for cases) and also filter preview, so he can know what it is filtering.
-
-In some situation, imported filter have dependency on some processes. To correctly display preview of filter and further
-correct function of filter, all these processes need to be uploaded in application. If some of these processes are
-missing, list of missing processes is displayed inside textarea as shown on next image.
-
-
-
-When user is satisfied with imported filters, their names and visibility, he can confirm creation of these filters by
-pressing **FINISH** button on the bottom right. If the imported filters are not according to his ideas, he can abort
-further creation of these filters by pressing **CANCEL** button or deleting uploaded xml file from file field.
-
-## Developer guide
-
-This guide is aimed at developers that wish to know the implementation detail of filter import/export, so they can
-interact with or override its implementation. If you are application user and wish to know how to use the options
-available to you read the User guide of filter import/export. Please read **Filter process guide** before u start
-reading this one, because of some references on this process.
-
-### Overview
-
-The filter import and export consists of two processes, which are independent of each other. Names of processes are
-self-explaining:
-
-- **Export of filters** - serves for export of filters into xml file `export_filters.xml`
-- **Import of filters** - serves for import of filters from xml file `import_filters.xml`
-
-New schema, which describes format of xml file for exported filters, were introduced
-`filter_export_schema.xml`. All imported xml files containing filters are validated against this schema to ensure
-filters will be correctly created and working. Also, new exception class for incorrect xml file format of imported
-filters was created to throw error after validating xml file against schema `IllegalFilterFileException`.
-
-Multiple classes were added to support serialization and deserialization of filters. These classes are:
-
-- Configuration
-- FilterImportExport
-- FilterImportExportList
-- FilterMetadataExport
-- Predicate
-- PredicateArray
-- PredicateValue
-- CustomFilterDeserializer
-
-`ActionDelegate` have two new functions, which support filters import/export:
-`exportFilters(Collection filtersToExport)` and `importFilters()`. Also, new service `FilterImportExportService`
-which is called by these two functions was created and which performs most functionality in importing and exporting
-functions.
-
-When creating new user, case for importing filters and also case for exporting filters is created for this user.
-
-Also, new property was added into `application.properties`, with name `nae.filter.export.file-name`, which defines name
-of xml file that contains exported filters. This property has default string value of **filters.xml**.
-
-When filters are exported, all of their ancestors are exported as well. If the exported filters have common ancestors,
-the ancestors are only exported once. The relationships between filters are reconstructed on import based on the
-exported filters case Ids. It is assumed that parents precede their children in the imported file
-(the exported file is generated in this way). If a filter cannot find its parent on import the connection will
-be severed and an error will be logged.
-
-### Processes
-
-#### Export filters
-
-Export process consists of one task `exportFilter`, which carry out whole exporting of filters. Process and also the
-only task consist of three data fields. First one is `exportable_filters`, which is **multichoice_map** that displays
-all filters, that are exportable by currently logged user. List is loaded from `ActionDelegate`
-method `findAllFilters()` and returns all filter cases, that are public and created by any user or that are private and
-created by currently logged user.
-
-After selecting filters to export and clicking on second data field **button** with id `export_btn`, the
-method `exportFilters()` from `ActionDelegate`
-is called. This method serialize selected filters into xml file and put this file into third **file** field with
-id `export_file`.
-
-Export filters can be run any number of times so the user can export any number of filters in selected combinations into
-any number of xml files.
-
-#### Import filters
-
-Import process also consists of one task `importFilter`, which carry out whole importing of filters. There are also just
-three data fields in this process. First one is **file** field `upload_file` that serves for upload of xml file with
-filters. After uploading this file and clicking on the next **button** field `import_file`, the `importFilters()` method
-from `ActionDelegate` is called, that validates uploaded xml file against xsd schema.
-
-- If file is valid, method creates **Filter** cases via **Automated new filter** task and move them into **Import
- filter** task. Method returns list of **Import filter** task ids, that are set as value for **taskRef**
- field `imported_filters`, so the user can update some values of these filters.
-- If file is not valid, error message is thrown.
-
-Pressing **FINISH** button in this task moves imported filters into state, where they can be added into group
-navigation.
-
-Pressing **CANCEL** button or changing value to `upload_file` deletes created filters, so there will not be unworkable
-filters saved in the application database.
-
-As well as export filters, import filters can be run any number of times, so user can upload how many filters as he
-wants.
diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
index e1bc462a28c..8c7007dedce 100644
--- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
+++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
@@ -49,8 +49,7 @@ import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetServi
import com.netgrif.application.engine.petrinet.service.interfaces.IProcessRoleService
import com.netgrif.application.engine.petrinet.service.interfaces.IUriService
import com.netgrif.application.engine.rules.domain.RuleRepository
-import com.netgrif.application.engine.startup.DefaultFiltersRunner
-import com.netgrif.application.engine.startup.FilterRunner
+
import com.netgrif.application.engine.startup.ImportHelper
import com.netgrif.application.engine.utils.FullPageRequest
import com.netgrif.application.engine.workflow.domain.Case
@@ -162,18 +161,9 @@ class ActionDelegate {
@Autowired
Scheduler scheduler
- @Autowired
- IUserFilterSearchService filterSearchService
-
@Autowired
IConfigurableMenuService configurableMenuService
- @Autowired
- IMenuImportExportService menuImportExportService
-
- @Autowired
- IFilterImportExportService filterImportExportService
-
@Autowired
IExportService exportService
@@ -1395,25 +1385,6 @@ class ActionDelegate {
return new DynamicValidation(rule, message)
}
- List findFilters(String userInput) {
- return filterSearchService.autocompleteFindFilters(userInput)
- }
-
- List findAllFilters() {
- return filterSearchService.autocompleteFindFilters("")
- }
-
- FileFieldValue exportFilters(Collection filtersToExport) {
- if (filtersToExport.isEmpty()) {
- return null
- }
- return filterImportExportService.exportFiltersToFile(filtersToExport)
- }
-
- List importFilters() {
- return filterImportExportService.importFilters()
- }
-
File exportCasesToFile(Closure predicate, String pathName, ExportDataConfig config = null,
int pageSize = exportConfiguration.getMongoPageSize()) {
File exportFile = new File(pathName)
@@ -1556,318 +1527,6 @@ class ActionDelegate {
return findTasks(requests, loggedUser, page, pageSize, locale, isIntersection)
}
- List findDefaultFilters() {
- if (!createDefaultFilters) {
- return []
- }
- return findCases({ it.processIdentifier.eq(FilterRunner.FILTER_PETRI_NET_IDENTIFIER).and(it.author.id.eq(userService.system.stringId)) })
- }
-
- /**
- * Creates filter instance of type {@value DefaultFiltersRunner#FILTER_TYPE_CASE}
- *
- * @param title filter case title
- * @param query elastic query for the view
- * @param icon filter case icon
- * @param allowedNets List of process identifiers
- * @param visibility Possible values: {@value DefaultFiltersRunner#FILTER_VISIBILITY_PRIVATE} or {@value DefaultFiltersRunner#FILTER_VISIBILITY_PUBLIC}
- * @param filterMetadata metadata for filter. If no value is provided, then default value is used: {@link #defaultFilterMetadata(String)}
- *
- * @return created {@link Case} instance of filter
- */
- @NamedVariant
- Case createCaseFilter(def title, String query, List allowedNets,
- String icon = "", String visibility = DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE, def filterMetadata = null) {
- return createFilter(title, query, DefaultFiltersRunner.FILTER_TYPE_CASE, allowedNets, icon, visibility, filterMetadata)
- }
-
- /**
- * Creates filter instance of type {@value DefaultFiltersRunner#FILTER_TYPE_TASK}
- *
- * @param title filter case title
- * @param query elastic query for the view
- * @param icon filter case icon
- * @param allowedNets List of process identifiers
- * @param visibility Possible values: {@value DefaultFiltersRunner#FILTER_VISIBILITY_PRIVATE} or {@value DefaultFiltersRunner#FILTER_VISIBILITY_PUBLIC}
- * @param filterMetadata metadata for filter. If no value is provided, then default value is used: {@link #defaultFilterMetadata(String)}
- *
- * @return created {@link Case} instance of filter
- */
- @NamedVariant
- Case createTaskFilter(def title, String query, List allowedNets,
- String icon = "", String visibility = DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE, def filterMetadata = null) {
- return createFilter(title, query, DefaultFiltersRunner.FILTER_TYPE_TASK, allowedNets, icon, visibility, filterMetadata)
- }
-
- /**
- * Creates filter instance.
- *
- * @param title filter case title
- * @param query elastic query for the view
- * @param type Filter type. Possible values: {@value DefaultFiltersRunner#FILTER_TYPE_CASE} or {@value DefaultFiltersRunner#FILTER_TYPE_TASK}
- * @param icon filter case icon
- * @param allowedNets List of process identifiers
- * @param visibility Possible values: {@value DefaultFiltersRunner#FILTER_VISIBILITY_PRIVATE} or {@value DefaultFiltersRunner#FILTER_VISIBILITY_PUBLIC}
- * @param filterMetadata metadata for filter. If no value is provided, then default value is used: {@link #defaultFilterMetadata(String)}
- *
- * @return created {@link Case} instance of filter
- */
- @NamedVariant
- Case createFilter(def title, String query, String type, List allowedNets,
- String icon, String visibility, def filterMetadata) {
- FilterBody body = new FilterBody()
- body.setTitle((title instanceof I18nString) ? title : new I18nString(title as String))
- body.setQuery(query)
- body.setType(type)
- body.setAllowedNets(allowedNets)
- body.setIcon(icon)
- body.setVisibility(visibility)
- body.setMetadata(filterMetadata)
- return menuItemService.createFilter(body)
- }
-
- /**
- * Changes data of provided filter instance. These attributes can be changed:
- *
*
* @param identifier the unique identifier of the template to retrieve
- * @return an Optional containing the Template instance if found, or empty Optional if no template exists for the given identifier
+ * @return an Optional containing the MenuItemBody instance if found, or empty Optional if no template exists for the given identifier
*/
- public static Optional get(String identifier) {
- return Optional.ofNullable(templates.get(identifier));
+ public static Optional get(String identifier) {
+ return Optional.ofNullable(templates.get(identifier)).map(Template::getTemplate);
+ }
+
+ /**
+ * Retrieves a template by its unique identifier and configures it with the provided URI and menu item identifier.
+ *
+ * This method looks up the Template instance associated with the provided identifier from the templates registry,
+ * and then configures the template's body with the specified URI and menu item identifier.
+ *
+ *
+ * @param identifier the unique identifier of the template to retrieve
+ * @param uri the URI to set on the template's body
+ * @param menuItemIdentifier the menu item identifier to set on the template's body
+ * @return an Optional containing the configured MenuItemBody instance if found, or empty Optional if no template exists for the given identifier
+ */
+ public static Optional get(String identifier, String uri, String menuItemIdentifier) {
+ return get(identifier).map(body -> {
+ body.setUri(uri);
+ body.setIdentifier(menuItemIdentifier);
+ return body;
+ });
+ }
+
+ /**
+ * Retrieves a template by its unique identifier and configures it with the provided URI and menu name.
+ *
+ * This method looks up the Template instance associated with the provided identifier from the templates registry,
+ * and then configures the template's body with the specified URI and internationalized menu name.
+ * The menu item identifier is automatically generated by sanitizing the default menu name value.
+ * If the default value is null or blank, the first available translation is used instead.
+ *
+ *
+ * @param identifier the unique identifier of the template to retrieve
+ * @param uri the URI to set on the template's body
+ * @param menuName the internationalized menu name to set on the template's body
+ * @return an Optional containing the configured MenuItemBody instance if found, or empty Optional if no template exists for the given identifier
+ * @throws IllegalArgumentException if the menu name has no default value or translations
+ */
+ public static Optional get(String identifier, String uri, I18nString menuName) {
+ return get(identifier).map(body -> {
+ body.setUri(uri);
+ body.setMenuName(menuName);
+ String defaultName = menuName.getDefaultValue();
+ if (defaultName == null || defaultName.isBlank()) {
+ defaultName = menuName.getTranslations().values().stream()
+ .findAny()
+ .orElseThrow(() -> new IllegalArgumentException("Please provide a name for the menu item"));
+ }
+ body.setIdentifier(MenuItemUtils.sanitize(defaultName));
+ return body;
+ });
}
/**
From c133fd7dd48c81ddcba51cd06c7f5aca08206beb Mon Sep 17 00:00:00 2001
From: chvostek
Date: Tue, 2 Jun 2026 10:12:22 +0200
Subject: [PATCH 45/49] [NAE-2439] Implement new filter data types - fix
MenuItemServiceTest
---
.../engine/menu/MenuItemServiceTest.java | 107 ++++++++----------
1 file changed, 48 insertions(+), 59 deletions(-)
diff --git a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
index 4f6233bcaf3..07ef28401f2 100644
--- a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
+++ b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
@@ -120,13 +120,11 @@ public void createMenuItemTest() throws TransitionNotExecutableException {
Map templateOptions = MenuItemTemplateHolder.transformToOptions();
templateOptions.keySet().forEach((templateIdentifier) -> {
- Optional templateOpt = MenuItemTemplateHolder.get(templateIdentifier);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
- menuItemBody.setUri(uriService.getRoot().getUriPath());
- menuItemBody.setIdentifier(templateIdentifier);
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(templateIdentifier,
+ uriService.getRoot().getUriPath(), templateIdentifier);
+ assertTrue(menuItemBodyOpt.isPresent());
try {
- createByTemplateAndAssert(menuItemBody);
+ createByTemplateAndAssert(menuItemBodyOpt.get());
} catch (TransitionNotExecutableException e) {
throw new RuntimeException(e);
}
@@ -198,9 +196,9 @@ private void createByTemplateAndAssert(MenuItemBody menuItemBody) throws Transit
@SuppressWarnings("unchecked")
private void createAndAssertDetailed() throws TransitionNotExecutableException {
- Optional templateOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER);
+ assertTrue(menuItemBodyOpt.isPresent());
+ MenuItemBody menuItemBody = menuItemBodyOpt.get();
menuItemBody.setUri("/netgrif/test");
menuItemBody.setIdentifier("new_menu_item");
menuItemBody.setMenuIcon("device_hub");
@@ -299,9 +297,9 @@ private void createAndAssertDetailed() throws TransitionNotExecutableException {
public void updateMenuItemTest() throws TransitionNotExecutableException {
assertThrows(IllegalArgumentException.class, () -> menuItemService.updateMenuItem(null, null));
- Optional templateOpt = MenuItemTemplateHolder.get(SimpleTaskViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(SimpleTaskViewTemplate.IDENTIFIER);
+ assertTrue(menuItemBodyOpt.isPresent());
+ MenuItemBody menuItemBody = menuItemBodyOpt.get();
menuItemBody.setUri(uriService.getRoot().getUriPath());
menuItemBody.setIdentifier("test");
Case menuItemCase = menuItemService.createMenuItem(menuItemBody);
@@ -315,13 +313,11 @@ public void updateMenuItemTest() throws TransitionNotExecutableException {
assertThrows(IllegalArgumentException.class, () -> menuItemService.updateMenuItem(menuItemCase, null));
assertThrows(IllegalArgumentException.class, () -> menuItemService.updateMenuItem(menuItemCase, new MenuItemBody()));
- templateOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- menuItemBody = templateOpt.get().getTemplate();
- menuItemBody.setUri(uriService.getRoot().getUriPath());
- menuItemBody.setIdentifier("test");
+ menuItemBodyOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER, uriService.getRoot().getUriPath(),
+ "test");
+ assertTrue(menuItemBodyOpt.isPresent());
- Case updatedMenuItemCase = menuItemService.updateMenuItem(menuItemCase, menuItemBody);
+ Case updatedMenuItemCase = menuItemService.updateMenuItem(menuItemCase, menuItemBodyOpt.get());
assertThrows(IllegalArgumentException.class, () -> workflowService.findOne(oldMenuItemCaseId));
assertThrows(IllegalArgumentException.class, () -> workflowService.findOne(oldViewCaseId));
@@ -342,14 +338,12 @@ public void updateMenuItemTest() throws TransitionNotExecutableException {
public void createOrUpdateMenuItemTest() throws TransitionNotExecutableException {
assertThrows(IllegalArgumentException.class, () -> menuItemService.createOrUpdateMenuItem(null));
- Optional templateOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
- menuItemBody.setUri(uriService.getRoot().getUriPath());
- menuItemBody.setIdentifier("test");
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER,
+ uriService.getRoot().getUriPath(), "test");
+ assertTrue(menuItemBodyOpt.isPresent());
- Case firstMenuItemCase = menuItemService.createOrUpdateMenuItem(menuItemBody);
- Case secondMenuItemCase = menuItemService.createOrUpdateMenuItem(menuItemBody);
+ Case firstMenuItemCase = menuItemService.createOrUpdateMenuItem(menuItemBodyOpt.get());
+ Case secondMenuItemCase = menuItemService.createOrUpdateMenuItem(menuItemBodyOpt.get());
assertNotEquals(firstMenuItemCase.getStringId(), secondMenuItemCase.getStringId());
assertThrows(IllegalArgumentException.class, () -> workflowService.findOne(firstMenuItemCase.getStringId()));
@@ -359,14 +353,12 @@ public void createOrUpdateMenuItemTest() throws TransitionNotExecutableException
public void createOrIgnoreMenuItemTest() throws TransitionNotExecutableException {
assertThrows(IllegalArgumentException.class, () -> menuItemService.createOrIgnoreMenuItem(null));
- Optional templateOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
- menuItemBody.setUri(uriService.getRoot().getUriPath());
- menuItemBody.setIdentifier("test");
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER,
+ uriService.getRoot().getUriPath(), "test");
+ assertTrue(menuItemBodyOpt.isPresent());
- Case firstMenuItemCase = menuItemService.createOrIgnoreMenuItem(menuItemBody);
- Case secondMenuItemCase = menuItemService.createOrIgnoreMenuItem(menuItemBody);
+ Case firstMenuItemCase = menuItemService.createOrIgnoreMenuItem(menuItemBodyOpt.get());
+ Case secondMenuItemCase = menuItemService.createOrIgnoreMenuItem(menuItemBodyOpt.get());
assertEquals(firstMenuItemCase.getStringId(), secondMenuItemCase.getStringId());
}
@@ -376,9 +368,9 @@ public void findMenuItemByIdentifierTest() throws TransitionNotExecutableExcepti
assertNull(menuItemService.findMenuItem(null));
assertNull(menuItemService.findMenuItem("wrong"));
- Optional templateOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER);
+ assertTrue(menuItemBodyOpt.isPresent());
+ MenuItemBody menuItemBody = menuItemBodyOpt.get();
menuItemBody.setUri(uriService.getRoot().getUriPath());
String identifier = "test";
menuItemBody.setIdentifier(identifier);
@@ -393,13 +385,10 @@ public void findFolderCaseTest() throws TransitionNotExecutableException {
assertThrows(IllegalArgumentException.class, () -> menuItemService.findFolderCase(null));
assertNull(menuItemService.findFolderCase(new UriNode()));
- Optional templateOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
- menuItemBody.setUri("/folderik");
- menuItemBody.setIdentifier("test");
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER, "/folderik", "test");
+ assertTrue(menuItemBodyOpt.isPresent());
- Case createdMenuItemCase = menuItemService.createMenuItem(menuItemBody);
+ Case createdMenuItemCase = menuItemService.createMenuItem(menuItemBodyOpt.get());
String parentFolderCaseId = MenuItemUtils.getCaseIdFromCaseRef(createdMenuItemCase, MenuItemConstants.FIELD_PARENT_ID);
assertNotNull(parentFolderCaseId);
UriNode node = uriService.findById(createdMenuItemCase.getUriNodeId());
@@ -412,9 +401,9 @@ public void existsMenuItemTest() throws TransitionNotExecutableException {
assertFalse(menuItemService.existsMenuItem(null));
assertFalse(menuItemService.existsMenuItem("wrong"));
- Optional templateOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER);
+ assertTrue(menuItemBodyOpt.isPresent());
+ MenuItemBody menuItemBody = menuItemBodyOpt.get();
menuItemBody.setUri(uriService.getRoot().getUriPath());
String identifier = "test";
menuItemBody.setIdentifier(identifier);
@@ -425,9 +414,9 @@ public void existsMenuItemTest() throws TransitionNotExecutableException {
@Test
public void moveItemTest() throws TransitionNotExecutableException {
- Optional templateOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER);
+ assertTrue(menuItemBodyOpt.isPresent());
+ MenuItemBody menuItemBody = menuItemBodyOpt.get();
menuItemBody.setUri("/netgrif/test");
menuItemBody.setIdentifier("new_menu_item");
Case newMenuItemCase = menuItemService.createMenuItem(menuItemBody);
@@ -486,9 +475,9 @@ public void moveItemTest() throws TransitionNotExecutableException {
@Test
public void duplicateFolderItemTest() throws TransitionNotExecutableException {
String starterUri = "/netgrif/test";
- Optional templateOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER);
+ assertTrue(menuItemBodyOpt.isPresent());
+ MenuItemBody menuItemBody = menuItemBodyOpt.get();
menuItemBody.setUri(starterUri);
menuItemBody.setIdentifier("new_menu_item");
menuItemService.createMenuItem(menuItemBody);
@@ -539,9 +528,9 @@ public void duplicateFolderItemTest() throws TransitionNotExecutableException {
@SuppressWarnings("unchecked")
public void duplicateLeafItemTest() throws TransitionNotExecutableException {
String starterUri = "/netgrif/test";
- Optional templateOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER);
+ assertTrue(menuItemBodyOpt.isPresent());
+ MenuItemBody menuItemBody = menuItemBodyOpt.get();
menuItemBody.setUri(starterUri);
menuItemBody.setIdentifier("new_menu_item");
Case originLeafItemCase = menuItemService.createMenuItem(menuItemBody);
@@ -592,9 +581,9 @@ public void duplicateLeafItemTest() throws TransitionNotExecutableException {
@Test
public void removeChildItemFromParentTest() throws TransitionNotExecutableException {
- Optional templateOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER);
+ assertTrue(menuItemBodyOpt.isPresent());
+ MenuItemBody menuItemBody = menuItemBodyOpt.get();
menuItemBody.setUri("/folderik");
menuItemBody.setIdentifier("test");
@@ -709,9 +698,9 @@ public void getAvailableViewsAsOptionsByViewIdentifierTest() {
@Test
void testRemoveMenuItem() throws TransitionNotExecutableException, InterruptedException {
- Optional templateOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER);
- assertTrue(templateOpt.isPresent());
- MenuItemBody menuItemBody = templateOpt.get().getTemplate();
+ Optional menuItemBodyOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER);
+ assertTrue(menuItemBodyOpt.isPresent());
+ MenuItemBody menuItemBody = menuItemBodyOpt.get();
menuItemBody.setUri("/netgrif/test");
menuItemBody.setIdentifier("new_menu_item");
Case leafItemCase = menuItemService.createMenuItem(menuItemBody);
From a3a07d431dbca6dc667283f2568e7f0c0764b23e Mon Sep 17 00:00:00 2001
From: chvostek
Date: Tue, 2 Jun 2026 12:02:38 +0200
Subject: [PATCH 46/49] [NAE-2439] Implement new filter data types - set up
headers for task lists
---
.../engine-processes/menu/case_view_configuration.xml | 2 +-
.../resources/petriNets/engine-processes/menu/menu_item.xml | 2 +-
.../engine-processes/menu/single_task_view_configuration.xml | 3 +--
.../engine-processes/menu/tabbed_ticket_view_configuration.xml | 2 +-
.../engine-processes/menu/task_view_configuration.xml | 2 +-
5 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
index 24888a9c39b..b0fcf009026 100644
--- a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
@@ -282,8 +282,8 @@
advanced_content_form
-
task-list
+ meta-caseId,meta-title
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 93d9f29867f..1fae6289dcd 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -651,7 +651,7 @@
task-list
-
+ meta-title
diff --git a/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml
index b2a823074b8..6169f762470 100644
--- a/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml
@@ -48,8 +48,8 @@
advanced_content_form
-
task-list
+ meta-title
@@ -118,7 +118,6 @@
112settings
-
admin
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
index 0c3a9eae3aa..aef7d669064 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
@@ -104,8 +104,8 @@
advanced_content_form
-
task-list
+ meta-title
diff --git a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
index 13646f442a8..339e89b52d6 100644
--- a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
@@ -162,8 +162,8 @@
advanced_content_form
-
task-list
+ meta-caseId,meta-title
From 9c02234a8acff251766da78cbf1ab3948262c34b Mon Sep 17 00:00:00 2001
From: chvostek
Date: Tue, 2 Jun 2026 14:46:59 +0200
Subject: [PATCH 47/49] [NAE-2439] Implement new filter data types - fix uri
change of menu item on creation
---
.../engine-processes/menu/menu_item.xml | 26 +++++++++----------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 1fae6289dcd..7540df96c56 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -928,19 +928,6 @@
change useCase.getField(fieldId) value { value }
}
-
- name: f.menu_name,
- menu_item_identifier: f.menu_item_identifier,
- menu_item_identifier_on_create: f.menu_item_identifier_on_create,
- dest: f.move_dest_uri;
-
- change menu_item_identifier value { menu_item_identifier_on_create.value }
-
- String newUri = dest.value.join("/")
- newUri = newUri.replace("//","/")
-
- changeMenuItem useCase uri { newUri }
-
trans: t.view_settings,
useTabIcon: f.use_tab_icon,
@@ -963,6 +950,19 @@
initializeAdvancedForm()
+
+ name: f.menu_name,
+ menu_item_identifier: f.menu_item_identifier,
+ menu_item_identifier_on_create: f.menu_item_identifier_on_create,
+ dest: f.move_dest_uri;
+
+ change menu_item_identifier value { menu_item_identifier_on_create.value }
+
+ String newUri = dest.value.join("/")
+ newUri = newUri.replace("//","/")
+
+ changeMenuItem useCase uri { newUri }
+ Create
From 97e9c1228098fa38cdc88a0b5b1d05c10ab0b843 Mon Sep 17 00:00:00 2001
From: chvostek
Date: Tue, 2 Jun 2026 15:22:59 +0200
Subject: [PATCH 48/49] [NAE-2439] Implement new filter data types - add
translation - update doc
---
docs/_sidebar.md | 1 -
docs/search/filter.md | 846 ------------------
.../menu/case_view_configuration.xml | 58 +-
.../engine-processes/menu/menu_item.xml | 12 +-
.../menu/single_task_view_configuration.xml | 20 +-
.../menu/tabbed_ticket_view_configuration.xml | 4 +-
.../menu/task_view_configuration.xml | 48 +-
7 files changed, 107 insertions(+), 882 deletions(-)
delete mode 100644 docs/search/filter.md
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index d4ef204c9a9..6679165c0b7 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -7,7 +7,6 @@
* [Event queue](events/event_queue.md)
* [Search]()
* [Elastic](search/elastic_mapping.md)
- * [Filter](search/filter.md)
* [Permissions]()
* [User list](roles/userlist.md)
* [Permissions](roles/permissions.md)
diff --git a/docs/search/filter.md b/docs/search/filter.md
deleted file mode 100644
index a227d7707da..00000000000
--- a/docs/search/filter.md
+++ /dev/null
@@ -1,846 +0,0 @@
-# Developers guide to the Filter process
-
-This document contains information about the Filter process and the API provided by the application engine for
-interacting with the instances of the Filter process.
-
-## Filter data variable
-
-This data variable stores a
-generated [elastic query string query](https://www.elastic.co/guide/en/elasticsearch/reference/6.6/query-dsl-query-string-query.html)
-as it’s value.
-
-It has two additional properties that store the metadata necessary for the reconstruction of a filter.
-
-* _**allowedNets**_ - similar to the case ref data variable type, a list of identifiers of “allowed nets“ is stored in
- the filter field. The stored nets represent the nets that can be used to construct search queries within the advanced
- search component.
-* _**filterMetadata**_ - data necessary for reconstructing the state of the advanced search component. The backend
- currently does not provide any utility methods for construction of the metadata object. An interface for this object
- can be found in
- the [documentation](https://developer.netgrif.com/projects/engine-frontend/5.4.1/nae/docs/interfaces/FilterMetadata.html)
- .
-
-Beware that while a filter field does not throw an error when displayed in editable mode and changes can be made to its
-frontend representation, these changes are not propagated to backend and do not fire a set data event on the field.
-Because of this we recommend only using filter fields in visible mode, to display their values set by the frontend
-filter API.
-
-Neither title nor any other meta attribute of the filter field (such as description) is displayed as part of the filter
-field component.
-
-
-
-*an editable filter field*
-
-
-
-*a visible filter field*
-
-
-
-*an immediate filter field displayed on a panel*
-
-## Filter process
-
-[//]: # (todo 23)
-
-The engine filter process is located under `resources/petriNets/engine-processes/filter.xml`
-
-
-
-The process is relatively simple. It stores and makes accessible the data necessary for the persistence of filters
-created by users of the application engine. All the process logic, the model contains is focussed solely on the creation
-process of the filter before it reaches its "initialized" state denoted by `p2`.
-
-The process can be overridden by the developers, by placing a net with the same file name and identifier into the
-location specified above. The process defines a “process interface” that must remain unchanged when overriding the
-process for the frontend APIs that connects to the process to work correctly. Attributes of the process that are part of
-the “process interface“ and must remain unchanged will be highlighted.
-
-### Filter process interface
-
-#### Data variables
-
-##### Filter
-
-[//]: # (todo 23)
-
-```xml
-
- filter
- Filter
-
-```
-
-This field contains the saved filter.
-
-It must be `immediate`, have the ID `filter` and be of type `filter`.
-
-##### Filter type
-
-```xml
-
- filter_type
- Filter type
-
-
-
-
-
-```
-
-This data variable stores the type of the filter stored in the filter field of the filter process. The keys are used for
-filtering of the filter processes.
-
-It must be `immediate`, have the ID `filter_type` and be of type `enumeration_map`. The available options must have the
-keys `Case` and `Task`. The values associated with these keys are not specified by the Filter process interface and can
-therefore be freely overridden.
-
-##### Filter visibility
-
-```xml
-
- visibility
- Filter visibility
-
-
-
-
- public
-
-```
-
-This data variable can be used for filtering of the filter processes based on some internal rules. The application
-engine queries this variable when listing filter during navigation menu customisation and during filter export. Only
-public and private filters authored by the logged user are offered as options during these selection processes.
-
-No universal restriction is applied to the search queries on these instances, however. The developer must ensure that no
-inappropriate filters become available to the user during any search at their own discretion. The queries used by the
-application engine can be overridden, by extending the `IUserFiltersSearchService` interface.
-
-It must be `immediate`, have the ID `visibility` and be of type `enumeration_map`. The available options must have the
-keys `public` and `private`. The values associated with these keys are not specified by the Filter process interface and
-can therefore be freely overridden.
-
-##### Origin ViewId
-
-```xml
-
- origin_view_id
- Origin ViewId
-
-```
-
-The viewId of the view where the filter was created is stored in this data variable.
-Is only set if the filter originates from a view with an in-app filter.
-
-When the field is set an action is triggered, that resolves the parent filter (if any) and populates the data variables
-that are responsible for displaying the parent filter with the appropriate values.
-
-It must be `immediate`, have the ID `origin_view_id` and be of type `text`.
-
-##### Parent filter Id
-
-```xml
-
- parent_filter_id
- parent filter ID
-
-```
-
-The case ID of the parent filter case (if any). Is only set if the filter originates from a different filter case (usually via configurable menu).
-
-When the field is set an action is triggered, that resolves the parent filter (if any) and populates the data variables
-that are responsible for displaying the parent filter with the appropriate values.
-
-It must have the ID `parent_filter_id` and be of type `text`.
-
-##### Filter name
-
-```xml
-
- new_title
- Filter name
-
-```
-
-This data variable is used to change the title of the newly created filter case according to the wishes of the user.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted.
-
-##### I18n filter name
-
-```xml
-
- i18n_filter_name
-
-
-```
-
-Contains the name of the filter with locale specific translations. Its value is used to generate configurable group
-navigation menu entry titles.
-
-It must be `immediate`, have the ID `i18n_filter_name` and be of type `enumeration`.
-
-The previously mentioned `new_title` data variable has a set action, that pushes its value into this variable. Since
-the `new_title` variable is a plain text field, a `I18nString` object with only its default value set is put into this
-variable. This field however does support translations, so localised menu entries can be created, or their names changed
-trough actions.
-
-##### Filter case id
-
-```xml
-
- filter_case_id
-
-
-```
-
-This variable is set during the case creation and stores the ID of this case. Since the frontend integration accesses
-the filter process mostly trough tasks, this field can be referenced in any of those tasks to grant direct access to the
-case ID, without having to execute an extra search query. The frontend integration does not use this variable currently,
-but it can be used in custom integrations.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted.
-
-##### And me
-
-```xml
-
- and_me
- AND
-
-```
-
-Stores the translated text of the word "AND" in order to create a visual representation of the true filter content. More
-information about this can be found in the transition section.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted.
-
-##### And view
-
-```xml
-
- and_view
- AND
-
-```
-
-Similarly to the `and_me` data variable, this too stores the translated text of the word "AND" in order to create a
-visual representation of the true filter content.
-
-This variable is referenced in the action associated with the `origin_view_id` data variable and on the `view_filter`
-and `view_as_ancestor` transitions. If it were to be removed this must be taken into the account.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted.
-
-##### Parent filter task ref
-
-```xml
-
- taskref_and_parent
-
-
-```
-
-Similarly to the `and_view` data variable, this too is used to create the visual representation of the true filter
-content, because it stores a reference to the task in the parent filter process that contains the true visualisation of
-the parent filter.
-
-Just like the `and_view` data variable, this too is referenced in the action associated with the `origin_view_id` data
-variable and on the `t1` and `t2` transitions. If it were to be removed this must be taken into the account.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted.
-
-##### Trimmed origin view id
-
-```xml
-
- trimmed_origin_view_id
- Filter from view with ID
-
-```
-
-Similarly to the `and_view` data variable, this too is used to create the visual representation of the true filter
-content, because it stores a trimmed version of the origin view id, that does not contain any tabbed view identifiers.
-
-Just like the `and_view` data variable, this too is referenced in the action associated with the `origin_view_id` data
-variable and on the `t1` and `t2` transitions. If it were to be removed this must be taken into the account.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted.
-
-##### Is imported
-
-```xml
-
- is_imported
-
- 0
-
-```
-
-This is the reference value for variable arc to decide, if the filter is imported by the Import filter process or is
-automatically created by the backend. When set, it sets the value of the `is_not_imported` data variable to `0`.
-
-It must have the ID `is_imported`, be of type `number` and have the initial value `0`.
-
-##### Is not imported
-
-```xml
-
- is_not_imported
-
- 1
-
-```
-
-This is the reference value for the alternative variable arc to decide, if the filter is imported by the Import filter
-process or is automatically created by the backend.
-
-It must have the ID `is_not_imported`, be of type `number` and have the initial value `1`.
-
-##### Missing allowed nets
-
-```xml
-
- missing_allowed_nets
- Missing processes
- List of missing processes for current filter
-
- htmltextarea
-
-
-```
-
-The purpose of this textarea is to show the list of missing allowed nets for the current (imported) filter. It is
-displayed in `import_filters` transition while importing new filter from XML.
-
-It must have the ID `missing_allowed_nets`, be of type `text` and have the component `htmltextarea`.
-
-##### Missing nets translations
-
-```xml
-
- missing_nets_translation
-
-
-
-
-
-
-
-```
-
-The purpose of this map filed is to translate the message in the `missing_allowed_nets` HTML textarea, because the
-message in the textarea is dynamically generated depending on the active locale.
-
-It must have the ID `missing_nets_translation` and be of type `enumeration_map`.
-
-##### My full filter
-
-```xml
-
- my_full_filter
-
-
-```
-
-This task ref holds a reference to the view_filter transition and is displayed in all tasks that should show the contents of the saved filter.
-
-It is not referenced by anything outside the process, so it can be freely modified, provided all the internal references are compatible.
-
-#### Functions
-
-##### initializeMyFullFilterTaskRef
-
-```xml
-
- {
- com.netgrif.application.engine.petrinet.domain.dataset.TaskField myFullFilter
- ->
- change myFullFilter value {return [findTask({it.caseId.eq(useCase.stringId).and(it.transitionId.eq("view_filter"))}).stringId]}
- }
-
-```
-
-This utility process-scoped function is used to set the value of the `my_full_filter` task ref data variable.
-
-It is called in a finish-post action of both the `frontend_create` and `auto_create` transitions,
-since their execution makes the searched transition (`view_filter`) executable.
-
-#### Roles
-
-##### System
-
-The system role is used to hide the `view_filter` and `view_as_ancestor` tasks from the user.
-
-The other transitions executed by the system are never searched for by the frontend API and therefore don't need to have this role assigned to them.
-
-#### Transitions
-
-The net consists of seven transitions. **Set filter metadata** and **create new filter** are used when creating filter
-from frontend filter API.
-**Automated new** is used, when creating filter from backend automatically. **Import filter** transition is used when
-importing filters in the
-_import\_filters_ process. The last three transitions **Details**, **View filter** and **View as ancestor** are used to
-display filter preview.
-
-##### Set filter metadata
-
-```xml
-
- frontend_create
-
-
-
- filter
-
- editable
-
-
-
- filter_type
-
- editable
-
-
-
- origin_view_id
-
- editable
-
-
-
-
-```
-
-This transition is executed automatically by the frontend filter API in order to set the necessary filter data into the
-process instance, when a new filter is created (saved).
-
-It must have the transition ID `frontend_create`.
-
-It must contain 4 data fields that are set via an API request:
-
-* `filter`
-* `filter_type`
-* `origin_view_id`
-* `parent_filter_id`
-
-Custom set data requests can be sent via the frontend filter API. These requests target this task. Therefore, if you
-want to set additional properties to your newly created filter instances you can do so by adding the necessary data
-variables to this task and then use the frontend API to set their values.
-
-Once it finishes the `view_filter` and `view_as_ancestor` tasks become executable.
-An action is bound to the finish of this transition, that sets the reference of the `my_full_filter` task ref.
-
-##### Create new filter
-
-```xml
-
- newFilter
- auto
-
-
-
- my_full_filter
-
- visible
-
-
-
-
-
- visibility
-
- editable
- required
-
-
-
- new_title
-
- editable
- required
-
-
-
-
-```
-
-This transition is displayed to the user in the side menu when a new filter case instance is created (a filter is saved)
-through the frontend filter API.
-
-It can reference any filter data variables. The default implementation references, the filter and its type in visible
-mode (they were set in the previous step), an editable visibility choice and an editable text field for setting the new
-filter case title (via a finish action).
-
-It must have the transition ID `newFilter`.
-
-##### Automated new filter
-
-```xml
-
- auto_create
-
-
-
- filter
-
- editable
-
-
-
- filter_type
-
- editable
-
-
-
- visibility
-
- editable
-
-
-
- origin_view_id
-
- editable
-
-
-
-
-```
-
-This transition is executed by the `DefaultFiltersRunner`, in order to set the data present in the previous two
-transitions with a single `setData` operation.
-
-It must have the transition ID `auto_create`.
-
-It must contain 5 data fields that are set via a `setData` API call.
-
-* `filter`
-* `filter_type`
-* `origin_view_id`
-* `parent_filter_id`
-* `visibility`
-
-Once it finishes the `view_filter` and `view_as_ancestor` tasks become executable.
-An action is bound to the finish of this transition, that sets the reference of the `my_full_filter` task ref.
-
-##### Import filter
-
-```xml
-
- import_filter
-
-
-
- new_title
-
- editable
-
-
-
- filter_type
-
- visible
-
-
-
- visibility
-
- editable
-
-
-
- missing_allowed_nets
-
- hidden
-
-
-
-
-
- my_full_filter
-
- visible
-
-
-
-
-```
-
-This transition is used in the `import_filters` process to allow editing of filter name and filter visibility when
-importing a new filter.
-
-It must have the transition ID `import_filter`.
-
-It should contain at least these data fields to display to the user which filter is being imported and allow them to
-change some fundamental filter properties:
-
-* `new_title` (editable)
-* `filter_type` (visible)
-* `visibility` (editable)
-* `my_full_filter` (visible)
-* `missing_allowed_nets` (hidden) - made visible by the import action if some nets are missing
-
-##### Details
-
-```xml
-
- t2
-
-
-
- filter
-
- visible
-
-
-
- filter_type
-
- visible
-
-
-
- visibility
-
- visible
-
-
-
-
-```
-
-This transition makes the filter metadata accessible to the users of the application.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted. Beware however, that it is referenced by the `origin_view_id` actions. This must be taken into the account when
-omitting this transition.
-
-##### View filter
-
-```xml
-
- view_filter
-
-
-
- filter
-
- visible
-
-
-
- filter_case_id
-
- hidden
-
-
-
- origin_view_id
-
- forbidden
-
-
-
- taskref_and_parent
-
- visible
-
-
-
-
- DataGroup_1
- grid
-
- and_view
-
- forbidden
-
-
-
- trimmed_origin_view_id
-
- forbidden
-
-
-
-
-```
-
-This transition displays the true content of the filter represented by the process. It can be task-reffed to show a
-referenced filter somewhere (such as in the configurable group navigation entries and filter previews). In addition to
-the filter field itself the ancestor filters are displayed here as well. Each of them is prefixed with an AND text to
-indicate that they are combined with the original filter with the **and** operator.
-
-If the root of the filter chain is a filter process (indicated by its `parent_filter_id` having a non-empty value)
-the root filters content will be the last entry displayed. If the root is a frontend filter
-(indicated by its `origin_view_id` field being set to a non-empty string) the last entry will
-contain a text indicating the view from which the filter originates. The displayed view ID excludes any tab suffixes.
-
-If the filter does not originate from a view its `origin_view_id` field must have its behavior set to `forbidden`,
-so that the frontend parsing mechanism won't find false positives during the interpretation of the filter.
-
-It must have the transition ID `view_filter`.
-
-It must contain 1 data field:
-
-* `filter`
-
-##### View as ancestor
-
-```xml
-
- view_as_ancestor
-
-
-
- and_me
-
- visible
-
-
-
- filter
-
- visible
-
-
-
- taskref_and_parent
-
- visible
-
-
-
-
- DataGroup_1
- grid
-
- and_view
-
- forbidden
-
-
-
- trimmed_origin_view_id
-
- forbidden
-
-
-
-
-```
-
-Contains the same information as the View filter task, but every row is prefixed with an AND text (including the first
-row that contains the filter field of this case).
-
-This task is referenced when resolving ancestor filters from the immediate children of this filter.
-
-## Backend filter API
-
-Since filters are implemented as Petriflow processes, they share the same API with any other process. The old filter
-related services, controllers and other classes have been deprecated in the 5.4.0 release and should no longer be used.
-
-There are a few things to keep in mind when working with filters.
-
-### Using filter fields for filtering of cases and tasks
-
-You can use the value and other attributes of a filter field to create database queries.
-
-When doing so, make sure to check if the filter has the appropriate type (that is only use Case filters to filter cases
-and Task filters to filter tasks).
-
-The value attribute of a filter field is
-an [elastic query string query](https://www.elastic.co/guide/en/elasticsearch/reference/6.6/query-dsl-query-string-query.html) (
-as mentioned above) and therefore cannot be used to filter entries from the Mongo database. It can however be turned
-into a `CaseSearchRequest` class instance, by populating the `query` attribute with the filter field value. This object
-can then be passed to the `ElasticCaseService` to find the cases that match the query. If you want to search for task
-instances then analogous classes and services exist.
-
-### Actions API
-
-Filter fields have only a limited support in the actions API.
-
-Only their value can be changed with the `change` action and this change is NOT propagated to the other filter field
-attributes (allowedNets and filterMetadata). Since these two attributes are the important part when restoring an
-advanced search component state on the frontend it can easily lead to “desynchronisation“ of these values. Changing
-these values should therefore be done cautiously.
-
-### Set data method
-
-The set data method of the `TaskDataService` can be used to modify all the attributes of a filter field. All of them
-must be set at the same time, any missing attributes will have its value set to `null`.
-
-### DefaultFiltersRunner
-
-This runner contains utility methods that can be used to create default system filter process instances. The author of
-these filters will be the system user.
-
-## Frontend filter API
-
-Since saved filters are cases you should use the existing API for interacting with cases. Utility methods exist for some
-operations. You should keep in mind however that, the frontend library does not provide a full filter persistence and
-management implementation, only the building blocks for creating one, therefore you must implement it on your own
-according to your wishes. An overview of these building blocks can be found in the following sections.
-
-### Filter constants
-
-The
-enum [UserFilterConstants](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/miscellaneous/enumerations.html#UserFilterConstants)
-contains identifiers and IDs of all parts of the filter process referenced by the frontend filters API.
-
-### UserFiltersService
-
-The [UserFiltersService](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/injectables/UserFiltersService.html)
-contains the API for saving, loading and deleting filter process instances.
-
-The search component contains buttons that trigger the save and load methods, so you do not have to use this service
-directly, but if you wish to create your own user interface you can re-use the implementation provided by this service.
-
-Injection tokens are available for the configuration of the side menu components used to save and load
-filters (`NAE_SAVE_FILTER_COMPONENT` and `NAE_LOAD_FILTER_COMPONENT`). An injection token is also available for
-configuring the filter that filters the filter process instances displayed when loading a filter (`NAE_FILTERS_FILTER`).
-
-### SearchComponent
-
-The [SearchComponent](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/classes/AbstractSearchComponent.html)
-is a wrapper for the two search mode components (fulltext and advanced)
-these components can be used independently of each other and the search component combines them into one and adds
-various control elements to them.
-
-The search component has two outputs - `filterLoaded` and `filterSaved`. These emit data related to either the saved
-filter, or the selected filter in the load pop-up. The developers must process these outputs in order to implement fully
-functional filter management.
-
-The component also has na input - `additionalFilterData`. This input can be used to modify the content of the set data
-request that is sent by the `UserFiltersService`, when a new filter is created. This way a less invasive option is
-available when overriding the default filter process and adding additional metadata necessary for your implementation of
-filter management.
-
-An injection token is available for the configuration of the search component - `NAE_SEARCH_COMPONENT_CONFIGURATION`. It
-provides
-a [SearchComponentConfiguration](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/interfaces/SearchComponentConfiguration.html)
-object that can be used to hide many elements of the search component (buttons mostly). This way you can remove the save
-and/or load filter buttons and therefore not allow the users to persist filters in some specific views.
-
-### SearchService
-
-The [SearchService](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/injectables/SearchService.html)
-provides two complementary methods - `createPredicateMetadata` and `loadFromMetadata`. These can be used to populate the
-search service with a predicate stored inside a filter field (`filterMetadata` attribute). If an advanced search
-component is connected to the search service, then it will automatically display the loaded predicate.
-
-### FilterExtractionService
-
-The [FilterExtractionService](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/injectables/FilterExtractionService.html)
-contains the functionality of extracting a frontend [Filter](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/classes/Filter.html)
-instance from the data of a configurable navigation entry task.
\ No newline at end of file
diff --git a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
index b0fcf009026..bd7228d7f41 100644
--- a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml
@@ -292,13 +292,13 @@
case_empty_content_text
- Empty content text
- If empty, default text will be used
+ Empty content text
+ If empty, default text will be usedcase_empty_content_icon
- Empty content icon
- If empty, default icon will be used
+ Empty content icon
+ If empty, default icon will be used0
@@ -320,15 +320,15 @@
case_empty_content_icon_preview
- Empty content icon preview
+ Empty content icon previewhtmltextareacase_filter
- Filter
- Use Elasticsearch string query
+ Filter
+ Use Elasticsearch string query*
@@ -337,16 +337,16 @@
case_allowed_nets
- Allowed nets
+ Allowed netscase_all_allowed_nets
- All allowed nets?
+ All allowed nets?truecase_inherit_allowed_nets
- Inherit allowed nets?
+ Inherit allowed nets?true
@@ -380,6 +380,20 @@
Typ zobrazeniaNastaveniePovoliť export?
+ Text prázdneho obsahu
+ Ak je toto pole prázdne, bude použitý predvolený text
+ Ikona prázdneho obsahu
+ Ak je toto pole prázdne, bude použitá predvolená ikona
+ Náhľad ikony prázdneho obsahu
+ Filter
+ Použi Elasticsearch string query
+ Povolené procesy
+ Povoliť všetky procesy?
+ Zdediť povolené procesy?
+ Filter
+ Zobrazenie po kliknutí na inštanciu
+ Tlačidlo vytvorenia inštancie
+ HlavičkySchaltflächentitel "Neuer Fall"
@@ -410,6 +424,22 @@
Der AnsichtstypEinstellungenErlaube Export?
+ Leerer Inhaltstext
+ Wenn dieses Feld leer ist, wird der Standardtext verwendet
+
+ Leeres Inhaltssymbol
+ Wenn dieses Feld leer ist, wird das Standardsymbol verwendet
+
+ Vorschau des leeren Inhaltssymbols
+ Filter
+ Verwenden Sie Elasticsearch String Query
+ Erlaubte Prozesse
+ Alle Prozesse erlauben?
+ Erlaubte Prozesse erben?
+ Filter
+ Ansicht nach dem Klicken auf die Instanz
+ Schaltfläche zum Erstellen einer Instanz
+ Kopfzeilen
@@ -622,7 +652,7 @@
filter_settings43248
-
+
filter_altauto
@@ -677,7 +707,7 @@
open_case_settings33648
-
+
queue_play_nextauto
@@ -748,7 +778,7 @@
create_case_btn_settings65648
-
+
add_boxauto
@@ -878,7 +908,7 @@
header_settings56048
-
+
view_columnauto
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 7540df96c56..438f426a228 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -656,7 +656,7 @@
configuration_templates
- Configuration template
+ Configuration templatecom.netgrif.application.engine.menu.service.MenuItemTemplateHolder.transformToOptions()
@@ -713,6 +713,9 @@
Vytvoriť menu položkuZoradiť pod-položkyVytvoriť menu položku
+ Konfiguračná šablóna
+ Rola
+ ZobrazenieIkonevorschau
@@ -764,6 +767,9 @@
Menüeintrag erstellenUntereintrage sortierenErstellen
+ Konfigurationsvorlage
+ Rolle
+ Ansicht
@@ -1448,7 +1454,7 @@
role_settings40048
-
+
personauto
@@ -1619,7 +1625,7 @@
view_settings52848
-
+
imageauto
diff --git a/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml
index 6169f762470..2929462fd3a 100644
--- a/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml
@@ -54,18 +54,18 @@
show_page_header
- Show page header
+ Show page headertrueshow_page_footer
- Show page footer
+ Show page footerfalsetask_filter
- Filter
- Use Elasticsearch string query
+ Filter
+ Use Elasticsearch string query*
@@ -73,10 +73,20 @@
Vybrať zobrazenieNastavenie
+ Zobraziť hlavičku stránky
+ Zobraziť pätu stránky
+ Filter
+ Použi Elasticsearch string query
+ FilterWählen Sie einen AnsichtstypEinstellungen
+ Seitenkopf anzeigen
+ Seitenfuß anzeigen
+ Filter
+ Elasticsearch-String-Abfrage verwenden
+ Filter
@@ -190,7 +200,7 @@
filter_settings43248
-
+
filter_altauto
diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
index aef7d669064..d306553a1b6 100644
--- a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml
@@ -114,10 +114,12 @@
ZobrazenieNastavenie
+ Ďalšie zobrazenieDer AnsichtstypEinstellungen
+ Nächste Ansicht
@@ -204,7 +206,7 @@
next_view_settings40048
-
+
queue_play_nextauto
diff --git a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
index 339e89b52d6..d06fed2f1e6 100644
--- a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml
@@ -77,8 +77,8 @@
task_filter
- Filter
- Use Elasticsearch string query
+ Filter
+ Use Elasticsearch string query*
@@ -168,13 +168,13 @@
task_empty_content_text
- Empty content text
- If empty, default text will be used
+ Empty content text
+ If empty, default text will be usedtask_empty_content_icon
- Empty content icon
- If empty, default icon will be used
+ Empty content icon
+ If empty, default icon will be used0
@@ -196,23 +196,23 @@
task_empty_content_icon_preview
- Empty content icon preview
+ Empty content icon previewhtmltextareatask_allowed_nets
- Allowed nets
+ Allowed netstask_all_allowed_nets
- All allowed nets?
+ All allowed nets?truetask_inherit_allowed_nets
- Inherit allowed nets?
+ Inherit allowed nets?true
@@ -236,6 +236,18 @@
Zobrazovať menu pre úlohovú položku?Nastavenie položkyVšeobecné
+ Text prázdneho obsahu
+ Ak je toto pole prázdne, bude použitý predvolený text
+ Ikona prázdneho obsahu
+ Ak je toto pole prázdne, bude použitá predvolená ikona
+ Náhľad ikony prázdneho obsahu
+ Povolené procesy
+ Povoliť všetky procesy?
+ Zdediť povolené procesy?
+ Filter
+ Použi Elasticsearch string query
+ Filter
+ HlavičkyVersteckt
@@ -256,6 +268,18 @@
Suchmodus im Aufgabenansicht"Erweiterte Optionen" Taste bei einzelnen Aufgaben anzeigenMenüeintrageinstellungen
+ Text für leeren Inhalt
+ Wenn dieses Feld leer ist, wird der Standardtext verwendet
+ Icon für leeren Inhalt
+ Wenn dieses Feld leer ist, wird das Standard-Icon verwendet
+ Vorschau des Icons für leeren Inhalt
+ Erlaubte Prozesse
+ Alle Prozesse erlauben?
+ Erlaubte Prozesse erben?
+ Filter
+ Elasticsearch String-Query verwenden
+ Filter
+ Kopfzeilen
@@ -454,7 +478,7 @@
filter_settings40048
-
+
filter_altauto
@@ -523,7 +547,7 @@
header_settings59248
-
+
view_columnauto
From 956e72dc54b9230801be552200012ae517bed95e Mon Sep 17 00:00:00 2001
From: chvostek
Date: Wed, 3 Jun 2026 11:18:47 +0200
Subject: [PATCH 49/49] [NAE-2439] Implement new filter data types - resolve PR
comments
---
.../application/engine/startup/MenuRunner.groovy | 4 ++--
.../menu/domain/ConfigurationTemplateOutcome.java | 11 ++++++++---
.../application/engine/menu/domain/FilterBody.java | 3 +++
.../menu/domain/configurations/CaseViewBody.java | 4 ++--
.../engine/menu/service/MenuItemService.java | 5 +++--
.../engine/menu/web/MenuController.java | 5 ++++-
.../web/responsebodies/MenuItemDataResponse.java | 2 +-
.../petriNets/engine-processes/menu/menu_item.xml | 2 +-
.../engine/menu/MenuImportExportTest.groovy | 2 +-
.../engine/menu/MenuItemServiceTest.java | 14 +++++++-------
10 files changed, 32 insertions(+), 20 deletions(-)
diff --git a/src/main/groovy/com/netgrif/application/engine/startup/MenuRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/MenuRunner.groovy
index 5e06e6f9917..472d9bb5a79 100644
--- a/src/main/groovy/com/netgrif/application/engine/startup/MenuRunner.groovy
+++ b/src/main/groovy/com/netgrif/application/engine/startup/MenuRunner.groovy
@@ -29,8 +29,8 @@ class MenuRunner extends AbstractOrderedCommandLineRunner {
helper.importProcess("Petri net for menu item", MenuItemConstants.PROCESS_IDENTIFIER, MENU_ITEM_FILE_NAME)
}
- private List createConfigurationNets() {
- return MenuItemViewType.values().each { view ->
+ private void createConfigurationNets() {
+ MenuItemViewType.values().each { view ->
String processIdentifier = view.getIdentifier() + "_configuration"
String filePath = String.format("engine-processes/menu/%s.xml", processIdentifier)
helper.importProcess(String.format("Petri net for %s", processIdentifier), processIdentifier, filePath)
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java b/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java
index d85be58223f..2b1447fbd93 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java
@@ -1,5 +1,7 @@
package com.netgrif.application.engine.menu.domain;
+import lombok.Getter;
+
import java.util.HashMap;
import java.util.Map;
@@ -7,7 +9,8 @@ public class ConfigurationTemplateOutcome {
/**
* Map of field data where the key is field ID and the value is field value.
*/
- public final Map mapping;
+ @Getter
+ private final Map mapping;
public ConfigurationTemplateOutcome() {
this.mapping = new HashMap<>();
@@ -15,8 +18,10 @@ public ConfigurationTemplateOutcome() {
public ConfigurationTemplateOutcome(ToDataSetOutcome toDataSetOutcome) {
this();
- toDataSetOutcome.getDataSet()
- .forEach((fieldId, fieldMap) -> this.mapping.put(fieldId, fieldMap.get("value")));
+ if (toDataSetOutcome != null) {
+ toDataSetOutcome.getDataSet()
+ .forEach((fieldId, fieldMap) -> this.mapping.put(fieldId, fieldMap.get("value")));
+ }
}
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java
index 4c1c549d23a..bfa7f29634c 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java
@@ -21,6 +21,9 @@ public class FilterBody {
* */
public ToDataSetOutcome toDataSet(ToDataSetOutcome viewDataSetOutcome, String filterFieldId) {
Map dataSetValues = new HashMap<>();
+ if (this.type == null) {
+ throw new IllegalArgumentException("Filter type is not provided");
+ }
dataSetValues.put("type", this.type.getName());
dataSetValues.put("value", this.query);
viewDataSetOutcome.getDataSet().put(filterFieldId, dataSetValues);
diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
index 4fb910b29f7..6a47b3b45b3 100644
--- a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
+++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java
@@ -26,7 +26,7 @@ public class CaseViewBody extends ViewBody {
private List headersMode = new ArrayList<>(List.of("sort", "edit", "search"));
private String headersDefaultMode = "sort";
private List defaultHeaders;
- private boolean isHeaderModeChangeable = true;
+ private boolean headerModeChangeable = true;
private boolean useDefaultHeaders = true;
private I18nString emptyContentText;
private String emptyContentIcon;
@@ -87,7 +87,7 @@ protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) {
this.defaultHeaders);
}
outcome.putDataSetEntry(CaseViewConstants.FIELD_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN,
- this.isHeaderModeChangeable);
+ this.headerModeChangeable);
outcome.putDataSetEntry(CaseViewConstants.FIELD_USE_DEFAULT_HEADERS, FieldType.BOOLEAN,
this.useDefaultHeaders);
if (this.emptyContentText != null) {
diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
index a4c6059948b..062dc9f1cbd 100644
--- a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
+++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java
@@ -184,10 +184,11 @@ public Case createOrIgnoreMenuItem(MenuItemBody body) throws TransitionNotExecut
if (body == null) {
throw new IllegalArgumentException("Menu item body cannot be null");
}
- Case itemCase = findMenuItem(body.getIdentifier());
+ String sanitizedIdentifier = MenuItemUtils.sanitize(body.getIdentifier());
+ Case itemCase = findMenuItem(sanitizedIdentifier);
if (itemCase != null) {
log.debug("Ignored creation or update of menu item case [{}] with identifier [{}].", itemCase.getStringId(),
- body.getIdentifier());
+ sanitizedIdentifier);
return itemCase;
} else {
return createMenuItem(body);
diff --git a/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java b/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java
index 99ffa224aaa..070e5e06615 100644
--- a/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java
+++ b/src/main/java/com/netgrif/application/engine/menu/web/MenuController.java
@@ -38,9 +38,12 @@ public EntityModel getMenuItemData(@PathVariable("encodedC
String caseId = new String(Base64.getDecoder().decode(encodedCaseId));
List dataGroups = menuItemService.getMenuItemData(caseId, locale);
return EntityModel.of(new MenuItemDataResponse(dataGroups));
+ } catch (IllegalArgumentException e) {
+ log.warn("Requested with invalid input", e);
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Requested with invalid input");
} catch (Exception e) {
log.error("Getting menu item data failed", e);
- throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Getting menu item data failed", e);
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Getting menu item data failed");
}
}
diff --git a/src/main/java/com/netgrif/application/engine/menu/web/responsebodies/MenuItemDataResponse.java b/src/main/java/com/netgrif/application/engine/menu/web/responsebodies/MenuItemDataResponse.java
index 78e7851e573..57669493c74 100644
--- a/src/main/java/com/netgrif/application/engine/menu/web/responsebodies/MenuItemDataResponse.java
+++ b/src/main/java/com/netgrif/application/engine/menu/web/responsebodies/MenuItemDataResponse.java
@@ -10,7 +10,7 @@
public class MenuItemDataResponse {
/**
- * Map containing menu item data where key is the view type and value is a list of immediate fields.
+ * List of data groups containing menu item data.
*/
@Getter
private final List data;
diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
index 438f426a228..c12ae83df96 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -930,7 +930,7 @@
def outcome = menuItemService.handleConfigurationTemplate(useCase)
- outcome.mapping.each { fieldId, value ->
+ outcome.getMapping().each { fieldId, value ->
change useCase.getField(fieldId) value { value }
}
diff --git a/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy b/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy
index f2ed343fdfb..98d06dc2b94 100644
--- a/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy
+++ b/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy
@@ -70,7 +70,7 @@ class MenuImportExportTest {
"Menu entry \"My tasks\": OK\n"
@Autowired
- MenuRunner filterRunner
+ MenuRunner menuRunner
@Autowired
TestHelper testHelper
diff --git a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
index 07ef28401f2..1ab30a687e7 100644
--- a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
+++ b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
@@ -563,7 +563,7 @@ public void duplicateLeafItemTest() throws TransitionNotExecutableException {
List originAllFormValue = (List) originLeafItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM);
assertNotNull(originAllFormValue);
assertEquals(1, originAllFormValue.size());
- assertNotEquals(duplicatedFormValue.get(0), originAllFormValue.get(0));
+ assertNotEquals(duplicatedAllFormValue.get(0), originAllFormValue.get(0));
Case duplicatedCaseViewCase = workflowService.findOne(duplicatedCaseViewId);
Case originCaseViewCase = workflowService.findOne(originCaseViewId);
@@ -632,17 +632,17 @@ public void handleConfigurationTemplateTest() throws TransitionNotExecutableExce
ConfigurationTemplateOutcome outcome = menuItemService.handleConfigurationTemplate(menuItemCase);
- assertTrue((Boolean) outcome.mapping.get(MenuItemConstants.FIELD_USE_TABBED_VIEW));
- assertFalse((Boolean) outcome.mapping.get(MenuItemConstants.FIELD_USE_CUSTOM_VIEW));
- assertEquals(MenuItemViewType.CASE_VIEW.getIdentifier(), outcome.mapping.get(MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE));
- List viewCaseIdAsList = (List) outcome.mapping.get(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID);
+ assertTrue((Boolean) outcome.getMapping().get(MenuItemConstants.FIELD_USE_TABBED_VIEW));
+ assertFalse((Boolean) outcome.getMapping().get(MenuItemConstants.FIELD_USE_CUSTOM_VIEW));
+ assertEquals(MenuItemViewType.CASE_VIEW.getIdentifier(), outcome.getMapping().get(MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE));
+ List viewCaseIdAsList = (List) outcome.getMapping().get(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID);
assertEquals(1, viewCaseIdAsList.size());
Case viewCase = workflowService.findOne(viewCaseIdAsList.get(0));
assertEquals("case_view_configuration", viewCase.getProcessIdentifier());
- List viewFormTaskIdAsList = (List) outcome.mapping.get(MenuItemConstants.FIELD_VIEW_CONFIGURATION_FORM);
+ List viewFormTaskIdAsList = (List) outcome.getMapping().get(MenuItemConstants.FIELD_VIEW_CONFIGURATION_FORM);
assertEquals(1, viewFormTaskIdAsList.size());
assertEquals(MenuItemUtils.findTaskIdInCase(viewCase, ViewConstants.TRANS_SETTINGS_ID), viewFormTaskIdAsList.get(0));
- List viewAllFormTaskIdAsList = (List) outcome.mapping.get(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM);
+ List viewAllFormTaskIdAsList = (List) outcome.getMapping().get(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM);
assertEquals(1, viewAllFormTaskIdAsList.size());
assertEquals(MenuItemUtils.findTaskIdInCase(viewCase, ViewConstants.TRANS_ALL_MENU_DATA_ID), viewAllFormTaskIdAsList.get(0));
}