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 f981a49dcaa..2094ea69815 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 @@ -17,12 +17,19 @@ import com.netgrif.application.engine.elastic.web.requestbodies.ElasticTaskSearc import com.netgrif.application.engine.export.configuration.ExportConfiguration import com.netgrif.application.engine.export.domain.ExportDataConfig import com.netgrif.application.engine.export.service.interfaces.IExportService -import com.netgrif.application.engine.impersonation.service.interfaces.IImpersonationService import com.netgrif.application.engine.history.service.IHistoryService +import com.netgrif.application.engine.impersonation.service.interfaces.IImpersonationService import com.netgrif.application.engine.importer.service.FieldFactory import com.netgrif.application.engine.mail.domain.MailDraft import com.netgrif.application.engine.mail.interfaces.IMailAttemptService 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.TabbedTaskViewBody +import com.netgrif.application.engine.menu.domain.configurations.ViewBody +import com.netgrif.application.engine.menu.services.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 @@ -52,8 +59,6 @@ import com.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes import com.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes.SetDataEventOutcome import com.netgrif.application.engine.workflow.domain.eventoutcomes.taskoutcomes.AssignTaskEventOutcome import com.netgrif.application.engine.workflow.domain.eventoutcomes.taskoutcomes.TaskEventOutcome -import com.netgrif.application.engine.workflow.domain.menu.MenuItemBody -import com.netgrif.application.engine.workflow.domain.menu.MenuItemConstants import com.netgrif.application.engine.workflow.service.FileFieldInputStream import com.netgrif.application.engine.workflow.service.TaskService import com.netgrif.application.engine.workflow.service.interfaces.* @@ -74,10 +79,8 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable -import java.text.Normalizer import java.time.ZoneId import java.util.stream.Collectors - /** * ActionDelegate class contains Actions API methods. */ @@ -190,6 +193,9 @@ class ActionDelegate { @Autowired PublicViewProperties publicViewProperties + @Autowired + IMenuItemService menuItemService + FrontendActionOutcome Frontend /** @@ -580,7 +586,7 @@ class ActionDelegate { }, where: { Closure closure -> [with: { Map dataSet -> - executeTasks(dataSet, taskId, closure) + executeTasks(dataSet, task.stringId, closure) }] }] } @@ -1598,32 +1604,15 @@ class ActionDelegate { @NamedVariant Case createFilter(def title, String query, String type, List allowedNets, String icon, String visibility, def filterMetadata) { - Case filterCase = createCase(FilterRunner.FILTER_PETRI_NET_IDENTIFIER, title as String) - filterCase.setIcon(icon) - filterCase.dataSet[DefaultFiltersRunner.FILTER_I18N_TITLE_FIELD_ID].value = (title instanceof I18nString) ? title : new I18nString(title as String) - filterCase = workflowService.save(filterCase) - Task newFilterTask = findTask { it._id.eq(new ObjectId(filterCase.tasks.find { it.transition == DefaultFiltersRunner.AUTO_CREATE_TRANSITION }.task)) } - assignTask(newFilterTask) - - def setDataMap = [ - (DefaultFiltersRunner.FILTER_TYPE_FIELD_ID) : [ - "type" : "enumeration_map", - "value": type - ], - (DefaultFiltersRunner.FILTER_VISIBILITY_FIELD_ID): [ - "type" : "enumeration_map", - "value": visibility - ], - (DefaultFiltersRunner.FILTER_FIELD_ID) : [ - "type" : "filter", - "value" : query, - "allowedNets" : allowedNets, - "filterMetadata": filterMetadata ?: defaultFilterMetadata(type) - ] - ] - setData(newFilterTask, setDataMap) - finishTask(newFilterTask) - return workflowService.findOne(filterCase.stringId) + 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) } /** @@ -1706,7 +1695,7 @@ class ActionDelegate { /** * deletes filter instance - * Note: do not call this method if given instance is referenced in any preference_item instance + * Note: do not call this method if given instance is referenced in any menu_item instance * @param filter * @return */ @@ -1732,15 +1721,14 @@ class ActionDelegate { filter.dataSet[FILTER_FIELD_I18N_FILTER_NAME].value as I18nString, null ) - body.setFilter(filter) - body.setCaseDefaultHeaders(caseDefaultHeaders) - body.setTaskDefaultHeaders(taskDefaultHeaders) body.setAllowedRoles(collectRolesForPreferenceItem(allowedRoles)) body.setBannedRoles(collectRolesForPreferenceItem(bannedRoles)) body.setUseCustomView(false) - body.setCaseRequireTitleInCreation(true) + body.setUseTabbedView(true) + + body.setView(createLegacyMenuItemViews(filter, caseDefaultHeaders, taskDefaultHeaders)) - return createMenuItem(body) + return menuItemService.createMenuItem(body) } /** @@ -1761,15 +1749,15 @@ class ActionDelegate { filter.dataSet[FILTER_FIELD_I18N_FILTER_NAME].value as I18nString, null ) - body.setFilter(filter) - body.setCaseDefaultHeaders(caseDefaultHeaders) - body.setTaskDefaultHeaders(taskDefaultHeaders) + body.setAllowedRoles(collectRolesForPreferenceItem(allowedRoles)) body.setBannedRoles(collectRolesForPreferenceItem(bannedRoles)) body.setUseCustomView(false) - body.setCaseRequireTitleInCreation(true) + body.setUseTabbedView(true) + + body.setView(createLegacyMenuItemViews(filter, caseDefaultHeaders, taskDefaultHeaders)) - return createMenuItem(body) + return menuItemService.createMenuItem(body) } /** @@ -1791,15 +1779,15 @@ class ActionDelegate { filter.dataSet[FILTER_FIELD_I18N_FILTER_NAME].value as I18nString, null ) - body.setFilter(filter) - body.setCaseDefaultHeaders(caseDefaultHeaders) - body.setTaskDefaultHeaders(taskDefaultHeaders) + body.setAllowedRoles(collectRolesForPreferenceItem(allowedRoles)) body.setBannedRoles(collectRolesForPreferenceItem(bannedRoles)) body.setUseCustomView(false) - body.setCaseRequireTitleInCreation(true) + body.setUseTabbedView(true) + + body.setView(createLegacyMenuItemViews(filter, caseDefaultHeaders, taskDefaultHeaders)) - return createMenuItem(body) + return menuItemService.createMenuItem(body) } /** @@ -1820,15 +1808,15 @@ class ActionDelegate { filter.dataSet[FILTER_FIELD_I18N_FILTER_NAME].value as I18nString, null ) - body.setFilter(filter) - body.setCaseDefaultHeaders(caseDefaultHeaders) - body.setTaskDefaultHeaders(taskDefaultHeaders) + body.setAllowedRoles(collectRolesForPreferenceItem(allowedRoles)) body.setBannedRoles(collectRolesForPreferenceItem(bannedRoles)) body.setUseCustomView(false) - body.setCaseRequireTitleInCreation(true) + body.setUseTabbedView(true) - return createMenuItem(body) + body.setView(createLegacyMenuItemViews(filter, caseDefaultHeaders, taskDefaultHeaders)) + + return menuItemService.createMenuItem(body) } /** @@ -1846,7 +1834,7 @@ class ActionDelegate { * @param caseDefaultHeaders List of headers displayed in case view * @param taskDefaultHeaders List of headers displayed in task view * - * @return created Case of preference_item + * @return created Case of menu_item * */ @NamedVariant Case createMenuItem(String uri, String identifier, def name, String icon = "filter_none", Case filter = null, @@ -1858,57 +1846,36 @@ class ActionDelegate { (name instanceof I18nString) ? name : new I18nString(name as String), icon ) - body.setFilter(filter) - body.setCaseDefaultHeaders(caseDefaultHeaders) - body.setTaskDefaultHeaders(taskDefaultHeaders) + body.setAllowedRoles(collectRolesForPreferenceItem(allowedRoles)) body.setBannedRoles(collectRolesForPreferenceItem(bannedRoles)) body.setUseCustomView(false) - body.setCaseRequireTitleInCreation(true) + body.setUseTabbedView(true) - return createMenuItem(body) + body.setView(createLegacyMenuItemViews(filter, caseDefaultHeaders, taskDefaultHeaders)) + + return menuItemService.createMenuItem(body) } /** - * Changes data of provided preference_item instance. These attributes can be changed: + * Changes data of provided menu_item instance. These attributes can be changed: * - * @param item {@link Case} instance of preference_item.xml + * @param item {@link Case} instance of menu_item.xml */ def changeMenuItem(Case item) { [allowedRoles : { cl -> - updateMenuItemRoles(item, cl as Closure, MenuItemConstants.PREFERENCE_ITEM_FIELD_ALLOWED_ROLES.attributeId) + updateMenuItemRoles(item, cl as Closure, MenuItemConstants.FIELD_ALLOWED_ROLES) }, bannedRoles : { cl -> - updateMenuItemRoles(item, cl as Closure, MenuItemConstants.PREFERENCE_ITEM_FIELD_BANNED_ROLES.attributeId) - }, - caseDefaultHeaders : { cl -> - String defaultHeaders = cl() as String - setData(MenuItemConstants.PREFERENCE_ITEM_SETTINGS_TRANS_ID.attributeId, item, [ - (MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_DEFAULT_HEADERS.attributeId): ["type": "text", "value": defaultHeaders] - ]) - }, - taskDefaultHeaders : { cl -> - String defaultHeaders = cl() as String - setData(MenuItemConstants.PREFERENCE_ITEM_SETTINGS_TRANS_ID.attributeId, item, [ - (MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_DEFAULT_HEADERS.attributeId): ["type": "text", "value": defaultHeaders] - ]) - }, - filter : { cl -> - def filter = cl() as Case - setData("change_filter", item, [ - (MenuItemConstants.PREFERENCE_ITEM_FIELD_NEW_FILTER_ID.attributeId): ["type": "text", "value": filter.stringId] - ]) + updateMenuItemRoles(item, cl as Closure, MenuItemConstants.FIELD_BANNED_ROLES) }, uri : { cl -> def uri = cl() as String @@ -1921,38 +1888,32 @@ class ActionDelegate { title : { cl -> def value = cl() I18nString newName = (value instanceof I18nString) ? value : new I18nString(value as String) - setData(MenuItemConstants.PREFERENCE_ITEM_SETTINGS_TRANS_ID.attributeId, item, [ - (MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_NAME.attributeId): ["type": "i18n", "value": newName] + setData(MenuItemConstants.TRANS_SETTINGS_ID, item, [ + (MenuItemConstants.FIELD_MENU_NAME): ["type": "i18n", "value": newName] ]) }, menuIcon : { cl -> def value = cl() - setData(MenuItemConstants.PREFERENCE_ITEM_SETTINGS_TRANS_ID.attributeId, item, [ - (MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_ICON.attributeId): ["type": "text", "value": value] + setData(MenuItemConstants.TRANS_SETTINGS_ID, item, [ + (MenuItemConstants.FIELD_MENU_ICON): ["type": "text", "value": value] ]) }, tabIcon : { cl -> def value = cl() - setData(MenuItemConstants.PREFERENCE_ITEM_SETTINGS_TRANS_ID.attributeId, item, [ - (MenuItemConstants.PREFERENCE_ITEM_FIELD_TAB_ICON.attributeId): ["type": "text", "value": value] - ]) - }, - requireTitleInCreation: { cl -> - def value = cl() - setData(MenuItemConstants.PREFERENCE_ITEM_SETTINGS_TRANS_ID.attributeId, item, [ - (MenuItemConstants.PREFERENCE_ITEM_FIELD_REQUIRE_TITLE_IN_CREATION.attributeId): ["type": "boolean", "value": value] + setData(MenuItemConstants.TRANS_SETTINGS_ID, item, [ + (MenuItemConstants.FIELD_TAB_ICON): ["type": "text", "value": value] ]) }, useCustomView : { cl -> def value = cl() - setData(MenuItemConstants.PREFERENCE_ITEM_SETTINGS_TRANS_ID.attributeId, item, [ - (MenuItemConstants.PREFERENCE_ITEM_FIELD_USE_CUSTOM_VIEW.attributeId): ["type": "boolean", "value": value] + setData(MenuItemConstants.TRANS_SETTINGS_ID, item, [ + (MenuItemConstants.FIELD_USE_CUSTOM_VIEW): ["type": "boolean", "value": value] ]) }, customViewSelector : { cl -> def value = cl() - setData(MenuItemConstants.PREFERENCE_ITEM_SETTINGS_TRANS_ID.attributeId, item, [ - (MenuItemConstants.PREFERENCE_ITEM_FIELD_CUSTOM_VIEW_SELECTOR.attributeId): ["type": "text", "value": value] + setData(MenuItemConstants.TRANS_SETTINGS_ID, item, [ + (MenuItemConstants.FIELD_CUSTOM_VIEW_SELECTOR): ["type": "text", "value": value] ]) }] @@ -2005,7 +1966,14 @@ class ActionDelegate { List defaultHeaders = [], String icon = "", String visibility = DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE) { - Case filter = createFilter(title, query, type, allowedNets, icon, visibility, null) + FilterBody filterBody = new FilterBody() + filterBody.setTitle((title instanceof I18nString) ? title : new I18nString(title as String)) + filterBody.setQuery(query) + filterBody.setType(type) + filterBody.setAllowedNets(allowedNets) + filterBody.setIcon(icon) + filterBody.setVisibility(visibility) + Case filter = menuItemService.createFilter(filterBody) Case menuItem = createMenuItem(uri, identifier, filter, groupName, allowedRoles, bannedRoles, defaultHeaders) return menuItem } @@ -2033,13 +2001,20 @@ class ActionDelegate { String icon = "", String visibility = DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE, Case orgGroup = null) { - Case filter = createFilter(title, query, type, allowedNets, icon, visibility, null) + FilterBody filterBody = new FilterBody() + filterBody.setTitle((title instanceof I18nString) ? title : new I18nString(title as String)) + filterBody.setQuery(query) + filterBody.setType(type) + filterBody.setAllowedNets(allowedNets) + filterBody.setIcon(icon) + filterBody.setVisibility(visibility) + Case filter = menuItemService.createFilter(filterBody) Case menuItem = createMenuItem(uri, identifier, filter, allowedRoles, bannedRoles, orgGroup, defaultHeaders) return menuItem } /** - * Creates filter and preference_item instances with given parameters. + * Creates filter and menu_item instances with given parameters. * * @param uri resource where the item is located in * @param itemIdentifier unique identifier of item @@ -2059,7 +2034,7 @@ class ActionDelegate { * @param itemTaskDefaultHeaders List of headers displayed in task view * @param filterMetadata metadata for filter. If no value is provided, then default value is used: {@link #defaultFilterMetadata(String)} * - * @return created {@link Case} instance of preference_item + * @return created {@link Case} instance of menu_item * */ @NamedVariant Case createFilterInMenu(String uri, String itemIdentifier, def itemAndFilterName, String filterQuery, @@ -2067,13 +2042,22 @@ class ActionDelegate { String itemAndFilterIcon = "filter_none", Map itemAllowedRoles = [:], Map itemBannedRoles = [:], List itemCaseDefaultHeaders = [], List itemTaskDefaultHeaders = [], def filterMetadata = null) { - Case filter = createFilter(itemAndFilterName, filterQuery, filterType, filterAllowedNets, itemAndFilterIcon, filterVisibility, filterMetadata) - Case menuItem = createMenuItem(uri, itemIdentifier, itemAndFilterName, itemAndFilterIcon, filter, itemAllowedRoles, itemBannedRoles, itemCaseDefaultHeaders, itemTaskDefaultHeaders) + FilterBody filterBody = new FilterBody() + filterBody.setTitle((itemAndFilterName instanceof I18nString) ? itemAndFilterName : new I18nString(itemAndFilterName as String)) + filterBody.setQuery(filterQuery) + filterBody.setType(filterType) + filterBody.setAllowedNets(filterAllowedNets) + filterBody.setIcon(itemAndFilterIcon) + filterBody.setVisibility(filterVisibility) + filterBody.setMetadata(filterMetadata as Map) + Case filter = menuItemService.createFilter(filterBody) + Case menuItem = createMenuItem(uri, itemIdentifier, itemAndFilterName, itemAndFilterIcon, filter, itemAllowedRoles, + itemBannedRoles, itemCaseDefaultHeaders, itemTaskDefaultHeaders) return menuItem } /** - * Creates filter and preference_item instances with given parameters. + * Creates filter and menu_item instances with given parameters. * * @param body configuration class for menu item creation * @param filterQuery elastic query for filter @@ -2084,99 +2068,57 @@ class ActionDelegate { * @param filterAllowedNets List of allowed nets. Element of list is process identifier * @param filterMetadata metadata for filter. If no value is provided, then default value is used: {@link #defaultFilterMetadata(String)} * - * @return created {@link Case} instance of preference_item + * @return created {@link Case} instance of menu_item * */ Case createFilterInMenu(MenuItemBody body, String filterQuery, String filterType, String filterVisibility, List filterAllowedNets = [], def filterMetadata = null) { - Case filter = createFilter(body.menuName, filterQuery, filterType, filterAllowedNets, body.menuIcon, filterVisibility, filterMetadata) - body.filter = filter + FilterBody filterBody = new FilterBody() + filterBody.setTitle(body.menuName) + filterBody.setQuery(filterQuery) + filterBody.setType(filterType) + filterBody.setAllowedNets(filterAllowedNets) + filterBody.setIcon(body.menuIcon) + filterBody.setVisibility(filterVisibility) + filterBody.setMetadata(filterMetadata as Map) + + body.setView(createLegacyMenuItemViews(filterBody)) + body.setUseTabbedView(true) + Case menuItem = createMenuItem(body) return menuItem } Case createMenuItem(MenuItemBody body) { - String sanitizedIdentifier = sanitize(body.identifier) - - if (existsMenuItem(sanitizedIdentifier)) { - throw new IllegalArgumentException("Menu item identifier $sanitizedIdentifier is not unique!") - } - - Case parentItemCase = getOrCreateFolderItem(body.uri) - I18nString newName = body.menuName ?: (body.filter?.dataSet[FILTER_FIELD_I18N_FILTER_NAME].value as I18nString) - - Case menuItemCase = createCase(FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER, newName?.defaultValue) - menuItemCase.setUriNodeId(uriService.findByUri(body.uri).stringId) - menuItemCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_ALLOWED_ROLES.attributeId].options = body.allowedRoles - menuItemCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_BANNED_ROLES.attributeId].options = body.bannedRoles - if (parentItemCase != null) { - parentItemCase = appendChildCaseIdAndSave(parentItemCase, menuItemCase.stringId) - } - menuItemCase = workflowService.save(menuItemCase) - Task newItemTask = findTask { it._id.eq(new ObjectId(menuItemCase.tasks.find { it.transition == MenuItemConstants.PREFERENCE_ITEM_FIELD_INIT_TRANS_ID.attributeId }.task)) } - String nodePath = createNodePath(body.uri, sanitizedIdentifier) - uriService.getOrCreate(nodePath, UriContentType.CASE) - - newItemTask = assignTask(newItemTask) - setData(newItemTask, body.toDataSet(parentItemCase.stringId, nodePath)) - finishTask(newItemTask) - - return workflowService.findOne(menuItemCase.stringId) - } - - protected String sanitize(String input) { - return Normalizer.normalize(input.trim(), Normalizer.Form.NFD) - .replaceAll("[^\\p{ASCII}]", "") - .replaceAll("\\p{InCombiningDiacriticalMarks}+", "") - .replaceAll("[\\W-]+", "-") - .toLowerCase() - } - - protected String createNodePath(String uri, String identifier) { - if (uri == uriService.getUriSeparator()) { - return uri + identifier - } else { - return uri + uriService.getUriSeparator() + identifier + return menuItemService.createMenuItem(body) + } + + protected ViewBody createLegacyMenuItemViews(Case filterCase, List caseDefaultHeaders = null, + List taskDefaultHeaders = null) { + FilterBody body = new FilterBody(filterCase) + body.setType((String) filterCase?.getFieldValue("filter_type")) + return createLegacyMenuItemViews(body, caseDefaultHeaders, taskDefaultHeaders) + } + + protected ViewBody createLegacyMenuItemViews(FilterBody filterBody, List caseDefaultHeaders = null, + List taskDefaultHeaders = null) { + if (filterBody.getType() == "Case") { + ViewBody caseView = new TabbedCaseViewBody() + caseView.setFilterBody(filterBody) + caseView.setDefaultHeaders(caseDefaultHeaders) + caseView.setRequireTitleInCreation(true) + + ViewBody taskView = new TabbedTaskViewBody() + taskView.setDefaultHeaders(taskDefaultHeaders) + caseView.setChainedView(taskView) + + return caseView + } else if (filterBody.getType() == "Task"){ + ViewBody taskView = new TabbedTaskViewBody() + taskView.setFilterBody(filterBody) + taskView.setDefaultHeaders(taskDefaultHeaders) + return taskView } - } - - protected Case getOrCreateFolderItem(String uri) { - UriNode node = uriService.getOrCreate(uri, UriContentType.CASE) - MenuItemBody body = new MenuItemBody(new I18nString(node.name), "folder") - return getOrCreateFolderRecursive(node, body) - } - - protected Case getOrCreateFolderRecursive(UriNode node, MenuItemBody body, Case childFolderCase = null) { - Case folder = findFolderCase(node) - if (folder != null) { - if (childFolderCase != null) { - folder = appendChildCaseIdAndSave(folder, childFolderCase.stringId) - initializeParentId(childFolderCase, folder.stringId) - } - return folder - } - - folder = createCase(FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER, body.menuName.toString()) - folder.setUriNodeId(node.parentId) - if (childFolderCase != null) { - folder = appendChildCaseIdAndSave(folder, childFolderCase.stringId) - initializeParentId(childFolderCase, folder.stringId) - } else { - folder = workflowService.save(folder) - } - Task newItemTask = findTask { it._id.eq(new ObjectId(folder.tasks.find { it.transition == MenuItemConstants.PREFERENCE_ITEM_FIELD_INIT_TRANS_ID.attributeId }.task)) } - assignTask(newItemTask) - setData(newItemTask, body.toDataSet(null, node.uriPath)) - finishTask(newItemTask) - - folder = workflowService.findOne(folder.stringId) - if (node.parentId != null) { - UriNode parentNode = uriService.findById(node.parentId) - body = new MenuItemBody(new I18nString(parentNode.name), "folder") - - getOrCreateFolderRecursive(parentNode, body, folder) - } - - return folder + return null } /** @@ -2184,197 +2126,29 @@ class ActionDelegate { * item is moved. Cyclic destination path is forbidden (f.e. from "/my_node" to * "/my_node/my_node2" * - * @param item Instance of preference_item to be moved + * @param item Instance of menu_item to be moved * @param destUri destination path where the item will be moved. F.e. "/my_new_node" * */ void moveMenuItem(Case item, String destUri) { - if (isCyclicNodePath(item, destUri)) { - throw new IllegalArgumentException("Cyclic path not supported. Destination path: ${destUri}") - } - - List casesToSave = new ArrayList<>() - - List parentIdList = item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value as ArrayList - if (parentIdList != null && parentIdList.size() > 0) { - Case oldParent = removeChildItemFromParent(parentIdList[0], item) - casesToSave.add(oldParent) - } - - UriNode destNode = uriService.getOrCreate(destUri, UriContentType.CASE) - Case newParent = getOrCreateFolderItem(destNode.uriPath) - if (newParent != null) { - item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value = [newParent.stringId] as ArrayList - newParent = appendChildCaseId(newParent, item.stringId) - casesToSave.add(newParent) - } else { - item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value = null - } - - item.uriNodeId = destNode.stringId - item = resolveAndHandleNewNodePath(item, destNode.uriPath) - casesToSave.add(item) - - if (hasChildren(item)) { - List childrenToSave = updateNodeInChildrenFoldersRecursive(item) - casesToSave.addAll(childrenToSave) - } - - for (aCase in casesToSave) { - if (aCase != null) { - workflowService.save(aCase) - } - } + menuItemService.moveItem(item, destUri) } /** - * Duplicates menu item. It creates new preference_item instance with the same {@link Case#dataSet} as the provided - * item instance. The only difference is in title, menu_item_identifier and associations + * Duplicates menu item. It creates new menu_item instance with the same dataSet as the provided + * item instance. The only difference is in title, menu_item_identifier and associations. Configuration cases are + * duplicated as well. * * @param originItem Menu item instance, which is duplicated * @param newTitle Title of menu item, that is displayed in menu and tab. Cannot be empty or null. * @param newIdentifier unique menu item identifier * - * @return duplicated {@link Case} instance of preference_item + * @return duplicated {@link Case} instance of menu_item + * + * @throws IllegalArgumentException if the input data are invalid or the menu item of the new identifier already + * exists * */ Case duplicateMenuItem(Case originItem, I18nString newTitle, String newIdentifier) { - if (!newIdentifier) { - throw new IllegalArgumentException("View item identifier is null!") - } - if (newTitle == null || newTitle.defaultValue == "") { - throw new IllegalArgumentException("Default title is empty") - } - String sanitizedIdentifier = sanitize(newIdentifier) - if (existsMenuItem(sanitizedIdentifier)) { - throw new IllegalArgumentException("View item identifier $sanitizedIdentifier is not unique!") - } - - Case duplicated = createCase(FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER, newTitle.defaultValue) - duplicated.uriNodeId = originItem.uriNodeId - duplicated.dataSet = originItem.dataSet - duplicated.title = newTitle.defaultValue - duplicated = workflowService.save(duplicated) - - UriNode node = uriService.findById(originItem.uriNodeId) - String newNodePath = createNodePath(node.uriPath, sanitizedIdentifier) - uriService.getOrCreate(newNodePath, UriContentType.CASE) - - Task newItemTask = findTask { it._id.eq(new ObjectId(duplicated.tasks.find { it.transition == MenuItemConstants.PREFERENCE_ITEM_FIELD_INIT_TRANS_ID.attributeId }.task)) } - Map updatedDataSet = [ - (MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_TITLE.attributeId) : [ - "value": null, - "type" : "text" - ], - (MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_IDENTIFIER.attributeId) : [ - "value": null, - "type" : "text" - ], - (MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_NAME.attributeId) : [ - "value": newTitle, - "type" : "i18n" - ], - (MenuItemConstants.PREFERENCE_ITEM_FIELD_TAB_NAME.attributeId) : [ - "value": newTitle, - "type" : "i18n" - ], - (MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId) : [ - "value": newNodePath, - "type" : "text" - ], - // Must be reset by button, because we have the same dataSet reference between originItem and duplicated - (MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_RESET_CHILD_ITEM_IDS.attributeId): [ - "value": 0, - "type" : "button" - ], - ] - assignTask(newItemTask) - dataService.setData(newItemTask, ImportHelper.populateDataset(updatedDataSet)) - finishTask(newItemTask) - - String parentId = (originItem.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value as ArrayList).get(0) - if (parentId) { - Case parent = workflowService.findOne(parentId) - appendChildCaseIdAndSave(parent, duplicated.stringId) - } - return workflowService.findOne(duplicated.stringId) - } - - private List updateNodeInChildrenFoldersRecursive(Case parentFolder) { - List childItemIds = parentFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as List - if (childItemIds == null || childItemIds.isEmpty()) { - return new ArrayList() - } - - List children = workflowService.findAllById(childItemIds) - - List casesToSave = new ArrayList<>() - for (child in children) { - UriNode parentNode = uriService.getOrCreate(parentFolder.getFieldValue(MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId) as String, UriContentType.CASE) - child.uriNodeId = parentNode.stringId - child = resolveAndHandleNewNodePath(child, parentNode.uriPath) - - casesToSave.add(child) - casesToSave.addAll(updateNodeInChildrenFoldersRecursive(child)) - } - - return casesToSave - } - - private Case resolveAndHandleNewNodePath(Case folderItem, String destUri) { - String newNodePath = resolveNewNodePath(folderItem, destUri) - UriNode newNode = uriService.getOrCreate(newNodePath, UriContentType.CASE) - folderItem.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId].value = newNode.uriPath - - return folderItem - } - - private String resolveNewNodePath(Case folderItem, String destUri) { - return destUri + - uriService.getUriSeparator() + - folderItem.getFieldValue(MenuItemConstants.PREFERENCE_ITEM_FIELD_IDENTIFIER.attributeId) as String - } - - private Case removeChildItemFromParent(String folderId, Case childItem) { - Case parentFolder = workflowService.findOne(folderId) - (parentFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as List).remove(childItem.stringId) - parentFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_HAS_CHILDREN.attributeId].value = hasChildren(parentFolder) - workflowService.save(parentFolder) - } - - private boolean isCyclicNodePath(Case folderItem, String destUri) { - String oldNodePath = folderItem.getFieldValue(MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId) - return destUri.contains(oldNodePath) - } - - private boolean hasChildren(Case folderItem) { - List children = folderItem.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as List - return children != null && children.size() > 0 - } - - private Case appendChildCaseIdAndSave(Case folderCase, String childItemCaseId) { - folderCase = appendChildCaseId(folderCase, childItemCaseId) - return workflowService.save(folderCase) - } - - private Case appendChildCaseId(Case folderCase, String childItemCaseId) { - List childIds = folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList - if (childIds == null) { - folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value = [childItemCaseId] as ArrayList - } else { - folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value = childIds + [childItemCaseId] as ArrayList - } - - folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_HAS_CHILDREN.attributeId].value = hasChildren(folderCase) - - return folderCase - } - - private Case initializeParentId(Case childFolderCase, String parentFolderCaseId) { - childFolderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value = [parentFolderCaseId] as ArrayList - return workflowService.save(childFolderCase) - } - - protected Case findFolderCase(UriNode node) { - return findCaseElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.nodePath.textValue.keyword:\"$node.uriPath\"") + return menuItemService.duplicateItem(originItem, newTitle, newIdentifier) } /** @@ -2393,10 +2167,19 @@ class ActionDelegate { * * @param menuItemIdentifier unique menu item identifier * - * @return found preference_item instance. Can be null + * @return found menu_item instance. Can be null */ Case findMenuItem(String menuItemIdentifier) { - return findCaseElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.menu_item_identifier.textValue.keyword:\"$menuItemIdentifier\"" as String) + return menuItemService.findMenuItem(menuItemIdentifier) + } + + /** + * @param node uri node + * + * @return folder menu item case by provided UriNode + * */ + Case findFolderCase(UriNode node) { + return menuItemService.findFolderCase(node) } /** @@ -2407,7 +2190,7 @@ class ActionDelegate { * @return true if the item exists * */ boolean existsMenuItem(String menuItemIdentifier) { - return countCasesElastic("processIdentifier:\"$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER\" AND dataSet.menu_item_identifier.fulltextValue.keyword:\"$menuItemIdentifier\"") > 0 + return menuItemService.existsMenuItem(menuItemIdentifier) } /** @@ -2417,13 +2200,7 @@ class ActionDelegate { * @return */ Case findMenuItem(String uri, String name) { - UriNode uriNode = uriService.findByUri(uri) - return findCaseElastic("processIdentifier:\"$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER\" AND title.keyword:\"$name\" AND uriNodeId:\"$uriNode.stringId\"") - } - - Case findMenuItemByUriAndIdentifier(String uri, String identifier) { - String nodePath = createNodePath(uri, identifier) - return findCaseElastic("processIdentifier:\"$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER\" AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue.keyword:\"$nodePath\"") + return menuItemService.findMenuItem(uri, name) } /** @@ -2450,32 +2227,31 @@ class ActionDelegate { return findMenuItem(uri, name) } - /** - * Retrieves filter case from preference_item {@link Case} - * - * @param item preference_item instance - * - * @return found filter instance. If not found, null is returned - */ - Case getFilterFromMenuItem(Case item) { - String filterId = (item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_FILTER_CASE.attributeId].value as List)[0] as String - return filterId ? workflowService.findOne(filterId) : null - } - /** * search elastic with string query for first occurrence - * @param query - * @return + * @param query string with search conditions + * @return one case which match search condition or null */ Case findCaseElastic(String query) { def result = findCasesElastic(query, PageRequest.of(0, 1)) return result ? result[0] : null } + /** + * search elastic with string query for cases and default page size of 100 cases + * @param query string with search conditions + * @param pageSize optional parameter which decides number of returned elements + * @return list of cases (default max 100) which match condition + */ + List findCasesElastic(String query, int pageSize = 100) { + this.findCasesElastic(query, PageRequest.of(0, pageSize)) + } + /** * search elastic with string query for cases - * @param query - * @return + * @param query string with search conditions + * @param pageable object which decides page size, page number and order of elements + * @return list of cases (size and order depends on pageable object) which match condition */ List findCasesElastic(String query, Pageable pageable) { CaseSearchRequest request = new CaseSearchRequest() @@ -2484,6 +2260,11 @@ class ActionDelegate { return result } + /** + * find count of cases which match condition + * @param query string with search conditions + * @return number of cases which match condition + */ long countCasesElastic(String query) { CaseSearchRequest request = new CaseSearchRequest() request.query = query @@ -2509,7 +2290,7 @@ class ActionDelegate { Map collectRolesForPreferenceItem(Map roles) { Map temp = [:] return roles.collectEntries { entry -> - if (entry.value.equals(GLOBAL_ROLE)) { + if (entry.value == GLOBAL_ROLE) { Set findGlobalRole = processRoleService.findAllByImportId(ProcessRole.GLOBAL + entry.key) if (findGlobalRole == null || findGlobalRole.isEmpty()) { return @@ -2579,27 +2360,24 @@ class ActionDelegate { } @Deprecated - Case createOrUpdateMenuItem(String id, String uri, String type, String query, String icon, String title, List allowedNets, Map roles = [:], Map bannedRoles = [:], Case group = null, List defaultHeaders = []) { - Case menuItem = findMenuItem(sanitize(id)) - if (!menuItem) { - Case filter = createFilter(title, query, type, allowedNets, icon, DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE, null) - createUri(uri, UriContentType.DEFAULT) + Case createOrUpdateMenuItem(String id, String uri, String type, String query, String icon, String title, List allowedNets, + Map roles = [:], Map bannedRoles = [:], Case group = null, + List defaultHeaders = []) { + MenuItemBody body = new MenuItemBody(uri, id, title, icon) + body.setAllowedRoles(collectRolesForPreferenceItem(roles)) + body.setBannedRoles(collectRolesForPreferenceItem(bannedRoles)) + body.setUseTabbedView(true) - return createMenuItem(uri, id, title, icon, filter, roles, bannedRoles) - } else { - Case filter = getFilterFromMenuItem(menuItem) - changeFilter filter query { query } - changeFilter filter allowedNets { allowedNets } - changeFilter filter title { title } - changeFilter filter icon { icon } - changeMenuItem menuItem allowedRoles { roles } - changeMenuItem menuItem bannedRoles { bannedRoles } - changeMenuItem menuItem defaultHeaders { defaultHeaders.join(",") } - changeMenuItem menuItem uri { uri } - changeMenuItem menuItem filter { filter } - - return workflowService.findOne(menuItem.stringId) - } + FilterBody filterBody = new FilterBody() + filterBody.setTitle(new I18nString(title as String)) + filterBody.setQuery(query) + filterBody.setType(type) + filterBody.setAllowedNets(allowedNets) + filterBody.setIcon(icon) + filterBody.setVisibility(DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE) + + body.setView(createLegacyMenuItemViews(filterBody, defaultHeaders)) + return menuItemService.createOrUpdateMenuItem(body) } /** @@ -2619,15 +2397,15 @@ class ActionDelegate { * * @return created or updated menu item instance * */ + @Deprecated(since = "6.5.0") Case createOrUpdateMenuItem(String uri, String identifier, def name, String icon = "filter_none", Case filter = null, Map allowedRoles = [:], Map bannedRoles = [:], List caseDefaultHeaders = [], List taskDefaultHeaders = []) { MenuItemBody body = new MenuItemBody(uri, identifier, name, icon) body.setAllowedRoles(collectRolesForPreferenceItem(allowedRoles)) body.setBannedRoles(collectRolesForPreferenceItem(bannedRoles)) - body.setCaseDefaultHeaders(caseDefaultHeaders) - body.setTaskDefaultHeaders(taskDefaultHeaders) - body.setFilter(filter) + body.setUseTabbedView(true) + body.setView(createLegacyMenuItemViews(filter, caseDefaultHeaders, taskDefaultHeaders)) return createOrUpdateMenuItem(body) } @@ -2657,6 +2435,7 @@ class ActionDelegate { * * @return created or updated menu item instance along with the actual filter * */ + @Deprecated(since = "6.5.0") Case createOrUpdateMenuItemAndFilter(String uri, String itemIdentifier, def itemAndFilterName, String filterQuery, String filterType, String filterVisibility, List filterAllowedNets = [], String itemAndFilterIcon = "filter_none", Map itemAllowedRoles = [:], @@ -2665,11 +2444,19 @@ class ActionDelegate { MenuItemBody body = new MenuItemBody(uri, itemIdentifier, itemAndFilterName, itemAndFilterIcon) body.allowedRoles = collectRolesForPreferenceItem(itemAllowedRoles) body.bannedRoles = collectRolesForPreferenceItem(itemBannedRoles) - body.caseDefaultHeaders = itemCaseDefaultHeaders - body.taskDefaultHeaders = itemTaskDefaultHeaders + body.setUseTabbedView(true) + + FilterBody filterBody = new FilterBody() + filterBody.setTitle((itemAndFilterName instanceof I18nString) ? itemAndFilterName : new I18nString(itemAndFilterName as String)) + filterBody.setQuery(filterQuery) + filterBody.setType(filterType) + filterBody.setAllowedNets(filterAllowedNets) + filterBody.setIcon(itemAndFilterIcon) + filterBody.setVisibility(filterVisibility) + filterBody.setMetadata(filterMetadata as Map) + body.setView(createLegacyMenuItemViews(filterBody, itemCaseDefaultHeaders, itemTaskDefaultHeaders)) - return createOrUpdateMenuItemAndFilter(body, filterQuery, filterType, filterVisibility, filterAllowedNets, - filterMetadata) + return menuItemService.createOrUpdateMenuItem(body) } /** @@ -2680,12 +2467,7 @@ class ActionDelegate { * @return created or updated menu item instance * */ Case createOrUpdateMenuItem(MenuItemBody body) { - Case item = findMenuItem(sanitize(body.identifier)) - if (item) { - return updateMenuItem(item, body) - } else { - return createMenuItem(body) - } + return menuItemService.createOrUpdateMenuItem(body) } /** @@ -2704,27 +2486,22 @@ class ActionDelegate { * * @return created or updated menu item instance along with the actual filter * */ + @Deprecated(since = "6.5.0") Case createOrUpdateMenuItemAndFilter(MenuItemBody body, String filterQuery, String filterType, String filterVisibility, List filterAllowedNets = [], def filterMetadata = null) { - Case item = findMenuItem(sanitize(body.identifier)) - if (item) { - Case filter = getFilterFromMenuItem(item) - if (filter) { - changeFilter filter query { filterQuery } - changeFilter filter visibility { filterVisibility } - changeFilter filter allowedNets { filterAllowedNets } - changeFilter filter filterMetadata { filterMetadata ?: defaultFilterMetadata(filterType) } - changeFilter filter title { body.menuName } - changeFilter filter icon { body.menuIcon } - } else { - body.filter = createFilter(body.menuName, filterQuery, filterType, filterAllowedNets, body.menuIcon, - filterVisibility, filterMetadata) - } + body.setUseTabbedView(true) - return updateMenuItem(item, body) - } else { - return createFilterInMenu(body, filterQuery, filterType, filterVisibility, filterAllowedNets, filterMetadata) - } + FilterBody filterBody = new FilterBody() + filterBody.setTitle(body.getMenuName()) + filterBody.setQuery(filterQuery) + filterBody.setType(filterType) + filterBody.setAllowedNets(filterAllowedNets) + filterBody.setIcon(body.getMenuIcon()) + filterBody.setVisibility(filterVisibility) + filterBody.setMetadata(filterMetadata as Map) + body.setView(createLegacyMenuItemViews(filterBody)) + + return menuItemService.createOrUpdateMenuItem(body) } /** @@ -2735,12 +2512,7 @@ class ActionDelegate { * @return created or existing menu item instance * */ Case createOrIgnoreMenuItem(MenuItemBody body) { - Case item = findMenuItem(body.identifier) - if (!item) { - return createMenuItem(body) - } else { - return item - } + return menuItemService.createOrIgnoreMenuItem(body) } /** @@ -2751,22 +2523,23 @@ class ActionDelegate { * * @return created or existing menu item instance * */ + @Deprecated(since = "6.5.0") Case createOrIgnoreMenuItemAndFilter(MenuItemBody body, String filterQuery, String filterType, String filterVisibility, List filterAllowedNets = [], def filterMetadata = null) { - Case item = findMenuItem(body.identifier) - if (!item) { - return createFilterInMenu(body, filterQuery, filterType, filterVisibility, filterAllowedNets, filterMetadata) - } else { - Case filter = getFilterFromMenuItem(item) - if (!filter) { - filter = createFilter(body.menuName, filterQuery, filterType, filterAllowedNets, body.menuIcon, filterVisibility, - filterMetadata) - changeMenuItem item filter { filter } - return workflowService.findOne(item.stringId) - } else { - return item - } - } + body.setUseTabbedView(true) + + FilterBody filterBody = new FilterBody() + filterBody.setTitle(body.getMenuName()) + filterBody.setQuery(filterQuery) + filterBody.setType(filterType) + filterBody.setAllowedNets(filterAllowedNets) + filterBody.setIcon(body.getMenuIcon()) + filterBody.setVisibility(filterVisibility) + filterBody.setMetadata(filterMetadata as Map) + + body.setView(createLegacyMenuItemViews(filterBody)) + + return menuItemService.createOrIgnoreMenuItem(body) } /** @@ -2778,18 +2551,15 @@ class ActionDelegate { * @return updated menu item instance * */ Case updateMenuItem(Case item, MenuItemBody body) { - def outcome = setData(MenuItemConstants.PREFERENCE_ITEM_SETTINGS_TRANS_ID.attributeId, item, body.toDataSet()) - return outcome.case + return menuItemService.updateMenuItem(item, body) } static Map defaultFilterMetadata(String type) { - return [ - "searchCategories" : [], - "predicateMetadata" : [], - "filterType" : type, - "defaultSearchCategories": true, - "inheritAllowedNets" : false - ] + return FilterBody.getDefaultMetadata(type) + } + + void removeChildItemFromParent(String folderId, Case childItem) { + menuItemService.removeChildItemFromParent(folderId, childItem) } String makeUrl(String publicViewUrl = publicViewProperties.url, String identifier) { diff --git a/src/main/groovy/com/netgrif/application/engine/startup/DashboardManagementRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/DashboardManagementRunner.groovy new file mode 100644 index 00000000000..a95e79215ce --- /dev/null +++ b/src/main/groovy/com/netgrif/application/engine/startup/DashboardManagementRunner.groovy @@ -0,0 +1,27 @@ +package com.netgrif.application.engine.startup + +import groovy.util.logging.Slf4j +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.stereotype.Component + +@Slf4j +@Component +@ConditionalOnProperty(value = "nae.dashboard-management.enabled", matchIfMissing = true) +class DashboardManagementRunner extends AbstractOrderedCommandLineRunner { + + @Autowired + private ImportHelper helper + + public static final String DASHBOARD_MANAGEMENT_NET_IDENTIFIER = "dashboard_management" + private static final String DASHBOARD_MANAGEMENT_FILE_NAME = "engine-processes/dashboard_management.xml" + + public static final String DASHBOARD_ITEM_NET_IDENTIFIER = "dashboard_item" + private static final String DASHBOARD_ITEM_FILE_NAME = "engine-processes/dashboard_item.xml" + + @Override + void run(String... args) throws Exception { + helper.importProcess("Petri net for filters", DASHBOARD_MANAGEMENT_NET_IDENTIFIER, DASHBOARD_MANAGEMENT_FILE_NAME) + helper.importProcess("Petri net for filter preferences", DASHBOARD_ITEM_NET_IDENTIFIER, DASHBOARD_ITEM_FILE_NAME) + } +} diff --git a/src/main/groovy/com/netgrif/application/engine/startup/DashboardRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/DashboardRunner.groovy index 4c3690da982..aeafd8a270b 100644 --- a/src/main/groovy/com/netgrif/application/engine/startup/DashboardRunner.groovy +++ b/src/main/groovy/com/netgrif/application/engine/startup/DashboardRunner.groovy @@ -1,8 +1,5 @@ package com.netgrif.application.engine.startup -import com.netgrif.application.engine.petrinet.domain.PetriNet -import com.netgrif.application.engine.petrinet.domain.VersionType -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService import groovy.util.logging.Slf4j import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty @@ -13,15 +10,9 @@ import org.springframework.stereotype.Component @ConditionalOnProperty(value = "nae.dashboard.enabled", matchIfMissing = false) class DashboardRunner extends AbstractOrderedCommandLineRunner { - @Autowired - private IPetriNetService petriNetService - @Autowired private ImportHelper helper - @Autowired - private SystemUserRunner systemCreator - public static final String DASHBOARD_NET_IDENTIFIER = "dashboard" private static final String DASHBOARD_FILE_NAME = "engine-processes/dashboard.xml" @@ -30,32 +21,7 @@ class DashboardRunner extends AbstractOrderedCommandLineRunner { @Override void run(String... args) throws Exception { - createDashboardNet() - createDashboardTileNet() - } - - Optional createDashboardNet() { - importProcess("Petri net for filters", DASHBOARD_NET_IDENTIFIER, DASHBOARD_FILE_NAME) - } - - Optional createDashboardTileNet() { - importProcess("Petri net for filter preferences", DASHBOARD_TILE_NET_IDENTIFIER, DASHBOARD_TILE_FILE_NAME) - } - - - Optional importProcess(String message, String netIdentifier, String netFileName) { - PetriNet filter = petriNetService.getNewestVersionByIdentifier(netIdentifier) - if (filter != null) { - log.info("${message} has already been imported.") - return Optional.of(filter) - } - - Optional filterNet = helper.createNet(netFileName, VersionType.MAJOR, systemCreator.loggedSystem) - - if (!filterNet.isPresent()) { - log.error("Import of ${message} failed!") - } - - return filterNet + helper.importProcess("Petri net for filters", DASHBOARD_NET_IDENTIFIER, DASHBOARD_FILE_NAME) + helper.importProcess("Petri net for filter preferences", DASHBOARD_TILE_NET_IDENTIFIER, DASHBOARD_TILE_FILE_NAME) } } diff --git a/src/main/groovy/com/netgrif/application/engine/startup/FilterRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/FilterRunner.groovy index 0e1608c6da4..a83dfbfcffc 100644 --- a/src/main/groovy/com/netgrif/application/engine/startup/FilterRunner.groovy +++ b/src/main/groovy/com/netgrif/application/engine/startup/FilterRunner.groovy @@ -1,9 +1,8 @@ package com.netgrif.application.engine.startup +import com.netgrif.application.engine.menu.registry.interfaces.IMenuItemViewRegistry import com.netgrif.application.engine.petrinet.domain.PetriNet -import com.netgrif.application.engine.petrinet.domain.VersionType import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService -import com.netgrif.application.engine.workflow.domain.eventoutcomes.petrinetoutcomes.ImportPetriNetEventOutcome import groovy.util.logging.Slf4j import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -13,19 +12,22 @@ import org.springframework.stereotype.Component class FilterRunner extends AbstractOrderedCommandLineRunner { @Autowired - private IPetriNetService petriNetService + private ImportHelper helper @Autowired - private ImportHelper helper + private IPetriNetService petriNetService @Autowired private SystemUserRunner systemCreator + @Autowired + private IMenuItemViewRegistry viewRegistry + private static final String FILTER_FILE_NAME = "engine-processes/filter.xml" public static final String FILTER_PETRI_NET_IDENTIFIER = "filter" - private static final String PREFERRED_ITEM_FILE_NAME = "engine-processes/preference_item.xml" - public static final String PREFERRED_ITEM_NET_IDENTIFIER = "preference_item" + private static final String MENU_ITEM_FILE_NAME = "engine-processes/menu/menu_item.xml" + public static final String MENU_NET_IDENTIFIER = "menu_item" private static final String EXPORT_FILTER_FILE_NAME = "engine-processes/export_filters.xml" private static final String EXPORT_NET_IDENTIFIER = "export_filters" @@ -35,41 +37,18 @@ class FilterRunner extends AbstractOrderedCommandLineRunner { @Override void run(String... args) throws Exception { - createFilterNet() - createPreferenceItemNet() - createImportFiltersNet() - createExportFiltersNet() - } - - Optional createFilterNet() { - importProcess("Petri net for filters", FILTER_PETRI_NET_IDENTIFIER, FILTER_FILE_NAME) + helper.importProcess("Petri net for filters", FILTER_PETRI_NET_IDENTIFIER, FILTER_FILE_NAME) + createConfigurationNets() + helper.importProcess("Petri net for filter preferences", MENU_NET_IDENTIFIER, MENU_ITEM_FILE_NAME) + helper.importProcess("Petri net for importing filters", IMPORT_NET_IDENTIFIER, IMPORT_FILTER_FILE_NAME) + helper.importProcess("Petri net for exporting filters", EXPORT_NET_IDENTIFIER, EXPORT_FILTER_FILE_NAME) } - Optional createPreferenceItemNet() { - importProcess("Petri net for filter preferences", PREFERRED_ITEM_NET_IDENTIFIER, PREFERRED_ITEM_FILE_NAME) - } - - Optional createImportFiltersNet() { - importProcess("Petri net for importing filters", IMPORT_NET_IDENTIFIER, IMPORT_FILTER_FILE_NAME) - } - - Optional createExportFiltersNet() { - importProcess("Petri net for exporting filters", EXPORT_NET_IDENTIFIER, EXPORT_FILTER_FILE_NAME) - } - - Optional importProcess(String message, String netIdentifier, String netFileName) { - PetriNet filter = petriNetService.getNewestVersionByIdentifier(netIdentifier) - if (filter != null) { - log.info("${message} has already been imported.") - return Optional.of(filter) - } - - Optional filterNet = helper.createNet(netFileName, VersionType.MAJOR, systemCreator.loggedSystem) - - if (!filterNet.isPresent()) { - log.error("Import of ${message} failed!") - } - - return filterNet + private List createConfigurationNets() { + return viewRegistry.getAllViews().each { viewEntry -> + String processIdentifier = viewEntry.getKey() + "_configuration" + String filePath = String.format("engine-processes/menu/%s.xml", processIdentifier) + helper.importProcess(String.format("Petri net for %s", processIdentifier), processIdentifier, filePath) + }.collect() } } diff --git a/src/main/groovy/com/netgrif/application/engine/startup/ImpersonationRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/ImpersonationRunner.groovy index d2766a4e413..93e07d36550 100644 --- a/src/main/groovy/com/netgrif/application/engine/startup/ImpersonationRunner.groovy +++ b/src/main/groovy/com/netgrif/application/engine/startup/ImpersonationRunner.groovy @@ -1,8 +1,5 @@ package com.netgrif.application.engine.startup -import com.netgrif.application.engine.petrinet.domain.PetriNet -import com.netgrif.application.engine.petrinet.domain.VersionType -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService import groovy.util.logging.Slf4j import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -11,14 +8,9 @@ import org.springframework.stereotype.Component @Component class ImpersonationRunner extends AbstractOrderedCommandLineRunner { - @Autowired - protected IPetriNetService petriNetService - @Autowired protected ImportHelper helper - @Autowired - protected SystemUserRunner systemCreator protected static final String IMPERSONATION_CONFIG_FILE_NAME = "engine-processes/impersonation_config.xml" public static final String IMPERSONATION_CONFIG_PETRI_NET_IDENTIFIER = "impersonation_config" @@ -28,26 +20,7 @@ class ImpersonationRunner extends AbstractOrderedCommandLineRunner { @Override void run(String... args) throws Exception { - createConfigNets() - } - - void createConfigNets() { - importProcess("Petri net for impersonation config", IMPERSONATION_CONFIG_PETRI_NET_IDENTIFIER, IMPERSONATION_CONFIG_FILE_NAME) - importProcess("Petri net for impersonation user select", IMPERSONATION_CONFIG_USER_SELECT_PETRI_NET_IDENTIFIER, IMPERSONATION_CONFIG_USER_SELECT_FILE_NAME) - } - - Optional importProcess(String message, String netIdentifier, String netFileName) { - PetriNet foundNet = petriNetService.getNewestVersionByIdentifier(netIdentifier) - if (foundNet != null) { - log.info("${message} has already been imported.") - return Optional.of(foundNet) - } - - Optional net = helper.createNet(netFileName, VersionType.MAJOR, systemCreator.loggedSystem) - if (!net.isPresent()) { - log.error("Import of ${message} failed!") - } - - return net + helper.importProcess("Petri net for impersonation config", IMPERSONATION_CONFIG_PETRI_NET_IDENTIFIER, IMPERSONATION_CONFIG_FILE_NAME) + helper.importProcess("Petri net for impersonation user select", IMPERSONATION_CONFIG_USER_SELECT_PETRI_NET_IDENTIFIER, IMPERSONATION_CONFIG_USER_SELECT_FILE_NAME) } } diff --git a/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy b/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy index 8b7de95626b..295c23f9f9f 100644 --- a/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy +++ b/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy @@ -90,6 +90,9 @@ class ImportHelper { @Autowired private IUriService uriService + @Autowired + private SystemUserRunner systemCreator + private final ClassLoader loader = ImportHelper.getClassLoader() @@ -239,6 +242,22 @@ class ImportHelper { superCreator.setAllToSuperUser(); } + Optional importProcess(String message, String netIdentifier, String netFileName) { + PetriNet filter = petriNetService.getNewestVersionByIdentifier(netIdentifier) + if (filter != null) { + log.info("${message} has already been imported.") + return Optional.of(filter) + } + + Optional filterNet = this.createNet(netFileName, VersionType.MAJOR, systemCreator.loggedSystem) + + if (!filterNet.isPresent()) { + log.error("Import of ${message} failed!") + } + + return filterNet + } + static ObjectNode populateDataset(Map> data) { ObjectMapper mapper = new ObjectMapper() String json = mapper.writeValueAsString(data) diff --git a/src/main/groovy/com/netgrif/application/engine/startup/MenuItemViewRegistryRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/MenuItemViewRegistryRunner.groovy new file mode 100644 index 00000000000..472de8218ba --- /dev/null +++ b/src/main/groovy/com/netgrif/application/engine/startup/MenuItemViewRegistryRunner.groovy @@ -0,0 +1,83 @@ +package com.netgrif.application.engine.startup + +import com.netgrif.application.engine.menu.domain.MenuItemView +import com.netgrif.application.engine.menu.registry.interfaces.IMenuItemViewRegistry +import com.netgrif.application.engine.petrinet.domain.I18nString +import groovy.util.logging.Slf4j +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component + +@Slf4j +@Component +class MenuItemViewRegistryRunner extends AbstractOrderedCommandLineRunner { + + @Autowired + private IMenuItemViewRegistry registry + + public static final String TABBED_CASE_VIEW_ID = "tabbed_case_view" + public static final String TABBED_TASK_VIEW_ID = "tabbed_task_view" + public static final String TABBED_TICKET_VIEW_ID = "tabbed_ticket_view" + public static final String TABBED_SINGLE_TASK_VIEW_ID = "tabbed_single_task_view" + + @Override + void run(String... args) throws Exception { + registry.unregisterAllViews() + + log.info("Registering menu item views...") + registerTabbedCaseView() + registerTabbedTaskView() + registerTabbedTicketView() + registerTabbedSingleTaskView() + + int viewCount = registry.getAllViews().size() + log.info("Registered {} views.", viewCount) + } + + private void registerTabbedCaseView() { + MenuItemView view = MenuItemView.with() + .name(new I18nString("Tabbed case view", Map.of("sk", "Zobrazenie prípadov v taboch", + "de", "Fallansicht mit Registerkarten"))) + .identifier(TABBED_CASE_VIEW_ID) + .allowedAssociatedViews(List.of(TABBED_TASK_VIEW_ID)) + .isTabbed(true) + .isPrimary(true) + .build() + registry.registerView(view) + } + + private void registerTabbedTaskView() { + MenuItemView view = MenuItemView.with() + .name(new I18nString("Tabbed task view", Map.of("sk", "Zobrazenie úloh v taboch", + "de", "Aufgabenansicht mit Registerkarten"))) + .identifier(TABBED_TASK_VIEW_ID) + .allowedAssociatedViews(List.of()) + .isTabbed(true) + .isPrimary(true) + .build() + registry.registerView(view) + } + + private void registerTabbedTicketView() { + MenuItemView view = MenuItemView.with() + .name(new I18nString("Tabbed ticket view", Map.of("sk", "Tiketové zobrazenie v taboch", + "de", "Ticketansicht mit Registerkarten"))) + .identifier(TABBED_TICKET_VIEW_ID) + .allowedAssociatedViews(List.of(TABBED_SINGLE_TASK_VIEW_ID)) + .isTabbed(true) + .isPrimary(true) + .build() + registry.registerView(view) + } + + private void registerTabbedSingleTaskView() { + MenuItemView view = MenuItemView.with() + .name(new I18nString("Tabbed single task view", Map.of("sk", "Zobrazenie jednej úlohy v taboch", + "de", "Einzelaufgabenansicht mit Registerkarten"))) + .identifier(TABBED_SINGLE_TASK_VIEW_ID) + .allowedAssociatedViews(List.of()) + .isTabbed(true) + .isPrimary(false) + .build() + registry.registerView(view) + } +} diff --git a/src/main/groovy/com/netgrif/application/engine/startup/RunnerController.groovy b/src/main/groovy/com/netgrif/application/engine/startup/RunnerController.groovy index f02d30e1c8f..396fdaf1b08 100644 --- a/src/main/groovy/com/netgrif/application/engine/startup/RunnerController.groovy +++ b/src/main/groovy/com/netgrif/application/engine/startup/RunnerController.groovy @@ -17,10 +17,12 @@ class RunnerController { UriRunner, FunctionsCacheRunner, FilterRunner, + MenuItemViewRegistryRunner, GroupRunner, DefaultFiltersRunner, ImpersonationRunner, DashboardRunner, + DashboardManagementRunner, SuperCreator, FlushSessionsRunner, MailRunner, diff --git a/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticPetriNet.java b/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticPetriNet.java index 48637e4c236..537dc92f369 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticPetriNet.java +++ b/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticPetriNet.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.netgrif.application.engine.elastic.domain.I18nField; import com.netgrif.application.engine.petrinet.domain.I18nString; import com.netgrif.application.engine.petrinet.domain.PetriNet; import com.netgrif.application.engine.petrinet.domain.version.Version; @@ -17,6 +18,8 @@ import org.springframework.data.elasticsearch.annotations.FieldType; import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; import static org.springframework.data.elasticsearch.annotations.FieldType.Keyword; @@ -40,7 +43,7 @@ public class ElasticPetriNet { @Field(type = Keyword) private String stringId; - private I18nString title; + private I18nField title; @Field(type = Keyword) private String initials; @@ -55,7 +58,7 @@ public ElasticPetriNet(PetriNet net) { this.version = net.getVersion(); this.uriNodeId = net.getUriNodeId(); this.stringId = net.getStringId(); - this.title = net.getTitle(); + this.title = this.transformToField(net.getTitle()); this.initials = net.getInitials(); this.creationDate = net.getCreationDate(); } @@ -68,4 +71,11 @@ public void update(ElasticPetriNet net) { this.title = net.getTitle(); this.initials = net.getInitials(); } + + protected I18nField transformToField(I18nString field) { + Set keys = field.getTranslations().keySet(); + Set values = new HashSet<>(field.getTranslations().values()); + values.add(field.getDefaultValue()); + return new I18nField(keys, values); + } } diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java index 3777f1ddb86..dbaf4ee85c4 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java @@ -1,20 +1,40 @@ package com.netgrif.application.engine.elastic.service; +import com.netgrif.application.engine.auth.domain.LoggedUser; +import com.netgrif.application.engine.configuration.ElasticsearchConfiguration; import com.netgrif.application.engine.elastic.domain.ElasticPetriNet; import com.netgrif.application.engine.elastic.domain.ElasticPetriNetRepository; + import com.netgrif.application.engine.elastic.service.executors.Executor; import com.netgrif.application.engine.elastic.service.interfaces.IElasticPetriNetService; import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.PetriNetSearch; import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; +import com.netgrif.application.engine.petrinet.web.responsebodies.PetriNetReference; +import com.netgrif.application.engine.utils.FullPageRequest; import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.SearchHitSupport; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.stereotype.Service; -import java.util.List; +import java.util.*; +import java.util.function.BinaryOperator; import java.util.stream.Collectors; +import static org.elasticsearch.index.query.QueryBuilders.*; + @Service @Slf4j public class ElasticPetriNetService implements IElasticPetriNetService { @@ -25,9 +45,15 @@ public class ElasticPetriNetService implements IElasticPetriNetService { private IPetriNetService petriNetService; - public ElasticPetriNetService(ElasticPetriNetRepository repository, Executor executors) { + private final ElasticsearchRestTemplate template; + + protected ElasticsearchConfiguration elasticsearchConfiguration; + + public ElasticPetriNetService(ElasticPetriNetRepository repository, Executor executors, ElasticsearchRestTemplate template, ElasticsearchConfiguration elasticsearchConfiguration) { this.repository = repository; this.executors = executors; + this.template = template; + this.elasticsearchConfiguration = elasticsearchConfiguration; } @Lazy @@ -89,4 +115,105 @@ public List findAllByUriNodeId(String uriNodeId) { List elasticPetriNets = repository.findAllByUriNodeId(uriNodeId); return petriNetService.findAllById(elasticPetriNets.stream().map(ElasticPetriNet::getStringId).collect(Collectors.toList())); } + + /** + * Method for search of PetriNets in Elastic + * @param requests - search body, for now only title working + * @param user - logged user + * @param pageable - pageable for paging + * @param locale - internacionalization + * @param isIntersection - property for merging filter, not implemented now, use false + * @return Page - page of PetriNetReferences + */ + @Override + public Page search(PetriNetSearch requests, LoggedUser user, Pageable pageable, Locale locale, Boolean isIntersection) { + if (requests == null) { + throw new IllegalArgumentException("Request can not be null!"); + } + log.debug("Searching for PetriNet query with logged user [{}]", user.getId()); + LoggedUser loggedOrImpersonated = user.getSelfOrImpersonated(); + NativeSearchQuery query = buildQuery(requests, loggedOrImpersonated, pageable, locale, isIntersection); + List netPage; + long total; + if (query != null) { + SearchHits hits = template.search(query, ElasticPetriNet.class, IndexCoordinates.of(elasticsearchConfiguration.elasticPetriNetIndex())); + Page indexedNets = (Page) SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(hits, query.getPageable())); + netPage = petriNetService.findAllById(indexedNets.get().map(ElasticPetriNet::getStringId).collect(Collectors.toList())); + total = indexedNets.getTotalElements(); + log.debug("Found [{}] total elements of page [{}]", netPage.size(), pageable.getPageNumber()); + } else { + netPage = Collections.emptyList(); + total = 0; + } + + return new PageImpl<>(netPage.stream().map(net -> new PetriNetReference(net, locale)).collect(Collectors.toList()), pageable, total); + } + + protected NativeSearchQuery buildQuery(PetriNetSearch request, LoggedUser user, Pageable pageable, Locale locale, Boolean isIntersection) { + List singleQueries = new LinkedList<>(); + singleQueries.add(buildSingleQuery(request, user, locale)); + + if (isIntersection && !singleQueries.stream().allMatch(Objects::nonNull)) { + // one of the queries evaluates to empty set => the entire result is an empty set + return null; + } else if (!isIntersection) { + singleQueries = singleQueries.stream().filter(Objects::nonNull).collect(Collectors.toList()); + if (singleQueries.isEmpty()) { + // all queries result in an empty set => the entire result is an empty set + return null; + } + } + + BinaryOperator reductionOperator = isIntersection ? BoolQueryBuilder::must : BoolQueryBuilder::should; + BoolQueryBuilder query = singleQueries.stream().reduce(new BoolQueryBuilder(), reductionOperator); + + NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder(); + return builder + .withQuery(query) + .withPageable(pageable) + .build(); + } + + protected BoolQueryBuilder buildSingleQuery(PetriNetSearch request, LoggedUser user, Locale locale) { + BoolQueryBuilder query = boolQuery(); + + buildFullTextQuery(request, query); + boolean resultAlwaysEmpty = buildGroupQuery(request, user, locale, query); + if (resultAlwaysEmpty) { + return null; + } + return query; + } + + protected void buildFullTextQuery(PetriNetSearch request, BoolQueryBuilder query) { + if (request.getTitle() == null || request.getTitle().isEmpty()) { + return; + } + + String searchText = "*" + request.getTitle() + "*"; + Map fields = new HashMap<>(); + fields.put("title.textValue", 2f); + fields.put("identifier", 1f); + QueryBuilder fullTextQuery = queryStringQuery(searchText).fields(fields); + query.must(fullTextQuery); + } + + protected boolean buildGroupQuery(PetriNetSearch request, LoggedUser user, Locale locale, BoolQueryBuilder query) { + if (request.getGroup() == null || request.getGroup().isEmpty()) { + return false; + } + + PetriNetSearch processQuery = new PetriNetSearch(); + processQuery.setGroup(request.getGroup()); + List groupProcesses = this.petriNetService.search(processQuery, user, new FullPageRequest(), locale).getContent(); + if (groupProcesses.isEmpty()) + return true; + + BoolQueryBuilder groupQuery = boolQuery(); + groupProcesses.stream().map(PetriNetReference::getIdentifier) + .map(netIdentifier -> termQuery("identifier", netIdentifier)) + .forEach(groupQuery::should); + query.filter(groupQuery); + return false; + } } diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/interfaces/IElasticPetriNetService.java b/src/main/java/com/netgrif/application/engine/elastic/service/interfaces/IElasticPetriNetService.java index 6be7daaf0a3..54a2c96abe9 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/interfaces/IElasticPetriNetService.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/interfaces/IElasticPetriNetService.java @@ -1,10 +1,16 @@ package com.netgrif.application.engine.elastic.service.interfaces; +import com.netgrif.application.engine.auth.domain.LoggedUser; import com.netgrif.application.engine.elastic.domain.ElasticPetriNet; import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.PetriNetSearch; +import com.netgrif.application.engine.petrinet.web.responsebodies.PetriNetReference; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.scheduling.annotation.Async; import java.util.List; +import java.util.Locale; public interface IElasticPetriNetService { @@ -19,4 +25,6 @@ public interface IElasticPetriNetService { List findAllByUriNodeId(String uriNodeId); + Page search(PetriNetSearch requests, LoggedUser user, Pageable pageable, Locale locale, Boolean isIntersection); + } 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 new file mode 100644 index 00000000000..de048064e33 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java @@ -0,0 +1,74 @@ +package com.netgrif.application.engine.menu.domain; + +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import com.netgrif.application.engine.startup.DefaultFiltersRunner; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.service.interfaces.IDataService; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Data +@NoArgsConstructor +public class FilterBody { + private Case filter; + private I18nString title; + private String query; + private String type; + private List allowedNets; + private String icon; + private String visibility; + private Map metadata; + + public FilterBody(Case filterCase) { + this.filter = filterCase; + } + + /** + * Gets default metadata with provided filter type + * + * @param type type of the filter + * + * @return metadata containing filter type as map + * */ + public static Map getDefaultMetadata(String type) { + Map resultMap = new HashMap<>(); + + resultMap.put("searchCategories", List.of()); + resultMap.put("predicateMetadata", List.of()); + resultMap.put("filterType", type); + resultMap.put("defaultSearchCategories", true); + resultMap.put("inheritAllowedNets", false); + + return resultMap; + } + + /** + * Transforms attributes into dataSet for {@link IDataService#setData} + * + * @return {@link ToDataSetOutcome} object with dataSet + * */ + public ToDataSetOutcome toDataSet() { + ToDataSetOutcome outcome = new ToDataSetOutcome(); + + outcome.putDataSetEntry(DefaultFiltersRunner.FILTER_TYPE_FIELD_ID, FieldType.ENUMERATION_MAP, this.type); + outcome.putDataSetEntry(DefaultFiltersRunner.FILTER_VISIBILITY_FIELD_ID, FieldType.ENUMERATION_MAP, this.visibility); + outcome.putDataSetEntry(DefaultFiltersRunner.FILTER_I18N_TITLE_FIELD_ID, FieldType.I18N, this.title); + Map metadata = this.metadata; + 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 + )); + + return outcome; + } +} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/Menu.java b/src/main/java/com/netgrif/application/engine/menu/domain/Menu.java similarity index 96% rename from src/main/java/com/netgrif/application/engine/workflow/domain/menu/Menu.java rename to src/main/java/com/netgrif/application/engine/menu/domain/Menu.java index 62e75dcae2c..cce9388ba21 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/Menu.java +++ b/src/main/java/com/netgrif/application/engine/menu/domain/Menu.java @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.workflow.domain.menu; +package com.netgrif.application.engine.menu.domain; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuAndFilters.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuAndFilters.java similarity index 96% rename from src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuAndFilters.java rename to src/main/java/com/netgrif/application/engine/menu/domain/MenuAndFilters.java index 13b77dbc185..e12f019fabe 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuAndFilters.java +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuAndFilters.java @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.workflow.domain.menu; +package com.netgrif.application.engine.menu.domain; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuEntry.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntry.java similarity index 96% rename from src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuEntry.java rename to src/main/java/com/netgrif/application/engine/menu/domain/MenuEntry.java index 40bb87973aa..c3900ddf511 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuEntry.java +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntry.java @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.workflow.domain.menu; +package com.netgrif.application.engine.menu.domain; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuEntryRole.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntryRole.java similarity index 96% rename from src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuEntryRole.java rename to src/main/java/com/netgrif/application/engine/menu/domain/MenuEntryRole.java index 66d6aab50e3..dd39ea86e8d 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuEntryRole.java +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntryRole.java @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.workflow.domain.menu; +package com.netgrif.application.engine.menu.domain; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.netgrif.application.engine.workflow.domain.AuthorizationType; 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 new file mode 100644 index 00000000000..d763de0c501 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java @@ -0,0 +1,183 @@ +package 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.utils.MenuItemUtils; +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 com.netgrif.application.engine.workflow.service.interfaces.IDataService; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Class, that holds configurable attributes of menu item. + */ +@Data +@NoArgsConstructor +public class MenuItemBody { + private String uri; + private String identifier; + + private String menuIcon = "filter_none"; + private I18nString menuName; + private Map allowedRoles = new HashMap<>(); + private Map bannedRoles = new HashMap<>(); + private boolean useCustomView = false; + private String customViewSelector; + private boolean isAutoSelect = false; + + private boolean useTabbedView; + private String tabIcon; + private boolean useTabIcon = true; + private I18nString tabName; + private ViewBody view; + + public MenuItemBody(I18nString name, String icon) { + this.menuName = name; + this.tabName = name; + this.menuIcon = icon; + this.tabIcon = icon; + } + + public MenuItemBody(I18nString menuName, I18nString tabName, String menuIcon, String tabIcon) { + this.menuName = menuName; + this.tabName = tabName; + this.menuIcon = menuIcon; + this.tabIcon = tabIcon; + } + + public MenuItemBody(String uri, String identifier, I18nString name, String icon) { + this.uri = uri; + this.identifier = identifier; + this.menuName = name; + this.tabName = name; + this.menuIcon = icon; + this.tabIcon = icon; + } + + public MenuItemBody(String uri, String identifier, I18nString menuName, I18nString tabName, String menuIcon, String tabIcon) { + this.uri = uri; + this.identifier = identifier; + this.menuName = menuName; + this.tabName = tabName; + this.menuIcon = menuIcon; + this.tabIcon = tabIcon; + } + + public MenuItemBody(String uri, String identifier, String name, String icon) { + this.uri = uri; + this.identifier = identifier; + this.menuName = new I18nString(name); + this.tabName = new I18nString(name); + this.menuIcon = icon; + this.tabIcon = icon; + } + + public MenuItemBody(String uri, String identifier, String menuName, String tabName, String menuIcon, String tabIcon) { + this.uri = uri; + this.identifier = identifier; + this.menuName = new I18nString(menuName); + this.tabName = new I18nString(tabName); + this.menuIcon = menuIcon; + this.tabIcon = tabIcon; + } + + public String getIdentifier() { + return MenuItemUtils.sanitize(this.identifier); + } + + public void setMenuName(I18nString name) { + this.menuName = name; + } + + public void setMenuName(String name) { + this.menuName = new I18nString(name); + } + + public void setTabName(I18nString name) { + this.tabName = name; + } + + public void setTabName(String name) { + this.tabName = new I18nString(name); + } + + /** + * @return true if the menu item contains view + * */ + public boolean hasView() { + return this.view != null; + } + + /** + * Transforms attributes into dataSet for {@link IDataService#setData} + * + * @return {@link ToDataSetOutcome} object with dataSet + */ + public ToDataSetOutcome toDataSet() { + return toDataSet(null, null, null); + } + + /** + * Transforms attributes into dataSet for {@link IDataService#setData} + * + * @param viewCase case instance of view. If provided, caseRef and taskRef are initialized + * + * @return {@link ToDataSetOutcome} object with dataSet + */ + public ToDataSetOutcome toDataSet(Case viewCase) { + return toDataSet(null, null, viewCase); + } + + /** + * Transforms attributes into dataSet for {@link IDataService#setData} + * + * @param parentId identifier of parent menu item instance + * @param nodePath uri, that represents the menu item (f.e.: "/myItem1/myItem2") + * @param viewCase case instance of view. If provided, caseRef and taskRef are initialized + * + * @return {@link ToDataSetOutcome} object with dataSet + */ + public ToDataSetOutcome toDataSet(String parentId, String nodePath, Case viewCase) { + ToDataSetOutcome outcome = new ToDataSetOutcome(); + + if (parentId != null) { + outcome.putDataSetEntry(MenuItemConstants.FIELD_PARENT_ID, FieldType.CASE_REF, List.of(parentId)); + } + if (nodePath != null) { + outcome.putDataSetEntry(MenuItemConstants.FIELD_NODE_PATH, FieldType.TEXT, nodePath); + } + 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); + outcome.putDataSetEntry(MenuItemConstants.FIELD_TAB_ICON, FieldType.TEXT, this.tabIcon); + if (this.identifier != null) { + outcome.putDataSetEntry(MenuItemConstants.FIELD_IDENTIFIER, FieldType.TEXT, this.getIdentifier()); + } + outcome.putDataSetEntry(MenuItemConstants.FIELD_USE_TAB_ICON, FieldType.BOOLEAN, this.useTabIcon); + outcome.putDataSetEntry(MenuItemConstants.FIELD_USE_CUSTOM_VIEW, FieldType.BOOLEAN, + this.useCustomView); + outcome.putDataSetEntry(MenuItemConstants.FIELD_CUSTOM_VIEW_SELECTOR, FieldType.TEXT, + this.customViewSelector); + 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); + + if (viewCase != null) { + outcome.putDataSetEntry(MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE, FieldType.ENUMERATION_MAP, + this.view.getViewIdentifier()); + outcome.putDataSetEntry(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID, FieldType.CASE_REF, + 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)); + } + + 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 new file mode 100644 index 00000000000..d6ac1a37afc --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java @@ -0,0 +1,35 @@ +package com.netgrif.application.engine.menu.domain; + +/** + * Here are declared constants of process menu_item.xml. + */ +public class MenuItemConstants { + public static final String FIELD_PARENT_ID = "parentId"; + public static final String FIELD_CHILD_ITEM_IDS = "childItemIds"; + public static final String FIELD_HAS_CHILDREN = "hasChildren"; + 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_ROLES = "allowed_roles"; + public static final String FIELD_BANNED_ROLES = "banned_roles"; + public static final String FIELD_MENU_NAME = "menu_name"; + public static final String FIELD_MENU_ICON = "menu_icon"; + public static final String FIELD_TAB_NAME = "tab_name"; + public static final String FIELD_USE_TABBED_VIEW = "use_tabbed_view"; + public static final String FIELD_USE_TAB_ICON = "use_tab_icon"; + public static final String FIELD_TAB_ICON = "tab_icon"; + public static final String FIELD_VIEW_CONFIGURATION_TYPE = "view_configuration_type"; + public static final String FIELD_NODE_PATH = "nodePath"; + public static final String FIELD_NODE_NAME = "nodeName"; + public static final String FIELD_DUPLICATE_TITLE = "duplicate_new_title"; + public static final String FIELD_DUPLICATE_IDENTIFIER = "duplicate_view_identifier"; + public static final String FIELD_DUPLICATE_RESET_CHILD_ITEM_IDS = "duplicate_reset_childItemIds"; + public static final String FIELD_USE_CUSTOM_VIEW = "use_custom_view"; + public static final String FIELD_CUSTOM_VIEW_SELECTOR = "custom_view_selector"; + 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 TRANS_SETTINGS_ID = "item_settings"; + public static final String TRANS_INIT_ID = "initialize"; + public static final String TRANS_SYNC_ID = "data_sync"; +} 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 new file mode 100644 index 00000000000..819fad43bfd --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemView.java @@ -0,0 +1,29 @@ +package com.netgrif.application.engine.menu.domain; + +import com.netgrif.application.engine.petrinet.domain.I18nString; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NonNull; + +import java.util.List; + +@Data +@AllArgsConstructor +@Builder(builderMethodName = "with") +public class MenuItemView { + @NonNull + private final I18nString name; + @NonNull + private final String identifier; + /** + * List of view identifiers of views, that can be associated with the view + * */ + private final List allowedAssociatedViews; + private final boolean isTabbed; + /** + * 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; +} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuList.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuList.java similarity index 95% rename from src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuList.java rename to src/main/java/com/netgrif/application/engine/menu/domain/MenuList.java index 9ee55560ed9..e76f0f7e40e 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuList.java +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuList.java @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.workflow.domain.menu; +package com.netgrif.application.engine.menu.domain; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/ToDataSetOutcome.java b/src/main/java/com/netgrif/application/engine/menu/domain/ToDataSetOutcome.java new file mode 100644 index 00000000000..7fdba433994 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/ToDataSetOutcome.java @@ -0,0 +1,53 @@ +package com.netgrif.application.engine.menu.domain; + +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import lombok.AllArgsConstructor; +import lombok.Data; + +import javax.annotation.Nullable; +import java.util.*; + +@Data +@AllArgsConstructor +public class ToDataSetOutcome { + private Map> dataSet; + + public ToDataSetOutcome() { + this.dataSet = new HashMap<>(); + } + + /** + * Puts provided value into {@link #dataSet} according to dataSet rules. + * + * @param fieldId importId of the field + * @param fieldType type of the field + * @param fieldValue new value of the field + * */ + public void putDataSetEntry(String fieldId, FieldType fieldType, @Nullable Object fieldValue) { + Map fieldMap = new LinkedHashMap<>(); + fieldMap.put("type", fieldType.getName()); + fieldMap.put("value", fieldValue); + this.dataSet.put(fieldId, fieldMap); + } + + /** + * Puts provided options into {@link #dataSet} according to dataSet rules. + * + * @param fieldId importId of the field + * @param fieldType type of the field + * @param options new options of the field + * */ + public void putDataSetEntryOptions(String fieldId, FieldType fieldType, @Nullable Map options) { + Map fieldMap = new LinkedHashMap<>(); + fieldMap.put("type", fieldType.getName()); + if (fieldType.equals(FieldType.MULTICHOICE_MAP)) { + fieldMap.put("value", Set.of()); + } + if (options == null) { + options = new HashMap<>(); + } + fieldMap.put("options", options); + this.dataSet.put(fieldId, fieldMap); + } +} 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 new file mode 100644 index 00000000000..08f0605c3cc --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewBody.java @@ -0,0 +1,76 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import com.netgrif.application.engine.startup.MenuItemViewRegistryRunner; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TabbedCaseViewBody extends ViewBody { + private String viewSearchType = "fulltext_advanced"; + private String createCaseButtonTitle; + private String createCaseButtonIcon = "add"; + private boolean requireTitleInCreation = true; + private boolean showCreateCaseButton = true; + private String bannedNetsInCreation; + private boolean showMoreMenu = false; + private boolean allowHeaderTableMode = true; + private List headersMode = new ArrayList<>(List.of("sort", "edit", "search")); + private String headersDefaultMode = "sort"; + private List defaultHeaders; + private boolean isHeaderModeChangeable = true; + private boolean useDefaultHeaders = true; + + private ViewBody chainedView; + + @Override + public ViewBody getAssociatedViewBody() { + return this.chainedView; + } + + @Override + public String getViewIdentifier() { + return MenuItemViewRegistryRunner.TABBED_CASE_VIEW_ID; + } + + @Override + 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, + this.createCaseButtonTitle); + outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_CREATE_CASE_BUTTON_ICON, FieldType.TEXT, + this.createCaseButtonIcon); + outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_REQUIRE_TITLE_IN_CREATION, FieldType.BOOLEAN, + this.requireTitleInCreation); + outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_SHOW_CREATE_CASE_BUTTON, FieldType.BOOLEAN, + this.showCreateCaseButton); + outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_BANNED_NETS_IN_CREATION, FieldType.TEXT, + this.bannedNetsInCreation); + outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_SHOW_MORE_MENU, FieldType.BOOLEAN, + this.showMoreMenu); + outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_ALLOW_HEADER_TABLE_MODE, FieldType.BOOLEAN, + this.allowHeaderTableMode); + outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_HEADERS_MODE, FieldType.MULTICHOICE_MAP, + this.headersMode == null ? new ArrayList<>() : this.headersMode); + outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP, + this.headersDefaultMode); + outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_DEFAULT_HEADERS, FieldType.TEXT, + this.defaultHeaders != null ? String.join(",", this.defaultHeaders) : null); + outcome.putDataSetEntry(TabbedCaseViewConstants.FIELD_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN, + this.isHeaderModeChangeable); + outcome.putDataSetEntry(TabbedCaseViewConstants.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/TabbedCaseViewConstants.java new file mode 100644 index 00000000000..b5c8ef2361b --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedCaseViewConstants.java @@ -0,0 +1,21 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +/** + * Here are declared constants of process tabbed_case_view_configuration.xml. + */ +public class TabbedCaseViewConstants 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"; + public static final String FIELD_VIEW_SEARCH_TYPE = "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_CASE_DEFAULT_HEADERS = "use_case_default_headers"; +} 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/TabbedSingleTaskViewBody.java new file mode 100644 index 00000000000..02ae0f9b5e3 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedSingleTaskViewBody.java @@ -0,0 +1,31 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import com.netgrif.application.engine.startup.MenuItemViewRegistryRunner; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TabbedSingleTaskViewBody extends ViewBody { + private String transitionId; + + @Override + public ViewBody getAssociatedViewBody() { + return null; + } + + @Override + public String getViewIdentifier() { + return MenuItemViewRegistryRunner.TABBED_SINGLE_TASK_VIEW_ID; + } + + @Override + protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) { + outcome.putDataSetEntry(TabbedSingleTaskViewConstants.FIELD_TRANSITION_ID, FieldType.TEXT, this.transitionId); + return outcome; + } +} 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 new file mode 100644 index 00000000000..c0d6949b328 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedSingleTaskViewConstants.java @@ -0,0 +1,5 @@ +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/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTaskViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTaskViewBody.java new file mode 100644 index 00000000000..2e654b28a76 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTaskViewBody.java @@ -0,0 +1,63 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import com.netgrif.application.engine.startup.MenuItemViewRegistryRunner; +import com.netgrif.application.engine.workflow.domain.Case; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TabbedTaskViewBody extends ViewBody { + private Case filter; + private boolean mergeFilters = true; + private String viewSearchType = "fulltext_advanced"; + private List headersMode = new ArrayList<>(List.of("sort", "edit")); + private String headersDefaultMode = "sort"; + private boolean isHeaderModeChangeable = true; + private boolean allowHeaderTableMode = true; + private boolean useDefaultHeaders = true; + private List defaultHeaders; + private boolean showMoreMenu = true; + + @Override + public ViewBody getAssociatedViewBody() { + return null; + } + + @Override + public String getViewIdentifier() { + return MenuItemViewRegistryRunner.TABBED_TASK_VIEW_ID; + } + + @Override + protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) { + + outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_MERGE_FILTERS, FieldType.BOOLEAN, + this.mergeFilters); + outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP, + this.viewSearchType); + outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_HEADERS_MODE, FieldType.MULTICHOICE_MAP, + this.headersMode == null ? new ArrayList<>() : this.headersMode); + outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP, + this.headersDefaultMode); + outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN, + this.isHeaderModeChangeable); + outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_ALLOW_HEADER_TABLE_MODE, FieldType.BOOLEAN, + this.allowHeaderTableMode); + outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_USE_DEFAULT_HEADERS, FieldType.BOOLEAN, + this.useDefaultHeaders); + outcome.putDataSetEntry(TabbedTaskViewConstants.FIELD_DEFAULT_HEADERS, FieldType.TEXT, + this.defaultHeaders != null ? String.join(",", this.defaultHeaders) : null); + outcome.putDataSetEntry(TabbedTaskViewConstants.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/TabbedTaskViewConstants.java new file mode 100644 index 00000000000..f895558fa77 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTaskViewConstants.java @@ -0,0 +1,16 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +/** + * Here are declared constants of process tabbed_task_view_configuration.xml. + */ +public class TabbedTaskViewConstants 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"; +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTicketViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTicketViewBody.java new file mode 100644 index 00000000000..e534ab73960 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTicketViewBody.java @@ -0,0 +1,30 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.startup.MenuItemViewRegistryRunner; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TabbedTicketViewBody extends ViewBody { + + private ViewBody chainedView; + + @Override + public ViewBody getAssociatedViewBody() { + return this.chainedView; + } + + @Override + public String getViewIdentifier() { + return MenuItemViewRegistryRunner.TABBED_TICKET_VIEW_ID; + } + + @Override + protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) { + return outcome; + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTicketViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTicketViewConstants.java new file mode 100644 index 00000000000..af308eba20c --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTicketViewConstants.java @@ -0,0 +1,4 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +public class TabbedTicketViewConstants extends ViewConstants { +} 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 new file mode 100644 index 00000000000..171967cb995 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewBody.java @@ -0,0 +1,82 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +import com.netgrif.application.engine.menu.domain.FilterBody; +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.menu.utils.MenuItemUtils; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.service.interfaces.IDataService; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.annotation.Nullable; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public abstract class ViewBody { + + @Nullable + protected FilterBody filterBody; + + public abstract ViewBody getAssociatedViewBody(); + public abstract String getViewIdentifier(); + /** + * Internal method, that must transform data in concrete class and add them into received outcome. Method must return + * the updated outcome. + * */ + protected abstract ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome); + + /** + * Checks if the view has associated view + * + * @return true if this view has associated view + * */ + public boolean hasAssociatedView() { + return this.getAssociatedViewBody() != null; + } + + /** + * @return returns process identifier for this view + * */ + public String getViewProcessIdentifier() { + return getViewIdentifier() + "_configuration"; + } + + /** + * Transforms data of this class into {@link ToDataSetOutcome}, which contains prepared data for the {@link IDataService#setData} + * + * @return {@link ToDataSetOutcome} object containing dataSet + * */ + public ToDataSetOutcome toDataSet() { + return toDataSet(null, null); + }; + + /** + * Transforms data of this class into {@link ToDataSetOutcome}, which contains prepared data for the {@link IDataService#setData} + * + * @param associatedViewCase case instance of associated view. If provided, caseRef and taskRef are initialized. + * @param filterCase case instance of filter. If provided, caseRef is initialized + * + * @return {@link ToDataSetOutcome} object containing dataSet + * */ + public ToDataSetOutcome toDataSet(Case associatedViewCase, Case filterCase) { + ToDataSetOutcome outcome = new ToDataSetOutcome(); + + if (associatedViewCase != null) { + outcome.putDataSetEntry(ViewConstants.FIELD_CONFIGURATION_TYPE, FieldType.ENUMERATION_MAP, + this.getAssociatedViewBody().getViewIdentifier()); + outcome.putDataSetEntry(ViewConstants.FIELD_VIEW_CONFIGURATION_ID, FieldType.CASE_REF, + 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)); + } + if (filterCase != null) { + outcome.putDataSetEntry(ViewConstants.FIELD_VIEW_FILTER_CASE, FieldType.CASE_REF, List.of(filterCase.getStringId())); + } + + return toDataSetInternal(outcome); + }; +} 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 new file mode 100644 index 00000000000..74c38803f06 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewConstants.java @@ -0,0 +1,16 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +/** + * Here are declared general constants of menu item configuration processes. + */ +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_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_SYNC_ID = "data_sync"; +} diff --git a/src/main/java/com/netgrif/application/engine/menu/registry/MenuItemViewRegistry.java b/src/main/java/com/netgrif/application/engine/menu/registry/MenuItemViewRegistry.java new file mode 100644 index 00000000000..ce41d924f9d --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/registry/MenuItemViewRegistry.java @@ -0,0 +1,120 @@ +package com.netgrif.application.engine.menu.registry; + +import com.netgrif.application.engine.menu.domain.MenuItemView; +import com.netgrif.application.engine.menu.registry.interfaces.IMenuItemViewRegistry; +import com.netgrif.application.engine.menu.registry.throwable.DuplicateViewException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class MenuItemViewRegistry implements IMenuItemViewRegistry { + + /** + * Map of registered views in app. Key is view identifier and value is {@link MenuItemView} + * */ + private final Map views; + + public MenuItemViewRegistry() { + this.views = new ConcurrentHashMap<>(); + } + + /** + * Registers new view. View must be valid. + * + * @param view valid view to be registered + * + * @throws DuplicateViewException if the view already exists + * */ + @Override + public void registerView(@Validated MenuItemView view) { + if (this.views.containsKey(view.getIdentifier())) { + throw new DuplicateViewException(view.getIdentifier()); + } + this.views.put(view.getIdentifier(), view); + log.debug("Registered menu item view [{}] with identifier [{}]", view.getName().getDefaultValue(), view.getIdentifier()); + } + + /** + * Unregisters view by identifier + * + * @param identifier view identifier + * */ + @Override + public void unregisterView(String identifier) { + this.views.remove(identifier); + } + + /** + * Unregisters all views in app + * */ + @Override + public void unregisterAllViews() { + Set viewIds = new HashSet<>(this.views.keySet()); + for (String viewId : viewIds) { + this.views.remove(viewId); + } + } + + /** + * Gets view by identifier + * + * @param identifier view identifier + * + * @return view or null if it isn't registered + * */ + @Override + public MenuItemView getViewByIdentifier(String identifier) { + return this.views.get(identifier); + } + + /** + * Gets all registered views + * + * @return map of registered views + * */ + @Override + public Map getAllViews() { + return this.views; + } + + /** + * Gets all views, that are tabbed or non-tabbed + * + * @param isTabbed if true, only tabbed values will be returned + * @param isPrimary if true, only views accessible directly from the menu_item will be returned + * + * @return List of views based on input parameters + * */ + @Override + public List getAllByIsTabbedAndIsPrimary(boolean isTabbed, boolean isPrimary) { + return this.views.values().stream() + .filter(menuItemView -> menuItemView.isTabbed() == isTabbed && menuItemView.isPrimary() == isPrimary) + .collect(Collectors.toList()); + } + + /** + * Gets all views, that are tabbed or non-tabbed and are defined in parent view as {@link MenuItemView#getAllowedAssociatedViews()} + * + * @param isTabbed if true, set of views is reduced to only tabbed views + * @param parentIdentifier identifier of the view, that contains returned views in {@link MenuItemView#getAllowedAssociatedViews()} + * + * @return List of views based on input parameters + * */ + @Override + public List getAllByIsTabbedAndParentIdentifier(boolean isTabbed, String parentIdentifier) { + MenuItemView parentView = getViewByIdentifier(parentIdentifier); + return this.views.values().stream() + .filter(menuItemView -> menuItemView.isTabbed() == isTabbed + && parentView.getAllowedAssociatedViews().contains(menuItemView.getIdentifier())) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/registry/interfaces/IMenuItemViewRegistry.java b/src/main/java/com/netgrif/application/engine/menu/registry/interfaces/IMenuItemViewRegistry.java new file mode 100644 index 00000000000..07beae485a7 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/registry/interfaces/IMenuItemViewRegistry.java @@ -0,0 +1,18 @@ +package com.netgrif.application.engine.menu.registry.interfaces; + +import com.netgrif.application.engine.menu.domain.MenuItemView; + +import java.util.Map; +import java.util.List; + +public interface IMenuItemViewRegistry { + + void registerView(MenuItemView view); + void unregisterView(String identifier); + void unregisterAllViews(); + MenuItemView getViewByIdentifier(String identifier); + Map getAllViews(); + List getAllByIsTabbedAndIsPrimary(boolean isTabbed, boolean isPrimary); + List getAllByIsTabbedAndParentIdentifier(boolean isTabbed, String parentIdentifier); + +} diff --git a/src/main/java/com/netgrif/application/engine/menu/registry/throwable/DuplicateViewException.java b/src/main/java/com/netgrif/application/engine/menu/registry/throwable/DuplicateViewException.java new file mode 100644 index 00000000000..c33e91fe9ff --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/registry/throwable/DuplicateViewException.java @@ -0,0 +1,8 @@ +package com.netgrif.application.engine.menu.registry.throwable; + +public class DuplicateViewException extends RuntimeException{ + + public DuplicateViewException(String viewIdentifier) { + super(String.format("View with identifier [%s] is already registered.", viewIdentifier)); + } +} 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 new file mode 100644 index 00000000000..e904788c61b --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java @@ -0,0 +1,745 @@ +package com.netgrif.application.engine.menu.services; + +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.auth.domain.LoggedUser; +import com.netgrif.application.engine.auth.service.RegistrationService; +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.*; +import com.netgrif.application.engine.menu.domain.configurations.ViewBody; +import com.netgrif.application.engine.menu.domain.configurations.ViewConstants; +import com.netgrif.application.engine.menu.registry.interfaces.IMenuItemViewRegistry; +import com.netgrif.application.engine.menu.services.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.FieldType; +import com.netgrif.application.engine.petrinet.domain.dataset.MapOptionsField; +import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; +import com.netgrif.application.engine.petrinet.service.interfaces.IUriService; +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.workflow.domain.Case; +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; +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import com.netgrif.application.engine.startup.MenuItemViewRegistryRunner; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.*; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class MenuItemService implements IMenuItemService { + protected final IWorkflowService workflowService; + protected final ITaskService taskService; + protected final IDataService dataService; + protected final IUserService userService; + protected final IUriService uriService; + protected final IElasticCaseService elasticCaseService; + protected final IMenuItemViewRegistry viewRegistry; + + protected static final String DEFAULT_FOLDER_ICON = "folder"; + + /** + * Creates new filter case + * + * @param body filter data used for creation + * + * @return initialized filter case instance with the provided data + * */ + @Override + public Case createFilter(FilterBody body) throws TransitionNotExecutableException { + IUser loggedUser = userService.getLoggedOrSystem(); + Case filterCase = createCase(FilterRunner.FILTER_PETRI_NET_IDENTIFIER, body.getTitle().getDefaultValue(), loggedUser.transformToLoggedUser()); + filterCase.setIcon(body.getIcon()); + filterCase = workflowService.save(filterCase); + ToDataSetOutcome dataSetOutcome = body.toDataSet(); + filterCase = setDataWithExecute(filterCase, DefaultFiltersRunner.AUTO_CREATE_TRANSITION, dataSetOutcome.getDataSet()); + log.trace("Created filter case [{}][{}]", filterCase.getStringId(), body.getTitle().getDefaultValue()); + return filterCase; + } + + /** + * Updates existing filter case + * + * @param filterCase filter to be updated + * @param body data values used for update + * + * @return updated filter case instance + * */ + @Override + public Case updateFilter(Case filterCase, FilterBody body) { + filterCase.setIcon(body.getIcon()); + filterCase = workflowService.save(filterCase); + ToDataSetOutcome dataSetOutcome = body.toDataSet(); + filterCase = setData(filterCase, DefaultFiltersRunner.DETAILS_TRANSITION, dataSetOutcome.getDataSet()); + log.trace("Updated filter case [{}][{}]", filterCase.getStringId(), body.getTitle().getDefaultValue()); + return filterCase; + } + + + /** + * Creates menu item case and it's configuration cases + * + * @param body data used for creation + * + * @return initialized menu item instance with the provided data + * + * @throws IllegalArgumentException if the provided menu identifier already exists + * */ + @Override + public Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableException { + 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)); + } + + Case parentItemCase = getOrCreateFolderItem(body.getUri()); + I18nString newName = body.getMenuName(); + if (newName == null) { + newName = new I18nString(body.getIdentifier()); + } + Case menuItemCase = createCase(FilterRunner.MENU_NET_IDENTIFIER, newName.getDefaultValue(), + loggedUser.transformToLoggedUser()); + menuItemCase.setUriNodeId(uriService.findByUri(body.getUri()).getStringId()); + menuItemCase = workflowService.save(menuItemCase); + + parentItemCase = appendChildCaseIdAndSave(parentItemCase, menuItemCase.getStringId()); + + String nodePath = createNodePath(body.getUri(), sanitizedIdentifier); + uriService.getOrCreate(nodePath, UriContentType.CASE); + + Case viewCase = null; + if (body.hasView()) { + ViewBody viewBody = body.getView(); + MenuItemView registeredView = viewRegistry.getViewByIdentifier(viewBody.getViewIdentifier()); + body.setUseTabbedView(registeredView.isTabbed()); + viewCase = createView(viewBody); + } + ToDataSetOutcome dataSetOutcome = body.toDataSet(parentItemCase.getStringId(), nodePath, viewCase); + menuItemCase = setDataWithExecute(menuItemCase, MenuItemConstants.TRANS_INIT_ID, dataSetOutcome.getDataSet()); + log.debug("Created menu item case [{}] with identifier [{}].", menuItemCase.getStringId(), body.getIdentifier()); + return menuItemCase; + } + + /** + * Updates menu item case and it's configuration cases + * + * @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) + * */ + @Override + public Case updateMenuItem(Case itemCase, MenuItemBody body) throws TransitionNotExecutableException { + 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); + } + + if (body.hasView()) { + ViewBody viewBody = body.getView(); + MenuItemView registeredView = viewRegistry.getViewByIdentifier(viewBody.getViewIdentifier()); + body.setUseTabbedView(registeredView.isTabbed()); + } + Case viewCase = findView(itemCase); + viewCase = handleView(viewCase, body.getView()); + 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()); + return itemCase; + } + + /** + * Creates or updates menu item. At first menu item is searched by identifier. If found, then menu item will be + * updated. If not, menu item will be created + * + * @param body data used for the update or creation + * + * @return updated or created menu item case + * */ + @Override + public Case createOrUpdateMenuItem(MenuItemBody body) throws TransitionNotExecutableException { + Case itemCase = findMenuItem(MenuItemUtils.sanitize(body.getIdentifier())); + if (itemCase != null) { + return updateMenuItem(itemCase, body); + } else { + return createMenuItem(body); + } + } + + /** + * Creates or ignore menu item. At first menu item is searched by identifier. If found, then nothing will happen. + * If not, menu item will be created + * + * @param body data used for the creation + * + * @return ignored or created menu item case + * */ + @Override + public Case createOrIgnoreMenuItem(MenuItemBody body) throws TransitionNotExecutableException { + Case itemCase = findMenuItem(body.getIdentifier()); + if (itemCase != null) { + log.debug("Ignored creation or update of menu item case [{}] with identifier [{}].", itemCase.getStringId(), + body.getIdentifier()); + return itemCase; + } else { + return createMenuItem(body); + } + } + + /** + * Finds menu item by identifier. + * + * @param identifier identifier of the menu item + * + * @return Found menu item case. If not found, null will be returned + * */ + @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); + } + + /** + * Finds menu item by uri and name. + * + * @param uri string id of UriNode where the item exists + * @param name name of the menu item + * + * @return Found menu item case. If not found, null will be returned + * */ + @Override + 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); + } + + /** + * Finds folder case by UriNode + * + * @param node UriNode, which folder case represents + * + * @return Found folder menu item case. If not found, null will be returned + * */ + @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); + } + + /** + * Checks if the menu item exists + * + * @param identifier identifier of the menu item + * + * @return true if the menu item exists + * */ + @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; + } + + /** + * Changes location of menu item. If non-existing location is provided, the new location is created and then the + * item is moved. Cyclic destination path is forbidden (f.e. from "/my_node" to + * "/my_node/my_node2" + * + * @param itemCase Instance of menu_item to be moved + * @param destUri destination path where the item will be moved. F.e. "/my_new_node" + * + * @throws IllegalArgumentException if the path is forbidden + * */ + @Override + public void moveItem(Case itemCase, String destUri) throws TransitionNotExecutableException { + log.debug("Move of menu item case [{}] started. Destination path [{}]", itemCase.getStringId(), destUri); + if (MenuItemUtils.isCyclicNodePath(itemCase, destUri)) { + throw new IllegalArgumentException(String.format("Cyclic path not supported. Destination path: %s", destUri)); + } + List casesToSave = new ArrayList<>(); + + List parentIdList = MenuItemUtils.getCaseIdsFromCaseRef(itemCase, MenuItemConstants.FIELD_PARENT_ID); + if (parentIdList != null && !parentIdList.isEmpty()) { + Case oldParent = removeChildItemFromParent(parentIdList.get(0), itemCase); + casesToSave.add(oldParent); + } + + UriNode destNode = uriService.getOrCreate(destUri, UriContentType.CASE); + Case newParent = getOrCreateFolderItem(destNode.getUriPath()); + if (newParent != null) { + itemCase.getDataField(MenuItemConstants.FIELD_PARENT_ID).setValue(List.of(newParent.getStringId())); + appendChildCaseIdInMemory(newParent, itemCase.getStringId()); + casesToSave.add(newParent); + } else { + itemCase.getDataField(MenuItemConstants.FIELD_PARENT_ID).setValue(null); + } + + itemCase.setUriNodeId(destNode.getStringId()); + resolveAndHandleNewNodePath(itemCase, destNode.getUriPath()); + casesToSave.add(itemCase); + + if (MenuItemUtils.hasFolderChildren(itemCase)) { + List childrenToSave = updateNodeInChildrenFoldersRecursive(itemCase); + casesToSave.addAll(childrenToSave); + } + + for (Case useCase : casesToSave) { + if (useCase != null) { + workflowService.save(useCase); + } + } + log.debug("Moved menu item case [{}]. Destination path was [{}]", itemCase.getStringId(), destUri); + } + + /** + * Duplicates menu item. It creates new menu_item instance with the same dataSet as the provided + * item instance. The only difference is in title, menu_item_identifier and associations. Configuration cases are + * duplicated as well. + * + * @param originItem Menu item instance, which is duplicated + * @param newTitle Title of menu item, that is displayed in menu and tab. Cannot be empty or null. + * @param newIdentifier unique menu item identifier + * + * @return duplicated {@link Case} instance of menu_item + * + * @throws IllegalArgumentException if the input data are invalid or the menu item of the new identifier already + * exists + * */ + @Override + public Case duplicateItem(Case originItem, I18nString newTitle, String newIdentifier) throws TransitionNotExecutableException { + log.debug("Duplication of menu item case [{}] started.", originItem.getStringId()); + if (newIdentifier == null || newIdentifier.isEmpty()) { + throw new IllegalArgumentException("View item identifier is null or empty!"); + } + if (newTitle == null || newTitle.getDefaultValue().isEmpty()) { + throw new IllegalArgumentException("Default title is null or empty"); + } + String sanitizedIdentifier = MenuItemUtils.sanitize(newIdentifier); + if (existsMenuItem(sanitizedIdentifier)) { + throw new IllegalArgumentException(String.format("View item identifier %s is not unique!", sanitizedIdentifier)); + } + + Case duplicatedViewCase = null; + if (MenuItemUtils.hasView(originItem)) { + String originViewId = MenuItemUtils.getCaseIdFromCaseRef(originItem, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID); + Case originViewCase = workflowService.findOne(originViewId); + duplicatedViewCase = duplicateView(originViewCase); + } + + Case duplicated = createCase(FilterRunner.MENU_NET_IDENTIFIER, newTitle.getDefaultValue(), + userService.getLoggedOrSystem().transformToLoggedUser()); + duplicated.setUriNodeId(originItem.getUriNodeId()); + duplicated.setDataSet(originItem.getDataSet()); + duplicated.setTitle(newTitle.getDefaultValue()); + duplicated = workflowService.save(duplicated); + + UriNode node = uriService.findById(originItem.getUriNodeId()); + String newNodePath = createNodePath(node.getUriPath(), sanitizedIdentifier); + uriService.getOrCreate(newNodePath, UriContentType.CASE); + + Map> dataSet = new HashMap<>(); + dataSet.put(MenuItemConstants.FIELD_DUPLICATE_TITLE, Map.of("type", FieldType.I18N.getName(), "value", + new I18nString(""))); + dataSet.put(MenuItemConstants.FIELD_DUPLICATE_IDENTIFIER, Map.of("type", FieldType.TEXT.getName(), + "value","")); + dataSet.put(MenuItemConstants.FIELD_MENU_NAME, Map.of("type", FieldType.I18N.getName(), + "value", newTitle)); + dataSet.put(MenuItemConstants.FIELD_TAB_NAME, Map.of("type", FieldType.I18N.getName(), + "value", newTitle)); + dataSet.put(MenuItemConstants.FIELD_NODE_PATH, Map.of("type", FieldType.TEXT.getName(), + "value", newNodePath)); + // Must be reset by button, because we have the same dataSet reference between originItem and duplicated + dataSet.put(MenuItemConstants.FIELD_DUPLICATE_RESET_CHILD_ITEM_IDS, Map.of("type", FieldType.BUTTON.getName(), + "value", 0)); + if (duplicatedViewCase != null) { + addConfigurationIntoDataSet(duplicatedViewCase, dataSet); + } + + setDataWithExecute(duplicated, MenuItemConstants.TRANS_INIT_ID, dataSet); + + List parentIdAsList = MenuItemUtils.getCaseIdsFromCaseRef(originItem, MenuItemConstants.FIELD_PARENT_ID); + if (parentIdAsList != null && !parentIdAsList.isEmpty()) { + Case parent = workflowService.findOne(parentIdAsList.get(0)); + appendChildCaseIdAndSave(parent, duplicated.getStringId()); + } + log.debug("Duplicated menu item case [{}]. New title [{}] and new identifier [{}].", originItem.getStringId(), + newTitle.getDefaultValue(), newIdentifier); + return workflowService.findOne(duplicated.getStringId()); + } + + /** + * Removes child menu item from the dataSet of the folder menu item case + * + * @param folderId menu item identifier of the folder case + * @param childItem menu item case of the child item to be removed + * + * @return updated folder menu item case + * */ + @Override + public Case removeChildItemFromParent(String folderId, Case childItem) { + Case parentFolder = workflowService.findOne(folderId); + List childIds = MenuItemUtils.getCaseIdsFromCaseRef(parentFolder, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + if (childIds == null || childIds.isEmpty()) { + return parentFolder; + } + childIds.remove(childItem.getStringId()); + parentFolder.getDataField(MenuItemConstants.FIELD_CHILD_ITEM_IDS).setValue(childIds); + parentFolder.getDataField(MenuItemConstants.FIELD_HAS_CHILDREN).setValue(MenuItemUtils.hasFolderChildren(parentFolder)); + return workflowService.save(parentFolder); + } + + /** + * Gets all tabbed or non-tabbed views + * + * @param isTabbed if true, only tabbed views will be returned + * @param isPrimary if true, only views accessible directly from the menu_item will be returned + * + * @return All available views defined in {@link MenuItemViewRegistryRunner} in consideration of input value. Views + * are returned as options for {@link MapOptionsField} + * */ + @Override + public Map getAvailableViewsAsOptions(boolean isTabbed, boolean isPrimary) { + return viewRegistry.getAllByIsTabbedAndIsPrimary(isTabbed, isPrimary).stream() + .collect(Collectors.toMap(MenuItemView::getIdentifier, MenuItemView::getName)); + } + + /** + * Gets all tabbed or non-tabbed views + * + * @param isTabbed if true, only tabbed views will be returned + * @param viewIdentifier identifier of view (defined in {@link MenuItemViewRegistryRunner}), which is parent to + * returned views + * + * @return All available views defined in {@link MenuItemViewRegistryRunner} in consideration of input values. Views + * are returned as options for {@link MapOptionsField} + * */ + @Override + public Map getAvailableViewsAsOptions(boolean isTabbed, String viewIdentifier) { + int index = viewIdentifier.lastIndexOf("_configuration"); + if (index > 0) { + viewIdentifier = viewIdentifier.substring(0, index); + } + return viewRegistry.getAllByIsTabbedAndParentIdentifier(isTabbed, viewIdentifier).stream() + .collect(Collectors.toMap(MenuItemView::getIdentifier, MenuItemView::getName)); + } + + protected Case findCase(String processIdentifier, String query) { + CaseSearchRequest request = CaseSearchRequest.builder() + .process(Collections.singletonList(new CaseSearchRequest.PetriNet(processIdentifier))) + .query(query) + .build(); + Page resultPage = elasticCaseService.search(List.of(request), userService.getLoggedOrSystem().transformToLoggedUser(), + PageRequest.of(0, 1), Locale.getDefault(), false); + + return resultPage.hasContent() ? resultPage.getContent().get(0) : null; + } + + protected long countCases(String processIdentifier, String query) { + CaseSearchRequest request = CaseSearchRequest.builder() + .process(Collections.singletonList(new CaseSearchRequest.PetriNet(processIdentifier))) + .query(query) + .build(); + return elasticCaseService.count(List.of(request), userService.getLoggedOrSystem().transformToLoggedUser(), + Locale.getDefault(), false); + } + + protected Case duplicateView(Case viewCase) throws TransitionNotExecutableException { + Case duplicatedAssociatedViewCase = null; + if (MenuItemUtils.hasView(viewCase)) { + String originViewId = MenuItemUtils.getCaseIdFromCaseRef(viewCase, ViewConstants.FIELD_VIEW_CONFIGURATION_ID); + Case originViewCase = workflowService.findOne(originViewId); + duplicatedAssociatedViewCase = duplicateView(originViewCase); + } + + Case duplicatedViewCase = createCase(viewCase.getProcessIdentifier(), viewCase.getTitle(), + userService.getLoggedOrSystem().transformToLoggedUser()); + duplicatedViewCase.setDataSet(viewCase.getDataSet()); + workflowService.save(duplicatedViewCase); + + Map> dataSet = new HashMap<>(); + if (duplicatedAssociatedViewCase != null) { + addConfigurationIntoDataSet(duplicatedAssociatedViewCase, dataSet); + } + + 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); + } + + protected Case findCaseInCaseRef(Case useCase, String caseRefId) { + try { + String caseId = MenuItemUtils.getCaseIdFromCaseRef(useCase, caseRefId); + return workflowService.findOne(caseId); + } catch (IllegalArgumentException | NullPointerException ignore) { + return null; + } + } + + protected Case handleView(Case existingViewCase, ViewBody body) throws TransitionNotExecutableException { + if (mustUpdateView(existingViewCase, body)) { + return updateView(existingViewCase, body); + } else if (mustCreateView(existingViewCase, body)) { + return createView(body); + } else if (mustRemoveView(existingViewCase, body)) { + removeView(existingViewCase); + return null; + } else if (mustRemoveAndCreateView(existingViewCase, body)) { + removeView(existingViewCase); + return createView(body); + } else { + return null; + } + } + + protected Case createView(ViewBody body) throws TransitionNotExecutableException { + IUser loggedUser = userService.getLoggedOrSystem(); + Case viewCase = createCase(body.getViewProcessIdentifier(), body.getViewProcessIdentifier(), + loggedUser.transformToLoggedUser()); + + Case associatedViewCase = null; + if (body.hasAssociatedView()) { + associatedViewCase = createView(body.getAssociatedViewBody()); + } + Case filterCase = null; + if (body.getFilterBody() != null) { + if (body.getFilterBody().getFilter() != null) { + filterCase = body.getFilterBody().getFilter(); + } else { + filterCase = createFilter(body.getFilterBody()); + } + } + ToDataSetOutcome dataSetOutcome = body.toDataSet(associatedViewCase, filterCase); + viewCase = setDataWithExecute(viewCase, ViewConstants.TRANS_INIT_ID, dataSetOutcome.getDataSet()); + + log.trace("Created configuration view case [{}] of identifier [{}]", viewCase.getStringId(), + body.getViewProcessIdentifier()); + return viewCase; + } + + protected Case updateView(Case viewCase, ViewBody body) throws TransitionNotExecutableException { + Case filterCase = findFilter(viewCase); + filterCase = handleFilter(filterCase, body.getFilterBody()); + + Case associatedViewCase = findView(viewCase); + associatedViewCase = handleView(associatedViewCase, body.getAssociatedViewBody()); + + 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()) { + return new ArrayList<>(); + } + + List children = workflowService.findAllById(childItemIds); + + List casesToSave = new ArrayList<>(); + for (Case childCase : children) { + UriNode parentNode = uriService.getOrCreate((String) parentFolder.getFieldValue(MenuItemConstants.FIELD_NODE_PATH), + UriContentType.CASE); + childCase.setUriNodeId(parentNode.getStringId()); + resolveAndHandleNewNodePath(childCase, parentNode.getUriPath()); + + casesToSave.add(childCase); + casesToSave.addAll(updateNodeInChildrenFoldersRecursive(childCase)); + } + + return casesToSave; + } + + protected void resolveAndHandleNewNodePath(Case folderItem, String destUri) { + String newNodePath = resolveNewNodePath(folderItem, destUri); + UriNode newNode = uriService.getOrCreate(newNodePath, UriContentType.CASE); + folderItem.getDataField(MenuItemConstants.FIELD_NODE_PATH).setValue(newNode.getUriPath()); + } + + protected String resolveNewNodePath(Case folderItem, String destUri) { + return destUri + uriService.getUriSeparator() + folderItem.getFieldValue(MenuItemConstants.FIELD_IDENTIFIER); + } + + protected String createNodePath(String uri, String identifier) { + if (Objects.equals(uri, uriService.getUriSeparator())) { + return uri + identifier; + } else { + return uri + uriService.getUriSeparator() + identifier; + } + } + + protected Case getOrCreateFolderItem(String uri) throws TransitionNotExecutableException { + UriNode node = uriService.getOrCreate(uri, UriContentType.CASE); + MenuItemBody body = new MenuItemBody(new I18nString(node.getName()), DEFAULT_FOLDER_ICON); + return getOrCreateFolderRecursive(node, body); + } + + protected Case getOrCreateFolderRecursive(UriNode node, MenuItemBody body) throws TransitionNotExecutableException { + return getOrCreateFolderRecursive(node, body, null); + } + protected Case getOrCreateFolderRecursive(UriNode node, MenuItemBody body, Case childFolderCase) throws TransitionNotExecutableException { + IUser loggedUser = userService.getLoggedOrSystem(); + Case folderCase = findFolderCase(node); + if (folderCase != null) { + if (childFolderCase != null) { + appendChildCaseIdAndSave(folderCase, childFolderCase.getStringId()); + } + return folderCase; + } + + folderCase = createCase(FilterRunner.MENU_NET_IDENTIFIER, body.getMenuName().getDefaultValue(), + loggedUser.transformToLoggedUser()); + folderCase.setUriNodeId(node.getParentId()); + folderCase = workflowService.save(folderCase); + + ToDataSetOutcome dataSetOutcome = body.toDataSet(null, node.getUriPath(), null); + if (childFolderCase != null) { + appendChildCaseIdInDataSet(folderCase, childFolderCase.getStringId(), dataSetOutcome.getDataSet()); + } + + if (node.getParentId() != null) { + UriNode parentNode = uriService.findById(node.getParentId()); + body = new MenuItemBody(new I18nString(parentNode.getName()), DEFAULT_FOLDER_ICON); + + Case parentFolderCase = getOrCreateFolderRecursive(parentNode, body, folderCase); + dataSetOutcome.putDataSetEntry(MenuItemConstants.FIELD_PARENT_ID, FieldType.CASE_REF, List.of(parentFolderCase.getStringId())); + } + folderCase = setDataWithExecute(folderCase, MenuItemConstants.TRANS_INIT_ID, dataSetOutcome.getDataSet()); + + log.trace("Created folder menu item [{}] with identifier [{}]", folderCase.getStringId(), body.getIdentifier()); + return folderCase; + } + + protected void appendChildCaseIdInDataSet(Case folderCase, String childItemCaseId, Map> dataSet) { + List childIds = MenuItemUtils.getCaseIdsFromCaseRef(folderCase, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + if (childIds == null || childIds.isEmpty()) { + dataSet.put(MenuItemConstants.FIELD_CHILD_ITEM_IDS, Map.of("type", FieldType.CASE_REF.getName(), + "value", List.of(childItemCaseId))); + } else { + childIds.add(childItemCaseId); + dataSet.put(MenuItemConstants.FIELD_CHILD_ITEM_IDS, Map.of("type", FieldType.CASE_REF.getName(), + "value", childIds)); + } + dataSet.put(MenuItemConstants.FIELD_HAS_CHILDREN, Map.of("type", FieldType.BOOLEAN.getName(), + "value", MenuItemUtils.hasFolderChildren(folderCase))); + } + + protected void appendChildCaseIdInMemory(Case folderCase, String childItemCaseId) { + List childIds = MenuItemUtils.getCaseIdsFromCaseRef(folderCase, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + if (childIds == null || childIds.isEmpty()) { + folderCase.getDataField(MenuItemConstants.FIELD_CHILD_ITEM_IDS).setValue(List.of(childItemCaseId)); + } else { + childIds.add(childItemCaseId); + folderCase.getDataField(MenuItemConstants.FIELD_CHILD_ITEM_IDS).setValue(childIds); + } + folderCase.getDataField(MenuItemConstants.FIELD_HAS_CHILDREN).setValue(MenuItemUtils.hasFolderChildren(folderCase)); + } + + protected Case appendChildCaseIdAndSave(Case folderCase, String childItemCaseId) { + Map> dataSet = new HashMap<>(); + appendChildCaseIdInDataSet(folderCase, childItemCaseId, dataSet); + return setData(folderCase, MenuItemConstants.TRANS_SYNC_ID, dataSet); + } + + protected void addConfigurationIntoDataSet(Case configurationCase, Map> dataSet) { + dataSet.put(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID, Map.of("type", FieldType.CASE_REF.getName(), + "value", List.of(configurationCase.getStringId()))); + String taskId = MenuItemUtils.findTaskIdInCase(configurationCase, ViewConstants.TRANS_SETTINGS_ID); + dataSet.put(MenuItemConstants.FIELD_VIEW_CONFIGURATION_FORM, Map.of("type", FieldType.TASK_REF.getName(), + "value", List.of(taskId))); + } + + protected Case createCase(String identifier, String title, LoggedUser loggedUser) { + return workflowService.createCaseByIdentifier(identifier, title, "",loggedUser).getCase(); + } + + protected Case setData(Case useCase, String transId, Map> dataSet) { + String taskId = MenuItemUtils.findTaskIdInCase(useCase, transId); + return setData(taskId, dataSet); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + protected Case setData(String taskId, Map> dataSet) { + return dataService.setData(taskId, ImportHelper.populateDataset((Map) dataSet)).getCase(); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + protected Case setDataWithExecute(Case useCase, String transId, Map> dataSet) throws TransitionNotExecutableException { + IUser loggedUser = userService.getLoggedOrSystem(); + String taskId = MenuItemUtils.findTaskIdInCase(useCase, transId); + Task task = taskService.findOne(taskId); + task = taskService.assignTask(task, loggedUser).getTask(); + task = dataService.setData(task, ImportHelper.populateDataset((Map) dataSet)).getTask(); + return taskService.finishTask(task, loggedUser).getCase(); + } + +} diff --git a/src/main/java/com/netgrif/application/engine/menu/services/interfaces/IMenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/services/interfaces/IMenuItemService.java new file mode 100644 index 00000000000..e3bb4aa8d1e --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/services/interfaces/IMenuItemService.java @@ -0,0 +1,29 @@ +package com.netgrif.application.engine.menu.services.interfaces; + +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 com.netgrif.application.engine.petrinet.domain.UriNode; +import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; +import com.netgrif.application.engine.workflow.domain.Case; + +import java.util.Map; + +public interface IMenuItemService { + + Case createFilter(FilterBody body) throws TransitionNotExecutableException; + Case updateFilter(Case filterCase, FilterBody body); + Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableException; + Case updateMenuItem(Case itemCase, MenuItemBody body) throws TransitionNotExecutableException; + Case createOrUpdateMenuItem(MenuItemBody body) throws TransitionNotExecutableException; + Case createOrIgnoreMenuItem(MenuItemBody body) throws TransitionNotExecutableException; + Case findMenuItem(String identifier); + Case findMenuItem(String uri, String name); + Case findFolderCase(UriNode node); + boolean existsMenuItem(String identifier); + 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 getAvailableViewsAsOptions(boolean isTabbed, boolean isPrimary); + Map getAvailableViewsAsOptions(boolean isTabbed, String viewIdentifier); +} 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 new file mode 100644 index 00000000000..d1fcaf8e590 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java @@ -0,0 +1,119 @@ +package com.netgrif.application.engine.menu.utils; + +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 java.text.Normalizer; +import java.util.List; + +public class MenuItemUtils { + + /** + * Sanitizes input. Removes any diacritical marks, replaces any special character with delimiter and lowers the + * characters + * + * @param input input string to be sanitized + * + * @return sanitized input string + * */ + public static String sanitize(String input) { + if (input == null) { + return null; + } + return Normalizer.normalize(input.trim(), Normalizer.Form.NFD) + .replaceAll("[^\\p{ASCII}]", "") + .replaceAll("\\p{InCombiningDiacriticalMarks}+", "") + .replaceAll("[\\W-]+", "-") + .toLowerCase(); + } + + /** + * Finds task id in the provided case instance by transition id + * + * @param useCase case instance containing the task id to be found + * @param transId transition identifier of the task + * + * @return id of found task or null otherwise + */ + public static String findTaskIdInCase(Case useCase, String transId) { + if (useCase == null || transId == null) { + return null; + } + + TaskPair resultPair = useCase.getTasks().stream() + .filter(taskPair -> taskPair.getTransition().equals(transId)) + .findFirst().orElse(null); + + if (resultPair == null) { + return null; + } + + return resultPair.getTask(); + } + + /** + * This method is mainly used for {@link IMenuItemService#moveItem(Case, String)} + * + * @param folderItem case instance of folder menu item + * @param destUri path of the uri node + * + * @return true, if the nodePath would become cyclic to folderItem's current nodePath after item move. F.e. + * "/node1/node2" would be cyclic to "/node1/node2/node3" after move + * */ + public static boolean isCyclicNodePath(Case folderItem, String destUri) { + String oldNodePath = (String) folderItem.getFieldValue(MenuItemConstants.FIELD_NODE_PATH); + return destUri.contains(oldNodePath); + } + + /** + * @param useCase case instance where the caseRef exists + * @param caseRefId id of caseRef field + * + * @return List of case ids inside caseRef field. Returns null if the field doesn't exist or the field's value is + * null. + * */ + @SuppressWarnings("unchecked") + public static List getCaseIdsFromCaseRef(Case useCase, String caseRefId) { + try { + return (List) useCase.getFieldValue(caseRefId); + } catch (NullPointerException ignore) { + return null; + } + } + + /** + * @param useCase case instance where the caseRef exists + * @param caseRefId id of caseRef field + * + * @return Case id inside caseRef field. Returns null if the field doesn't exist or the caseRef is empty. + * */ + public static String getCaseIdFromCaseRef(Case useCase, String caseRefId) { + List caseIds = getCaseIdsFromCaseRef(useCase, caseRefId); + if (caseIds == null || caseIds.isEmpty()) { + return null; + } + return caseIds.get(0); + } + + /** + * @param menuItemCase case instance of menu item + * + * @return true if the menu item contains view + * */ + public static boolean hasView(Case menuItemCase) { + return getCaseIdFromCaseRef(menuItemCase, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID) != null; + } + + /** + * @param folderCase case instance of folder menu item + * + * @return true if the folder case contains any child menu item cases + * */ + public static boolean hasFolderChildren(Case folderCase) { + List childIds = MenuItemUtils.getCaseIdsFromCaseRef(folderCase, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + return childIds != null && !childIds.isEmpty(); + } + +} diff --git a/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java b/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java index 4b983dcf1c1..ed29ce7f6a6 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java @@ -2,6 +2,7 @@ import com.netgrif.application.engine.AsyncRunner; import com.netgrif.application.engine.auth.domain.LoggedUser; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticPetriNetService; import com.netgrif.application.engine.eventoutcomes.LocalisedEventOutcomeFactory; import com.netgrif.application.engine.importer.service.Importer; import com.netgrif.application.engine.importer.service.throwable.MissingIconKeyException; @@ -71,6 +72,9 @@ public class PetriNetController { @Autowired private IPetriNetService service; + @Autowired + private IElasticPetriNetService elasticService; + @Autowired private IProcessRoleService roleService; @@ -197,6 +201,20 @@ PagedModel searchPetriNets(@RequestBody PetriNetSearc return resources; } + @Operation(summary = "Search elastic processes", security = {@SecurityRequirement(name = "BasicAuth")}) + @PostMapping(value = "/search_elastic", produces = MediaTypes.HAL_JSON_VALUE) + public @ResponseBody + PagedModel searchElasticPetriNets(@RequestBody PetriNetSearch criteria, Authentication auth, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { + LoggedUser user = (LoggedUser) auth.getPrincipal(); + // TODO: add Merge Filters and its operations + Page nets = elasticService.search(criteria, user, pageable, locale,false); + Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(PetriNetController.class) + .searchElasticPetriNets(criteria, auth, pageable, assembler, locale)).withRel("search_elastic"); + PagedModel resources = assembler.toModel(nets, new PetriNetReferenceResourceAssembler(), selfLink); + PetriNetReferenceResourceAssembler.buildLinks(resources); + return resources; + } + @PreAuthorize("@petriNetAuthorizationService.canCallProcessDelete(#auth.getPrincipal(), #processId)") @Operation(summary = "Delete process", description = "Caller must have the ADMIN role. Removes the specified process, along with it's cases, tasks and process roles.", diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuItemBody.java b/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuItemBody.java deleted file mode 100644 index 0faac7601fb..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuItemBody.java +++ /dev/null @@ -1,265 +0,0 @@ -package com.netgrif.application.engine.workflow.domain.menu; - -import com.netgrif.application.engine.petrinet.domain.I18nString; -import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; -import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.ActionDelegate; -import com.netgrif.application.engine.workflow.domain.Case; -import lombok.Data; -import lombok.NoArgsConstructor; - -import javax.annotation.Nullable; -import java.text.Normalizer; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * Class, that holds configurable attributes of menu item. In case of attribute addition, please update also - * {@link MenuItemBody#toDataSet(String, String, boolean)} method. - */ -@Data -@NoArgsConstructor -public class MenuItemBody { - - // generic attributes - private I18nString menuName; - private I18nString tabName; - private String menuIcon = "filter_none"; - private String tabIcon; - private String uri; - private String identifier; - private Case filter; - private Map allowedRoles; - private Map bannedRoles; - private boolean useTabIcon = true; - private boolean useCustomView = false; - private String customViewSelector; - - // case view attributes - private String caseViewSearchType = "fulltext_advanced"; - private String createCaseButtonTitle; - private String createCaseButtonIcon = "add"; - private boolean caseRequireTitleInCreation = true; - private boolean showCreateCaseButton = true; - private String bannedNetsInCreation; - private boolean caseShowMoreMenu = false; - private boolean caseAllowHeaderTableMode = true; - private List caseHeadersMode = new ArrayList<>(List.of("sort", "edit", "search")); - private String caseHeadersDefaultMode = "sort"; - private List caseDefaultHeaders; - private boolean caseIsHeaderModeChangeable = true; - private boolean caseUseDefaultHeaders = true; - - // task view attributes - private Case additionalFilter; - private boolean mergeFilters = true; - private String taskViewSearchType = "fulltext_advanced"; - private List taskHeadersMode = new ArrayList<>(List.of("sort", "edit")); - private String taskHeadersDefaultMode = "sort"; - private boolean taskIsHeaderModeChangeable = true; - private boolean taskAllowHeaderTableMode = true; - private boolean taskUseDefaultHeaders = true; - private List taskDefaultHeaders; - private boolean taskShowMoreMenu = true; - - public MenuItemBody(I18nString name, String icon) { - this.menuName = name; - this.tabName = name; - this.menuIcon = icon; - this.tabIcon = icon; - } - - public MenuItemBody(I18nString menuName, I18nString tabName, String menuIcon, String tabIcon) { - this.menuName = menuName; - this.tabName = tabName; - this.menuIcon = menuIcon; - this.tabIcon = tabIcon; - } - - public MenuItemBody(String uri, String identifier, I18nString name, String icon) { - this.uri = uri; - this.identifier = identifier; - this.menuName = name; - this.tabName = name; - this.menuIcon = icon; - this.tabIcon = icon; - } - - public MenuItemBody(String uri, String identifier, I18nString menuName, I18nString tabName, String menuIcon, String tabIcon) { - this.uri = uri; - this.identifier = identifier; - this.menuName = menuName; - this.tabName = tabName; - this.menuIcon = menuIcon; - this.tabIcon = tabIcon; - } - - public MenuItemBody(String uri, String identifier, String name, String icon) { - this.uri = uri; - this.identifier = identifier; - this.menuName = new I18nString(name); - this.tabName = new I18nString(name); - this.menuIcon = icon; - this.tabIcon = icon; - } - - public MenuItemBody(String uri, String identifier, String menuName, String tabName, String menuIcon, String tabIcon) { - this.uri = uri; - this.identifier = identifier; - this.menuName = new I18nString(menuName); - this.tabName = new I18nString(tabName); - this.menuIcon = menuIcon; - this.tabIcon = tabIcon; - } - - private static void putDataSetEntry(Map> dataSet, MenuItemConstants fieldId, FieldType fieldType, - @Nullable Object fieldValue) { - Map fieldMap = new LinkedHashMap<>(); - fieldMap.put("type", fieldType.getName()); - fieldMap.put("value", fieldValue); - dataSet.put(fieldId.getAttributeId(), fieldMap); - } - - private static String sanitize(String input) { - if (input == null) { - return null; - } - return Normalizer.normalize(input.trim(), Normalizer.Form.NFD) - .replaceAll("[^\\p{ASCII}]", "") - .replaceAll("\\p{InCombiningDiacriticalMarks}+", "") - .replaceAll("[\\W-]+", "-") - .toLowerCase(); - } - - public String getIdentifier() { - return sanitize(this.identifier); - } - - public void setMenuName(I18nString name) { - this.menuName = name; - } - - public void setMenuName(String name) { - this.menuName = new I18nString(name); - } - - public void setTabName(I18nString name) { - this.tabName = name; - } - - public void setTabName(String name) { - this.tabName = new I18nString(name); - } - - /** - * Transforms attributes into dataSet for {@link ActionDelegate#setData} - * - * @return created dataSet from attributes - */ - public Map> toDataSet() { - return toDataSet(null, null, true); - } - - /** - * Transforms attributes into dataSet for {@link ActionDelegate#setData} - * - * @param parentId id of parent menu item instance - * @param nodePath uri, that represents the menu item (f.e.: "/myItem1/myItem2") - * @return created dataSet from attributes - */ - public Map> toDataSet(String parentId, String nodePath) { - return toDataSet(parentId, nodePath, false); - } - - private Map> toDataSet(String parentId, String nodePath, boolean ignoreParentId) { - Map> dataSet = new LinkedHashMap<>(); - - // GENERIC - ArrayList filterIdCaseRefValue = new ArrayList<>(); - if (this.filter != null) { - filterIdCaseRefValue.add(this.filter.getStringId()); - } - ArrayList parentIdCaseRef = new ArrayList<>(); - if (parentId != null) { - parentIdCaseRef.add(parentId); - } - - if (nodePath != null) { - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH, FieldType.TEXT, nodePath); - } - if (!ignoreParentId) { - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID, FieldType.CASE_REF, parentIdCaseRef); - } - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_NAME, FieldType.I18N, this.menuName); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_ICON, FieldType.TEXT, this.menuIcon); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TAB_NAME, FieldType.I18N, this.tabName); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TAB_ICON, FieldType.TEXT, this.tabIcon); - if (this.identifier != null) { - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_IDENTIFIER, FieldType.TEXT, this.getIdentifier()); - } - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_FILTER_CASE, FieldType.CASE_REF, filterIdCaseRefValue); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_USE_TAB_ICON, FieldType.BOOLEAN, this.useTabIcon); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_USE_CUSTOM_VIEW, FieldType.BOOLEAN, - this.useCustomView); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CUSTOM_VIEW_SELECTOR, FieldType.TEXT, - this.customViewSelector); - - // CASE - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP, - this.caseViewSearchType); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CREATE_CASE_BUTTON_TITLE, FieldType.TEXT, - this.createCaseButtonTitle); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CREATE_CASE_BUTTON_ICON, FieldType.TEXT, - this.createCaseButtonIcon); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_REQUIRE_TITLE_IN_CREATION, FieldType.BOOLEAN, - this.caseRequireTitleInCreation); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_SHOW_CREATE_CASE_BUTTON, FieldType.BOOLEAN, - this.showCreateCaseButton); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_BANNED_NETS_IN_CREATION, FieldType.TEXT, - this.bannedNetsInCreation); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_SHOW_MORE_MENU, FieldType.BOOLEAN, - this.caseShowMoreMenu); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_ALLOW_HEADER_TABLE_MODE, FieldType.BOOLEAN, - this.caseAllowHeaderTableMode); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_HEADERS_MODE, FieldType.MULTICHOICE_MAP, - this.caseHeadersMode == null ? new ArrayList<>() : this.caseHeadersMode); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP, - this.caseHeadersDefaultMode); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_DEFAULT_HEADERS, FieldType.TEXT, - this.caseDefaultHeaders != null ? String.join(",", this.caseDefaultHeaders) : null); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN, - this.caseIsHeaderModeChangeable); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_USE_CASE_DEFAULT_HEADERS, FieldType.BOOLEAN, - this.caseUseDefaultHeaders); - - // TASK - ArrayList additionalFilterIdCaseRefValue = new ArrayList<>(); - if (this.additionalFilter != null) { - additionalFilterIdCaseRefValue.add(this.additionalFilter.getStringId()); - } - - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_ADDITIONAL_FILTER_CASE, FieldType.CASE_REF, - additionalFilterIdCaseRefValue); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_MERGE_FILTERS, FieldType.BOOLEAN, - this.mergeFilters); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP, - this.taskViewSearchType); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_HEADERS_MODE, FieldType.MULTICHOICE_MAP, - this.taskHeadersMode == null ? new ArrayList<>() : this.taskHeadersMode); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP, - this.taskHeadersDefaultMode); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN, - this.taskIsHeaderModeChangeable); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_ALLOW_HEADER_TABLE_MODE, FieldType.BOOLEAN, - this.taskAllowHeaderTableMode); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_USE_TASK_DEFAULT_HEADERS, FieldType.BOOLEAN, - this.taskUseDefaultHeaders); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_DEFAULT_HEADERS, FieldType.TEXT, - this.taskDefaultHeaders != null ? String.join(",", this.taskDefaultHeaders) : null); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_SHOW_MORE_MENU, FieldType.BOOLEAN, - this.taskShowMoreMenu); - - return dataSet; - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuItemConstants.java b/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuItemConstants.java deleted file mode 100644 index 2b4f5a0fec9..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuItemConstants.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.netgrif.application.engine.workflow.domain.menu; - -import lombok.Getter; - -/** - * Enumeration for menu items. It contains any constants needed in application. - */ -public enum MenuItemConstants { - - // FIELDS - PREFERENCE_ITEM_FIELD_NEW_FILTER_ID("new_filter_id"), - PREFERENCE_ITEM_FIELD_FILTER_CASE("filter_case"), - PREFERENCE_ITEM_FIELD_PARENT_ID("parentId"), - PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS("childItemIds"), - PREFERENCE_ITEM_FIELD_HAS_CHILDREN("hasChildren"), - PREFERENCE_ITEM_FIELD_CASE_DEFAULT_HEADERS("case_default_headers"), - PREFERENCE_ITEM_FIELD_TASK_DEFAULT_HEADERS("task_default_headers"), - PREFERENCE_ITEM_FIELD_IDENTIFIER("menu_item_identifier"), - PREFERENCE_ITEM_FIELD_APPEND_MENU_ITEM("append_menu_item_stringId"), - PREFERENCE_ITEM_FIELD_ALLOWED_ROLES("allowed_roles"), - PREFERENCE_ITEM_FIELD_BANNED_ROLES("banned_roles"), - PREFERENCE_ITEM_FIELD_MENU_NAME("menu_name"), - PREFERENCE_ITEM_FIELD_MENU_ICON("menu_icon"), - PREFERENCE_ITEM_FIELD_TAB_NAME("tab_name"), - PREFERENCE_ITEM_FIELD_USE_TAB_ICON("use_tab_icon"), - PREFERENCE_ITEM_FIELD_TAB_ICON("tab_icon"), - PREFERENCE_ITEM_FIELD_NODE_PATH("nodePath"), - PREFERENCE_ITEM_FIELD_NODE_NAME("nodeName"), - PREFERENCE_ITEM_FIELD_DUPLICATE_TITLE("duplicate_new_title"), - PREFERENCE_ITEM_FIELD_DUPLICATE_IDENTIFIER("duplicate_view_identifier"), - PREFERENCE_ITEM_FIELD_DUPLICATE_RESET_CHILD_ITEM_IDS("duplicate_reset_childItemIds"), - PREFERENCE_ITEM_FIELD_REQUIRE_TITLE_IN_CREATION("case_require_title_in_creation"), - PREFERENCE_ITEM_FIELD_USE_CUSTOM_VIEW("use_custom_view"), - PREFERENCE_ITEM_FIELD_CUSTOM_VIEW_SELECTOR("custom_view_selector"), - PREFERENCE_ITEM_FIELD_CASE_VIEW_SEARCH_TYPE("case_view_search_type"), - PREFERENCE_ITEM_FIELD_CREATE_CASE_BUTTON_TITLE("create_case_button_title"), - PREFERENCE_ITEM_FIELD_CREATE_CASE_BUTTON_ICON("create_case_button_icon"), - PREFERENCE_ITEM_FIELD_BANNED_NETS_IN_CREATION("case_banned_nets_in_creation"), - PREFERENCE_ITEM_FIELD_SHOW_CREATE_CASE_BUTTON("show_create_case_button"), - PREFERENCE_ITEM_FIELD_CASE_SHOW_MORE_MENU("case_show_more_menu"), - PREFERENCE_ITEM_FIELD_CASE_ALLOW_HEADER_TABLE_MODE("case_allow_header_table_mode"), - PREFERENCE_ITEM_FIELD_CASE_HEADERS_MODE("case_headers_mode"), - PREFERENCE_ITEM_FIELD_CASE_HEADERS_DEFAULT_MODE("case_headers_default_mode"), - PREFERENCE_ITEM_FIELD_CASE_IS_HEADER_MODE_CHANGEABLE("case_is_header_mode_changeable"), - PREFERENCE_ITEM_FIELD_USE_CASE_DEFAULT_HEADERS("case_is_header_mode_changeable"), - PREFERENCE_ITEM_FIELD_ADDITIONAL_FILTER_CASE("additional_filter_case"), - PREFERENCE_ITEM_FIELD_MERGE_FILTERS("merge_filters"), - PREFERENCE_ITEM_FIELD_TASK_VIEW_SEARCH_TYPE("task_view_search_type"), - PREFERENCE_ITEM_FIELD_TASK_HEADERS_MODE("task_headers_mode"), - PREFERENCE_ITEM_FIELD_TASK_HEADERS_DEFAULT_MODE("task_headers_default_mode"), - PREFERENCE_ITEM_FIELD_TASK_IS_HEADER_MODE_CHANGEABLE("task_is_header_mode_changeable"), - PREFERENCE_ITEM_FIELD_TASK_ALLOW_HEADER_TABLE_MODE("task_allow_header_table_mode"), - PREFERENCE_ITEM_FIELD_USE_TASK_DEFAULT_HEADERS("use_task_default_headers"), - PREFERENCE_ITEM_FIELD_TASK_SHOW_MORE_MENU("task_show_more_menu"), - - // TRANSITIONS - PREFERENCE_ITEM_SETTINGS_TRANS_ID("item_settings"), - PREFERENCE_ITEM_FIELD_INIT_TRANS_ID("initialize"); - @Getter - private final String attributeId; - - MenuItemConstants(String attributeId) { - this.attributeId = attributeId; - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java b/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java index edb99e46623..68af8b19387 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java @@ -20,10 +20,10 @@ import com.netgrif.application.engine.startup.ImportHelper; import com.netgrif.application.engine.utils.InputStreamToString; import com.netgrif.application.engine.workflow.domain.*; -import com.netgrif.application.engine.workflow.domain.menu.Menu; -import com.netgrif.application.engine.workflow.domain.menu.MenuAndFilters; -import com.netgrif.application.engine.workflow.domain.menu.MenuEntry; -import com.netgrif.application.engine.workflow.domain.menu.MenuEntryRole; +import com.netgrif.application.engine.menu.domain.Menu; +import com.netgrif.application.engine.menu.domain.MenuAndFilters; +import com.netgrif.application.engine.menu.domain.MenuEntry; +import com.netgrif.application.engine.menu.domain.MenuEntryRole; import com.netgrif.application.engine.workflow.service.interfaces.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IMenuImportExportService.java b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IMenuImportExportService.java index 60a3cf857f7..fed6dae2275 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IMenuImportExportService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IMenuImportExportService.java @@ -8,7 +8,7 @@ import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; import com.netgrif.application.engine.workflow.domain.Case; import com.netgrif.application.engine.workflow.domain.IllegalMenuFileException; -import com.netgrif.application.engine.workflow.domain.menu.MenuEntry; +import com.netgrif.application.engine.menu.domain.MenuEntry; import java.io.IOException; import java.util.List; diff --git a/src/main/resources/petriNets/engine-processes/dashboard_item.xml b/src/main/resources/petriNets/engine-processes/dashboard_item.xml new file mode 100644 index 00000000000..33f2ce15d4c --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/dashboard_item.xml @@ -0,0 +1,731 @@ + + dashboard_item + DBI + Dashboard item + crop_square + true + false + false + + + system + + true + true + true + + + + admin + + true + true + true + + + + + system + System + + + admin + Admin + + + + item_id + Dashboard item ID + Unique identifier of dashboard item + + item_id_set + + + item_id: f.item_id; + + if (item_id.value == "") { + return + } + def itemWithIdExists = findCaseElastic("processIdentifier:\"dashboard_item\" AND dataSet.item_id.textValue.keyword:\"${item_id.value}\"" as String) + if (itemWithIdExists != null) { + throw new IllegalArgumentException("Dashboard item with given ID already exists.") + } + + + + + + preference_items_list + Existing menu items + List of all existing menu items + + autocomplete_dynamic + + + preference_items_list_get + + + preference_items_list: f.preference_items_list; + + def preferenceItemCases = findCasesElastic("processIdentifier:\"menu_item\"" as String) + .collectEntries { [(it.stringId): it.dataSet["menu_name"].value] } + change preference_items_list options { preferenceItemCases } + + + + + preference_items_list_set + + + icon_color: f.icon_color, + font_color: f.font_color, + font_weight: f.font_weight, + inherit_name: f.inherit_name, + inherit_icon: f.inherit_icon, + preference_item_taskRef: f.preference_item_taskRef, + filter_divider: f.filter_divider, + preference_items_list: f.preference_items_list, + trans: t.configuration; + + change inherit_icon value { true } + change inherit_name value { true } + if (preference_items_list.value != null && org.bson.types.ObjectId.isValid(preference_items_list.value)) { + make [inherit_name, inherit_icon, icon_color, font_color, font_weight], editable on trans when { true } + make [preference_item_taskRef, filter_divider], visible on trans when { true } + def preferenceItemCase = workflowService.findOne(preference_items_list.value) + change preference_item_taskRef value { [preferenceItemCase.tasks.find { it.transition == "item_settings" }?.task] } + } else { + make [inherit_name, inherit_icon, icon_color, font_color, font_weight], visible on trans when { true } + make [preference_item_taskRef, filter_divider], hidden on trans when { true } + change preference_item_taskRef value { [] } + } + + + + + + is_internal + Internal item + Internal menu item or link to external website URL + true + + is_internal_set + + + item_name: f.item_name, + item_icon: f.item_icon, + external_icon: f.external_icon, + preference_items_list: f.preference_items_list, + external_url: f.external_url, + is_internal: f.is_internal; + + change external_icon value { false } + if (is_internal.value) { + change preference_items_list value { "" } + } else { + change item_icon value { "" } + change item_name value { new com.netgrif.application.engine.petrinet.domain.I18nString("") } + change external_url value { "" } + } + + + font_color: f.font_color, + font_weight: f.font_weight, + is_internal: f.is_internal, + item_name: f.item_name, + item_icon: f.item_icon, + external_icon: f.external_icon, + preference_items_list: f.preference_items_list, + item_icon_preview: f.item_icon_preview, + external_url: f.external_url, + inherit_name: f.inherit_name, + inherit_icon: f.inherit_icon, + preference_item_taskRef: f.preference_item_taskRef, + filter_divider: f.filter_divider, + trans: t.configuration; + + if (is_internal.value) { + make [preference_items_list], editable on trans when { true } + make [external_url], hidden on trans when { true } + } else { + make [item_icon, item_name, external_url, external_icon, font_color, font_weight], editable on trans when { true } + make item_icon_preview, visible on trans when { true } + make [inherit_name, inherit_icon, preference_items_list, preference_item_taskRef, filter_divider], hidden on trans when { true } + } + + + + + + external_url + External URL + Link for external website URL + + + external_icon + Icon from URL + Icon will be loaded from external website URL + + external_icon_set + + + preference_items_list: f.preference_items_list, + inherit_icon: f.inherit_icon, + external_icon: f.external_icon, + icon_color: f.icon_color, + item_icon: f.item_icon, + is_internal: f.is_internal, + trans: t.configuration; + + if (!inherit_icon.value || !is_internal.value) { + change item_icon value { "" } + } + if (external_icon.value) { + make icon_color, visible on trans when { true } + } else if (!(is_internal.value && (preference_items_list.value == null || !org.bson.types.ObjectId.isValid(preference_items_list.value)))) { + make icon_color, editable on trans when { true } + } + + + + + + icon_color + Icon color + Icon color of item. Valid CSS property value font-color. + #0790ff + + icon_color_set + + + icon_color: f.icon_color, + icon: f.item_icon, + iconPreview: f.item_icon_preview; + + change iconPreview value { + + icon_color.value + """]]> + icon.value + """]]> + } + + + + + + item_icon + Item icon identifier + Material icon identifier. List of icons with identifiers is available online. Icon will be displayed in dashboard. + + icon_color: f.icon_color, + external_icon: f.external_icon, + icon: f.this, + iconPreview: f.item_icon_preview; + + if (icon.value == "") { + change iconPreview value {"""]]>} + return; + } + if (external_icon.value) { + change iconPreview value { + + icon.value + """]]> + } + } else { + change iconPreview value { + + icon_color.value + """]]> + icon.value + """]]> + } + } + + + + + item_icon_preview + + <component> + <name>htmltextarea</name> + </component> + </data> + <data type="text" immediate="true"> + <id>font_color</id> + <title name="font_color">Font color + Font color of item name. Valid CSS property value font-color. + #0790ff + + + font_weight + Font weight + Font weight of item name. Valid CSS property value font-weight. + 400 + + + item_name + Item name + Item name which will be displayed in dashboard + + + inherit_icon + Inherit icon + Check if icon should be inherited from menu item + true + + inherit_icon_set + + + inherit_icon: f.inherit_icon, + item_icon_preview: f.item_icon_preview, + external_icon: f.external_icon, + item_icon: f.item_icon, + preference_items_list: f.preference_items_list, + trans: t.configuration; + + change external_icon value { false } + if (inherit_icon.value) { + make [item_icon, item_icon_preview, external_icon], visible on trans when { true } + if (preference_items_list.value != null && org.bson.types.ObjectId.isValid(preference_items_list.value)) { + def preferenceItemCase = workflowService.findOne(preference_items_list.value) + change item_icon value { preferenceItemCase.dataSet["menu_icon"].value } + } else { + change item_icon value { "" } + } + } else { + make [item_icon, external_icon], editable on trans when { true } + make item_icon_preview, visible on trans when { true } + change item_icon value { "" } + } + + + + + + inherit_name + Inherit name from menu item + Name should be inherited from menu item + true + + inherit_name_set + + + inherit_name: f.inherit_name, + item_name: f.item_name, + preference_items_list: f.preference_items_list; + + if (inherit_name.value) { + if (preference_items_list.value != null && org.bson.types.ObjectId.isValid(preference_items_list.value)) { + def preferenceItemCase = workflowService.findOne(preference_items_list.value) + change item_name value { preferenceItemCase.dataSet["menu_name"].value } + } else { + change item_name value { new com.netgrif.application.engine.petrinet.domain.I18nString("") } + } + } else { + change item_name value { new com.netgrif.application.engine.petrinet.domain.I18nString("") } + } + + + inherit_name: f.inherit_name, + item_name: f.item_name, + trans: t.configuration; + + if (inherit_name.value) { + make item_name, visible on trans when { true } + } else { + make item_name, editable on trans when { true } + } + + + + + + preference_item_taskRef + + </data> + <data type="i18n"> + <id>filter_divider</id> + <title/> + <init dynamic="true"> + new com.netgrif.application.engine.petrinet.domain.I18nString("Menu item data", [sk: "Dáta menu položky", de: "Daten zum Menüpunkt"]) + </init> + <component> + <name>divider</name> + <property key="fontSize">16</property> + </component> + </data> + <data type="boolean" immediate="true"> + <id>is_active</id> + <title/> + <init>false</init> + </data> + + + <!-- I18NS --> + <i18n locale="sk"> + <i18nString name="item_id">ID položky panelu</i18nString> + <i18nString name="item_id_desc">Unikátny identifikátor položky panelu</i18nString> + <i18nString name="preference_items_list">Existujúce menu položky</i18nString> + <i18nString name="preference_items_list_desc">Zoznam všetkých existujúcich menu položiek</i18nString> + <i18nString name="is_internal">Interná položka</i18nString> + <i18nString name="is_internal_desc">Položka interného menu alebo odkaz na externú webovú adresu URL</i18nString> + <i18nString name="external_url">Externá adresa URL</i18nString> + <i18nString name="external_url_desc">Odkaz na externú webovú adresu URL</i18nString> + <i18nString name="item_icon">Identifikátor ikony položky</i18nString> + <i18nString name="item_icon_desc">Identifikátor Material ikony. Zoznam ikon s identifikátormi je dostupný online. Ikona sa zobrazí v panely.</i18nString> + <i18nString name="external_icon">Ikona z adresy URL</i18nString> + <i18nString name="external_icon_desc">Ikona bude načítaná z externej URL adresy</i18nString> + <i18nString name="icon_color">Farba ikony</i18nString> + <i18nString name="icon_color_desc">Farba ikony položky. Valídna CSS hodnota font-color.</i18nString> + <i18nString name="font_color">Farba písma</i18nString> + <i18nString name="font_color_desc">Farba písma názvu položky. Valídna CSS hodnota font-color.</i18nString> + <i18nString name="font_weight">Hrúbka písma</i18nString> + <i18nString name="font_weight_desc">Hrúbka písma menu položky. Valídna CSS hodnota font-weight.</i18nString> + <i18nString name="item_name">Názov položky</i18nString> + <i18nString name="item_name_desc">Názov položky, ktorá sa zobrazí v panely</i18nString> + <i18nString name="inherit_icon">Prebrať ikonu</i18nString> + <i18nString name="inherit_icon_desc">Ikona by mala byť prebratá z menu položky</i18nString> + <i18nString name="inherit_name">Prebrať názov</i18nString> + <i18nString name="inherit_name_desc">Názov by mal byť prebratý z menu položky</i18nString> + <i18nString name="configuration_label">Konfigurácia položky panelu</i18nString> + </i18n> + <i18n locale="de"> + <i18nString name="item_id">Dashboard-Element-ID</i18nString> + <i18nString name="item_id_desc">Eindeutiger Bezeichner des Dashboard-Elements</i18nString> + <i18nString name="preference_items_list">Vorhandene Menüpunkte</i18nString> + <i18nString name="preference_items_list_desc">Liste aller vorhandenen Menüpunkte</i18nString> + <i18nString name="is_internal">Interner Posten</i18nString> + <i18nString name="is_internal_desc">Interner Menüpunkt oder Link zu externer Website-URL</i18nString> + <i18nString name="external_url">Externe URL</i18nString> + <i18nString name="external_url_desc">Link für externe Website-URL</i18nString> + <i18nString name="item_icon">Bezeichner des Artikelsymbols</i18nString> + <i18nString name="item_icon_desc">Kennung der Materialikone. Die Liste der Symbole mit den Bezeichnungen ist online verfügbar. Das Symbol wird im Dashboard angezeigt.</i18nString> + <i18nString name="external_icon">Symbol aus URL</i18nString> + <i18nString name="external_icon_desc">Das Symbol wird von einer externen Website-URL geladen</i18nString> + <i18nString name="icon_color">Farbe des Symbols</i18nString> + <i18nString name="icon_color_desc">Symbolfarbe des Elements. Gültiger CSS-Eigenschaftswert font-color.</i18nString> + <i18nString name="font_color">Schriftfarbe</i18nString> + <i18nString name="font_color_desc">Schriftfarbe des Artikelnamens. Gültiger CSS-Eigenschaftswert font-color.</i18nString> + <i18nString name="font_weight">Gewicht der Schriftart</i18nString> + <i18nString name="font_weight_desc">Schriftstärke des Artikelnamens. Gültiger CSS-Eigenschaftswert font-weight.</i18nString> + <i18nString name="item_name">Name des Gegenstands</i18nString> + <i18nString name="item_name_desc">Artikelbezeichnung, die im Dashboard angezeigt wird</i18nString> + <i18nString name="inherit_icon">Symbol vererben</i18nString> + <i18nString name="inherit_icon_desc">Prüfen, ob das Symbol vom Menüpunkt geerbt werden soll</i18nString> + <i18nString name="inherit_name">Name von Menüpunkt vererben</i18nString> + <i18nString name="inherit_name_desc">Name sollte vom Menüpunkt geerbt werden</i18nString> + <i18nString name="configuration_label">Konfiguration von Dashboard-Elementen</i18nString> + </i18n> + + <!-- TRANSITIONS --> + <transition> + <id>configuration</id> + <x>400</x> + <y>112</y> + <label name="configuration_label">Dashboard item configuration</label> + <icon>tune</icon> + <assignPolicy>auto</assignPolicy> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + <view>true</view> + <cancel>true</cancel> + <assign>true</assign> + </logic> + </roleRef> + <dataGroup> + <id>configuration_d1</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>item_id</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>is_internal</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>2</x> + <y>0</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <dataGroup> + <id>configuration_internal</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>preference_items_list</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>inherit_name</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>2</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>inherit_icon</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>3</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <dataGroup> + <id>configuration_general</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>item_name</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>font_color</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>2</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>font_weight</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>3</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>item_icon</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>external_icon</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>1</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>standard</appearance> + </layout> + </dataRef> + <dataRef> + <id>icon_color</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>2</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>item_icon_preview</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>3</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>standard</appearance> + </layout> + </dataRef> + <dataRef> + <id>filter_divider</id> + <logic> + <behavior>hidden</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>preference_item_taskRef</id> + <logic> + <behavior>hidden</behavior> + </logic> + <layout> + <x>0</x> + <y>3</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <dataGroup> + <id>configuration_external</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>external_url</id> + <logic> + <behavior>hidden</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>configuration_assign</id> + <actions phase="post"> + <action> + is_active: f.is_active; + + change is_active value { false } + </action> + </actions> + </event> + <event type="finish"> + <id>configuration_finish</id> + <actions phase="pre"> + <action> + item_id: f.item_id, + item_name: f.item_name, + is_internal: f.is_internal, + preference_items_list: f.preference_items_list, + external_url: f.external_url, + is_active: f.is_active; + + if (item_id.value == null || item_id.value == "") { + throw new IllegalArgumentException("Dashboard item ID cannot be empty.") + } + def itemWithIdExists = findCaseElastic("processIdentifier:\"dashboard_item\" AND dataSet.item_id.textValue.keyword:\"${item_id.value}\"" as String) + if (itemWithIdExists != null) { + throw new IllegalArgumentException("Dashboard item with given ID already exists.") + } + if (is_internal.value) { + if (preference_items_list.value == null || !org.bson.types.ObjectId.isValid(preference_items_list.value)){ + throw new IllegalArgumentException("Internal menu item must be selected.") + } + } else { + if (external_url.value == null || external_url.value == "") { + throw new IllegalArgumentException("External URL cannot be empty.") + } + } + change is_active value { true } + </action> + </actions> + </event> + </transition> + <place> + <id>p1</id> + <x>208</x> + <y>112</y> + <label/> + <tokens>1</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>regular</type> + <sourceId>p1</sourceId> + <destinationId>configuration</destinationId> + <multiplicity>1</multiplicity> + <breakpoint> + <x>304</x> + <y>144</y> + </breakpoint> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>configuration</sourceId> + <destinationId>p1</destinationId> + <multiplicity>1</multiplicity> + <breakpoint> + <x>304</x> + <y>80</y> + </breakpoint> + </arc> +</document> diff --git a/src/main/resources/petriNets/engine-processes/dashboard_management.xml b/src/main/resources/petriNets/engine-processes/dashboard_management.xml new file mode 100644 index 00000000000..575e23eb537 --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/dashboard_management.xml @@ -0,0 +1,732 @@ +<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://petriflow.com/petriflow.schema.xsd"> + <id>dashboard_management</id> + <initials>DBM</initials> + <title>Dashboard management + dashboard + true + false + false + + + system + + true + true + true + + + + admin + + true + true + true + + + + + system + System + + + admin + Admin + + + + dashboard_id + Dashboard ID + Unique identifier of dashboard + + dashboard_id_set + + + dashboard_id: f.dashboard_id; + + if (dashboard_id.value == "") { + return + } + def dashboardWithIdExists = findCaseElastic("processIdentifier:\"dashboard_management\" AND dataSet.dashboard_id.textValue.keyword:\"${dashboard_id.value}\"" as String) + if (dashboardWithIdExists != null) { + throw new IllegalArgumentException("Dashboard with given ID already exists.") + } + + + + + + dashboard_name + Dashboard name + Dashboard name to be displayed in application dashboard + + + dashboard_logo + Dashboard logo + Dashboard logo to be displayed in application dashboard + assets/img/netgrif_full_white.svg + + + dashboard_item_list + Dashboard items + List of dashboard items to be displayed in application dashboard + + list + + + + dashboard_item_to_preference_item + + </data> + <data type="button"> + <id>order_down</id> + <title/> + <placeholder>south</placeholder> + <component> + <name>icon</name> + <property key="stretch">true</property> + </component> + <event type="set"> + <id>order_down_set</id> + <actions phase="post"> + <action> + dashboard_item_to_preference_item: f.dashboard_item_to_preference_item, + items_order: f.items_order, + dashboard_item_list: f.dashboard_item_list; + + if (dashboard_item_list.value == null || dashboard_item_list.value == "") { + return + } + def keysDashboard = dashboard_item_list.options.keySet() as List + def keysPreference = dashboard_item_to_preference_item.options.keySet() as List + def currentIndex = keysDashboard.indexOf(dashboard_item_list.value) + if (currentIndex == keysDashboard.size() - 1) { + return + } + java.util.Collections.swap(keysDashboard, currentIndex, currentIndex + 1) + java.util.Collections.swap(keysPreference, currentIndex, currentIndex + 1) + def newOrderDashboard = [:] + def newOrderPreference = [:] + keysDashboard.forEach { newOrderDashboard[it] = dashboard_item_list.options[it]} + keysPreference.forEach { newOrderPreference[it] = dashboard_item_to_preference_item.options[it]} + change dashboard_item_list options { newOrderDashboard } + change dashboard_item_to_preference_item options { newOrderPreference } + change items_order value { String.join(",", newOrderDashboard.keySet()) } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>order_up</id> + <title/> + <placeholder>north</placeholder> + <component> + <name>icon</name> + <property key="stretch">true</property> + </component> + <event type="set"> + <id>order_up_set</id> + <actions phase="post"> + <action> + dashboard_item_to_preference_item: f.dashboard_item_to_preference_item, + items_order: f.items_order, + dashboard_item_list: f.dashboard_item_list; + + if (dashboard_item_list.value == null || dashboard_item_list.value == "") { + return + } + def keysDashboard = dashboard_item_list.options.keySet() as List + def keysPreference = dashboard_item_to_preference_item.options.keySet() as List + def currentIndex = keysDashboard.indexOf(dashboard_item_list.value) + if (currentIndex == 0) { + return + } + java.util.Collections.swap(keysDashboard, currentIndex, currentIndex - 1) + java.util.Collections.swap(keysPreference, currentIndex, currentIndex - 1) + def newOrderDashboard = [:] + def newOrderPreference = [:] + keysDashboard.forEach { newOrderDashboard[it] = dashboard_item_list.options[it]} + keysPreference.forEach { newOrderPreference[it] = dashboard_item_to_preference_item.options[it]} + change dashboard_item_list options { newOrderDashboard } + change dashboard_item_to_preference_item options { newOrderPreference } + change items_order value { String.join(",", newOrderDashboard.keySet()) } + </action> + </actions> + </event> + </data> + <data type="enumeration_map"> + <id>existing_menu_items</id> + <title name="existing_menu_items">Existing dashboard items + List of all existing dashboard items which can be added to dashboard + + autocomplete_dynamic + + + existing_menu_items_get + + + dashboard_item_list: f.dashboard_item_list, + existing_menu_items: f.existing_menu_items; + + def dashboardItemCases = findCasesElastic("processIdentifier:\"dashboard_item\" AND dataSet.is_active.booleanValue:true" as String) + .collectEntries { [(it.stringId): it.dataSet["item_name"].value] } + dashboardItemCases.keySet().removeAll(dashboard_item_list.options.keySet()) + change existing_menu_items options { dashboardItemCases } + + + + + existing_menu_items_set + + + item_divider: f.item_divider, + dashboard_item_taskRef: f.dashboard_item_taskRef, + existing_menu_items: f.existing_menu_items, + trans: t.configuration; + + if (existing_menu_items.value == null || !org.bson.types.ObjectId.isValid(existing_menu_items.value)) { + change dashboard_item_taskRef value { [] } + make item_divider, hidden on trans when { true } + return + } + def dashboardItemCase = workflowService.findOne(existing_menu_items.value) + make item_divider, visible on trans when { true } + change dashboard_item_taskRef value { [dashboardItemCase.tasks.find { it.transition == "configuration" }?.task] } + + + + + + add_new_item + + <placeholder>Add new item</placeholder> + <event type="set"> + <id>add_new_item_set</id> + <actions phase="post"> + <action> + dashboard_item_to_preference_item: f.dashboard_item_to_preference_item, + items_order: f.items_order, + dashboard_item_list: f.dashboard_item_list, + existing_menu_items: f.existing_menu_items; + + if (existing_menu_items.value == null || !org.bson.types.ObjectId.isValid(existing_menu_items.value)) { + throw new IllegalArgumentException("No menu item was selected.") + } + def dashboardItemOptions = dashboard_item_list.options + dashboardItemOptions.put(existing_menu_items.value, existing_menu_items.options.get(existing_menu_items.value)) + change dashboard_item_list options { dashboardItemOptions } + change items_order value { String.join(",", dashboardItemOptions.keySet()) } + + def dashboardItemCase = workflowService.findOne(existing_menu_items.value) + def preferenceItemOptions = dashboard_item_to_preference_item.options + if (dashboardItemCase.dataSet["is_internal"].value) { + preferenceItemOptions.put(existing_menu_items.value, dashboardItemCase.dataSet["preference_items_list"].value) + } else { + preferenceItemOptions.put(existing_menu_items.value, null) + } + change dashboard_item_to_preference_item options { preferenceItemOptions } + + def dashboardItemCases = findCasesElastic("processIdentifier:\"dashboard_item\" AND dataSet.is_active.booleanValue:true" as String) + .collectEntries { [(it.stringId): it.dataSet["item_name"].value] } + dashboardItemCases.keySet().removeAll(dashboard_item_list.options.keySet()) + change existing_menu_items options { dashboardItemCases } + change existing_menu_items value { "" } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>remove_new_item</id> + <title/> + <placeholder>Remove item</placeholder> + <event type="set"> + <id>remove_new_item_set</id> + <actions phase="post"> + <action> + dashboard_item_to_preference_item: f.dashboard_item_to_preference_item, + dashboard_item_list: f.dashboard_item_list, + items_order: f.items_order, + existing_menu_items: f.existing_menu_items; + + if (dashboard_item_list.value == null || !org.bson.types.ObjectId.isValid(dashboard_item_list.value)) { + throw new IllegalArgumentException("No menu item was selected.") + } + def dashboardItemOptions = dashboard_item_list.options + dashboardItemOptions.keySet().removeAll([dashboard_item_list.value]) + change dashboard_item_list options { dashboardItemOptions } + change items_order value { String.join(",", dashboardItemOptions.keySet()) } + + def preferenceItemOptions = dashboard_item_to_preference_item.options + preferenceItemOptions.keySet().removeAll([dashboard_item_list.value]) + change dashboard_item_to_preference_item options { preferenceItemOptions } + + def dashboardItemCases = findCasesElastic("processIdentifier:\"dashboard_item\" AND dataSet.is_active.booleanValue:true" as String) + .collectEntries { [(it.stringId): it.dataSet["item_name"].value] } + dashboardItemCases.keySet().removeAll(dashboard_item_list.options.keySet()) + change existing_menu_items options { dashboardItemCases } + change existing_menu_items value { "" } + </action> + </actions> + </event> + </data> + <data type="taskRef"> + <id>dashboard_item_taskRef</id> + <title/> + </data> + <data type="boolean"> + <id>is_active</id> + <title/> + </data> + <data type="i18n"> + <id>item_divider</id> + <title/> + <init dynamic="true"> + new com.netgrif.application.engine.petrinet.domain.I18nString("Dashboard item data", [sk: "Dáta položky panelu", de: "Daten zum Dashboard-Element"]) + </init> + <component> + <name>divider</name> + <property key="fontSize">18</property> + </component> + </data> + <data type="boolean" immediate="true"> + <id>simple_dashboard_toolbar</id> + <title name="simple_dashboard_toolbar">Simple dashboard toolbar + Should dashboard toolbar contains menu with options + true + + + profile_dashboard_toolbar + Profile in dashboard toolbar + Should dashboard toolbar contains profile button + false + + profile_dashboard_toolbar_set + + + profile_dashboard_toolbar: f.profile_dashboard_toolbar, + profile_url: f.profile_url, + trans: t.configuration; + + make profile_url, visible on trans when { !profile_dashboard_toolbar.value } + make profile_url, editable on trans when { profile_dashboard_toolbar.value } + + + + + + language_dashboard_toolbar + Language select in dashboard toolbar + Should dashboard toolbar contains menu with language selection + false + + + logout_dashboard_toolbar + Logout in dashboard toolbar + Should dashboard toolbar contains logout button + false + + logout_dashboard_toolbar_set + + + logout_dashboard_toolbar: f.logout_dashboard_toolbar, + login_url: f.login_url, + trans: t.configuration; + + make login_url, visible on trans when { !logout_dashboard_toolbar.value } + make login_url, editable on trans when { logout_dashboard_toolbar.value } + + + + + + items_order + + </data> + <data type="text" immediate="true"> + <id>profile_url</id> + <title name="profile_url">Profile URL + URL address of profile page + profile + + + login_url + Login URL + URL address of login page. Let empty to decide by application. + + + + + ID panelu + Unikátny identifikátor panelu + Názov panelu + Názov panelu, ktorý bude zobrazený v panely aplikácie + Logo panelu + Logo panelu, ktoré bude zobrazené v panely aplikácie + Položky panelu + Zoznam položiek panalu, ktoré budú zobrazené v panely aplikácie + Existujúce položky panelu + Zoznam všetkých existujúcich položiek panelu, ktoré budú zobrazené v panely aplikácie + Konfigurácia panela + + Jednoduchý panel nástrojov + Panel nástrojov by mal obsahovať ponuku s možnosťami + Profil na paneli nástrojov + Mal by panel nástrojov obsahovať tlačidlo profilu + Výber jazyka na paneli nástrojov + Mal by panel nástrojov obsahovať menu s výberom jazyka + Odhlásenie na paneli nástrojov + Mal by panel nástrojov obsahovať tlačidlo na odhlásenie + + URL profilu + URL adresa na stránku profilu + URL prihlásenia + URL adresa na stránku prihlásenia. Nechajte prázdne pre rozhodnutie aplikáciou. + + + Dashboard-ID + Eindeutiger Bezeichner des Dashboards + Name des Dashboards + Name des Dashboards, der im Dashboard der Anwendung angezeigt werden soll + Logo für das Armaturenbrett + Dashboard-Logo, das im Dashboard der Anwendung angezeigt werden soll + Dashboard-Elemente + Liste der Dashboard-Elemente, die im Dashboard der Anwendung angezeigt werden sollen + Vorhandene Dashboard-Elemente + Liste aller vorhandenen Dashboard-Elemente, die dem Dashboard hinzugefügt werden können + Konfiguration des Dashboards + + Einfache Dashboard-Symbolleiste + Die Symbolleiste des Dashboards sollte ein Menü mit Optionen enthalten + Profil in der Dashboard-Symbolleiste + Die Symbolleiste des Dashboards sollte eine Profilschaltfläche enthalten + Sprachauswahl in der Dashboard-Symbolleiste + Sollte die Symbolleiste des Dashboards ein Menü mit Sprachauswahl enthalten + Abmelden in der Dashboard-Symbolleiste + Die Dashboard-Symbolleiste sollte eine Abmeldeschaltfläche enthalten + + Profil-URL + URL-Adresse der Profilseite + Anmelde-URL + URL-Adresse der Anmeldeseite. Lassen Sie leer, um durch Anwendung zu entscheiden. + + + + + configuration + 400 + 112 + + settings + auto + + admin + + true + true + true + true + + + + configuration_d1 + 4 + grid + + dashboard_id + + editable + + + 0 + 0 + 1 + 2 + + outline + + + + dashboard_name + + editable + + + 0 + 1 + 1 + 2 + + outline + + + + dashboard_logo + + editable + + + 2 + 1 + 1 + 2 + + outline + + + + simple_dashboard_toolbar + + editable + + + 0 + 2 + 1 + 1 + + outline + + + + profile_dashboard_toolbar + + editable + + + 1 + 2 + 1 + 1 + + outline + + + + language_dashboard_toolbar + + editable + + + 2 + 2 + 1 + 1 + + outline + + + + logout_dashboard_toolbar + + editable + + + 3 + 2 + 1 + 1 + + outline + + + + profile_url + + visible + + + 0 + 3 + 1 + 2 + + outline + + + + login_url + + visible + + + 2 + 3 + 1 + 2 + + outline + + + + dashboard_item_list + + editable + + + 0 + 4 + 2 + 2 + + outline + + + + order_up + + editable + + + 2 + 4 + 1 + 1 + + outline + + + + order_down + + editable + + + 2 + 5 + 1 + 1 + + outline + + + + remove_new_item + + editable + + + 3 + 4 + 1 + 1 + + outline + + + + existing_menu_items + + editable + + + 0 + 6 + 1 + 2 + + outline + + + + add_new_item + + editable + + + 2 + 6 + 1 + 2 + + outline + + + + item_divider + + hidden + + + 0 + 7 + 1 + 4 + + outline + + + + dashboard_item_taskRef + + visible + + + 0 + 8 + 1 + 4 + + outline + + + + + configuration_assign + + + is_active: f.is_active; + + change is_active value { false } + + + + + configuration_finish + + + dashboard_id: f.dashboard_id, + dashboard_name: f.dashboard_name, + is_active: f.is_active; + + if (dashboard_id.value == null || dashboard_id.value == "") { + throw new IllegalArgumentException("Dashboard ID cannot be empty.") + } + def dashboardWithIdExists = findCaseElastic("processIdentifier:\"dashboard_management\" AND dataSet.dashboard_id.textValue.keyword:\"${dashboard_id.value}\"" as String) + if (dashboardWithIdExists != null) { + throw new IllegalArgumentException("Dashboard with given ID already exists.") + } + change is_active value { true } + + + + + + p1 + 208 + 112 + + + a1 + regular + p1 + configuration + 1 + + 304 + 144 + + + + a2 + regular + configuration + p1 + 1 + + 304 + 80 + + + diff --git a/src/main/resources/petriNets/engine-processes/menu/menu_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml new file mode 100644 index 00000000000..327dcc656c6 --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml @@ -0,0 +1,1494 @@ + + menu_item + MNI + Menu Item + check_box_outline_blank + true + false + false + + system + + true + true + true + + + + admin + + true + true + true + + + + default + + false + false + true + + + + + menu_item_delete + + + removeItemChildCases() + removeViewCase() + + + + + + system + System + + + admin + Admin + + + { -> + + def childCaseIds = useCase.dataSet['childItemIds'].value + if (childCaseIds == null || childCaseIds.isEmpty()) { + return + } + + removeChildItemFromParent(useCase.dataSet['parentId'].value[0], useCase) + + def childCases = workflowService.findAllById(childCaseIds) + async.run { + childCases.each { + workflowService.deleteCase(it) + } + } + } + + + { -> + + def viewIdAsList = useCase.dataSet['view_configuration_id'].value + if (viewIdAsList == null || viewIdAsList.isEmpty()) { + return + } + + async.run { + workflowService.deleteCase(viewIdAsList[0]) + } + } + + + { boolean useTabIcon, boolean useTabbedView, String transId = "item_settings" -> + def settingsTrans = useCase.petriNet.transitions[transId] + + make [useCase.getField("use_tab_icon"), useCase.getField("tab_icon"), useCase.getField("tab_name")], + editable on settingsTrans when { useTabbedView && useTabIcon } + make useCase.getField("tab_icon_preview"), visible on settingsTrans when { useTabbedView && useTabIcon } + + make useCase.getField("use_tab_icon"), editable on settingsTrans when { useTabbedView && !useTabIcon } + make [useCase.getField("tab_icon_preview"), useCase.getField("tab_icon"), useCase.getField("tab_name")], + hidden on settingsTrans when { useTabbedView && !useTabIcon } + + make [useCase.getField("use_tab_icon"), useCase.getField("tab_icon_preview"), useCase.getField("tab_icon"), + useCase.getField("tab_name")], hidden on settingsTrans when { !useTabbedView } + } + + + { + -> + String query = String.format("processIdentifier:menu_item AND uriNodeId:%s AND dataSet.is_auto_select.booleanValue:true AND NOT stringId:%s", + useCase.uriNodeId, useCase.stringId) + def itemCase = findCaseElastic(query) + if (itemCase == null) { + return + } + + setData("item_settings", itemCase, [ + "is_auto_select": [ + "value": false, + "type": "boolean" + ] + ]) + } + + + + parentId + + <allowedNets> + <allowedNet>menu_item</allowedNet> + </allowedNets> + </data> + <data type="text"> + <id>move_previous_dest_uri</id> + <title/> + </data> + <data type="multichoice_map"> + <id>move_dest_uri</id> + <title name="move_dest_uri">Destination URI + List of nodes representing destination URI + + autocomplete + + + moveDestUri: f.move_dest_uri; + + String uriNodeId = elasticCaseService.findUriNodeId(useCase) + def node = uriService.findById(uriNodeId) + updateMultichoiceWithCurrentNode(moveDestUri, node) + + + prevDestUri: f.move_previous_dest_uri, + moveDestUri: f.move_dest_uri; + + String newUri = moveDestUri.value.join("/") + newUri = newUri.replace("//","/") + + String corrected = getCorrectedUri(newUri) + + if (corrected == newUri) { + def node = uriService.findByUri(newUri) + change moveDestUri options { findOptionsBasedOnSelectedNode(node) } + } else { + change moveDestUri value { splitUriPath(corrected) } + } + + + + move_dest_uri_new_node + New node to be added + Enter new node name + + + move_add_node + + <placeholder name="move_add_node">Add</placeholder> + <component> + <name>raised</name> + </component> + <action trigger="set"> + newNodeName: f.move_dest_uri_new_node, + selectedUri: f.move_dest_uri; + + if (newNodeName.value == null || newNodeName.value == "") { + return + } + + String prefixUri = selectedUri.value.join("/") + prefixUri = prefixUri.replace("//","/") + + String newUri = prefixUri + uriService.getUriSeparator() + newNodeName.value + def newNode = uriService.getOrCreate(newUri, com.netgrif.application.engine.petrinet.domain.UriContentType.CASE) + + change selectedUri value { splitUriPath(newNode.uriPath) } + + change newNodeName value { null } + </action> + </data> + <data type="i18n"> + <id>duplicate_new_title</id> + <title name="duplicate_new_title">Title of duplicated view + + + duplicate_view_identifier + View identifier + Must be unique + + + childItemIds + + <allowedNets> + <allowedNet>menu_item</allowedNet> + </allowedNets> + </data> + <data type="taskRef"> + <id>childItemForms</id> + <title/> + </data> + <data type="boolean" immediate="true"> + <id>hasChildren</id> + <title/> + </data> + <data type="button"> + <id>duplicate_reset_childItemIds</id> + <title/> + <action trigger="set"> + hasChildren: f.hasChildren, + childItemIds: f.childItemIds; + + change childItemIds value { [] } + change hasChildren value { false } + </action> + </data> + <data type="text" immediate="true"> + <id>menu_item_identifier</id> + <title name="menu_item_identifier">Menu item identifier + + + nodePath + Item URI + + 0 + + + nodePath: f.nodePath, + menu_item_identifier: f.menu_item_identifier; + + change menu_item_identifier value { + def idx = nodePath.value.lastIndexOf(uriService.getUriSeparator()) + return nodePath.value.substring(idx + 1) + } + + + + + + + + menu_icon + Menu icon identifier + Material icon identifier. List of icons with identifiers is available online. + + icon: f.this, + iconPreview: f.menu_icon_preview; + + changeCaseProperty "icon" about { icon.value; } + + if (icon.value == "") { + change iconPreview value {"""]]>} + return; + } + + change iconPreview value { + """]]> + icon.value + """]]> + } + + + + menu_icon_preview + Menu icon preview + + htmltextarea + + + + menu_name_as_visible + Name of the item + Is shown in the menu + + autocomplete + + + + 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; + + changeCaseProperty "title" about { name.value } + change menu_name_as_visible choices { [name.value] } + change menu_name_as_visible value { name.value } + + + + add_allowed_roles + + <placeholder name="allow_roles">Allow view for roles</placeholder> + <action trigger="set"> + allowedRoles: f.allowed_roles, + processesAvailable: f.processes_available, + rolesAvailable: f.roles_available; + + change allowedRoles options {return configurableMenuService.addSelectedRoles(allowedRoles, processesAvailable, rolesAvailable)} + + change rolesAvailable value {[]} + change rolesAvailable options {[:]} + change processesAvailable value {null} + </action> + </data> + <data type="button"> + <id>remove_allowed_roles</id> + <title/> + <placeholder name="remove_from_allowed_roles">Remove from allowed roles</placeholder> + <action trigger="set"> + allowedRoles: f.allowed_roles, + processesAvailable: f.processes_available, + rolesAvailable: f.roles_available; + + change allowedRoles options {return configurableMenuService.removeSelectedRoles(allowedRoles)} + + change allowedRoles value {[]} + change rolesAvailable value {[]} + change rolesAvailable options {[:]} + change processesAvailable value {null} + </action> + </data> + <data type="button"> + <id>add_banned_roles</id> + <title/> + <placeholder name="ban_roles">Ban view for roles</placeholder> + <action trigger="set"> + bannedRoles: f.banned_roles, + processesAvailable: f.processes_available, + rolesAvailable: f.roles_available; + + change bannedRoles options {return configurableMenuService.addSelectedRoles(bannedRoles, processesAvailable, rolesAvailable)} + + change rolesAvailable value {[]} + change rolesAvailable options {[:]} + change processesAvailable value {null} + </action> + </data> + <data type="button"> + <id>remove_banned_roles</id> + <title/> + <placeholder name="remove_from_banned_roles">Remove from banned roles</placeholder> + <action trigger="set"> + bannedRoles: f.banned_roles, + processesAvailable: f.processes_available, + rolesAvailable: f.roles_available; + + change bannedRoles options { return configurableMenuService.removeSelectedRoles(bannedRoles) } + + change bannedRoles value { [] } + change rolesAvailable value { [] } + change rolesAvailable options { [:] } + change processesAvailable value { null } + </action> + </data> + <data type="enumeration_map" immediate="true"> + <id>processes_available</id> + <title name="available_processes">Your processes + Select a process containing roles you wish to add to allowed or banned roles lists. + + processes: f.this; + + change processes options { return configurableMenuService.getNetsByAuthorAsMapOptions(loggedUser(), org.springframework.context.i18n.LocaleContextHolder.locale) } + + + processes: f.this, + allowedRoles: f.allowed_roles, + bannedRoles: f.banned_roles, + rolesAvailable: f.roles_available; + + if (processes.value != null) { + change rolesAvailable options { return configurableMenuService.getAvailableRolesFromNet(processes, allowedRoles, bannedRoles) } + } else { + change rolesAvailable options { [:] } + } + change rolesAvailable value { [] } + + + + roles_available + Available roles from selected process + + + allowed_roles + Allowed roles + List of roles allowed to view this menu entry. + + [:] + + + + banned_roles + Banned roles + List of roles not allowed to view this menu entry. + + [:] + + + + use_custom_view + Use custom view? + false + + + custom_view_selector + Custom view configuration selector + Example: "demo-tabbed-views" + + + is_auto_select + View auto selection + If selected, the view will be automatically opened + false + + is_auto_select: f.is_auto_select; + + if (!is_auto_select.value) + return + + removeAnyAutoSelectFlagInFolder() + + + + use_tabbed_view + Do you want to use view with tabs? + + + + + tab_icon + Tab icon identifier + Material icon identifier. List of icons with identifiers is available online. + + icon: f.this, + iconPreview: f.tab_icon_preview; + + if (icon.value == "") { + change iconPreview value {"""]]>} + return; + } + + change iconPreview value { + """]]> + icon.value + """]]> + } + + + + tab_icon_preview + Tab icon preview + + htmltextarea + + + + use_tab_icon + Display tab icon? + true + + + tab_name + Name of the item + Will be shown in tab + + + view_configuration_type + Pick view type + + menuItemService.getAvailableViewsAsOptions(false, true) + + + use_tabbed_view: f.use_tabbed_view, + view_configuration_type: f.view_configuration_type; + + if (view_configuration_type.options == null || view_configuration_type.options.isEmpty()) { + change view_configuration_type options { menuItemService.getAvailableViewsAsOptions(use_tabbed_view.value, true) } + } + + + + view_configuration_id + + <allowedNets> + <allowedNet>tabbed_case_view_configuration</allowedNet> + <allowedNet>tabbed_task_view_configuration</allowedNet> + <allowedNet>tabbed_ticket_view_configuration</allowedNet> + </allowedNets> + </data> + <data type="taskRef"> + <id>view_configuration_form</id> + <title/> + </data> + <data type="button"> + <id>order_down</id> + <title/> + <placeholder>south</placeholder> + <component> + <name>icon</name> + <property key="stretch">true</property> + </component> + <action trigger="set"> + parentId: f.parentId; + + def parentCase = workflowService.findOne(parentId.value[0]) + def taskId = useCase.tasks.find { it.transition == "row_for_ordering" }.task + def taskRefValue = parentCase.dataSet['childItemForms'].value + int taskRefValueSize = taskRefValue.size() + def caseRefValue = parentCase.dataSet['childItemIds'].value + int caseRefValueSize = caseRefValue.size() + + int idxInTaskRef = taskRefValue.indexOf(taskId) + if (idxInTaskRef < taskRefValueSize - 1) { + Collections.swap(taskRefValue, idxInTaskRef, idxInTaskRef + 1) + } + + int idxInCaseRef = caseRefValue.indexOf(useCase.stringId) + if (idxInCaseRef < caseRefValueSize - 1) { + Collections.swap(caseRefValue, idxInCaseRef, idxInCaseRef + 1) + } + + setData("children_order", parentCase, [ + "childItemForms" : [ + "value" : taskRefValue, + "type" : "taskRef" + ], + "childItemIds" : [ + "value" : caseRefValue, + "type" : "caseRef" + ] + ]) + </action> + </data> + <data type="button"> + <id>order_up</id> + <title/> + <placeholder>north</placeholder> + <component> + <name>icon</name> + <property key="stretch">true</property> + </component> + <action trigger="set"> + parentId: f.parentId; + + def parentCase = workflowService.findOne(parentId.value[0]) + def taskId = useCase.tasks.find { it.transition == "row_for_ordering" }.task + def taskRefValue = parentCase.dataSet['childItemForms'].value + def caseRefValue = parentCase.dataSet['childItemIds'].value + + int idxInTaskRef = taskRefValue.indexOf(taskId) + if (idxInTaskRef > 0) { + Collections.swap(taskRefValue, idxInTaskRef - 1, idxInTaskRef) + } else { + return + } + + int idxInCaseRef = caseRefValue.indexOf(useCase.stringId) + if (idxInCaseRef > 0) { + Collections.swap(caseRefValue, idxInCaseRef - 1, idxInCaseRef) + } else { + return + } + + setData("children_order", parentCase, [ + "childItemForms" : [ + "value" : taskRefValue, + "type" : "taskRef" + ], + "childItemIds" : [ + "value" : caseRefValue, + "type" : "caseRef" + ] + ]) + </action> + </data> + + <!-- I18NS --> + <i18n locale="sk"> + <i18nString name="icon_preview">Náhľad ikony</i18nString> + <i18nString name="icon_identifier">Identifikátor ikony</i18nString> + <i18nString name="icon_identifier_desc">Identifikátor Material ikony. Zoznam ikon s identifikátormi je dostupný online.</i18nString> + <i18nString name="allow_roles">Pridaj k povoleným roliam</i18nString> + <i18nString name="remove_from_allowed_roles">Odstráň z povolených rolí</i18nString> + <i18nString name="ban_roles">Pridaj k zakázaným roliam</i18nString> + <i18nString name="remove_from_banned_roles">Odstráň zo zakázaných rolí</i18nString> + <i18nString name="available_processes">Vaše procesy</i18nString> + <i18nString name="available_processes_desc">Vyberte proces obsahujúci roly ktoré chcete pridať do zoznamu povolených alebo zakázaných rolí.</i18nString> + <i18nString name="available_roles">Dostupné roly</i18nString> + <i18nString name="move_dest_uri">Cieľové URI</i18nString> + <i18nString name="duplicate_new_title">Názov duplikovanej položky</i18nString> + <i18nString name="duplicate_view_identifier">Identifikátor duplikovanej položky</i18nString> + <i18nString name="duplicate_view_identifier_desc">Musí byť jedinečný</i18nString> + <i18nString name="name">Názov položky</i18nString> + <i18nString name="name_desc">Bude zobrazený v menu</i18nString> + <i18nString name="tab_icon">Identifikátor ikony v karte</i18nString> + <i18nString name="tab_icon_identifier_desc">Identifikátor Material ikony. Zoznam ikon s identifikátormi je dostupný online.</i18nString> + <i18nString name="display_tab_icon">Zobraziť ikonu v karte?</i18nString> + <i18nString name="tab_name">Názov položky</i18nString> + <i18nString name="tab_name_desc">Bude zobrazený v karte</i18nString> + <i18nString name="use_custom_view">Použiť vlastné zobrazenie?</i18nString> + <i18nString name="custom_view_selector">Konfiguračný identifikátor vlastného zobrazenia</i18nString> + <i18nString name="custom_view_selector_desc">Napríklad: "demo-tabbed-views"</i18nString> + <i18nString name="item_settings">Nastavenie položky</i18nString> + <i18nString name="roles_management_title">Roly</i18nString> + <i18nString name="move_item">Presunúť položku</i18nString> + <i18nString name="move_item_finish">Presunúť</i18nString> + <i18nString name="duplicate_item">Duplikovať položku</i18nString> + <i18nString name="duplicate_item_finish">Duplikovať</i18nString> + <i18nString name="roles_allowed">Povolené roly</i18nString> + <i18nString name="roles_allowed_desc">Zoznam povolených rolí, ktoré môžu zobraziť túto položku</i18nString> + <i18nString name="roles_banned">Zakázané roly</i18nString> + <i18nString name="roles_banned_desc">Zoznam zakázaných rolí, ktoré nemôžu zobraziť túto položku</i18nString> + <i18nString name="item_settings_general">Všeobecné</i18nString> + <i18nString name="menu_item_identifier">Identifikátor položky</i18nString> + <i18nString name="nodePath">URI položky</i18nString> + <i18nString name="move_dest_uri_new_node">Nový uzol</i18nString> + <i18nString name="move_dest_uri_new_node_desc">Uveďte názov uzlu, ktorý chcete pridať</i18nString> + <i18nString name="move_dest_uri_desc">Zoznam uzlov reprezentujúce cieľovú URI</i18nString> + <i18nString name="move_add_node">Pridať</i18nString> + <i18nString name="is_auto_select">Automatické zvolenie zobrazenia</i18nString> + <i18nString name="is_auto_select_desc">Po automatickom zvolení sa dané zobrazenie používateľovi otvorí</i18nString> + <i18nString name="use_tabbed_view">Použiť zobrazenie v taboch?</i18nString> + <i18nString name="view_configuration_type">Vybrať zobrazenie</i18nString> + <i18nString name="configuration_view">Nastavenie zobrazenia</i18nString> + </i18n> + <i18n locale="de"> + <i18nString name="icon_preview">Ikonevorschau</i18nString> + <i18nString name="icon_identifier">Ikone ID</i18nString> + <i18nString name="icon_identifier_desc">Material Ikone ID. Liste den Ikonen mit IDs ist online verfügbar.</i18nString> + <i18nString name="allow_roles">Zu zulässigen Rollen hinzufügen</i18nString> + <i18nString name="remove_from_allowed_roles">Aus zulässigen Rollen entfernen</i18nString> + <i18nString name="ban_roles">Zu verbotenen Rollen hinzufügen</i18nString> + <i18nString name="remove_from_banned_roles">Aus verbotenen Rollen entfernen</i18nString> + <i18nString name="available_processes">Ihre Prozesse</i18nString> + <i18nString name="available_processes_desc">Wählen Sie einen Prozess mit Rollen aus, die Sie zu Listen mit zulässigen oder verbotenen Rollen hinzufügen möchten.</i18nString> + <i18nString name="available_roles">Verfügbare Rollen</i18nString> + <i18nString name="tab_icon_identifier_desc">Material Ikone ID. Liste den Ikonen mit IDs ist online verfügbar.</i18nString> + <i18nString name="custom_view_selector_desc">Beispiel: "demo-tabbed-views"</i18nString> + <i18nString name="roles_management_title">Rollen</i18nString> + <i18nString name="roles_allowed">Zulässige Rollen</i18nString> + <i18nString name="item_settings_general">Allgemein</i18nString> + <i18nString name="menu_item_identifier">Identifikationsnummer des Menüeintrages</i18nString> + <i18nString name="nodePath">Menüeintrag-URI</i18nString> + <i18nString name="move_dest_uri_new_node">Neuer Knoten</i18nString> + <i18nString name="move_add_node">Hinzufügen</i18nString> + <i18nString name="move_dest_uri">Ziel URI</i18nString> + <i18nString name="duplicate_new_title">Titel der kopierten Ansicht</i18nString> + <i18nString name="duplicate_view_identifier">Identifikator der kopierten Ansicht</i18nString> + <i18nString name="duplicate_view_identifier_desc">Muss einzigartig sein</i18nString> + <i18nString name="name">Titel des Eintrages</i18nString> + <i18nString name="name_desc">Wird im Menü angezeigt</i18nString> + <i18nString name="tab_icon">Ikonen Identifikator der Registerkarte</i18nString> + <i18nString name="display_tab_icon">Zeige die Registerkarte Ikone an?</i18nString> + <i18nString name="tab_name">Titel der Registerkarte</i18nString> + <i18nString name="tab_name_desc">Wird in der Registerkarte angezeigt</i18nString> + <i18nString name="use_custom_view">Eigener Ansicht anwenden?</i18nString> + <i18nString name="custom_view_selector">Konfigurationsidentifikator der eigenen Ansicht</i18nString> + <i18nString name="item_settings">Menüeintrageinstellungen</i18nString> + <i18nString name="move_item">Menüeintrag verschieben</i18nString> + <i18nString name="move_item_finish">verschieben</i18nString> + <i18nString name="duplicate_item">Menüeintrag duplizieren</i18nString> + <i18nString name="duplicate_item_finish">duplizieren</i18nString> + <i18nString name="roles_allowed_desc">Rollen mit Zugriff auf diesen Menüeintrag</i18nString> + <i18nString name="roles_banned">Verbotene Rollen</i18nString> + <i18nString name="roles_banned_desc">Rollen, für die wird den Menüeintrag ausgeblendet</i18nString> + <i18nString name="move_dest_uri_new_node_desc">Nächste URI-Teil angeben</i18nString> + <i18nString name="move_dest_uri_desc">Teile der Ziel URI</i18nString> + <i18nString name="is_auto_select">Automatische Anzeigeauswahl</i18nString> + <i18nString name="is_auto_select_desc">Wenn ausgewählt, wird die Ansicht automatisch geöffnet</i18nString> + <i18nString name="use_tabbed_view">Möchten Sie die Ansicht mit Registerkarten verwenden?</i18nString> + <i18nString name="view_configuration_type">Wählen Sie einen Ansichtstyp</i18nString> + <i18nString name="configuration_view">Die Ansichtskonfiguration</i18nString> + </i18n> + + <!-- TRANSITIONS --> + <transition> + <id>initialize</id> + <x>340</x> + <y>220</y> + <label>initialize [await sync]</label> + <icon>hourglass_empty</icon> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + <view>true</view> + <cancel>true</cancel> + <assign>true</assign> + </logic> + </roleRef> + </transition> + <transition> + <id>data_sync</id> + <x>340</x> + <y>340</y> + <label>Data sync</label> + <roleRef> + <id>system</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + </transition> + + <transition> + <id>item_settings</id> + <x>460</x> + <y>100</y> + <label name="item_settings">Item settings</label> + <icon>settings</icon> + <assignPolicy>auto</assignPolicy> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + <view>true</view> + <cancel>true</cancel> + <assign>true</assign> + </logic> + </roleRef> + <dataGroup> + <id>general_0</id> + <cols>4</cols> + <layout>grid</layout> + <title name="item_settings_general">General + + menu_item_identifier + + visible + + + 0 + 0 + 1 + 2 + + outline + + + + nodePath + + visible + + + 2 + 0 + 1 + 2 + + outline + + + + menu_name + + editable + + + 0 + 1 + 1 + 2 + + outline + + + + menu_icon + + editable + + + 2 + 1 + 1 + 1 + + outline + + + + menu_icon_preview + + visible + + + 3 + 1 + 1 + 1 + + standard + + + + is_auto_select + + editable + + + 1 + 2 + 1 + 1 + + standard + + + + + roles_management + 5 + grid + Roles + + processes_available + + editable + + + 0 + 0 + 2 + 1 + 0 + + outline + + + + roles_available + + editable + + + 1 + 0 + 2 + 1 + 0 + + outline + + + + add_allowed_roles + + editable + + + 2 + 0 + 1 + 1 + 0 + + + + + allowed_roles + + editable + + + 3 + 0 + 1 + 1 + 0 + + outline + + + + remove_allowed_roles + + editable + + + 4 + 0 + 1 + 1 + 0 + + + + + add_banned_roles + + editable + + + 2 + 1 + 1 + 1 + 0 + + + + + banned_roles + + editable + + + 3 + 1 + 1 + 1 + 0 + + outline + + + + remove_banned_roles + + editable + + + 4 + 1 + 1 + 1 + 0 + + + + + + configuration_view + 4 + grid + View configuration + + use_custom_view + + editable + + + 0 + 0 + 1 + 1 + + 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 + + hidden + + + 1 + 0 + 1 + 3 + + outline + + + + use_tabbed_view + + editable + + + 1 + 0 + 1 + 1 + + standard + + + 0 + + + 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) } + + + + + + tab_name + + hidden + + + 2 + 0 + 1 + 1 + + outline + + + + use_tab_icon + + hidden + + + 3 + 0 + 1 + 1 + 0 + + + + 0 + + + use_tabbed_view: f.use_tabbed_view, + useIcon: f.use_tab_icon; + + manageBehaviorOfTabFields(useIcon.value, use_tabbed_view.value) + + + + + + tab_icon + + hidden + + + 0 + 1 + 1 + 1 + + outline + + + + tab_icon_preview + + hidden + + + 1 + 1 + 1 + 2 + + standard + + + + view_configuration_type + + editable + + + 0 + 2 + 1 + 4 + + 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 + + editable + + + 0 + 3 + 1 + 4 + + outline + + + + + + + move_item + 580 + 100 + + move_down + auto + + admin + + true + true + true + true + + + + move + 4 + grid + + move_dest_uri + + editable + required + + + 0 + 0 + 1 + 2 + + outline + + + + move_dest_uri_new_node + + editable + + + 2 + 0 + 1 + 1 + + outline + + + + move_add_node + + editable + + + 3 + 0 + 1 + 1 + + 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 + true + true + true + + + + duplicate + 4 + grid + + duplicate_new_title + + editable + required + + + 0 + 0 + 1 + 4 + + outline + + + + duplicate_view_identifier + + editable + required + + + 0 + 1 + 1 + 4 + + 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 + true + true + 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 + + outline + + + + + + row_for_ordering + 741 + 219 + + + system + + true + true + true + true + + + + row_for_ordering_0 + 6 + grid + + menu_item_identifier + + visible + + + 0 + 0 + 1 + 2 + + outline + + + + menu_name_as_visible + + visible + + + 2 + 0 + 1 + 2 + + outline + + + + order_down + + editable + + + 4 + 0 + 1 + 1 + 1 + + outline + + + + order_up + + editable + + + 5 + 0 + 1 + 1 + 1 + + outline + + + + + finish + + + + delegate + + + + + + + uninitialized + 220 + 220 + + 1 + false + + + initialized + 460 + 220 + + 0 + false + + + + + a1 + regular + uninitialized + initialize + 1 + + + a7 + read + initialized + item_settings + 1 + + + a8 + regular + initialize + initialized + 1 + + + a9 + read + initialized + move_item + 1 + + + a10 + read + initialized + duplicate_item + 1 + + + a13 + read + initialized + children_order + 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 new file mode 100644 index 00000000000..3e1adad7aa7 --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_case_view_configuration.xml @@ -0,0 +1,936 @@ + + tabbed_case_view_configuration + TCV + Tabbed case view configuration + check_box_outline_blank + true + false + false + + system + + true + true + true + + + + admin + + true + true + true + + + + default + + false + false + true + + + + + view_delete + + + removeViewCase() + + + + + + system + System + + + admin + Admin + + + + { + com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField filterAutocomplete, + com.netgrif.application.engine.petrinet.domain.dataset.TaskField previewTaskRef, + com.netgrif.application.engine.petrinet.domain.dataset.CaseField selectedFilterRef, + com.netgrif.application.engine.petrinet.domain.dataset.ButtonField updateBtn, + com.netgrif.application.engine.petrinet.domain.Transition trans + -> + if (filterAutocomplete.getOptions().containsKey(filterAutocomplete.value)) { + change previewTaskRef value { + return [findTask({it.caseId.eq(filterAutocomplete.value).and(it.transitionId.eq("view_filter"))}).stringId] + } + make updateBtn,editable on trans when { true } + } else { + change filterAutocomplete options { + def findAllPredicate = { filterCase -> !selectedFilterRef.value.contains(filterCase.stringId) + && filterCase.dataSet["filter_type"].value == "Case" } + return findFilters(filterAutocomplete.value != null ? filterAutocomplete.value : "") + .findAll(findAllPredicate) + .collectEntries({filterCase -> [filterCase.stringId, filterCase.title]}) + } + change previewTaskRef value { [] } + make updateBtn,visible on trans when { true } + } + } + + + { -> + + def viewIdAsList = useCase.dataSet['view_configuration_id'].value + if (viewIdAsList == null || viewIdAsList.isEmpty()) { + return + } + + async.run { + workflowService.deleteCase(viewIdAsList[0]) + } + } + + + { + com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField toBeUpdated, + com.netgrif.application.engine.petrinet.domain.dataset.MultichoiceMapField valueSelector, + com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField optionsHolder + -> + def existingOptions = optionsHolder.options + def selectedValues = valueSelector.value + def newOptions = [:] + + if (selectedValues != null) { + existingOptions.each { key, value -> + if (selectedValues.contains(key)) { + newOptions.put(key, value) + } + } + } + + if (!newOptions.containsKey(toBeUpdated.value)) { + change toBeUpdated value { null } + } + + change toBeUpdated options { newOptions } + } + + + + + selected_filter_preview + + </data> + <data type="taskRef"> + <id>current_filter_preview</id> + <title/> + </data> + <data type="i18n"> + <id>filter_header</id> + <title/> + <init name="filter_header">Current filter</init> + <component> + <name>divider</name> + </component> + </data> + <data type="text"> + <id>new_filter_id</id> + <title/> + </data> + <data type="boolean" immediate="true"> + <id>contains_filter</id> + <title/> + <init>false</init> + </data> + <data type="enumeration_map"> + <id>filter_autocomplete_selection</id> + <title name="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 + + <placeholder name="update_filter">Update view with selected filter</placeholder> + <component> + <name>raised</name> + </component> + <action trigger="set"> + 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 } + </action> + </data> + <data type="caseRef"> + <id>filter_case</id> + <title/> + <action trigger="set"> + 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 } + </action> + <allowedNets> + <allowedNet>filter</allowedNet> + </allowedNets> + </data> + + <!-- VIEW CONFIGURATION DATA --> + <data type="enumeration_map" immediate="true"> + <id>view_search_type</id> + <title name="view_search_type">Search type for case view + + + + + + fulltext_advanced + + + create_case_button_title + "New case" button title + + + create_case_button_icon_preview + Icon preview + add]]> + + htmltextarea + + + + create_case_button_icon + "New case" button icon identifier + add + + create_case_button_icon_preview: f.create_case_button_icon_preview, + create_case_button_icon: f.create_case_button_icon; + + + if (create_case_button_icon.value == "") { + change create_case_button_icon_preview value {"""]]>} + return; + } + + change create_case_button_icon_preview value { + """]]> + create_case_button_icon.value + """]]> + } + + + + show_create_case_button + Show create case button? + true + + + require_title_in_creation + Require title input in case creation? + true + + + banned_nets_in_creation + Banned processes for creation + Write down process identifiers separated by comma. Example: mynet1,mynet2 + + bannedNets: f.this; + + String trimmed = bannedNets.value?.replaceAll("\\s","") + if (bannedNets.value != trimmed) { + change bannedNets value { trimmed } + } + + + + view_header + + <init name="view_header">Case view</init> + <component> + <name>divider</name> + </component> + </data> + <data type="boolean" immediate="true"> + <id>show_more_menu</id> + <title name="show_more_menu">Show more menu for case item? + false + + + allow_header_table_mode + Allow table mode for headers? + true + + + headers_mode + Header mode + + + + + + sort,edit,search + + headersMode: f.headers_mode, + defaultMode: f.headers_default_mode, + holder: f.headers_options_holder; + + updateOptionsBasedOnValue(defaultMode, headersMode, holder) + + + headersMode: f.headers_mode, + defaultMode: f.headers_default_mode, + holder: f.headers_options_holder; + + updateOptionsBasedOnValue(defaultMode, headersMode, holder) + + + + headers_default_mode + Default header mode + + + + + + sort + + + headers_options_holder + + <options> + <option key="sort" name="sort">Sort</option> + <option key="search" name="search">Search</option> + <option key="edit" name="edit">Edit</option> + </options> + </data> + <data type="boolean" immediate="true"> + <id>is_header_mode_changeable</id> + <title name="is_header_mode_changeable">Can header mode be changed? + true + + + use_case_default_headers + Use custom default headers? + true + + + default_headers + Set default headers + Example: "meta-title,meta-visualId" + + defaultHeaders: f.this; + + String trimmed = defaultHeaders.value?.replaceAll("\\s","") + if (defaultHeaders.value != trimmed) { + change defaultHeaders value { trimmed } + } + + + + view_configuration_type + Pick view type + + menuItemService.getAvailableViewsAsOptions(true, useCase.processIdentifier) + + + view_configuration_type: f.view_configuration_type; + + if (view_configuration_type.options == null || view_configuration_type.options.isEmpty()) { + change view_configuration_type options { menuItemService.getAvailableViewsAsOptions(true, useCase.processIdentifier) } + } + + + + view_configuration_id + + <allowedNets> + <allowedNet>tabbed_task_view_configuration</allowedNet> + </allowedNets> + </data> + <data type="taskRef"> + <id>view_configuration_form</id> + <title/> + </data> + + <!-- I18NS --> + <i18n locale="sk"> + <i18nString name="create_case_button_title">Názov tlačidla "Nová inštancia"</i18nString> + <i18nString name="create_case_button_icon">Identifikátor ikony tlačidla "Nová inštancia"</i18nString> + <i18nString name="create_case_button_icon_preview">Náhľad ikony</i18nString> + <i18nString name="default_headers">Predvolené hlavičky</i18nString> + <i18nString name="default_headers_desc">Napríklad: "meta-title,meta-visualId"</i18nString> + <i18nString name="filter_autocomplete_selection">Zvoľte nový filter</i18nString> + <i18nString name="update_filter">Aktualizovať zobrazenie s vybraným filtrom</i18nString> + <i18nString name="view_search_type">Typ vyhľadávania prípadov</i18nString> + <i18nString name="hidden">Skryté</i18nString> + <i18nString name="fulltext">Fulltext</i18nString> + <i18nString name="fulltext_advanced">Fulltext a rozšírené</i18nString> + <i18nString name="require_title_in_creation">Vyžadovať názov inštancii pri vytváraní?</i18nString> + <i18nString name="banned_nets_in_creation">Zakázané siete pri vytváraní</i18nString> + <i18nString name="banned_nets_in_creation_desc">Uveďte identifikátory procesov oddelené čiarkou. Napríklad: mynet1,mynet2</i18nString> + <i18nString name="show_create_case_button">Zobraziť tlačidlo na vytvorenie prípadu?</i18nString> + <i18nString name="show_more_menu">Zobrazovať menu pre prípadovú položku?</i18nString> + <i18nString name="sort">Zoraďovanie</i18nString> + <i18nString name="search">Vyhľadávanie</i18nString> + <i18nString name="edit">Upravovanie</i18nString> + <i18nString name="headers_mode">Mód hlavičiek</i18nString> + <i18nString name="headers_default_mode">Predvolený mód hlavičiek</i18nString> + <i18nString name="is_header_mode_changeable">Môže byť mód hlavičiek zmenený?</i18nString> + <i18nString name="allow_header_table_mode">Povoliť tabuľkový mód pre hlavičky?</i18nString> + <i18nString name="use_default_headers">Použiť vlastné predvolené hlavičky?</i18nString> + <i18nString name="item_settings">Nastavenie položky</i18nString> + <i18nString name="filter_update_title">Filter</i18nString> + <i18nString name="filter_header">Súčasný filter</i18nString> + <i18nString name="view_header">Zobrazenie prípadov</i18nString> + <i18nString name="item_settings_general">Všeobecné</i18nString> + <i18nString name="use_case_default_headers">Použiť predvolené hlavičky</i18nString> + <i18nString name="view_configuration_type">Vybrať zobrazenie</i18nString> + <i18nString name="settings">Nastavenie</i18nString> + <i18nString name="associated_view">Asociované zobrazenie</i18nString> + </i18n> + <i18n locale="de"> + <i18nString name="create_case_button_title">Schaltflächentitel "Neuer Fall"</i18nString> + <i18nString name="create_case_button_icon">Ikone ID</i18nString> + <i18nString name="create_case_button_icon_preview">Ikonevorschau</i18nString> + <i18nString name="default_headers">Anzuzeigende Attributmenge auswählen</i18nString> + <i18nString name="filter_autocomplete_selection">Neue Filter auswählen</i18nString> + <i18nString name="default_headers_desc">Beispiel: "meta-title,meta-visualId"</i18nString> + <i18nString name="hidden">Versteckt</i18nString> + <i18nString name="fulltext">Einfacher Suchmodus</i18nString> + <i18nString name="sort">Sortieren</i18nString> + <i18nString name="search">Suchen</i18nString> + <i18nString name="edit">Bearbeiten</i18nString> + <i18nString name="headers_mode">Kopfzeilenmodus</i18nString> + <i18nString name="headers_default_mode">Standardkopfzeilenmodus</i18nString> + <i18nString name="is_header_mode_changeable">Erlaube Änderung des Kopfzeilenmodus?</i18nString> + <i18nString name="allow_header_table_mode">Erlaube Tabellenmodus?</i18nString> + <i18nString name="use_default_headers">Eigene Kopfzeilen verwenden?</i18nString> + <i18nString name="filter_update_title">Filter</i18nString> + <i18nString name="filter_header">Aktueller Filter</i18nString> + <i18nString name="item_settings_general">Allgemein</i18nString> + <i18nString name="update_filter">Aktualisiere die Ansicht mit dem ausgewählten Filter</i18nString> + <i18nString name="view_search_type">Suchmodus im Fallansicht</i18nString> + <i18nString name="fulltext_advanced">Einfacher und erweiterter Suchmodus</i18nString> + <i18nString name="require_title_in_creation">Erforde den Titel beim erzeugen von Fällen?</i18nString> + <i18nString name="banned_nets_in_creation">Ausgeschlossene Prozesse</i18nString> + <i18nString name="banned_nets_in_creation_desc">Trenne die Prozessidentifikatoren mit einer Komma. z.B.: netz1,netz2</i18nString> + <i18nString name="show_create_case_button">Schaltfläche „Fall erstellen“ anzeigen?</i18nString> + <i18nString name="show_more_menu">"Erweiterte Optionen" Taste bei einzelnen Fällen anzeigen</i18nString> + <i18nString name="item_settings">Menüeintrageinstellungen</i18nString> + <i18nString name="view_header">Fallansicht</i18nString> + <i18nString name="use_case_default_headers">Benutzerdefinierte Standardheader verwenden?</i18nString> + <i18nString name="view_configuration_type">Wählen Sie einen Ansichtstyp</i18nString> + <i18nString name="settings">Einstellungen</i18nString> + <i18nString name="associated_view">zugehörige Ansicht</i18nString> + </i18n> + + <transition> + <id>initialize</id> + <x>368</x> + <y>208</y> + <label>initialize [await sync]</label> + <icon>hourglass_empty</icon> + </transition> + <transition> + <id>data_sync</id> + <x>368</x> + <y>328</y> + <label>Data sync</label> + </transition> + <transition> + <id>settings</id> + <x>496</x> + <y>112</y> + <label name="settings">Settings</label> + <icon>settings</icon> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + <view>true</view> + <cancel>true</cancel> + <assign>true</assign> + </logic> + </roleRef> + <dataGroup> + <id>form_title</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>view_header</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <dataGroup> + <id>filter_update</id> + <cols>4</cols> + <layout>grid</layout> + <title name="filter_update_title">Filter + + filter_autocomplete_selection + + editable + + + 0 + 0 + 1 + 3 + + outline + + + + update_filter + + visible + + + 3 + 0 + 1 + 1 + + standard + + + + selected_filter_preview + + visible + + + 0 + 1 + 1 + 4 + + standard + + + + filter_header + + visible + + + 0 + 2 + 1 + 4 + + outline + + + + current_filter_preview + + visible + + + 0 + 3 + 1 + 4 + + standard + + + + + view_dataGroup + 4 + grid + + view_search_type + + editable + required + + + 0 + 0 + 1 + 2 + + outline + + + + show_create_case_button + + editable + required + + + 2 + 0 + 1 + 1 + + outline + + + + show_more_menu + + editable + required + + + 3 + 0 + 1 + 1 + + outline + + + + create_case_button_title + + editable + + + 0 + 1 + 1 + 1 + 0 + + outline + + + + create_case_button_icon + + editable + + + 1 + 1 + 1 + 1 + 0 + + outline + + + + create_case_button_icon_preview + + visible + + + 2 + 1 + 1 + 1 + 0 + + standard + + + + require_title_in_creation + + editable + + + 3 + 1 + 1 + 1 + 0 + + standard + + + + banned_nets_in_creation + + editable + + + 0 + 2 + 1 + 4 + 0 + + outline + + + + + view_headers + 5 + grid + + is_header_mode_changeable + + editable + required + + trans: t.this, + isChangeable: f.is_header_mode_changeable, + mode: f.headers_mode, + defaultMode: f.headers_default_mode; + + make [mode, defaultMode], editable on trans when { isChangeable.value } + make [mode, defaultMode], required on trans when { isChangeable.value } + + make [mode, defaultMode], hidden on trans when { !isChangeable.value } + make [mode, defaultMode], optional on trans when { !isChangeable.value } + + + + 0 + 0 + 1 + 1 + 0 + + outline + + + + allow_header_table_mode + + editable + required + + + 1 + 0 + 1 + 1 + 0 + + outline + + + + headers_mode + + editable + required + + + 2 + 0 + 1 + 2 + 0 + + outline + + + + headers_default_mode + + editable + required + + + 4 + 0 + 1 + 1 + 0 + + outline + + + + use_case_default_headers + + editable + + 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 } + + + + 0 + 1 + 1 + 1 + 0 + + outline + + + + default_headers + + editable + + + 1 + 1 + 1 + 4 + 0 + + outline + + + + + associated_view + 4 + grid + Associated view + + view_configuration_type + + editable + + + 0 + 0 + 1 + 4 + 0 + + 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 + + editable + + + 0 + 1 + 1 + 4 + 0 + + outline + + + + + + initialized + 496 + 208 + + 0 + false + + + uninitialized + 240 + 208 + + 1 + false + + + a1 + read + initialized + settings + 1 + + + a2 + regular + uninitialized + initialize + 1 + + + a3 + regular + initialize + initialized + 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 new file mode 100644 index 00000000000..10c34b150e8 --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_single_task_view_configuration.xml @@ -0,0 +1,316 @@ + + tabbed_single_task_view_configuration + TST + Tabbed single task view configuration + check_box_outline_blank + true + false + false + + system + + true + true + true + + + + admin + + true + true + true + + + + default + + false + false + true + + + + system + System + + + admin + Admin + + + + { + com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField filterAutocomplete, + com.netgrif.application.engine.petrinet.domain.dataset.TaskField previewTaskRef, + com.netgrif.application.engine.petrinet.domain.dataset.CaseField selectedFilterRef, + com.netgrif.application.engine.petrinet.domain.dataset.ButtonField updateBtn, + com.netgrif.application.engine.petrinet.domain.Transition trans + -> + if (filterAutocomplete.getOptions().containsKey(filterAutocomplete.value)) { + change previewTaskRef value { + return [findTask({it.caseId.eq(filterAutocomplete.value).and(it.transitionId.eq("view_filter"))}).stringId] + } + make updateBtn,editable on trans when { true } + } else { + change filterAutocomplete options { + def findAllPredicate = { filterCase -> !selectedFilterRef.value.contains(filterCase.stringId) + && filterCase.dataSet["filter_type"].value == "Case" } + return findFilters(filterAutocomplete.value != null ? filterAutocomplete.value : "") + .findAll(findAllPredicate) + .collectEntries({filterCase -> [filterCase.stringId, filterCase.title]}) + } + change previewTaskRef value { [] } + make updateBtn,visible on trans when { true } + } + } + + + + + selected_filter_preview + + </data> + <data type="taskRef"> + <id>current_filter_preview</id> + <title/> + </data> + <data type="i18n"> + <id>filter_header</id> + <title/> + <init name="filter_header">Current filter</init> + <component> + <name>divider</name> + </component> + </data> + <data type="text"> + <id>new_filter_id</id> + <title/> + </data> + <data type="boolean" immediate="true"> + <id>contains_filter</id> + <title/> + <init>false</init> + </data> + <data type="enumeration_map"> + <id>filter_autocomplete_selection</id> + <title name="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 + + <placeholder name="update_filter">Update view with selected filter</placeholder> + <component> + <name>raised</name> + </component> + <action trigger="set"> + 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 != "Task") { + throw new IllegalArgumentException("Filter is of wrong type. Only filter of Task type allowed.") + } + } + + change contains_filter value { containsFilter } + change filter_case value { [filterAutocomplete.value] } + change filterAutocomplete value { "" } + make update_filter,visible on trans when { true } + </action> + </data> + <data type="caseRef"> + <id>filter_case</id> + <title/> + <action trigger="set"> + 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 } + </action> + <allowedNets> + <allowedNet>filter</allowedNet> + </allowedNets> + </data> + + <!-- VIEW CONFIGURATION DATA --> + <data type="i18n"> + <id>view_header</id> + <title/> + <init name="view_header">Single task view</init> + <component> + <name>divider</name> + </component> + </data> + <data type="text"> + <id>transition_id</id> + <title name="transition_id">Transition id + + + + + + Zvoľte nový filter + Aktualizovať zobrazenie s vybraným filtrom + Filter + Súčasný filter + Vybrať zobrazenie + Nastavenie + Asociované zobrazenie + Zobrazenie jednej úlohy + ID prechodu + + + Neue Filter auswählen + Filter + Aktueller Filter + Aktualisiere die Ansicht mit dem ausgewählten Filter + Wählen Sie einen Ansichtstyp + Einstellungen + zugehörige Ansicht + Einzelaufgabenansicht + Übergangs-ID + + + + initialize + 368 + 208 + + hourglass_empty + + + data_sync + 368 + 328 + + + + settings + 496 + 112 + + settings + + admin + + true + true + true + true + + + + form_title + 4 + grid + + view_header + + visible + + + 0 + 0 + 1 + 4 + + outline + + + + + view_dataGroup + 4 + grid + + transition_id + + editable + + + 0 + 0 + 1 + 4 + + outline + + + + + + initialized + 496 + 208 + + 0 + false + + + uninitialized + 240 + 208 + + 1 + false + + + a1 + read + initialized + settings + 1 + + + a2 + regular + uninitialized + initialize + 1 + + + a3 + regular + initialize + initialized + 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 new file mode 100644 index 00000000000..66caae1268f --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_task_view_configuration.xml @@ -0,0 +1,718 @@ + + tabbed_task_view_configuration + TTV + Tabbed task view configuration + check_box_outline_blank + true + false + false + + system + + true + true + true + + + + admin + + true + true + true + + + + default + + false + false + true + + + + system + System + + + admin + Admin + + + + { + com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField filterAutocomplete, + com.netgrif.application.engine.petrinet.domain.dataset.TaskField previewTaskRef, + com.netgrif.application.engine.petrinet.domain.dataset.CaseField selectedFilterRef, + com.netgrif.application.engine.petrinet.domain.dataset.ButtonField updateBtn, + com.netgrif.application.engine.petrinet.domain.Transition trans + -> + if (filterAutocomplete.getOptions().containsKey(filterAutocomplete.value)) { + change previewTaskRef value { + return [findTask({it.caseId.eq(filterAutocomplete.value).and(it.transitionId.eq("view_filter"))}).stringId] + } + make updateBtn,editable on trans when { true } + } else { + change filterAutocomplete options { + def findAllPredicate = { filterCase -> !selectedFilterRef.value.contains(filterCase.stringId) + && filterCase.dataSet["filter_type"].value == "Task" } + def options = findFilters(filterAutocomplete.value != null ? filterAutocomplete.value : "") + .findAll(findAllPredicate) + .collectEntries({filterCase -> [filterCase.stringId, filterCase.title]}) + return options + } + change previewTaskRef value { [] } + make updateBtn,visible on trans when { true } + } + } + + + { + com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField toBeUpdated, + com.netgrif.application.engine.petrinet.domain.dataset.MultichoiceMapField valueSelector, + com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField optionsHolder + -> + def existingOptions = optionsHolder.options + def selectedValues = valueSelector.value + def newOptions = [:] + + if (selectedValues != null) { + existingOptions.each { key, value -> + if (selectedValues.contains(key)) { + newOptions.put(key, value) + } + } + } + + if (!newOptions.containsKey(toBeUpdated.value)) { + change toBeUpdated value { null } + } + + change toBeUpdated options { newOptions } + } + + + + + selected_filter_preview + + </data> + <data type="taskRef"> + <id>current_filter_preview</id> + <title/> + </data> + <data type="i18n"> + <id>filter_header</id> + <title/> + <init name="filter_header">Current filter</init> + <component> + <name>divider</name> + </component> + </data> + <data type="text"> + <id>new_filter_id</id> + <title/> + </data> + <data type="boolean" immediate="true"> + <id>contains_filter</id> + <title/> + <init>false</init> + </data> + <data type="enumeration_map"> + <id>filter_autocomplete_selection</id> + <title name="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 + + <placeholder name="update_filter">Update view with selected filter</placeholder> + <component> + <name>raised</name> + </component> + <action trigger="set"> + 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 != "Task") { + throw new IllegalArgumentException("Filter is of wrong type. Only filter of Task type allowed.") + } + } + + change contains_filter value { containsFilter } + change filter_case value { [filterAutocomplete.value] } + change filterAutocomplete value { "" } + make update_filter,visible on trans when { true } + </action> + </data> + <data type="caseRef"> + <id>filter_case</id> + <title/> + <action trigger="set"> + trans: t.settings, + filterHeader: f.filter_header, + removeButton: f.remove_filter, + contains_filter: f.contains_filter, + mergeFilters: f.merge_filters, + filterTaskRef: f.current_filter_preview, + filterCaseRef: f.filter_case; + + if (filterCaseRef.value == null || filterCaseRef.value == []) { + make [mergeFilters, removeButton], hidden on trans when { true } + make filterHeader, hidden on trans when { true } + 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] + } + + make [mergeFilters, removeButton], editable on trans when { true } + make filterHeader,visible on trans when { true } + change contains_filter value { true } + </action> + <allowedNets> + <allowedNet>filter</allowedNet> + </allowedNets> + </data> + <data type="button"> + <id>remove_filter</id> + <title/> + <placeholder name="remove_filter">Remove filter</placeholder> + <component> + <name>raised</name> + </component> + <action trigger="set"> + filterCase: f.filter_case; + + change filterCase value { [] } + </action> + </data> + <data type="boolean" immediate="true"> + <id>merge_filters</id> + <title name="merge_filters">Merge with base filter? + true + + + + + view_header + + <init name="view_header">Task view</init> + <component> + <name>divider</name> + </component> + </data> + <data type="enumeration_map" immediate="true"> + <id>view_search_type</id> + <title name="view_search_type">Search type for task view + + + + + + fulltext_advanced + + + headers_mode + Header mode + + + + + sort,edit + + headersMode: f.headers_mode, + defaultMode: f.headers_default_mode, + holder: f.headers_options_holder; + + updateOptionsBasedOnValue(defaultMode, headersMode, holder) + + + headersMode: f.headers_mode, + defaultMode: f.headers_default_mode, + holder: f.headers_options_holder; + + updateOptionsBasedOnValue(defaultMode, headersMode, holder) + + + + headers_default_mode + Default header mode + + + + + sort + + + headers_options_holder + + <options> + <option key="sort" name="sort">Sort</option> + <option key="edit" name="edit">Edit</option> + </options> + </data> + <data type="boolean" immediate="true"> + <id>is_header_mode_changeable</id> + <title name="is_header_mode_changeable">Can header mode be changed? + true + + + allow_header_table_mode + Allow table mode for headers? + true + + + use_default_headers + Use custom default headers? + true + + + default_headers + Set default headers + Example: "meta-title,meta-user" + + defaultHeaders: f.this; + + String trimmed = defaultHeaders.value?.replaceAll("\\s","") + if (defaultHeaders.value != trimmed) { + change defaultHeaders value { trimmed } + } + + + + show_more_menu + Show more menu for task item? + true + + + + + Zvoľte nový filter + Aktualizovať zobrazenie s vybraným filtrom + Skryté + Fulltext + Fulltext a rozšírené + Zoraďovanie + Vyhľadávanie + Upravovanie + Zjednotiť 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 + Napríklad: "meta-title,meta-user" + Zobrazovať menu pre úlohovú položku? + Nastavenie položky + Filter + Súčasný filter + Zobrazenie úloh + Všeobecné + Vymazať filter + + + Neue Filter auswählen + Versteckt + Einfacher Suchmodus + Sortieren + Suchen + Bearbeiten + Kopfzeilenmodus + Standardkopfzeilenmodus + Erlaube Änderung des Kopfzeilenmodus? + Erlaube Tabellenmodus? + Eigene Kopfzeilen verwenden? + Anzuzeigende Attributmenge auswählen + Beispiel: "meta-title,meta-user" + Filter + Aktueller Filter + Allgemein + Aktualisiere die Ansicht mit dem ausgewählten Filter + Einfacher und erweiterter Suchmodus + Mit dem Basisfilter kombinieren? + Suchmodus im Aufgabenansicht + "Erweiterte Optionen" Taste bei einzelnen Aufgaben anzeigen + Menüeintrageinstellungen + Aufgabenansicht + Filter entfernen + + + + initialize + 368 + 208 + + hourglass_empty + + + data_sync + 368 + 328 + + + + settings + 496 + 112 + + settings + + admin + + true + true + true + true + + + + form_title + 4 + grid + + view_header + + visible + + + 0 + 0 + 1 + 4 + + outline + + + + + filter_update + 4 + grid + Filter + + filter_autocomplete_selection + + editable + + + 0 + 0 + 1 + 3 + + outline + + + + update_filter + + visible + + + 3 + 0 + 1 + 1 + + standard + + + + selected_filter_preview + + visible + + + 0 + 1 + 1 + 4 + + standard + + + + + current_additional_filter + 4 + grid + + filter_header + + visible + + + 0 + 0 + 1 + 4 + + outline + + + + current_filter_preview + + visible + + + 0 + 1 + 1 + 4 + + standard + + + + merge_filters + + hidden + + + 0 + 2 + 1 + 1 + + standard + + + + remove_filter + + hidden + + + 1 + 2 + 1 + 1 + + standard + + + + + view_dataGroup + 4 + grid + + view_search_type + + editable + required + + + 0 + 0 + 1 + 3 + + outline + + + + show_more_menu + + editable + required + + + 3 + 0 + 1 + 1 + + outline + + + + + view_headers + 5 + grid + + is_header_mode_changeable + + editable + required + + trans: t.this, + isChangeable: f.is_header_mode_changeable, + mode: f.headers_mode, + defaultMode: f.headers_default_mode; + + make [mode, defaultMode], editable on trans when { isChangeable.value } + make [mode, defaultMode], required on trans when { isChangeable.value } + + make [mode, defaultMode], hidden on trans when { !isChangeable.value } + make [mode, defaultMode], optional on trans when { !isChangeable.value } + + + + 0 + 0 + 1 + 1 + 0 + + outline + + + + allow_header_table_mode + + editable + required + + + 1 + 0 + 1 + 1 + + outline + + + + headers_mode + + editable + required + + + 2 + 0 + 1 + 2 + + outline + + + + headers_default_mode + + editable + required + + + 4 + 0 + 1 + 1 + + outline + + + + use_default_headers + + editable + + 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 } + + + + 0 + 1 + 1 + 1 + 0 + + outline + + + + default_headers + + editable + + + 1 + 1 + 1 + 4 + 0 + + outline + + + + + + initialized + 496 + 208 + + 0 + false + + + uninitialized + 240 + 208 + + 1 + false + + + a1 + read + initialized + settings + 1 + + + a2 + regular + uninitialized + initialize + 1 + + + a3 + regular + initialize + initialized + 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 new file mode 100644 index 00000000000..b68407bc78a --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml @@ -0,0 +1,405 @@ + + tabbed_ticket_view_configuration + TVC + Tabbed ticket view configuration + check_box_outline_blank + true + false + false + + system + + true + true + true + + + + admin + + true + true + true + + + + default + + false + false + true + + + + + view_delete + + + removeViewCase() + + + + + + system + System + + + admin + Admin + + + + { + com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField filterAutocomplete, + com.netgrif.application.engine.petrinet.domain.dataset.TaskField previewTaskRef, + com.netgrif.application.engine.petrinet.domain.dataset.CaseField selectedFilterRef, + com.netgrif.application.engine.petrinet.domain.dataset.ButtonField updateBtn, + com.netgrif.application.engine.petrinet.domain.Transition trans + -> + if (filterAutocomplete.getOptions().containsKey(filterAutocomplete.value)) { + change previewTaskRef value { + return [findTask({it.caseId.eq(filterAutocomplete.value).and(it.transitionId.eq("view_filter"))}).stringId] + } + make updateBtn,editable on trans when { true } + } else { + change filterAutocomplete options { + def findAllPredicate = { filterCase -> !selectedFilterRef.value.contains(filterCase.stringId) + && filterCase.dataSet["filter_type"].value == "Case" } + return findFilters(filterAutocomplete.value != null ? filterAutocomplete.value : "") + .findAll(findAllPredicate) + .collectEntries({filterCase -> [filterCase.stringId, filterCase.title]}) + } + change previewTaskRef value { [] } + make updateBtn,visible on trans when { true } + } + } + + + { -> + + def viewIdAsList = useCase.dataSet['view_configuration_id'].value + if (viewIdAsList == null || viewIdAsList.isEmpty()) { + return + } + + async.run { + workflowService.deleteCase(viewIdAsList[0]) + } + } + + + + + selected_filter_preview + + </data> + <data type="taskRef"> + <id>current_filter_preview</id> + <title/> + </data> + <data type="i18n"> + <id>filter_header</id> + <title/> + <init name="filter_header">Current filter</init> + <component> + <name>divider</name> + </component> + </data> + <data type="text"> + <id>new_filter_id</id> + <title/> + </data> + <data type="boolean" immediate="true"> + <id>contains_filter</id> + <title/> + <init>false</init> + </data> + <data type="enumeration_map"> + <id>filter_autocomplete_selection</id> + <title name="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 + + <placeholder name="update_filter">Update view with selected filter</placeholder> + <component> + <name>raised</name> + </component> + <action trigger="set"> + 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 } + </action> + </data> + <data type="caseRef"> + <id>filter_case</id> + <title/> + <action trigger="set"> + 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 } + </action> + <allowedNets> + <allowedNet>filter</allowedNet> + </allowedNets> + </data> + + <!-- ASSOCIATED VIEW: this section can be removed if needed --> + <data type="enumeration_map" immediate="true"> + <id>view_configuration_type</id> + <title name="view_configuration_type">Pick view type + + menuItemService.getAvailableViewsAsOptions(true, useCase.processIdentifier) + + + view_configuration_type: f.view_configuration_type; + + if (view_configuration_type.options == null || view_configuration_type.options.isEmpty()) { + change view_configuration_type options { menuItemService.getAvailableViewsAsOptions(true, useCase.processIdentifier) } + } + + + + view_configuration_id + + <allowedNets> + <allowedNet>tabbed_single_task_view_configuration</allowedNet> + </allowedNets> + </data> + <data type="taskRef"> + <id>view_configuration_form</id> + <title/> + </data> + <!-- END OF ASSOCIATED VIEW --> + + <!-- VIEW CONFIGURATION DATA --> + <data type="i18n"> + <id>view_header</id> + <title/> + <init name="view_header">Ticket view</init> + <component> + <name>divider</name> + </component> + </data> + <!-- END OF VIEW CONFIGURATION DATA --> + + <!-- I18NS --> + <i18n locale="sk"> + <i18nString name="filter_autocomplete_selection">Zvoľte nový filter</i18nString> + <i18nString name="update_filter">Aktualizovať zobrazenie s vybraným filtrom</i18nString> + <i18nString name="filter_update_title">Filter</i18nString> + <i18nString name="filter_header">Súčasný filter</i18nString> + <i18nString name="view_configuration_type">Vybrať zobrazenie</i18nString> + <i18nString name="settings">Nastavenie</i18nString> + <i18nString name="associated_view">Asociované zobrazenie</i18nString> + <i18nString name="view_header">Tiketové zobrazenie</i18nString> + </i18n> + <i18n locale="de"> + <i18nString name="filter_autocomplete_selection">Neue Filter auswählen</i18nString> + <i18nString name="filter_update_title">Filter</i18nString> + <i18nString name="filter_header">Aktueller Filter</i18nString> + <i18nString name="update_filter">Aktualisiere die Ansicht mit dem ausgewählten Filter</i18nString> + <i18nString name="view_configuration_type">Wählen Sie einen Ansichtstyp</i18nString> + <i18nString name="settings">Einstellungen</i18nString> + <i18nString name="associated_view">zugehörige Ansicht</i18nString> + <i18nString name="view_header">Ticketansicht</i18nString> + </i18n> + + <transition> + <id>initialize</id> + <x>368</x> + <y>208</y> + <label>initialize [await sync]</label> + <icon>hourglass_empty</icon> + </transition> + <transition> + <id>data_sync</id> + <x>368</x> + <y>328</y> + <label>Data sync</label> + </transition> + <transition> + <id>settings</id> + <x>496</x> + <y>112</y> + <label name="settings">Settings</label> + <icon>settings</icon> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + <view>true</view> + <cancel>true</cancel> + <assign>true</assign> + </logic> + </roleRef> + <dataGroup> + <id>form_title</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>view_header</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <dataGroup> + <id>associated_view</id> + <cols>4</cols> + <layout>grid</layout> + <title name="associated_view">Associated view + + view_configuration_type + + editable + + + 0 + 0 + 1 + 4 + 0 + + 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) + change view_configuration_id value { [configurationCase.stringId] } + configurationCase = workflowService.findOne(configurationCase.stringId) + change view_configuration_form value { [configurationCase.tasks.find { it.transition == "settings" }.task] } + + + + + + view_configuration_form + + editable + + + 0 + 1 + 1 + 4 + 0 + + outline + + + + + + initialized + 496 + 208 + + 0 + false + + + uninitialized + 240 + 208 + + 1 + false + + + a1 + read + initialized + settings + 1 + + + a2 + regular + uninitialized + initialize + 1 + + + a3 + regular + initialize + initialized + 1 + + \ No newline at end of file diff --git a/src/main/resources/petriNets/engine-processes/preference_item.xml b/src/main/resources/petriNets/engine-processes/preference_item.xml deleted file mode 100644 index 81deeec6d7f..00000000000 --- a/src/main/resources/petriNets/engine-processes/preference_item.xml +++ /dev/null @@ -1,2665 +0,0 @@ - - preference_item - PRI - Preference Item - check_box_outline_blank - true - false - false - - system - - true - true - true - - - - admin - - true - true - true - - - - default - - false - false - true - - - - - preference_item_delete - - - removeItemChildCases(useCase) - - - - - - system - System - - - admin - Admin - - - { com.netgrif.application.engine.workflow.domain.Case useCase -> - - def childCaseIds = useCase.dataSet['childItemIds'].value - if (childCaseIds == null || childCaseIds.isEmpty()) { - return - } - - removeChildItemFromParent(useCase.dataSet['parentId'].value[0], useCase) - - def childCases = workflowService.findAllById(childCaseIds) - async.run { - childCases.each { - workflowService.deleteCase(it) - } - } - } - - - { - com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField filterAutocomplete, - com.netgrif.application.engine.petrinet.domain.dataset.TaskField previewTaskRef, - com.netgrif.application.engine.petrinet.domain.dataset.CaseField selectedFilterRef, - com.netgrif.application.engine.petrinet.domain.dataset.ButtonField updateBtn, - com.netgrif.application.engine.petrinet.domain.Transition trans, - boolean taskTypeOnly - -> - if (filterAutocomplete.getOptions().containsKey(filterAutocomplete.value)) { - change previewTaskRef value { - return [findTask({it.caseId.eq(filterAutocomplete.value).and(it.transitionId.eq("view_filter"))}).stringId] - } - make updateBtn,editable on trans when { true } - } else { - change filterAutocomplete options { - def findAllPredicate - if (taskTypeOnly) { - findAllPredicate = { filterCase -> - !selectedFilterRef.value.contains(filterCase.stringId) && - filterCase.dataSet["filter_type"].value == "Task" - } - } else { - findAllPredicate = { filterCase -> !selectedFilterRef.value.contains(filterCase.stringId) } - } - return findFilters(filterAutocomplete.value != null ? filterAutocomplete.value : "") - .findAll(findAllPredicate) - .collectEntries({filterCase -> [filterCase.stringId, filterCase.title]}) - } - change previewTaskRef value { [] } - make updateBtn,visible on trans when { true } - } - } - - - { - com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField toBeUpdated, - com.netgrif.application.engine.petrinet.domain.dataset.MultichoiceMapField valueSelector, - com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField optionsHolder - -> - def existingOptions = optionsHolder.options - def selectedValues = valueSelector.value - def newOptions = [:] - - if (selectedValues != null) { - existingOptions.each { key, value -> - if (selectedValues.contains(key)) { - newOptions.put(key, value) - } - } - } - - if (!newOptions.containsKey(toBeUpdated.value)) { - change toBeUpdated value { null } - } - - change toBeUpdated options { newOptions } - } - - - - parentId - - <allowedNets> - <allowedNet>preference_item</allowedNet> - </allowedNets> - </data> - <data type="text"> - <id>move_previous_dest_uri</id> - <title/> - </data> - <data type="multichoice_map"> - <id>move_dest_uri</id> - <title name="move_dest_uri">Destination URI - List of nodes representing destination URI - - autocomplete - - - moveDestUri: f.move_dest_uri; - - String uriNodeId = elasticCaseService.findUriNodeId(useCase) - def node = uriService.findById(uriNodeId) - updateMultichoiceWithCurrentNode(moveDestUri, node) - - - prevDestUri: f.move_previous_dest_uri, - moveDestUri: f.move_dest_uri; - - String newUri = moveDestUri.value.join("/") - newUri = newUri.replace("//","/") - - String corrected = getCorrectedUri(newUri) - - if (corrected == newUri) { - def node = uriService.findByUri(newUri) - change moveDestUri options { findOptionsBasedOnSelectedNode(node) } - } else { - change moveDestUri value { splitUriPath(corrected) } - } - - - - move_dest_uri_new_node - New node to be added - Enter new node name - - - move_add_node - - <placeholder name="move_add_node">Add</placeholder> - <component> - <name>raised</name> - </component> - <action trigger="set"> - newNodeName: f.move_dest_uri_new_node, - selectedUri: f.move_dest_uri; - - if (newNodeName.value == null || newNodeName.value == "") { - return - } - - String prefixUri = selectedUri.value.join("/") - prefixUri = prefixUri.replace("//","/") - - String newUri = prefixUri + uriService.getUriSeparator() + newNodeName.value - def newNode = uriService.getOrCreate(newUri, com.netgrif.application.engine.petrinet.domain.UriContentType.CASE) - - change selectedUri value { splitUriPath(newNode.uriPath) } - - change newNodeName value { null } - </action> - </data> - <data type="i18n"> - <id>duplicate_new_title</id> - <title name="duplicate_new_title">Title of duplicated view - - - duplicate_view_identifier - View identifier - Must be unique - - - childItemIds - - <allowedNets> - <allowedNet>preference_item</allowedNet> - </allowedNets> - </data> - <data type="taskRef"> - <id>childItemForms</id> - <title/> - </data> - <data type="boolean" immediate="true"> - <id>hasChildren</id> - <title/> - </data> - <data type="button"> - <id>duplicate_reset_childItemIds</id> - <title/> - <action trigger="set"> - hasChildren: f.hasChildren, - childItemIds: f.childItemIds; - - change childItemIds value { [] } - change hasChildren value { false } - </action> - </data> - <data type="text" immediate="true"> - <id>menu_item_identifier</id> - <title name="menu_item_identifier">Menu item identifier - - - nodePath - Item URI - - 0 - - - nodePath: f.nodePath, - menu_item_identifier: f.menu_item_identifier; - - change menu_item_identifier value { - def idx = nodePath.value.lastIndexOf(uriService.getUriSeparator()) - return nodePath.value.substring(idx + 1) - } - - - - - - - - menu_icon - Menu icon identifier - Material icon identifier. List of icons with identifiers is available online. - - icon: f.this, - iconPreview: f.menu_icon_preview; - - changeCaseProperty "icon" about { icon.value; } - - if (icon.value == "") { - change iconPreview value {"""]]>} - return; - } - - change iconPreview value { - """]]> + icon.value + """]]> - } - - - - menu_icon_preview - Menu icon preview - - htmltextarea - - - - menu_name_as_visible - Name of the item - Is shown in the menu - - autocomplete - - - - 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; - - changeCaseProperty "title" about { name.value } - change menu_name_as_visible choices { [name.value] } - change menu_name_as_visible value { name.value } - - - - tab_icon - Tab icon identifier - Material icon identifier. List of icons with identifiers is available online. - - icon: f.this, - iconPreview: f.tab_icon_preview; - - if (icon.value == "") { - change iconPreview value {"""]]>} - return; - } - - change iconPreview value { - """]]> + icon.value + """]]> - } - - - - tab_icon_preview - Tab icon preview - - htmltextarea - - - - use_tab_icon - Display tab icon? - true - - - tab_name - Name of the item - Will be shown in tab - - - add_allowed_roles - - <placeholder name="allow_roles">Allow view for roles</placeholder> - <action trigger="set"> - allowedRoles: f.allowed_roles, - processesAvailable: f.processes_available, - rolesAvailable: f.roles_available; - - change allowedRoles options {return configurableMenuService.addSelectedRoles(allowedRoles, processesAvailable, rolesAvailable)} - - change rolesAvailable value {[]} - change rolesAvailable options {[:]} - change processesAvailable value {null} - </action> - </data> - <data type="button"> - <id>remove_allowed_roles</id> - <title/> - <placeholder name="remove_from_allowed_roles">Remove from allowed roles</placeholder> - <action trigger="set"> - allowedRoles: f.allowed_roles, - processesAvailable: f.processes_available, - rolesAvailable: f.roles_available; - - change allowedRoles options {return configurableMenuService.removeSelectedRoles(allowedRoles)} - - change allowedRoles value {[]} - change rolesAvailable value {[]} - change rolesAvailable options {[:]} - change processesAvailable value {null} - </action> - </data> - <data type="button"> - <id>add_banned_roles</id> - <title/> - <placeholder name="ban_roles">Ban view for roles</placeholder> - <action trigger="set"> - bannedRoles: f.banned_roles, - processesAvailable: f.processes_available, - rolesAvailable: f.roles_available; - - change bannedRoles options {return configurableMenuService.addSelectedRoles(bannedRoles, processesAvailable, rolesAvailable)} - - change rolesAvailable value {[]} - change rolesAvailable options {[:]} - change processesAvailable value {null} - </action> - </data> - <data type="button"> - <id>remove_banned_roles</id> - <title/> - <placeholder name="remove_from_banned_roles">Remove from banned roles</placeholder> - <action trigger="set"> - bannedRoles: f.banned_roles, - processesAvailable: f.processes_available, - rolesAvailable: f.roles_available; - - change bannedRoles options { return configurableMenuService.removeSelectedRoles(bannedRoles) } - - change bannedRoles value { [] } - change rolesAvailable value { [] } - change rolesAvailable options { [:] } - change processesAvailable value { null } - </action> - </data> - <data type="enumeration_map" immediate="true"> - <id>processes_available</id> - <title name="available_processes">Your processes - Select a process containing roles you wish to add to allowed or banned roles lists. - - processes: f.this; - - change processes options { return configurableMenuService.getNetsByAuthorAsMapOptions(loggedUser(), org.springframework.context.i18n.LocaleContextHolder.locale) } - - - processes: f.this, - allowedRoles: f.allowed_roles, - bannedRoles: f.banned_roles, - rolesAvailable: f.roles_available; - - if (processes.value != null) { - change rolesAvailable options { return configurableMenuService.getAvailableRolesFromNet(processes, allowedRoles, bannedRoles) } - } else { - change rolesAvailable options { [:] } - } - change rolesAvailable value { [] } - - - - roles_available - Available roles from selected process - - - allowed_roles - Allowed roles - List of roles allowed to view this menu entry. - - [:] - - - - banned_roles - Banned roles - List of roles not allowed to view this menu entry. - - [:] - - - - selected_filter_preview - - </data> - <data type="taskRef"> - <id>current_filter_preview</id> - <title/> - </data> - <data type="i18n"> - <id>filter_header</id> - <title/> - <init name="filter_header">Current filter</init> - <component> - <name>divider</name> - </component> - </data> - <data type="text"> - <id>new_filter_id</id> - <title/> - </data> - <data type="enumeration_map"> - <id>filter_autocomplete_selection</id> - <title name="filter_autocomplete_selection">Select new filter - - autocomplete_dynamic - - - trans: t.item_settings, - useCustomView: f.use_custom_view, - 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, false) - - make update_filter,hidden on trans when { useCustomView.value } - - - trans: t.item_settings, - useCustomView: f.use_custom_view, - 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, false) - - make update_filter,hidden on trans when { useCustomView.value } - - - - update_filter - - <placeholder name="update_filter">Update view with selected filter</placeholder> - <component> - <name>raised</name> - </component> - <action trigger="set"> - trans: t.item_settings, - update_filter: f.update_filter, - filter_case: f.filter_case, - filterAutocomplete: f.filter_autocomplete_selection; - - change filter_case value { [filterAutocomplete.value] } - change filterAutocomplete value { "" } - make update_filter,visible on trans when { true } - </action> - </data> - <data type="caseRef"> - <id>filter_case</id> - <title/> - <action trigger="set"> - additionalFilterCase: f.additional_filter_case, - additionalAutocomplete: f.additional_filter_autocomplete_selection, - additionalUpdate: f.update_additional_filter, - additionalFilterPreview: f.selected_additional_filter_preview, - mergeFilters: f.merge_filters, - filterHeader: f.filter_header, - currentAdditionalFilterPreview: f.current_additional_filter_preview, - taskSettingsTrans: t.task_view_settings, - settingsTrans: t.item_settings, - caseViewHeader: f.case_view_header, - caseViewSettingsTaskRef: f.case_view_settings_taskRef, - taskViewHeader: f.task_view_header, - taskViewSettingsTaskRef: f.task_view_settings_taskRef, - filterTaskRef: f.current_filter_preview, - filterCaseRef: f.filter_case; - - if (filterCaseRef.value == null || filterCaseRef.value == []) { - 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]} - - if (filterCase.dataSet["filter_type"].value == "Case") { - make caseViewHeader,editable on settingsTrans when { true } - make caseViewSettingsTaskRef,editable on settingsTrans when { true } - - make additionalAutocomplete,editable on taskSettingsTrans when { true } - make additionalUpdate,visible on taskSettingsTrans when { true } - make additionalFilterPreview,visible on taskSettingsTrans when { true } - make mergeFilters,visible on taskSettingsTrans when { true } - make filterHeader,visible on taskSettingsTrans when { true } - make currentAdditionalFilterPreview,visible on taskSettingsTrans when { true } - } else { - make caseViewHeader,hidden on settingsTrans when { true } - make caseViewSettingsTaskRef,hidden on settingsTrans when { true } - - make additionalAutocomplete,hidden on taskSettingsTrans when { true } - make additionalUpdate,hidden on taskSettingsTrans when { true } - make additionalFilterPreview,hidden on taskSettingsTrans when { true } - make mergeFilters,hidden on taskSettingsTrans when { true } - make filterHeader,hidden on taskSettingsTrans when { true } - make currentAdditionalFilterPreview,hidden on taskSettingsTrans when { true } - } - make taskViewHeader,editable on settingsTrans when { true } - make taskViewSettingsTaskRef,editable on settingsTrans when { true } - - change additionalFilterCase value { [] } - </action> - <allowedNets> - <allowedNet>filter</allowedNet> - </allowedNets> - </data> - <data type="boolean" immediate="true"> - <id>use_custom_view</id> - <title name="use_custom_view">Use custom view? - false - - - custom_view_selector - Custom view configuration selector - Example: "demo-tabbed-views" - - - - - case_view_search_type - Search type for case view - - - - - - fulltext_advanced - - - create_case_button_title - "New case" button title - - - create_case_button_icon_preview - Icon preview - add]]> - - htmltextarea - - - - create_case_button_icon - "New case" button icon identifier - add - - create_case_button_icon_preview: f.create_case_button_icon_preview, - create_case_button_icon: f.create_case_button_icon; - - - if (create_case_button_icon.value == "") { - change create_case_button_icon_preview value {"""]]>} - return; - } - - change create_case_button_icon_preview value { - """]]> + create_case_button_icon.value + """]]> - } - - - - show_create_case_button - Show create case button? - true - - - case_require_title_in_creation - Require title input in case creation? - true - - - case_banned_nets_in_creation - Banned processes for creation - Write down process identifiers separated by comma. Example: mynet1,mynet2 - - bannedNets: f.this; - - String trimmed = bannedNets.value?.replaceAll("\\s","") - if (bannedNets.value != trimmed) { - change bannedNets value { trimmed } - } - - - - case_view_header - - <init name="case_view_header">Case view</init> - <component> - <name>divider</name> - </component> - </data> - <data type="taskRef"> - <id>case_view_settings_taskRef</id> - <title/> - <init>case_view_settings</init> - </data> - <data type="boolean" immediate="true"> - <id>case_show_more_menu</id> - <title name="case_show_more_menu">Show more menu for case item? - false - - - case_allow_header_table_mode - Allow table mode for headers? - true - - - case_headers_mode - Header mode - - - - - - sort,edit,search - - headersMode: f.case_headers_mode, - defaultMode: f.case_headers_default_mode, - holder: f.case_headers_options_holder; - - updateOptionsBasedOnValue(defaultMode, headersMode, holder) - - - headersMode: f.case_headers_mode, - defaultMode: f.case_headers_default_mode, - holder: f.case_headers_options_holder; - - updateOptionsBasedOnValue(defaultMode, headersMode, holder) - - - - case_headers_default_mode - Default header mode - - - - - - sort - - - case_headers_options_holder - - <options> - <option key="sort" name="sort">Sort</option> - <option key="search" name="search">Search</option> - <option key="edit" name="edit">Edit</option> - </options> - </data> - <data type="boolean" immediate="true"> - <id>case_is_header_mode_changeable</id> - <title name="is_header_mode_changeable">Can header mode be changed? - true - - - use_case_default_headers - Use custom default headers? - true - - - case_default_headers - Set default headers - Example: "meta-title,meta-visualId" - - defaultHeaders: f.this; - - String trimmed = defaultHeaders.value?.replaceAll("\\s","") - if (defaultHeaders.value != trimmed) { - change defaultHeaders value { trimmed } - } - - - - - - selected_additional_filter_preview - - </data> - <data type="taskRef"> - <id>current_additional_filter_preview</id> - <title/> - </data> - <data type="enumeration_map"> - <id>additional_filter_autocomplete_selection</id> - <title name="filter_autocomplete_selection">Select new filter - - autocomplete_dynamic - - - trans: t.task_view_settings, - filterAutocomplete: f.this, - filterCase: f.additional_filter_case, - updateFilter: f.update_additional_filter, - previewTaskRef: f.selected_additional_filter_preview; - - updateFilterAutocompleteOptions(filterAutocomplete, previewTaskRef, filterCase, updateFilter, trans, true) - - - trans: t.task_view_settings, - filterAutocomplete: f.this, - filterCase: f.additional_filter_case, - updateFilter: f.update_additional_filter, - previewTaskRef: f.selected_additional_filter_preview; - - updateFilterAutocompleteOptions(filterAutocomplete, previewTaskRef, filterCase, updateFilter, trans, true) - - - - update_additional_filter - - <placeholder name="update_filter">Update view with selected filter</placeholder> - <component> - <name>raised</name> - </component> - <action trigger="set"> - trans: t.task_view_settings, - updateFilter: f.update_additional_filter, - filterCase: f.additional_filter_case, - filterAutocomplete: f.additional_filter_autocomplete_selection; - - change filterCase value { [filterAutocomplete.value] } - change filterAutocomplete value { "" } - make updateFilter,visible on trans when { true } - </action> - </data> - <data type="button"> - <id>remove_additional_filter</id> - <title/> - <placeholder name="remove_additional_filter">Remove additional filter</placeholder> - <component> - <name>raised</name> - </component> - <action trigger="set"> - filterCase: f.additional_filter_case; - - change filterCase value { [] } - </action> - </data> - <data type="caseRef"> - <id>additional_filter_case</id> - <title/> - <action trigger="set"> - taskViewTrans: t.task_view_settings, - mergeFilters: f.merge_filters, - filterHeader: f.filter_header, - filterTaskRef: f.current_additional_filter_preview, - removeButton: f.remove_additional_filter, - filterCaseRef: f.additional_filter_case; - - if (filterCaseRef.value[0] == null) { - make mergeFilters,hidden on taskViewTrans when { true } - make filterHeader,hidden on taskViewTrans when { true } - make removeButton,hidden on taskViewTrans when { true } - change filterTaskRef value { [] } - 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] } - make mergeFilters,editable on taskViewTrans when { true } - make filterHeader,visible on taskViewTrans when { true } - make removeButton,editable on taskViewTrans when { true } - </action> - <allowedNets> - <allowedNet>filter</allowedNet> - </allowedNets> - </data> - <data type="boolean" immediate="true"> - <id>merge_filters</id> - <title name="merge_filters">Merge with base filter? - true - - - task_view_settings_taskRef - - <init>task_view_settings</init> - </data> - <data type="i18n"> - <id>task_view_header</id> - <title/> - <init name="task_view_header">Task view</init> - <component> - <name>divider</name> - </component> - </data> - <data type="enumeration_map" immediate="true"> - <id>task_view_search_type</id> - <title name="task_view_search_type">Search type for task view - - - - - - fulltext_advanced - - - task_headers_mode - Header mode - - - - - sort,edit - - headersMode: f.task_headers_mode, - defaultMode: f.task_headers_default_mode, - holder: f.task_headers_options_holder; - - updateOptionsBasedOnValue(defaultMode, headersMode, holder) - - - headersMode: f.case_headers_mode, - defaultMode: f.case_headers_default_mode, - holder: f.case_headers_options_holder; - - updateOptionsBasedOnValue(defaultMode, headersMode, holder) - - - - task_headers_default_mode - Default header mode - - - - - sort - - - task_headers_options_holder - - <options> - <option key="sort" name="sort">Sort</option> - <option key="edit" name="edit">Edit</option> - </options> - </data> - <data type="boolean" immediate="true"> - <id>task_is_header_mode_changeable</id> - <title name="is_header_mode_changeable">Can header mode be changed? - true - - - task_allow_header_table_mode - Allow table mode for headers? - true - - - use_task_default_headers - Use custom default headers? - true - - - task_default_headers - Set default headers - Example: "meta-title,meta-user" - - defaultHeaders: f.this; - - String trimmed = defaultHeaders.value?.replaceAll("\\s","") - if (defaultHeaders.value != trimmed) { - change defaultHeaders value { trimmed } - } - - - - task_show_more_menu - Show more menu for task item? - true - - - order_down - - <placeholder>south</placeholder> - <component> - <name>icon</name> - <property key="stretch">true</property> - </component> - <action trigger="set"> - parentId: f.parentId; - - def parentCase = workflowService.findOne(parentId.value[0]) - def taskId = useCase.tasks.find { it.transition == "row_for_ordering" }.task - def taskRefValue = parentCase.dataSet['childItemForms'].value - int taskRefValueSize = taskRefValue.size() - def caseRefValue = parentCase.dataSet['childItemIds'].value - int caseRefValueSize = caseRefValue.size() - - int idxInTaskRef = taskRefValue.indexOf(taskId) - if (idxInTaskRef < taskRefValueSize - 1) { - Collections.swap(taskRefValue, idxInTaskRef, idxInTaskRef + 1) - } - - int idxInCaseRef = caseRefValue.indexOf(useCase.stringId) - if (idxInCaseRef < caseRefValueSize - 1) { - Collections.swap(caseRefValue, idxInCaseRef, idxInCaseRef + 1) - } - - setData("children_order", parentCase, [ - "childItemForms" : [ - "value" : taskRefValue, - "type" : "taskRef" - ], - "childItemIds" : [ - "value" : caseRefValue, - "type" : "caseRef" - ] - ]) - </action> - </data> - <data type="button"> - <id>order_up</id> - <title/> - <placeholder>north</placeholder> - <component> - <name>icon</name> - <property key="stretch">true</property> - </component> - <action trigger="set"> - parentId: f.parentId; - - def parentCase = workflowService.findOne(parentId.value[0]) - def taskId = useCase.tasks.find { it.transition == "row_for_ordering" }.task - def taskRefValue = parentCase.dataSet['childItemForms'].value - def caseRefValue = parentCase.dataSet['childItemIds'].value - - int idxInTaskRef = taskRefValue.indexOf(taskId) - if (idxInTaskRef > 0) { - Collections.swap(taskRefValue, idxInTaskRef - 1, idxInTaskRef) - } else { - return - } - - int idxInCaseRef = caseRefValue.indexOf(useCase.stringId) - if (idxInCaseRef > 0) { - Collections.swap(caseRefValue, idxInCaseRef - 1, idxInCaseRef) - } else { - return - } - - setData("children_order", parentCase, [ - "childItemForms" : [ - "value" : taskRefValue, - "type" : "taskRef" - ], - "childItemIds" : [ - "value" : caseRefValue, - "type" : "caseRef" - ] - ]) - </action> - </data> - - <!-- I18NS --> - <i18n locale="sk"> - <i18nString name="icon_preview">Náhľad ikony</i18nString> - <i18nString name="icon_identifier">Identifikátor ikony</i18nString> - <i18nString name="icon_identifier_desc">Identifikátor Material ikony. Zoznam ikon s identifikátormi je dostupný online.</i18nString> - <i18nString name="allow_roles">Pridaj k povoleným roliam</i18nString> - <i18nString name="remove_from_allowed_roles">Odstráň z povolených rolí</i18nString> - <i18nString name="ban_roles">Pridaj k zakázaným roliam</i18nString> - <i18nString name="remove_from_banned_roles">Odstráň zo zakázaných rolí</i18nString> - <i18nString name="available_processes">Vaše procesy</i18nString> - <i18nString name="available_processes_desc">Vyberte proces obsahujúci roly ktoré chcete pridať do zoznamu povolených alebo zakázaných rolí.</i18nString> - <i18nString name="available_roles">Dostupné roly</i18nString> - <i18nString name="create_case_button_title">Názov tlačidla "Nová inštancia"</i18nString> - <i18nString name="create_case_button_icon">Identifikátor ikony tlačidla "Nová inštancia"</i18nString> - <i18nString name="create_case_button_icon_preview">Náhľad ikony</i18nString> - <i18nString name="default_headers">Predvolené hlavičky</i18nString> - <i18nString name="default_headers_desc">Napríklad: "meta-title,meta-visualId"</i18nString> - <i18nString name="filter_autocomplete_selection">Zvoľte nový filter</i18nString> - <i18nString name="move_dest_uri">Cieľové URI</i18nString> - <i18nString name="duplicate_new_title">Názov duplikovanej položky</i18nString> - <i18nString name="duplicate_view_identifier">Identifikátor duplikovanej položky</i18nString> - <i18nString name="duplicate_view_identifier_desc">Musí byť jedinečný</i18nString> - <i18nString name="name">Názov položky</i18nString> - <i18nString name="name_desc">Bude zobrazený v menu</i18nString> - <i18nString name="tab_icon">Identifikátor ikony v karte</i18nString> - <i18nString name="tab_icon_identifier_desc">Identifikátor Material ikony. Zoznam ikon s identifikátormi je dostupný online.</i18nString> - <i18nString name="display_tab_icon">Zobraziť ikonu v karte?</i18nString> - <i18nString name="tab_name">Názov položky</i18nString> - <i18nString name="tab_name_desc">Bude zobrazený v karte</i18nString> - <i18nString name="update_filter">Aktualizovať zobrazenie s vybraným filtrom</i18nString> - <i18nString name="use_custom_view">Použiť vlastné zobrazenie?</i18nString> - <i18nString name="custom_view_selector">Konfiguračný identifikátor vlastného zobrazenia</i18nString> - <i18nString name="custom_view_selector_desc">Napríklad: "demo-tabbed-views"</i18nString> - <i18nString name="case_view_search_type">Typ vyhľadávania prípadov</i18nString> - <i18nString name="hidden">Skryté</i18nString> - <i18nString name="fulltext">Fulltext</i18nString> - <i18nString name="fulltext_advanced">Fulltext a rozšírené</i18nString> - <i18nString name="case_require_title_in_creation">Vyžadovať názov inštancii pri vytváraní?</i18nString> - <i18nString name="case_banned_nets_in_creation">Zakázané siete pri vytváraní</i18nString> - <i18nString name="case_banned_nets_in_creation_desc">Uveďte identifikátory procesov oddelené čiarkou. Napríklad: mynet1,mynet2</i18nString> - <i18nString name="show_create_case_button">Zobraziť tlačidlo na vytvorenie prípadu?</i18nString> - <i18nString name="case_show_more_menu">Zobrazovať menu pre prípadovú položku?</i18nString> - <i18nString name="sort">Zoraďovanie</i18nString> - <i18nString name="search">Vyhľadávanie</i18nString> - <i18nString name="edit">Upravovanie</i18nString> - <i18nString name="merge_filters">Zjednotiť filter so základným filtrom?</i18nString> - <i18nString name="task_view_search_type">Typ vyhľadávania úloh</i18nString> - <i18nString name="headers_mode">Mód hlavičiek</i18nString> - <i18nString name="headers_default_mode">Predvolený mód hlavičiek</i18nString> - <i18nString name="is_header_mode_changeable">Môže byť mód hlavičiek zmenený?</i18nString> - <i18nString name="allow_header_table_mode">Povoliť tabuľkový mód pre hlavičky?</i18nString> - <i18nString name="use_default_headers">Použiť vlastné predvolené hlavičky?</i18nString> - <i18nString name="task_default_headers">Predvolené hlavičky</i18nString> - <i18nString name="task_default_headers_desc">Napríklad: "meta-title,meta-user"</i18nString> - <i18nString name="task_show_more_menu">Zobrazovať menu pre úlohovú položku?</i18nString> - <i18nString name="item_settings">Nastavenie položky</i18nString> - <i18nString name="roles_management_title">Roly</i18nString> - <i18nString name="filter_update_title">Filter</i18nString> - <i18nString name="move_item">Presunúť položku</i18nString> - <i18nString name="move_item_finish">Presunúť</i18nString> - <i18nString name="duplicate_item">Duplikovať položku</i18nString> - <i18nString name="duplicate_item_finish">Duplikovať</i18nString> - <i18nString name="additional_filter_update">Dodatočný filter</i18nString> - <i18nString name="filter_header">Súčasný filter</i18nString> - <i18nString name="case_view_header">Zobrazenie prípadov</i18nString> - <i18nString name="task_view_header">Zobrazenie úloh</i18nString> - <i18nString name="roles_allowed">Povolené roly</i18nString> - <i18nString name="roles_allowed_desc">Zoznam povolených rolí, ktoré môžu zobraziť túto položku</i18nString> - <i18nString name="roles_banned">Zakázané roly</i18nString> - <i18nString name="roles_banned_desc">Zoznam zakázaných rolí, ktoré nemôžu zobraziť túto položku</i18nString> - <i18nString name="item_settings_general">Všeobecné</i18nString> - <i18nString name="menu_item_identifier">Identifikátor položky</i18nString> - <i18nString name="remove_additional_filter">Odstrániť dodatočný filter</i18nString> - <i18nString name="nodePath">URI položky</i18nString> - <i18nString name="move_dest_uri_new_node">Nový uzol</i18nString> - <i18nString name="move_dest_uri_new_node_desc">Uveďte názov uzlu, ktorý chcete pridať</i18nString> - <i18nString name="move_dest_uri_desc">Zoznam uzlov reprezentujúce cieľovú URI</i18nString> - <i18nString name="move_add_node">Pridať</i18nString> - </i18n> - <i18n locale="de"> - <i18nString name="icon_preview">Ikonevorschau</i18nString> - <i18nString name="icon_identifier">Ikone ID</i18nString> - <i18nString name="icon_identifier_desc">Material Ikone ID. Liste den Ikonen mit IDs ist online verfügbar.</i18nString> - <i18nString name="allow_roles">Zu zulässigen Rollen hinzufügen</i18nString> - <i18nString name="remove_from_allowed_roles">Aus zulässigen Rollen entfernen</i18nString> - <i18nString name="ban_roles">Zu verbotenen Rollen hinzufügen</i18nString> - <i18nString name="remove_from_banned_roles">Aus verbotenen Rollen entfernen</i18nString> - <i18nString name="available_processes">Ihre Prozesse</i18nString> - <i18nString name="available_processes_desc">Wählen Sie einen Prozess mit Rollen aus, die Sie zu Listen mit zulässigen oder verbotenen Rollen hinzufügen möchten.</i18nString> - <i18nString name="available_roles">Verfügbare Rollen</i18nString> - <i18nString name="create_case_button_title">Schaltflächentitel "Neuer Fall"</i18nString> - <i18nString name="create_case_button_icon">Ikone ID</i18nString> - <i18nString name="create_case_button_icon_preview">Ikonevorschau</i18nString> - <i18nString name="default_headers">Anzuzeigende Attributmenge auswählen</i18nString> - <i18nString name="filter_autocomplete_selection">Neue Filter auswählen</i18nString> - <i18nString name="default_headers_desc">Beispiel: "meta-title,meta-visualId"</i18nString> - <i18nString name="tab_icon_identifier_desc">Material Ikone ID. Liste den Ikonen mit IDs ist online verfügbar.</i18nString> - <i18nString name="custom_view_selector_desc">Beispiel: "demo-tabbed-views"</i18nString> - <i18nString name="hidden">Versteckt</i18nString> - <i18nString name="fulltext">Einfacher Suchmodus</i18nString> - <i18nString name="sort">Sortieren</i18nString> - <i18nString name="search">Suchen</i18nString> - <i18nString name="edit">Bearbeiten</i18nString> - <i18nString name="headers_mode">Kopfzeilenmodus</i18nString> - <i18nString name="headers_default_mode">Standardkopfzeilenmodus</i18nString> - <i18nString name="is_header_mode_changeable">Erlaube Änderung des Kopfzeilenmodus?</i18nString> - <i18nString name="allow_header_table_mode">Erlaube Tabellenmodus?</i18nString> - <i18nString name="use_default_headers">Eigene Kopfzeilen verwenden?</i18nString> - <i18nString name="task_default_headers">Anzuzeigende Attributmenge auswählen</i18nString> - <i18nString name="task_default_headers_desc">Beispiel: "meta-title,meta-user"</i18nString> - <i18nString name="roles_management_title">Rollen</i18nString> - <i18nString name="filter_update_title">Filter</i18nString> - <i18nString name="additional_filter_update">Zusätzlicher Filter</i18nString> - <i18nString name="filter_header">Aktueller Filter</i18nString> - <i18nString name="roles_allowed">Zulässige Rollen</i18nString> - <i18nString name="item_settings_general">Allgemein</i18nString> - <i18nString name="menu_item_identifier">Identifikationsnummer des Menüeintrages</i18nString> - <i18nString name="remove_additional_filter">Zusatzfilter entfernen</i18nString> - <i18nString name="nodePath">Menüeintrag-URI</i18nString> - <i18nString name="move_dest_uri_new_node">Neuer Knoten</i18nString> - <i18nString name="move_add_node">Hinzufügen</i18nString> - <i18nString name="move_dest_uri">Ziel URI</i18nString> - <i18nString name="duplicate_new_title">Titel der kopierten Ansicht</i18nString> - <i18nString name="duplicate_view_identifier">Identifikator der kopierten Ansicht</i18nString> - <i18nString name="duplicate_view_identifier_desc">Muss einzigartig sein</i18nString> - <i18nString name="name">Titel des Eintrages</i18nString> - <i18nString name="name_desc">Wird im Menü angezeigt</i18nString> - <i18nString name="tab_icon">Ikonen Identifikator der Registerkarte</i18nString> - <i18nString name="display_tab_icon">Zeige die Registerkarte Ikone an?</i18nString> - <i18nString name="tab_name">Titel der Registerkarte</i18nString> - <i18nString name="tab_name_desc">Wird in der Registerkarte angezeigt</i18nString> - <i18nString name="update_filter">Aktualisiere die Ansicht mit dem ausgewählten Filter</i18nString> - <i18nString name="use_custom_view">Eigener Ansicht anwenden?</i18nString> - <i18nString name="custom_view_selector">Konfigurationsidentifikator der eigenen Ansicht</i18nString> - <i18nString name="case_view_search_type">Suchmodus im Fallansicht</i18nString> - <i18nString name="fulltext_advanced">Einfacher und erweiterter Suchmodus</i18nString> - <i18nString name="case_require_title_in_creation">Erforde den Titel beim erzeugen von Fällen?</i18nString> - <i18nString name="case_banned_nets_in_creation">Ausgeschlossene Prozesse</i18nString> - <i18nString name="case_banned_nets_in_creation_desc">Trenne die Prozessidentifikatoren mit einer Komma. z.B.: netz1,netz2</i18nString> - <i18nString name="show_create_case_button">Schaltfläche „Fall erstellen“ anzeigen?</i18nString> - <i18nString name="case_show_more_menu">"Erweiterte Optionen" Taste bei einzelnen Fällen anzeigen</i18nString> - <i18nString name="merge_filters">Mit dem Basisfilter kombinieren?</i18nString> - <i18nString name="task_view_search_type">Suchmodus im Aufgabenansicht</i18nString> - <i18nString name="task_show_more_menu">"Erweiterte Optionen" Taste bei einzelnen Aufgaben anzeigen</i18nString> - <i18nString name="item_settings">Menüeintrageinstellungen</i18nString> - <i18nString name="move_item">Menüeintrag verschieben</i18nString> - <i18nString name="move_item_finish">verschieben</i18nString> - <i18nString name="duplicate_item">Menüeintrag duplizieren</i18nString> - <i18nString name="duplicate_item_finish">duplizieren</i18nString> - <i18nString name="case_view_header">Fallansicht</i18nString> - <i18nString name="task_view_header">Aufgabenansicht</i18nString> - <i18nString name="roles_allowed_desc">Rollen mit Zugriff auf diesen Menüeintrag</i18nString> - <i18nString name="roles_banned">Verbotene Rollen</i18nString> - <i18nString name="roles_banned_desc">Rollen, für die wird den Menüeintrag ausgeblendet</i18nString> - <i18nString name="move_dest_uri_new_node_desc">Nächste URI-Teil angeben</i18nString> - <i18nString name="move_dest_uri_desc">Teile der Ziel URI</i18nString> - </i18n> - - <!-- TRANSITIONS --> - <transition> - <id>initialize</id> - <x>340</x> - <y>220</y> - <label>initialize [await sync]</label> - <icon>hourglass_empty</icon> - <roleRef> - <id>admin</id> - <logic> - <perform>true</perform> - <view>true</view> - <cancel>true</cancel> - <assign>true</assign> - </logic> - </roleRef> - <dataGroup> - <id>view</id> - <dataRef> - <id>filter_case</id> - <logic> - <behavior>forbidden</behavior> - <action trigger="set"> - filterCaseRef: f.filter_case, - menu_name: f.menu_name; - - if (filterCaseRef.value == null || filterCaseRef.value == []) { - return - } - - def filterCase = findCase({it._id.eq(filterCaseRef.value[0])}) - if (!menu_name.value) { - change menu_name value {return filterCase.dataSet["i18n_filter_name"].value} - } - </action> - </logic> - </dataRef> - </dataGroup> - </transition> - - <transition> - <id>item_settings</id> - <x>460</x> - <y>100</y> - <label name="item_settings">Item settings</label> - <icon>settings</icon> - <assignPolicy>auto</assignPolicy> - <roleRef> - <id>admin</id> - <logic> - <perform>true</perform> - <view>true</view> - <cancel>true</cancel> - <assign>true</assign> - </logic> - </roleRef> - <dataGroup> - <id>pre_general</id> - <cols>4</cols> - <layout>grid</layout> - <dataRef> - <id>menu_item_identifier</id> - <logic> - <behavior>visible</behavior> - </logic> - <layout> - <x>0</x> - <y>0</y> - <rows>1</rows> - <cols>2</cols> - <template>material</template> - <appearance>outline</appearance> - </layout> - </dataRef> - <dataRef> - <id>nodePath</id> - <logic> - <behavior>visible</behavior> - </logic> - <layout> - <x>2</x> - <y>0</y> - <rows>1</rows> - <cols>2</cols> - <template>material</template> - <appearance>outline</appearance> - </layout> - </dataRef> - </dataGroup> - <dataGroup> - <id>general_0</id> - <cols>4</cols> - <layout>grid</layout> - <title name="item_settings_general">General - - menu_name - - editable - - - 0 - 0 - 1 - 2 - - outline - - - - menu_icon - - editable - - - 2 - 0 - 1 - 1 - - outline - - - - menu_icon_preview - - visible - - - 3 - 0 - 1 - 1 - - standard - - - - tab_name - - editable - - - 0 - 1 - 1 - 1 - - outline - - - - use_tab_icon - - editable - - - 1 - 1 - 1 - 1 - 0 - - - - 0 - - - trans: t.this, - iconPreview: f.tab_icon_preview, - icon: f.tab_icon, - useIcon: f.use_tab_icon; - - make iconPreview,visible on trans when { useIcon.value } - make icon,editable on trans when { useIcon.value } - - make iconPreview,hidden on trans when { !useIcon.value } - make icon,hidden on trans when { !useIcon.value } - - - - - - tab_icon - - editable - - - 2 - 1 - 1 - 1 - - outline - - - - tab_icon_preview - - visible - - - 3 - 1 - 1 - 1 - - standard - - - - use_custom_view - - editable - - - 0 - 2 - 1 - 1 - - outline - - - 0 - - - trans: t.this, - caseHeader: f.case_view_header, - caseTaskRef: f.case_view_settings_taskRef, - taskHeader: f.task_view_header, - taskTaskRef: f.task_view_settings_taskRef, - - useTabIcon: f.use_tab_icon, - tabIconPreview: f.tab_icon_preview, - tabName: f.tab_name, - tabIcon: f.tab_icon, - filterSelection: f.filter_autocomplete_selection, - updateFilter: f.update_filter, - selectedFilterPreview: f.selected_filter_preview, - currentFilterHeader: f.filter_header, - currentFilterPreview: f.current_filter_preview, - - use: f.use_custom_view, - selector: f.custom_view_selector; - - make selector,editable on trans when { use.value } - make selector,visible on trans when { !use.value } - - make caseHeader,visible on trans when { !use.value } - make caseHeader,hidden on trans when { use.value } - make caseTaskRef,editable on trans when { !use.value } - make caseTaskRef,hidden on trans when { use.value } - - make taskHeader,visible on trans when { !use.value } - make taskHeader,hidden on trans when { use.value } - make taskTaskRef,editable on trans when { !use.value } - make taskTaskRef,hidden on trans when { use.value } - - make useTabIcon,editable on trans when { !use.value } - make useTabIcon,hidden on trans when { use.value } - make tabIconPreview,visible on trans when { !use.value } - make tabIconPreview,hidden on trans when { use.value } - make tabName,editable on trans when { !use.value } - make tabName,hidden on trans when { use.value } - make tabIcon,editable on trans when { !use.value } - make tabIcon,hidden on trans when { use.value } - make filterSelection,editable on trans when { !use.value } - make filterSelection,hidden on trans when { use.value } - make updateFilter,visible on trans when { !use.value } - make updateFilter,hidden on trans when { use.value } - make selectedFilterPreview,visible on trans when { !use.value } - make selectedFilterPreview,hidden on trans when { use.value } - make currentFilterHeader,visible on trans when { !use.value } - make currentFilterHeader,hidden on trans when { use.value } - make currentFilterPreview,visible on trans when { !use.value } - make currentFilterPreview,hidden on trans when { use.value } - - - - - - custom_view_selector - - visible - - - 1 - 2 - 1 - 3 - - outline - - - - - roles_management - 5 - grid - Roles - - processes_available - - editable - - - 0 - 0 - 2 - 1 - 0 - - outline - - - - roles_available - - editable - - - 1 - 0 - 2 - 1 - 0 - - outline - - - - add_allowed_roles - - editable - - - 2 - 0 - 1 - 1 - 0 - - - - - allowed_roles - - editable - - - 3 - 0 - 1 - 1 - 0 - - outline - - - - remove_allowed_roles - - editable - - - 4 - 0 - 1 - 1 - 0 - - - - - add_banned_roles - - editable - - - 2 - 1 - 1 - 1 - 0 - - - - - banned_roles - - editable - - - 3 - 1 - 1 - 1 - 0 - - outline - - - - remove_banned_roles - - editable - - - 4 - 1 - 1 - 1 - 0 - - - - - - filter_update - 4 - grid - Filter - - filter_autocomplete_selection - - editable - - - 0 - 0 - 1 - 3 - - outline - - - - update_filter - - visible - - - 3 - 0 - 1 - 1 - - standard - - - - selected_filter_preview - - visible - - - 0 - 1 - 1 - 4 - - standard - - - - - current_filter - 4 - grid - - filter_header - - visible - - - 0 - 0 - 1 - 4 - - outline - - - - current_filter_preview - - visible - - - 0 - 1 - 1 - 4 - - standard - - - - - case_view_settings_dataGroup - 4 - grid - - case_view_header - - hidden - - - 0 - 0 - 1 - 4 - - outline - - - - case_view_settings_taskRef - - hidden - - - 0 - 1 - 1 - 4 - - outline - - - - - task_view_settings_dataGroup - 4 - grid - - task_view_header - - hidden - - - 0 - 0 - 1 - 4 - - outline - - - - task_view_settings_taskRef - - hidden - - - 0 - 1 - 1 - 4 - - outline - - - - - - - move_item - 580 - 100 - - move_down - auto - - admin - - true - true - true - true - - - - move - 4 - grid - - move_dest_uri - - editable - required - - - 0 - 0 - 1 - 2 - - outline - - - - move_dest_uri_new_node - - editable - - - 2 - 0 - 1 - 1 - - outline - - - - move_add_node - - editable - - - 3 - 0 - 1 - 1 - - 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 - true - true - true - - - - duplicate - 4 - grid - - duplicate_new_title - - editable - required - - - 0 - 0 - 1 - 4 - - outline - - - - duplicate_view_identifier - - editable - required - - - 0 - 1 - 1 - 4 - - outline - - - - - finish - - - identifier: f.duplicate_view_identifier, - title: f.duplicate_new_title; - - duplicateMenuItem(useCase, title.value, identifier.value) - - - Duplicate - - - - - change_filter - 460 - 340 - - - system - - true - - - - new_filter_id - - editable - required - - - set_event_0 - - - new_filter_id: f.new_filter_id, - filterTaskRef: f.current_filter_preview, - filterCaseRef: f.filter_case; - - change filterCaseRef value { [new_filter_id.value] } - 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]} - - - - - - - - case_view_settings - 340 - 100 - - - system - - true - - - - case_view_dataGroup - 4 - grid - - case_view_search_type - - editable - required - - - 0 - 0 - 1 - 2 - - outline - - - - show_create_case_button - - editable - required - - - 2 - 0 - 1 - 1 - - outline - - - - case_show_more_menu - - editable - required - - - 3 - 0 - 1 - 1 - - outline - - - - create_case_button_title - - editable - - - 0 - 1 - 1 - 1 - 0 - - outline - - - - create_case_button_icon - - editable - - - 1 - 1 - 1 - 1 - 0 - - outline - - - - create_case_button_icon_preview - - visible - - - 2 - 1 - 1 - 1 - 0 - - standard - - - - case_require_title_in_creation - - editable - - - 3 - 1 - 1 - 1 - 0 - - standard - - - - case_banned_nets_in_creation - - editable - - - 0 - 2 - 1 - 4 - 0 - - outline - - - - - case_view_headers - 5 - grid - - case_is_header_mode_changeable - - editable - required - - trans: t.this, - isChangeable: f.case_is_header_mode_changeable, - mode: f.case_headers_mode, - defaultMode: f.case_headers_default_mode; - - make mode,editable on trans when { isChangeable.value } - make mode,required on trans when { isChangeable.value } - make defaultMode,editable on trans when { isChangeable.value } - make defaultMode,required on trans when { isChangeable.value } - - make mode,hidden on trans when { !isChangeable.value } - make mode,optional on trans when { !isChangeable.value } - make defaultMode,hidden on trans when { !isChangeable.value } - make defaultMode,optional on trans when { !isChangeable.value } - - - - 0 - 0 - 1 - 1 - 0 - - outline - - - - case_allow_header_table_mode - - editable - required - - - 1 - 0 - 1 - 1 - 0 - - outline - - - - case_headers_mode - - editable - required - - - 2 - 0 - 1 - 2 - 0 - - outline - - - - case_headers_default_mode - - editable - required - - - 4 - 0 - 1 - 1 - 0 - - outline - - - - use_case_default_headers - - editable - - trans: t.this, - 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 } - - - - 0 - 1 - 1 - 1 - 0 - - outline - - - - case_default_headers - - editable - - - 1 - 1 - 1 - 4 - 0 - - outline - - - - - - - task_view_settings - 340 - 340 - - - system - - true - - - - task_view_dataGroup - 4 - grid - - task_view_search_type - - editable - required - - - 0 - 0 - 1 - 3 - - outline - - - - task_show_more_menu - - editable - required - - - 3 - 0 - 1 - 1 - - outline - - - - - task_view_headers - 5 - grid - - task_is_header_mode_changeable - - editable - required - - trans: t.this, - isChangeable: f.task_is_header_mode_changeable, - mode: f.task_headers_mode, - defaultMode: f.task_headers_default_mode; - - make mode,editable on trans when { isChangeable.value } - make mode,required on trans when { isChangeable.value } - make defaultMode,editable on trans when { isChangeable.value } - make defaultMode,required on trans when { isChangeable.value } - - make mode,hidden on trans when { !isChangeable.value } - make mode,optional on trans when { !isChangeable.value } - make defaultMode,hidden on trans when { !isChangeable.value } - make defaultMode,optional on trans when { !isChangeable.value } - - - - 0 - 0 - 1 - 1 - 0 - - outline - - - - task_allow_header_table_mode - - editable - required - - - 1 - 0 - 1 - 1 - - outline - - - - task_headers_mode - - editable - required - - - 2 - 0 - 1 - 2 - - outline - - - - task_headers_default_mode - - editable - required - - - 4 - 0 - 1 - 1 - - outline - - - - use_task_default_headers - - editable - - trans: t.this, - 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 } - - - - 0 - 1 - 1 - 1 - 0 - - outline - - - - task_default_headers - - editable - - - 1 - 1 - 1 - 4 - 0 - - outline - - - - - additional_filter_update - 4 - grid - Additional filter - - additional_filter_autocomplete_selection - - editable - - - 0 - 0 - 1 - 3 - - outline - - - - update_additional_filter - - editable - - - 3 - 0 - 1 - 1 - - standard - - - - selected_additional_filter_preview - - visible - - - 0 - 1 - 1 - 4 - - standard - - - - - current_additional_filter - 4 - grid - - filter_header - - hidden - - - 0 - 0 - 1 - 4 - - outline - - - - current_additional_filter_preview - - visible - - - 0 - 1 - 1 - 4 - - standard - - - - merge_filters - - hidden - - - 0 - 2 - 1 - 1 - - standard - - - - remove_additional_filter - - hidden - - - 1 - 2 - 1 - 1 - - standard - - - - - - children_order - 580 - 220 - - low_priority - auto - - admin - - true - true - true - 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 - - outline - - - - - - row_for_ordering - 741 - 219 - - - system - - true - true - true - true - - - - row_for_ordering_0 - 6 - grid - - menu_item_identifier - - visible - - - 0 - 0 - 1 - 2 - - outline - - - - menu_name_as_visible - - visible - - - 2 - 0 - 1 - 2 - - outline - - - - order_down - - editable - - - 4 - 0 - 1 - 1 - 1 - - outline - - - - order_up - - editable - - - 5 - 0 - 1 - 1 - 1 - - outline - - - - - finish - - - - delegate - - - - - - - uninitialized - 220 - 220 - - 1 - false - - - initialized - 460 - 220 - - 0 - false - - - - - a1 - regular - uninitialized - initialize - 1 - - - a7 - read - initialized - item_settings - 1 - - - a8 - regular - initialize - initialized - 1 - - - a9 - read - initialized - move_item - 1 - - - a10 - read - initialized - duplicate_item - 1 - - - a12 - read - initialized - change_filter - 1 - - - a13 - read - initialized - children_order - 1 - - \ No newline at end of file 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 1422efaebf9..93e15745f2c 100644 --- a/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy @@ -1,10 +1,14 @@ package com.netgrif.application.engine.action - import com.netgrif.application.engine.TestHelper 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.MenuItemConstants + +import com.netgrif.application.engine.menu.domain.configurations.TabbedCaseViewConstants +import com.netgrif.application.engine.menu.domain.configurations.TabbedTaskViewConstants +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 import com.netgrif.application.engine.petrinet.domain.UriContentType @@ -12,14 +16,13 @@ import com.netgrif.application.engine.petrinet.domain.UriNode import com.netgrif.application.engine.petrinet.service.interfaces.IUriService import com.netgrif.application.engine.startup.FilterRunner import com.netgrif.application.engine.startup.ImportHelper +import com.netgrif.application.engine.startup.MenuItemViewRegistryRunner import com.netgrif.application.engine.workflow.domain.Case import com.netgrif.application.engine.workflow.domain.QCase import com.netgrif.application.engine.workflow.service.interfaces.IDataService import com.netgrif.application.engine.workflow.service.interfaces.ITaskService import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService -import com.netgrif.application.engine.workflow.domain.menu.MenuItemConstants import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired @@ -32,7 +35,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension import static org.junit.jupiter.api.Assertions.assertThrows -@Disabled @SpringBootTest @ActiveProfiles(["test"]) @ExtendWith(SpringExtension.class) @@ -82,44 +84,64 @@ class MenuItemApiTest { Thread.sleep(4000) UriNode leafNode = uriService.findByUri("/netgrif/test/new_menu_item") + assert leafNode != null assert item.uriNodeId == uriService.findByUri("/netgrif/test").stringId - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_ICON.attributeId].value == "device_hub" - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_NAME.attributeId].value == new I18nString("FILTER") - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_IDENTIFIER.attributeId].value.toString() == "new_menu_item" - assert (item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_FILTER_CASE.attributeId].value as List)[0] == filter.stringId - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_ALLOWED_ROLES.attributeId].options.containsKey("role_1:filter_api_test") - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_BANNED_ROLES.attributeId].options.containsKey("role_2:filter_api_test") - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_DEFAULT_HEADERS.attributeId].value == "meta-title,meta-title" - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_DEFAULT_HEADERS.attributeId].value == "meta-title,meta-title" + assert item.dataSet[MenuItemConstants.FIELD_MENU_ICON].value == "device_hub" + assert item.dataSet[MenuItemConstants.FIELD_MENU_NAME].value == new I18nString("FILTER") + assert item.dataSet[MenuItemConstants.FIELD_IDENTIFIER].value.toString() == "new_menu_item" + 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 == MenuItemViewRegistryRunner.TABBED_CASE_VIEW_ID assert filter.dataSet["filter"].filterMetadata["filterType"] == "Case" - assert filter.dataSet["filter"].allowedNets == ["filter", "preference_item"] - assert filter.dataSet["filter"].value == "processIdentifier:filter OR processIdentifier:preference_item" + assert filter.dataSet["filter"].allowedNets == ["filter", "menu_item"] + assert filter.dataSet["filter"].value == "processIdentifier:filter OR processIdentifier:menu_item" assert filter.dataSet["filter_type"].value == "Case" - assert leafNode != null - Case testFolder = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue.keyword:\"/netgrif/test\"", PageRequest.of(0, 1))[0] - Case netgrifFolder = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue.keyword:\"/netgrif\"", PageRequest.of(0, 1))[0] + 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 == MenuItemViewRegistryRunner.TABBED_TASK_VIEW_ID + + 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" + + 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] UriNode testNode = uriService.findByUri("/netgrif") UriNode netgrifNode = uriService.getRoot() - Case rootFolder = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue.keyword:\"/\"", PageRequest.of(0, 1))[0] + Case rootFolder = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue.keyword:\"/\"", PageRequest.of(0, 1))[0] assert testFolder != null && testNode != null assert testFolder.uriNodeId == testNode.stringId - assert testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value == [netgrifFolder.stringId] - assert (testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList).contains(item.stringId) - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value == [testFolder.stringId] + assert testFolder.dataSet[MenuItemConstants.FIELD_PARENT_ID].value == [netgrifFolder.stringId] + assert (testFolder.dataSet[MenuItemConstants.FIELD_CHILD_ITEM_IDS].value as ArrayList).contains(item.stringId) + assert item.dataSet[MenuItemConstants.FIELD_PARENT_ID].value == [testFolder.stringId] assert netgrifFolder.uriNodeId == netgrifNode.stringId - assert netgrifFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value == [rootFolder.stringId] - assert (netgrifFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList).contains(testFolder.stringId) - assert rootFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value == [] - assert (rootFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList).contains(netgrifFolder.stringId) + assert netgrifFolder.dataSet[MenuItemConstants.FIELD_PARENT_ID].value == [rootFolder.stringId] + assert (netgrifFolder.dataSet[MenuItemConstants.FIELD_CHILD_ITEM_IDS].value as ArrayList).contains(testFolder.stringId) + assert rootFolder.dataSet[MenuItemConstants.FIELD_PARENT_ID].value == [] + assert (rootFolder.dataSet[MenuItemConstants.FIELD_CHILD_ITEM_IDS].value as ArrayList).contains(netgrifFolder.stringId) } @Test void testChangeFilterAndMenuItems() { Case caze = createMenuItem() Thread.sleep(3000) + + 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) + def newUri = uriService.getOrCreate("/netgrif/test_new", UriContentType.DEFAULT) caze = setData(caze, [ "uri": newUri.uriPath, @@ -130,18 +152,33 @@ class MenuItemApiTest { "icon": "", "change_filter_and_menu": "0" ]) - Case item = getMenuItem(caze) + item = getMenuItem(caze) Case filter = getFilter(caze) - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_NAME.attributeId].value.toString() == "CHANGED FILTER" - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_ALLOWED_ROLES.attributeId].options.entrySet()[0].key.contains("role_2") - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_DEFAULT_HEADERS.attributeId].value == "meta-title,meta-title,meta-title" - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_DEFAULT_HEADERS.attributeId].value == "meta-title,meta-title,meta-title" + 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 == MenuItemViewRegistryRunner.TABBED_CASE_VIEW_ID assert item.uriNodeId == newUri.stringId assert filter.dataSet["filter"].allowedNets == ["filter"] assert filter.dataSet["filter"].filterMetadata["defaultSearchCategories"] == false assert filter.dataSet["filter"].value == "processIdentifier:filter" + + String tabbedCaseViewId = MenuItemUtils.getCaseIdFromCaseRef(item, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID) + assert tabbedCaseViewId != null && tabbedCaseViewId == 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 == MenuItemViewRegistryRunner.TABBED_TASK_VIEW_ID + + String tabbedTaskViewId = MenuItemUtils.getCaseIdFromCaseRef(tabbedCaseView, TabbedCaseViewConstants.FIELD_VIEW_CONFIGURATION_ID) + assert tabbedTaskViewId != null && tabbedTaskViewId == 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" } @Test @@ -163,7 +200,6 @@ class MenuItemApiTest { apiCase = createMenuItem("/netgrif2/test2", "new_menu_item2") String viewId2 = apiCase.dataSet["menu_stringId"].value - // move view Thread.sleep(2000) apiCase = setData(apiCase, [ @@ -177,13 +213,12 @@ class MenuItemApiTest { Thread.sleep(2000) UriNode node = uriService.findByUri("/netgrif2") - Case folderCase = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue:\"/netgrif2\"", PageRequest.of(0, 1))[0] + Case folderCase = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_IDENTIFIER AND dataSet.${MenuItemConstants.FIELD_NODE_PATH}.textValue:\"/netgrif2\"", PageRequest.of(0, 1))[0] assert viewCase.uriNodeId == node.stringId - ArrayList childIds = folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList + ArrayList childIds = folderCase.dataSet[MenuItemConstants.FIELD_CHILD_ITEM_IDS].value as ArrayList assert childIds.contains(viewId) && childIds.size() == 2 - // cyclic move assertThrows(IllegalArgumentException.class, () -> { setData(apiCase, [ @@ -194,7 +229,6 @@ class MenuItemApiTest { ]) }) - // move folder setData(apiCase, [ "move_dest_uri": "/netgrif/test3", @@ -204,23 +238,23 @@ class MenuItemApiTest { ]) Thread.sleep(2000) - folderCase = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue:\"/netgrif/test3\"", PageRequest.of(0, 1))[0] - Case folderCase2 = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue:\"/netgrif\"", PageRequest.of(0, 1))[0] - assert folderCase != null && folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value == [folderCase2.stringId] + 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] + assert folderCase != null && folderCase.dataSet[MenuItemConstants.FIELD_PARENT_ID].value == [folderCase2.stringId] - folderCase = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue:\"/netgrif/test3/netgrif2\"", PageRequest.of(0, 1))[0] + folderCase = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_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 assert folderCase.uriNodeId == node.stringId - assert folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId].value == "/netgrif/test3/netgrif2" + assert folderCase.dataSet[MenuItemConstants.FIELD_NODE_PATH].value == "/netgrif/test3/netgrif2" - childIds = folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList + childIds = folderCase.dataSet[MenuItemConstants.FIELD_CHILD_ITEM_IDS].value as ArrayList assert childIds.size() == 2 folderCase = workflowService.findOne(childIds[0]) node = uriService.findByUri("/netgrif/test3/netgrif2") - assert folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId].value == "/netgrif/test3/netgrif2/test2" + assert folderCase.dataSet[MenuItemConstants.FIELD_NODE_PATH].value == "/netgrif/test3/netgrif2/test2" assert folderCase.uriNodeId == node.stringId viewCase = workflowService.findOne(viewId2) @@ -232,9 +266,11 @@ class MenuItemApiTest { void testDuplicateMenuItem() { String starterUri = "/netgrif/test" Case apiCase = createMenuItem(starterUri, "new_menu_item") + Thread.sleep(2000) + String itemId = apiCase.dataSet["menu_stringId"].value Case origin = workflowService.findOne(itemId) - Case testFolder = workflowService.findOne((origin.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value as ArrayList)[0]) + Case testFolder = workflowService.findOne((origin.dataSet[MenuItemConstants.FIELD_PARENT_ID].value as ArrayList)[0]) String newTitle = "New title" String newIdentifier = "new_identifier" @@ -243,39 +279,40 @@ class MenuItemApiTest { taskService.assignTask(duplicateTaskId) assertThrows(IllegalArgumentException.class, () -> { - testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_TITLE.attributeId].value = new I18nString("") - testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_IDENTIFIER.attributeId].value = newIdentifier + testFolder.dataSet[MenuItemConstants.FIELD_DUPLICATE_TITLE].value = new I18nString("") + testFolder.dataSet[MenuItemConstants.FIELD_DUPLICATE_IDENTIFIER].value = newIdentifier testFolder = workflowService.save(testFolder) taskService.finishTask(duplicateTaskId) }) assertThrows(IllegalArgumentException.class, () -> { - testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_TITLE.attributeId].value = new I18nString(newTitle) - testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_IDENTIFIER.attributeId].value = "new_menu_item" + testFolder.dataSet[MenuItemConstants.FIELD_DUPLICATE_TITLE].value = new I18nString(newTitle) + testFolder.dataSet[MenuItemConstants.FIELD_DUPLICATE_IDENTIFIER].value = "new_menu_item" testFolder = workflowService.save(testFolder) taskService.finishTask(duplicateTaskId) }) - testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_TITLE.attributeId].value = new I18nString(newTitle) - testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_IDENTIFIER.attributeId].value = newIdentifier + testFolder.dataSet[MenuItemConstants.FIELD_DUPLICATE_TITLE].value = new I18nString(newTitle) + testFolder.dataSet[MenuItemConstants.FIELD_DUPLICATE_IDENTIFIER].value = newIdentifier testFolder = workflowService.save(testFolder) taskService.finishTask(duplicateTaskId) - Case duplicated = workflowService.searchOne(QCase.case$.processIdentifier.eq("preference_item").and(QCase.case$.dataSet.get(MenuItemConstants.PREFERENCE_ITEM_FIELD_IDENTIFIER.attributeId).value.eq(newIdentifier))) + Case duplicated = workflowService.searchOne(QCase.case$.processIdentifier.eq("menu_item") + & QCase.case$.dataSet.get(MenuItemConstants.FIELD_IDENTIFIER).value.eq(newIdentifier)) assert duplicated != null UriNode leafNode = uriService.findByUri("/netgrif/" + newIdentifier) assert duplicated.uriNodeId == testFolder.uriNodeId assert leafNode != null - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_TITLE.attributeId].value == null - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_IDENTIFIER.attributeId].value == null + assert duplicated.dataSet[MenuItemConstants.FIELD_DUPLICATE_TITLE].value == new I18nString("") + assert duplicated.dataSet[MenuItemConstants.FIELD_DUPLICATE_IDENTIFIER].value == "" assert duplicated.title == newTitle - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_NAME.attributeId].value == new I18nString(newTitle) - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_IDENTIFIER.attributeId].value == newIdentifier - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId].value == "/netgrif/" + newIdentifier - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value == [] - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_HAS_CHILDREN.attributeId].value == false + assert duplicated.dataSet[MenuItemConstants.FIELD_MENU_NAME].value == new I18nString(newTitle) + assert duplicated.dataSet[MenuItemConstants.FIELD_IDENTIFIER].value == newIdentifier + assert duplicated.dataSet[MenuItemConstants.FIELD_NODE_PATH].value == "/netgrif/" + newIdentifier + assert duplicated.dataSet[MenuItemConstants.FIELD_CHILD_ITEM_IDS].value == [] + assert duplicated.dataSet[MenuItemConstants.FIELD_HAS_CHILDREN].value == false assert duplicated.activePlaces["initialized"] == 1 } @@ -291,8 +328,8 @@ class MenuItemApiTest { caze = setData(caze, [ "uri": uri, "title": "FILTER", - "allowed_nets": "filter,preference_item", - "query": "processIdentifier:filter OR processIdentifier:preference_item", + "allowed_nets": "filter,menu_item", + "query": "processIdentifier:filter OR processIdentifier:menu_item", "type": "Case", "identifier": identifier, "icon": "device_hub", @@ -305,25 +342,37 @@ class MenuItemApiTest { void testRemoveMenuItem() { String starterUri = "/netgrif/test" Case apiCase = createMenuItem(starterUri, "new_menu_item") - String leafItemId = apiCase.dataSet["menu_stringId"].value + Case leafItemCase = getMenuItem(apiCase) - Case testFolder = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue:\"/netgrif/test\"", PageRequest.of(0, 1))[0] - String netgrifFolderId = (testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value as ArrayList)[0] + sleep(2000) + Case testFolder = findCasesElastic("processIdentifier:$FilterRunner.MENU_NET_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) - assert (netgrifFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList).contains(testFolder.stringId) + assert (netgrifFolder.dataSet[MenuItemConstants.FIELD_CHILD_ITEM_IDS].value as ArrayList).contains(testFolder.stringId) assert workflowService.findOne(testFolder.stringId) != null - assert workflowService.findOne(leafItemId) != null + assert workflowService.findOne(leafItemCase.stringId) != null + 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) + assert tabbedTaskViewId != null workflowService.deleteCase(testFolder) sleep(2000) netgrifFolder = workflowService.findOne(netgrifFolderId) - assert !(netgrifFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList).contains(testFolder.stringId) + assert !(netgrifFolder.dataSet[MenuItemConstants.FIELD_CHILD_ITEM_IDS].value as ArrayList).contains(testFolder.stringId) assertThrows(IllegalArgumentException.class, () -> { workflowService.findOne(testFolder.stringId) }) assertThrows(IllegalArgumentException.class, () -> { - workflowService.findOne(leafItemId) + workflowService.findOne(leafItemCase.stringId) + }) + assertThrows(IllegalArgumentException.class, () -> { + workflowService.findOne(tabbedCaseViewId) + }) + assertThrows(IllegalArgumentException.class, () -> { + workflowService.findOne(tabbedTaskViewId) }) } 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 cf6e0108859..940fa30b79e 100644 --- a/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy @@ -15,7 +15,7 @@ import com.netgrif.application.engine.workflow.domain.QCase import com.netgrif.application.engine.workflow.domain.QTask import com.netgrif.application.engine.workflow.domain.Task import com.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes.SetDataEventOutcome -import com.netgrif.application.engine.workflow.domain.menu.MenuAndFilters +import com.netgrif.application.engine.menu.domain.MenuAndFilters import com.netgrif.application.engine.workflow.domain.repositories.CaseRepository import com.netgrif.application.engine.workflow.service.UserFilterSearchService import com.netgrif.application.engine.workflow.service.interfaces.IDataService diff --git a/src/test/java/com/netgrif/application/engine/menuitem/MenuItemViewRegistryTest.java b/src/test/java/com/netgrif/application/engine/menuitem/MenuItemViewRegistryTest.java new file mode 100644 index 00000000000..9b5ae86a580 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/menuitem/MenuItemViewRegistryTest.java @@ -0,0 +1,90 @@ +package com.netgrif.application.engine.menuitem; + +import com.netgrif.application.engine.menu.domain.MenuItemView; +import com.netgrif.application.engine.menu.registry.interfaces.IMenuItemViewRegistry; +import com.netgrif.application.engine.menu.registry.throwable.DuplicateViewException; +import com.netgrif.application.engine.petrinet.domain.I18nString; +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 static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class MenuItemViewRegistryTest { + + @Autowired + private IMenuItemViewRegistry viewRegistry; + + @BeforeEach + public void before() { + viewRegistry.unregisterAllViews(); + } + + @Test + public void testRegisterView() { + assert viewRegistry.getAllViews().isEmpty(); + + MenuItemView view = buildView("tabbed_case_view", true, true, List.of()); + viewRegistry.registerView(view); + assert viewRegistry.getAllViews().size() == 1; + + assertThrows(DuplicateViewException.class, () -> viewRegistry.registerView(view)); + } + + @Test + public void testUnregisterView() { + MenuItemView view = buildView("tabbed_case_view", true, true, List.of()); + viewRegistry.registerView(view); + assert viewRegistry.getAllViews().size() == 1; + viewRegistry.unregisterView("test_view"); + assert viewRegistry.getAllViews().isEmpty(); + } + + @Test + public void testGetViewByIdentifier() { + MenuItemView view = buildView("tabbed_case_view", true, true, List.of()); + viewRegistry.registerView(view); + assert viewRegistry.getViewByIdentifier("tabbed_case_view") != null; + } + + @Test + public void testGetAllByIsTabbedAndIsPrimary() { + MenuItemView view1 = buildView("tabbed_case_view", true, true, List.of()); + MenuItemView view2 = buildView("tabbed_task_view", true, false, List.of()); + MenuItemView view3 = buildView("tabbed_ticket_view", false, true, List.of()); + viewRegistry.registerView(view1); + viewRegistry.registerView(view2); + viewRegistry.registerView(view3); + assert viewRegistry.getAllByIsTabbedAndIsPrimary(true, true).size() == 1; + } + + @Test + public void testGetAllByIsTabbedAndParentIdentifier() { + MenuItemView view1 = buildView("tabbed_case_view", true, true, List.of("tabbed_task_view", "tabbed_ticket_view")); + MenuItemView view2 = buildView("tabbed_task_view", true, true, List.of()); + MenuItemView view3 = buildView("tabbed_ticket_view", false, true, List.of()); + viewRegistry.registerView(view1); + viewRegistry.registerView(view2); + viewRegistry.registerView(view3); + assert viewRegistry.getAllByIsTabbedAndParentIdentifier(true, "tabbed_case_view").size() == 1; + } + + private MenuItemView buildView(String identifier, boolean isTabbed, boolean isPrimary, List associatedViewIds) { + return MenuItemView.with() + .name(new I18nString("Test view")) + .identifier(identifier) + .allowedAssociatedViews(associatedViewIds) + .isTabbed(isTabbed) + .isPrimary(isPrimary) + .build(); + } +} diff --git a/src/test/resources/petriNets/filter_api_test.xml b/src/test/resources/petriNets/filter_api_test.xml index 5bb13019442..ab00d976034 100644 --- a/src/test/resources/petriNets/filter_api_test.xml +++ b/src/test/resources/petriNets/filter_api_test.xml @@ -107,31 +107,19 @@ title: f.title, allowed_nets: f.allowed_nets, query: f.query, - group: f.group, identifier: f.identifier, icon: f.icon; - def item = findMenuItem(identifier.value) - def filter = getFilterFromMenuItem(item) - - changeFilter filter query { query.value } - changeFilter filter allowedNets { allowed_nets.value.split(",") as List } - changeFilter filter icon { icon.value } - changeFilter filter title { title.value } - changeFilter filter filterMetadata { [ + def metadata = [ "searchCategories" : [], "predicateMetadata" : [], "filterType" : type, "defaultSearchCategories": false, "inheritAllowedNets" : false - ] } - - changeMenuItem item filter { filter } - changeMenuItem item allowedRoles { ["role_2": "filter_api_test"] } - changeMenuItem item uri { uri.value } - changeMenuItem item title { title.value } - changeMenuItem item caseDefaultHeaders { "meta-title,meta-title,meta-title" } - changeMenuItem item taskDefaultHeaders { "meta-title,meta-title,meta-title" } + ] + createOrUpdateMenuItemAndFilter(uri.value, identifier.value, title.value, query.value, "Case", + "private", allowed_nets.value.split(",") as List, icon.value, ["role_2": "filter_api_test"], + [:], ["meta-title","meta-title","meta-title"], ["meta-title","meta-title","meta-title"], metadata)