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 ea8ef6dd76..3d42b8da67 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
@@ -10,9 +10,7 @@
import lombok.Data;
import lombok.NoArgsConstructor;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
/**
* Class, that holds configurable attributes of menu item.
@@ -26,6 +24,7 @@ public class MenuItemBody {
private String menuIcon = "filter_none";
private I18nString menuName;
+ private List allowedAuthorities = List.of("ROLE_USER");
private Map allowedRoles = new HashMap<>();
private Map bannedRoles = new HashMap<>();
private boolean useCustomView = false;
@@ -198,6 +197,7 @@ public ToDataSetOutcome toDataSet(String parentId, String nodePath, Case viewCas
outcome.putDataSetEntry(MenuItemConstants.FIELD_CUSTOM_VIEW_SELECTOR, FieldType.TEXT,
this.customViewSelector);
outcome.putDataSetEntry(MenuItemConstants.FIELD_IS_AUTO_SELECT, FieldType.BOOLEAN, this.isAutoSelect);
+ outcome.putDataSetEntry(MenuItemConstants.FIELD_ALLOWED_AUTHORITIES, FieldType.MULTICHOICE_MAP, this.allowedAuthorities);
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);
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 c4ab1f141c..895f7fc55c 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
@@ -9,6 +9,7 @@ public class MenuItemConstants {
public static final String FIELD_IS_FOLDER = "is_folder";
public static final String FIELD_IDENTIFIER = "menu_item_identifier";
public static final String FIELD_APPEND_MENU_ITEM = "append_menu_item_stringId";
+ public static final String FIELD_ALLOWED_AUTHORITIES = "allowed_authorities";
public static final String FIELD_ALLOWED_ROLES = "allowed_roles";
public static final String FIELD_BANNED_ROLES = "banned_roles";
public static final String FIELD_MENU_NAME = "menu_name";
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 c3573264c2..8037e245da 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
@@ -5,6 +5,7 @@
import com.netgrif.application.engine.menu.utils.MenuItemUtils;
import com.netgrif.application.engine.petrinet.domain.I18nString;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -26,16 +27,19 @@ public class MenuItemTemplateHolder {
* The key is the template identifier (String), and the value is the Template instance.
*
*/
- private final 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(),
- CustomViewTemplate.IDENTIFIER, new CustomViewTemplate(),
- FolderTemplate.IDENTIFIER, new FolderTemplate()
- );
+ private final static Map templates;
+
+ static {
+ templates = new LinkedHashMap<>();
+ templates.put(FolderTemplate.IDENTIFIER, new FolderTemplate());
+ templates.put(TabbedCaseViewTemplate.IDENTIFIER, new TabbedCaseViewTemplate());
+ templates.put(TabbedTaskViewTemplate.IDENTIFIER, new TabbedTaskViewTemplate());
+ templates.put(SimpleCaseViewTemplate.IDENTIFIER, new SimpleCaseViewTemplate());
+ templates.put(SimpleTaskViewTemplate.IDENTIFIER, new SimpleTaskViewTemplate());
+ templates.put(SingleTaskViewTemplate.IDENTIFIER, new SingleTaskViewTemplate());
+ templates.put(TabbedTicketViewTemplate.IDENTIFIER, new TabbedTicketViewTemplate());
+ templates.put(CustomViewTemplate.IDENTIFIER, new CustomViewTemplate());
+ }
/**
@@ -116,7 +120,9 @@ public static Map transformToOptions() {
return templates.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
- entry -> entry.getValue().getName()
+ entry -> entry.getValue().getName(),
+ (a, b) -> a,
+ LinkedHashMap::new
));
}
}
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 b7b3107a8b..a899f69815 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
@@ -92,7 +92,10 @@ 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 oldNodePath != null && destUri.contains(oldNodePath);
+ if (oldNodePath == null || destUri == null) {
+ return false;
+ }
+ return destUri.contains(oldNodePath + "/") || destUri.endsWith(oldNodePath);
}
/**
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 77bf89b0f7..2329ace50e 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
@@ -155,6 +155,24 @@
show_create_case_button
Show create case button?
true
+
+ 0
+
+
+ trans: t.create_case_btn_settings,
+ show_create_case_button: f.show_create_case_button,
+ require_title_in_creation: f.require_title_in_creation,
+ create_case_button_title: f.create_case_button_title,
+ create_case_button_icon: f.create_case_button_icon,
+ create_case_button_icon_preview: f.create_case_button_icon_preview,
+ banned_nets_in_creation: f.banned_nets_in_creation;
+
+ make [require_title_in_creation, create_case_button_title, create_case_button_icon, create_case_button_icon_preview, banned_nets_in_creation], hidden on trans when { !show_create_case_button.value }
+ make [require_title_in_creation, create_case_button_title, create_case_button_icon, banned_nets_in_creation], editable on trans when { show_create_case_button.value }
+ make create_case_button_icon_preview, visible on trans when { show_create_case_button.value }
+
+
+
require_title_in_creation
@@ -351,6 +369,20 @@
case_all_allowed_nets
All allowed nets?
true
+
+ 0
+
+
+ trans: t.settings,
+ case_all_allowed_nets: f.case_all_allowed_nets,
+ case_allowed_nets: f.case_allowed_nets,
+ case_inherit_allowed_nets: f.case_inherit_allowed_nets;
+
+ make [case_allowed_nets, case_inherit_allowed_nets], hidden on trans when { case_all_allowed_nets.value }
+ make [case_allowed_nets, case_inherit_allowed_nets], editable on trans when { !case_all_allowed_nets.value }
+
+
+
case_inherit_allowed_nets
@@ -534,7 +566,7 @@
case_show_more_menu
editable
-
+
1
0
@@ -562,7 +594,7 @@
case_empty_content_text
editable
-
+
0
1
@@ -576,7 +608,7 @@
case_empty_content_icon
editable
-
+
1
1
@@ -590,7 +622,7 @@
case_empty_content_icon_preview
visible
-
+
2
1
@@ -604,7 +636,7 @@
case_all_allowed_nets
editable
-
+
0
2
@@ -617,7 +649,7 @@
case_allowed_nets
- editable
+ hidden
1
@@ -631,8 +663,8 @@
case_inherit_allowed_nets
- editable
-
+ hidden
+
2
2
@@ -1063,7 +1095,6 @@
case_headers_sort_mode_active
editable
- required
0
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 01b2dc56b0..3102eb6243 100644
--- a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
+++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml
@@ -256,6 +256,20 @@
childItemForms
+
+ empty_child_items_info
+
+
+ new com.netgrif.application.engine.petrinet.domain.I18nString("This menu item has no child items to order.",
+ [sk: "Táto menu položka neobsahuje žiadnu položku na zoradenie", de: "Zu diesem Menüpunkt gibt es keine Untergerichte."])
+
+
+ text
+ true
+ 20
+ true
+
+
is_folder
@@ -299,7 +313,7 @@
menu_icon
Menu icon identifier
- Material icon identifier. List of icons with identifiers is available online.
+ Material icon identifier
icon: f.this,
iconPreview: f.menu_icon_preview;
@@ -326,7 +340,6 @@
menu_name_as_visible
Name of the item
- Will be shown in the menu
autocomplete
@@ -334,7 +347,6 @@
menu_name
Name of the item
- Will be shown in the menu
menu_name_as_visible: f.menu_name_as_visible,
name: f.menu_name;
@@ -457,6 +469,16 @@
[:]
+
+ allowed_authorities
+ Allowed authorities
+
+
+
+
+
+ ROLE_USER
+
use_custom_view
Use custom view?
@@ -502,7 +524,7 @@
tab_icon
Tab icon identifier
- Material icon identifier. List of icons with identifiers is available online.
+ Material icon identifier
icon: f.this,
iconPreview: f.tab_icon_preview;
@@ -665,13 +687,16 @@
com.netgrif.application.engine.menu.service.MenuItemTemplateHolder.transformToOptions()
+
+ autocomplete
+
Náhľad ikony
Identifikátor ikony
- Identifikátor Material ikony. Zoznam ikon s identifikátormi je dostupný online.
+ Identifikátor Material ikony.
Pridaj k povoleným roliam
Odstráň z povolených rolí
Pridaj k zakázaným roliam
@@ -684,9 +709,8 @@
Identifikátor duplikovanej položky
Musí byť jedinečný
Názov položky
- Bude zobrazený v menu
Identifikátor ikony v karte
- Identifikátor Material ikony. Zoznam ikon s identifikátormi je dostupný online.
+ Identifikátor Material ikony.
Náhľad ikony v karte
Zobraziť ikonu v karte?
Názov položky
@@ -719,14 +743,18 @@
Zoradiť pod-položky
Vytvoriť menu položku
Konfiguračná šablóna
- Rola
- Zobrazenie
+ Povolenia
+ Zobrazenie v karte
Menu položka
+ Povolené autority
+ Používateľ
+ Admin
+ Anonymný používateľ
Ikonevorschau
Ikone ID
- Material Ikone ID. Liste den Ikonen mit IDs ist online verfügbar.
+ Material Ikone ID
Zu zulässigen Rollen hinzufügen
Aus zulässigen Rollen entfernen
Zu verbotenen Rollen hinzufügen
@@ -734,7 +762,7 @@
Ihre Prozesse
Wählen Sie einen Prozess mit Rollen aus, die Sie zu Listen mit zulässigen oder verbotenen Rollen hinzufügen möchten.
Verfügbare Rollen
- Material Ikone ID. Liste den Ikonen mit IDs ist online verfügbar.
+ Material Ikone ID
Beispiel: "demo-tabbed-views"
Rollen
Zulässige Rollen
@@ -747,7 +775,6 @@
Identifikator der kopierten Ansicht
Muss einzigartig sein
Titel des Eintrages
- Wird im Menü angezeigt
Ikonen Identifikator der Registerkarte
Vorschau der Registerkarte Ikone
Zeige die Registerkarte Ikone an?
@@ -774,9 +801,13 @@
Untereintrage sortieren
Erstellen
Konfigurationsvorlage
- Rolle
- Ansicht
+ Berechtigungen
+ Ansicht der Registerkarte
Menüeintrag
+ Zulässige Behörden
+ Benutzer
+ Admin
+ Anonymer Nutzer
@@ -874,61 +905,46 @@
- move_dest_uri
+ configuration_templates
editable
required
0
- 1
- 1
- 2
- material
- outline
-
-
-
- move_dest_uri_new_node
-
- editable
-
-
- 2
- 1
+ 2
1
- 1
+ 4
material
outline
- move_add_node
+ menu_icon
editable
- 3
- 1
+ 0
+ 3
1
- 1
+ 2
material
outline
- configuration_templates
+ menu_icon_preview
- editable
- required
+ visible
- 0
- 2
+ 2
+ 3
1
- 4
+ 2
material
- outline
+ standard
@@ -1294,6 +1310,28 @@
material
outline
+
+ 0
+
+
+ duplicate_view_identifier: f.duplicate_view_identifier,
+ duplicate_new_title: f.duplicate_new_title;
+
+ if (duplicate_view_identifier.value != null && !duplicate_view_identifier.value.isEmpty()
+ || duplicate_new_title.value == null) {
+ return
+ }
+
+ String defaultName = duplicate_new_title.value.defaultValue
+ if (defaultName == null || defaultName.isEmpty()) {
+ defaultName = duplicate_new_title.value.translations?.values()?.find()
+ }
+
+ String identifier = com.netgrif.application.engine.menu.utils.MenuItemUtils.sanitize(defaultName)
+ change duplicate_view_identifier value { identifier }
+
+
+
duplicate_view_identifier
@@ -1309,6 +1347,27 @@
material
outline
+
+ 0
+
+
+ duplicate_view_identifier: f.duplicate_view_identifier;
+
+ if (menuItemService.existsMenuItem(duplicate_view_identifier.value)) {
+ change duplicate_view_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 duplicate_view_identifier validations { null }
+ String sanitizedIdentifier = com.netgrif.application.engine.menu.utils.MenuItemUtils.sanitize(duplicate_view_identifier.value)
+ if (duplicate_view_identifier.value != sanitizedIdentifier) {
+ change duplicate_view_identifier value { sanitizedIdentifier }
+ }
+ }
+
+
+
@@ -1319,6 +1378,7 @@
title: f.duplicate_new_title;
duplicateMenuItem(useCase, title.value, identifier.value)
+ Frontend.reloadMenu()
Duplicate
@@ -1343,30 +1403,53 @@
children_order_0
- 4
+ 3
grid
+
+ empty_child_items_info
+
+ hidden
+
+
+ 1
+ 0
+ 1
+ 1
+ material
+ outline
+
+
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
1
- 4
+ 3
material
outline
+
+ assign
+
+
+ trans: t.this,
+ empty_child_items_info: f.empty_child_items_info,
+ forms: f.childItemForms,
+ ids: f.childItemIds;
+
+ def orderedTaskIds = ids.value?.collect { id -> workflowService.findOne(id).tasks.find { it.transition == "row_for_ordering" }.task } ?: []
+ make empty_child_items_info, visible on trans when { orderedTaskIds.isEmpty() }
+ make empty_child_items_info, hidden on trans when { !orderedTaskIds.isEmpty() }
+ change forms value { orderedTaskIds }
+
+
+
row_for_ordering
@@ -1495,7 +1578,7 @@
role_settings
400
48
-
+
person
auto
@@ -1515,13 +1598,28 @@
3
grid
- processes_available
+ allowed_authorities
editable
0
0
+ 1
+ 3
+ 0
+ material
+ outline
+
+
+
+ processes_available
+
+ editable
+
+
+ 0
+ 1
2
1
0
@@ -1536,7 +1634,7 @@
1
- 0
+ 1
2
1
0
@@ -1551,7 +1649,7 @@
2
- 0
+ 1
1
1
0
@@ -1565,7 +1663,7 @@
2
- 1
+ 2
1
1
0
@@ -1579,7 +1677,7 @@
0
- 2
+ 3
1
3
0
@@ -1593,7 +1691,7 @@
0
- 3
+ 4
1
2
0
@@ -1608,7 +1706,7 @@
2
- 3
+ 4
1
1
0
@@ -1622,7 +1720,7 @@
0
- 4
+ 5
1
2
0
@@ -1637,7 +1735,7 @@
2
- 4
+ 5
1
1
0
@@ -1666,7 +1764,7 @@
view_settings
528
48
-
+
image
auto
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 0b1ea4c7be..37b6906162 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
@@ -706,7 +706,6 @@
task_headers_sort_mode_active
editable
- required
0
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 1f18dc3f42..d7ba08735d 100644
--- a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
+++ b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java
@@ -135,6 +135,7 @@ private void createByTemplateAndAssert(MenuItemBody menuItemBody) throws Transit
assertEquals(menuItemBody.isUseTabbedView(), menuItemCase.getFieldValue(MenuItemConstants.FIELD_USE_TABBED_VIEW));
assertEquals(menuItemBody.isUseCustomView(), menuItemCase.getFieldValue(MenuItemConstants.FIELD_USE_CUSTOM_VIEW));
assertEquals(menuItemBody.getConfigurationTemplateIdentifier(), menuItemCase.getFieldValue(MenuItemConstants.FIELD_CONFIGURATION_TEMPLATES));
+ assertEquals(List.of("ROLE_USER"), menuItemCase.getFieldValue(MenuItemConstants.FIELD_ALLOWED_AUTHORITIES));
assertEquals((menuItemBody.getUri() + uriService.getUriSeparator() + menuItemBody.getIdentifier()).replaceAll("//", uriService.getUriSeparator()),
menuItemCase.getFieldValue(MenuItemConstants.FIELD_NODE_PATH));
diff --git a/src/test/java/com/netgrif/application/engine/menu/MenuItemUtilsTest.java b/src/test/java/com/netgrif/application/engine/menu/MenuItemUtilsTest.java
new file mode 100644
index 0000000000..8f6cbd3899
--- /dev/null
+++ b/src/test/java/com/netgrif/application/engine/menu/MenuItemUtilsTest.java
@@ -0,0 +1,56 @@
+package com.netgrif.application.engine.menu;
+
+import com.netgrif.application.engine.menu.domain.MenuItemConstants;
+import com.netgrif.application.engine.menu.utils.MenuItemUtils;
+import com.netgrif.application.engine.workflow.domain.Case;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@SpringBootTest
+@ActiveProfiles({"test"})
+@ExtendWith(SpringExtension.class)
+public class MenuItemUtilsTest {
+
+ @Test
+ public void testIsCyclicNodePath_withCyclicPath_returnsTrue() {
+ Case folderItem = mock(Case.class);
+
+ when(folderItem.getFieldValue(MenuItemConstants.FIELD_NODE_PATH)).thenReturn("/node1/node2");
+ boolean result = MenuItemUtils.isCyclicNodePath(folderItem, "/node1/node2/node3");
+ assertTrue(result);
+
+ when(folderItem.getFieldValue(MenuItemConstants.FIELD_NODE_PATH)).thenReturn("/node1/node2");
+ result = MenuItemUtils.isCyclicNodePath(folderItem, "/node4/node1/node2/node3");
+ assertTrue(result);
+ }
+
+ @Test
+ public void testIsCyclicNodePath_withNonCyclicPath_returnsFalse() {
+ Case folderItem = mock(Case.class);
+
+ when(folderItem.getFieldValue(MenuItemConstants.FIELD_NODE_PATH)).thenReturn("/node1/node2");
+ boolean result = MenuItemUtils.isCyclicNodePath(folderItem, "/node3/node4");
+ assertFalse(result);
+
+ when(folderItem.getFieldValue(MenuItemConstants.FIELD_NODE_PATH)).thenReturn("/node1/node2");
+ result = MenuItemUtils.isCyclicNodePath(folderItem, null);
+ assertFalse(result);
+
+ when(folderItem.getFieldValue(MenuItemConstants.FIELD_NODE_PATH)).thenReturn("/node1/node2");
+ result = MenuItemUtils.isCyclicNodePath(folderItem, "/node1/node2_2/node3");
+ assertFalse(result);
+
+
+ when(folderItem.getFieldValue(MenuItemConstants.FIELD_NODE_PATH)).thenReturn(null);
+ result = MenuItemUtils.isCyclicNodePath(folderItem, "/node1/node2");
+ assertFalse(result);
+ }
+
+}