diff --git a/.github/workflows/master-build.yml b/.github/workflows/master-build.yml
index a02889fd08e..fa0249ede8f 100644
--- a/.github/workflows/master-build.yml
+++ b/.github/workflows/master-build.yml
@@ -56,7 +56,7 @@ jobs:
--health-retries=10
minio:
- image: docker.io/bitnamilegacy/minio:2025.7.23
+ image: docker.io/bawix/minio:2022
ports:
- 9000:9000
- 9001:9001
diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml
index ff3edb08caa..9b361e4f30e 100644
--- a/.github/workflows/pr-build.yml
+++ b/.github/workflows/pr-build.yml
@@ -46,7 +46,7 @@ jobs:
options: -e="discovery.type=single-node" -e="xpack.security.enabled=false" --health-cmd="curl http://localhost:9200/_cluster/health" --health-interval=10s --health-timeout=5s --health-retries=10
minio:
- image: docker.io/bitnamilegacy/minio:2025.7.23
+ image: docker.io/bawix/minio:2022
ports:
- 9000:9000
- 9001:9001
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 64f8d110c21..15f7028765f 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -51,7 +51,7 @@ jobs:
options: -e="discovery.type=single-node" -e="xpack.security.enabled=false" --health-cmd="curl http://localhost:9200/_cluster/health" --health-interval=10s --health-timeout=5s --health-retries=10
minio:
- image: docker.io/bitnamilegacy/minio:2025.7.23
+ image: docker.io/bawix/minio:2022
ports:
- 9000:9000
- 9001:9001
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7706b72c5ac..4be69db08bc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,29 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-Full Changelog: [https://github.com/netgrif/application-engine/commits/v6.4.2](https://github.com/netgrif/application-engine/commits/v6.4.2)
+## [6.5.0](https://github.com/netgrif/application-engine/releases/tag/v6.5.0) (2026-07-20)
+
+### Fixed
+- [NAE-2099] MenuItemService.appendChildCaseIdInDataSet not setting hasChildren correctly
+- [NAE 2424] UserRefs negative view permissions aren't resolved
+
+### Added
+- [NAE-2033] Welcome dashboard
+- [NAE-2439] Implement new filter data types
+- [NAE-2443] PFQL support
+- [ETASK-23] Dynamic view configuration
+- [NAE-2447] System process Process
+- [NAE-2450] Filter field PFQL support
+
+### Changed
+- [NAE-2034] Open first view
+- [NAE-2051] Implement configurable view in menu items
+- [NAE-2039] Search in workflow view
+- [NAE-2052] Integrate ticket view with menu items
+- [NAE-2063] Action API 6.5.0
+- [NAE-2136] Speed up Elasticsearch reindex
+- [NAE-2390] Action API Improvements
+- [NAE 2441] Event permissions
## [6.4.2](https://github.com/netgrif/application-engine/releases/tag/v6.4.2) (2025-05-16)
diff --git a/docker-compose.yml b/docker-compose.yml
index bc0f999de32..b6b8d2052f2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -40,7 +40,7 @@ services:
- "6379:6379"
minio:
- image: docker.io/bitnamilegacy/minio:2025.7.23
+ image: bawix/minio:2022
ports:
- '9000:9000'
- '9001:9001'
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 0bd5aacf956..6679165c0b7 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -7,8 +7,6 @@
* [Event queue](events/event_queue.md)
* [Search]()
* [Elastic](search/elastic_mapping.md)
- * [Filter](search/filter.md)
- * [Import / Export](search/filter_import_export.md)
* [Permissions]()
* [User list](roles/userlist.md)
* [Permissions](roles/permissions.md)
diff --git a/docs/search/filter.md b/docs/search/filter.md
deleted file mode 100644
index 0d9924921b2..00000000000
--- a/docs/search/filter.md
+++ /dev/null
@@ -1,842 +0,0 @@
-# Developers guide to the Filter process
-
-This document contains information about the Filter process and the API provided by the application engine for
-interacting with the instances of the Filter process.
-
-## Filter data variable
-
-This data variable stores a
-generated [elastic query string query](https://www.elastic.co/guide/en/elasticsearch/reference/6.6/query-dsl-query-string-query.html)
-as it’s value.
-
-It has two additional properties that store the metadata necessary for the reconstruction of a filter.
-
-* _**allowedNets**_ - similar to the case ref data variable type, a list of identifiers of “allowed nets“ is stored in
- the filter field. The stored nets represent the nets that can be used to construct search queries within the advanced
- search component.
-* _**filterMetadata**_ - data necessary for reconstructing the state of the advanced search component. The backend
- currently does not provide any utility methods for construction of the metadata object. An interface for this object
- can be found in
- the [documentation](https://developer.netgrif.com/projects/engine-frontend/5.4.1/nae/docs/interfaces/FilterMetadata.html)
- .
-
-Beware that while a filter field does not throw an error when displayed in editable mode and changes can be made to its
-frontend representation, these changes are not propagated to backend and do not fire a set data event on the field.
-Because of this we recommend only using filter fields in visible mode, to display their values set by the frontend
-filter API.
-
-Neither title nor any other meta attribute of the filter field (such as description) is displayed as part of the filter
-field component.
-
-
-
-*an editable filter field*
-
-
-
-*a visible filter field*
-
-
-
-*an immediate filter field displayed on a panel*
-
-## Filter process
-
-The engine filter process is located under `resources/petriNets/engine-processes/filter.xml`
-
-
-
-The process is relatively simple. It stores and makes accessible the data necessary for the persistence of filters
-created by users of the application engine. All the process logic, the model contains is focussed solely on the creation
-process of the filter before it reaches its "initialized" state denoted by `p2`.
-
-The process can be overridden by the developers, by placing a net with the same file name and identifier into the
-location specified above. The process defines a “process interface” that must remain unchanged when overriding the
-process for the frontend APIs that connects to the process to work correctly. Attributes of the process that are part of
-the “process interface“ and must remain unchanged will be highlighted.
-
-### Filter process interface
-
-#### Data variables
-
-##### Filter
-
-```xml
-
- filter
- Filter
-
-```
-
-This field contains the saved filter.
-
-It must be `immediate`, have the ID `filter` and be of type `filter`.
-
-##### Filter type
-
-```xml
-
- filter_type
- Filter type
-
-
-
-
-
-```
-
-This data variable stores the type of the filter stored in the filter field of the filter process. The keys are used for
-filtering of the filter processes.
-
-It must be `immediate`, have the ID `filter_type` and be of type `enumeration_map`. The available options must have the
-keys `Case` and `Task`. The values associated with these keys are not specified by the Filter process interface and can
-therefore be freely overridden.
-
-##### Filter visibility
-
-```xml
-
- visibility
- Filter visibility
-
-
-
-
- public
-
-```
-
-This data variable can be used for filtering of the filter processes based on some internal rules. The application
-engine queries this variable when listing filter during navigation menu customisation and during filter export. Only
-public and private filters authored by the logged user are offered as options during these selection processes.
-
-No universal restriction is applied to the search queries on these instances, however. The developer must ensure that no
-inappropriate filters become available to the user during any search at their own discretion. The queries used by the
-application engine can be overridden, by extending the `IUserFiltersSearchService` interface.
-
-It must be `immediate`, have the ID `visibility` and be of type `enumeration_map`. The available options must have the
-keys `public` and `private`. The values associated with these keys are not specified by the Filter process interface and
-can therefore be freely overridden.
-
-##### Origin ViewId
-
-```xml
-
- origin_view_id
- Origin ViewId
-
-```
-
-The viewId of the view where the filter was created is stored in this data variable.
-Is only set if the filter originates from a view with an in-app filter.
-
-When the field is set an action is triggered, that resolves the parent filter (if any) and populates the data variables
-that are responsible for displaying the parent filter with the appropriate values.
-
-It must be `immediate`, have the ID `origin_view_id` and be of type `text`.
-
-##### Parent filter Id
-
-```xml
-
- parent_filter_id
- parent filter ID
-
-```
-
-The case ID of the parent filter case (if any). Is only set if the filter originates from a different filter case (usually via configurable menu).
-
-When the field is set an action is triggered, that resolves the parent filter (if any) and populates the data variables
-that are responsible for displaying the parent filter with the appropriate values.
-
-It must have the ID `parent_filter_id` and be of type `text`.
-
-##### Filter name
-
-```xml
-
- new_title
- Filter name
-
-```
-
-This data variable is used to change the title of the newly created filter case according to the wishes of the user.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted.
-
-##### I18n filter name
-
-```xml
-
- i18n_filter_name
-
-
-```
-
-Contains the name of the filter with locale specific translations. Its value is used to generate configurable group
-navigation menu entry titles.
-
-It must be `immediate`, have the ID `i18n_filter_name` and be of type `enumeration`.
-
-The previously mentioned `new_title` data variable has a set action, that pushes its value into this variable. Since
-the `new_title` variable is a plain text field, a `I18nString` object with only its default value set is put into this
-variable. This field however does support translations, so localised menu entries can be created, or their names changed
-trough actions.
-
-##### Filter case id
-
-```xml
-
- filter_case_id
-
-
-```
-
-This variable is set during the case creation and stores the ID of this case. Since the frontend integration accesses
-the filter process mostly trough tasks, this field can be referenced in any of those tasks to grant direct access to the
-case ID, without having to execute an extra search query. The frontend integration does not use this variable currently,
-but it can be used in custom integrations.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted.
-
-##### And me
-
-```xml
-
- and_me
- AND
-
-```
-
-Stores the translated text of the word "AND" in order to create a visual representation of the true filter content. More
-information about this can be found in the transition section.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted.
-
-##### And view
-
-```xml
-
- and_view
- AND
-
-```
-
-Similarly to the `and_me` data variable, this too stores the translated text of the word "AND" in order to create a
-visual representation of the true filter content.
-
-This variable is referenced in the action associated with the `origin_view_id` data variable and on the `view_filter`
-and `view_as_ancestor` transitions. If it were to be removed this must be taken into the account.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted.
-
-##### Parent filter task ref
-
-```xml
-
- taskref_and_parent
-
-
-```
-
-Similarly to the `and_view` data variable, this too is used to create the visual representation of the true filter
-content, because it stores a reference to the task in the parent filter process that contains the true visualisation of
-the parent filter.
-
-Just like the `and_view` data variable, this too is referenced in the action associated with the `origin_view_id` data
-variable and on the `t1` and `t2` transitions. If it were to be removed this must be taken into the account.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted.
-
-##### Trimmed origin view id
-
-```xml
-
- trimmed_origin_view_id
- Filter from view with ID
-
-```
-
-Similarly to the `and_view` data variable, this too is used to create the visual representation of the true filter
-content, because it stores a trimmed version of the origin view id, that does not contain any tabbed view identifiers.
-
-Just like the `and_view` data variable, this too is referenced in the action associated with the `origin_view_id` data
-variable and on the `t1` and `t2` transitions. If it were to be removed this must be taken into the account.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted.
-
-##### Is imported
-
-```xml
-
- is_imported
-
- 0
-
-```
-
-This is the reference value for variable arc to decide, if the filter is imported by the Import filter process or is
-automatically created by the backend. When set, it sets the value of the `is_not_imported` data variable to `0`.
-
-It must have the ID `is_imported`, be of type `number` and have the initial value `0`.
-
-##### Is not imported
-
-```xml
-
- is_not_imported
-
- 1
-
-```
-
-This is the reference value for the alternative variable arc to decide, if the filter is imported by the Import filter
-process or is automatically created by the backend.
-
-It must have the ID `is_not_imported`, be of type `number` and have the initial value `1`.
-
-##### Missing allowed nets
-
-```xml
-
- missing_allowed_nets
- Missing processes
- List of missing processes for current filter
-
- htmltextarea
-
-
-```
-
-The purpose of this textarea is to show the list of missing allowed nets for the current (imported) filter. It is
-displayed in `import_filters` transition while importing new filter from XML.
-
-It must have the ID `missing_allowed_nets`, be of type `text` and have the component `htmltextarea`.
-
-##### Missing nets translations
-
-```xml
-
- missing_nets_translation
-
-
-
-
-
-
-
-```
-
-The purpose of this map filed is to translate the message in the `missing_allowed_nets` HTML textarea, because the
-message in the textarea is dynamically generated depending on the active locale.
-
-It must have the ID `missing_nets_translation` and be of type `enumeration_map`.
-
-##### My full filter
-
-```xml
-
- my_full_filter
-
-
-```
-
-This task ref holds a reference to the view_filter transition and is displayed in all tasks that should show the contents of the saved filter.
-
-It is not referenced by anything outside the process, so it can be freely modified, provided all the internal references are compatible.
-
-#### Functions
-
-##### initializeMyFullFilterTaskRef
-
-```xml
-
- {
- com.netgrif.application.engine.petrinet.domain.dataset.TaskField myFullFilter
- ->
- change myFullFilter value {return [findTask({it.caseId.eq(useCase.stringId).and(it.transitionId.eq("view_filter"))}).stringId]}
- }
-
-```
-
-This utility process-scoped function is used to set the value of the `my_full_filter` task ref data variable.
-
-It is called in a finish-post action of both the `frontend_create` and `auto_create` transitions,
-since their execution makes the searched transition (`view_filter`) executable.
-
-#### Roles
-
-##### System
-
-The system role is used to hide the `view_filter` and `view_as_ancestor` tasks from the user.
-
-The other transitions executed by the system are never searched for by the frontend API and therefore don't need to have this role assigned to them.
-
-#### Transitions
-
-The net consists of seven transitions. **Set filter metadata** and **create new filter** are used when creating filter
-from frontend filter API.
-**Automated new** is used, when creating filter from backend automatically. **Import filter** transition is used when
-importing filters in the
-_import\_filters_ process. The last three transitions **Details**, **View filter** and **View as ancestor** are used to
-display filter preview.
-
-##### Set filter metadata
-
-```xml
-
- frontend_create
-
-
-
- filter
-
- editable
-
-
-
- filter_type
-
- editable
-
-
-
- origin_view_id
-
- editable
-
-
-
-
-```
-
-This transition is executed automatically by the frontend filter API in order to set the necessary filter data into the
-process instance, when a new filter is created (saved).
-
-It must have the transition ID `frontend_create`.
-
-It must contain 4 data fields that are set via an API request:
-
-* `filter`
-* `filter_type`
-* `origin_view_id`
-* `parent_filter_id`
-
-Custom set data requests can be sent via the frontend filter API. These requests target this task. Therefore, if you
-want to set additional properties to your newly created filter instances you can do so by adding the necessary data
-variables to this task and then use the frontend API to set their values.
-
-Once it finishes the `view_filter` and `view_as_ancestor` tasks become executable.
-An action is bound to the finish of this transition, that sets the reference of the `my_full_filter` task ref.
-
-##### Create new filter
-
-```xml
-
- newFilter
- auto
-
-
-
- my_full_filter
-
- visible
-
-
-
-
-
- visibility
-
- editable
- required
-
-
-
- new_title
-
- editable
- required
-
-
-
-
-```
-
-This transition is displayed to the user in the side menu when a new filter case instance is created (a filter is saved)
-through the frontend filter API.
-
-It can reference any filter data variables. The default implementation references, the filter and its type in visible
-mode (they were set in the previous step), an editable visibility choice and an editable text field for setting the new
-filter case title (via a finish action).
-
-It must have the transition ID `newFilter`.
-
-##### Automated new filter
-
-```xml
-
- auto_create
-
-
-
- filter
-
- editable
-
-
-
- filter_type
-
- editable
-
-
-
- visibility
-
- editable
-
-
-
- origin_view_id
-
- editable
-
-
-
-
-```
-
-This transition is executed by the `DefaultFiltersRunner`, in order to set the data present in the previous two
-transitions with a single `setData` operation.
-
-It must have the transition ID `auto_create`.
-
-It must contain 5 data fields that are set via a `setData` API call.
-
-* `filter`
-* `filter_type`
-* `origin_view_id`
-* `parent_filter_id`
-* `visibility`
-
-Once it finishes the `view_filter` and `view_as_ancestor` tasks become executable.
-An action is bound to the finish of this transition, that sets the reference of the `my_full_filter` task ref.
-
-##### Import filter
-
-```xml
-
- import_filter
-
-
-
- new_title
-
- editable
-
-
-
- filter_type
-
- visible
-
-
-
- visibility
-
- editable
-
-
-
- missing_allowed_nets
-
- hidden
-
-
-
-
-
- my_full_filter
-
- visible
-
-
-
-
-```
-
-This transition is used in the `import_filters` process to allow editing of filter name and filter visibility when
-importing a new filter.
-
-It must have the transition ID `import_filter`.
-
-It should contain at least these data fields to display to the user which filter is being imported and allow them to
-change some fundamental filter properties:
-
-* `new_title` (editable)
-* `filter_type` (visible)
-* `visibility` (editable)
-* `my_full_filter` (visible)
-* `missing_allowed_nets` (hidden) - made visible by the import action if some nets are missing
-
-##### Details
-
-```xml
-
- t2
-
-
-
- filter
-
- visible
-
-
-
- filter_type
-
- visible
-
-
-
- visibility
-
- visible
-
-
-
-
-```
-
-This transition makes the filter metadata accessible to the users of the application.
-
-The Filter process interface does not specify any restrictions for this data variable, it can be freely overridden or
-omitted. Beware however, that it is referenced by the `origin_view_id` actions. This must be taken into the account when
-omitting this transition.
-
-##### View filter
-
-```xml
-
- view_filter
-
-
-
- filter
-
- visible
-
-
-
- filter_case_id
-
- hidden
-
-
-
- origin_view_id
-
- forbidden
-
-
-
- taskref_and_parent
-
- visible
-
-
-
-
- DataGroup_1
- grid
-
- and_view
-
- forbidden
-
-
-
- trimmed_origin_view_id
-
- forbidden
-
-
-
-
-```
-
-This transition displays the true content of the filter represented by the process. It can be task-reffed to show a
-referenced filter somewhere (such as in the configurable group navigation entries and filter previews). In addition to
-the filter field itself the ancestor filters are displayed here as well. Each of them is prefixed with an AND text to
-indicate that they are combined with the original filter with the **and** operator.
-
-If the root of the filter chain is a filter process (indicated by its `parent_filter_id` having a non-empty value)
-the root filters content will be the last entry displayed. If the root is a frontend filter
-(indicated by its `origin_view_id` field being set to a non-empty string) the last entry will
-contain a text indicating the view from which the filter originates. The displayed view ID excludes any tab suffixes.
-
-If the filter does not originate from a view its `origin_view_id` field must have its behavior set to `forbidden`,
-so that the frontend parsing mechanism won't find false positives during the interpretation of the filter.
-
-It must have the transition ID `view_filter`.
-
-It must contain 1 data field:
-
-* `filter`
-
-##### View as ancestor
-
-```xml
-
- view_as_ancestor
-
-
-
- and_me
-
- visible
-
-
-
- filter
-
- visible
-
-
-
- taskref_and_parent
-
- visible
-
-
-
-
- DataGroup_1
- grid
-
- and_view
-
- forbidden
-
-
-
- trimmed_origin_view_id
-
- forbidden
-
-
-
-
-```
-
-Contains the same information as the View filter task, but every row is prefixed with an AND text (including the first
-row that contains the filter field of this case).
-
-This task is referenced when resolving ancestor filters from the immediate children of this filter.
-
-## Backend filter API
-
-Since filters are implemented as Petriflow processes, they share the same API with any other process. The old filter
-related services, controllers and other classes have been deprecated in the 5.4.0 release and should no longer be used.
-
-There are a few things to keep in mind when working with filters.
-
-### Using filter fields for filtering of cases and tasks
-
-You can use the value and other attributes of a filter field to create database queries.
-
-When doing so, make sure to check if the filter has the appropriate type (that is only use Case filters to filter cases
-and Task filters to filter tasks).
-
-The value attribute of a filter field is
-an [elastic query string query](https://www.elastic.co/guide/en/elasticsearch/reference/6.6/query-dsl-query-string-query.html) (
-as mentioned above) and therefore cannot be used to filter entries from the Mongo database. It can however be turned
-into a `CaseSearchRequest` class instance, by populating the `query` attribute with the filter field value. This object
-can then be passed to the `ElasticCaseService` to find the cases that match the query. If you want to search for task
-instances then analogous classes and services exist.
-
-### Actions API
-
-Filter fields have only a limited support in the actions API.
-
-Only their value can be changed with the `change` action and this change is NOT propagated to the other filter field
-attributes (allowedNets and filterMetadata). Since these two attributes are the important part when restoring an
-advanced search component state on the frontend it can easily lead to “desynchronisation“ of these values. Changing
-these values should therefore be done cautiously.
-
-### Set data method
-
-The set data method of the `TaskDataService` can be used to modify all the attributes of a filter field. All of them
-must be set at the same time, any missing attributes will have its value set to `null`.
-
-### DefaultFiltersRunner
-
-This runner contains utility methods that can be used to create default system filter process instances. The author of
-these filters will be the system user.
-
-## Frontend filter API
-
-Since saved filters are cases you should use the existing API for interacting with cases. Utility methods exist for some
-operations. You should keep in mind however that, the frontend library does not provide a full filter persistence and
-management implementation, only the building blocks for creating one, therefore you must implement it on your own
-according to your wishes. An overview of these building blocks can be found in the following sections.
-
-### Filter constants
-
-The
-enum [UserFilterConstants](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/miscellaneous/enumerations.html#UserFilterConstants)
-contains identifiers and IDs of all parts of the filter process referenced by the frontend filters API.
-
-### UserFiltersService
-
-The [UserFiltersService](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/injectables/UserFiltersService.html)
-contains the API for saving, loading and deleting filter process instances.
-
-The search component contains buttons that trigger the save and load methods, so you do not have to use this service
-directly, but if you wish to create your own user interface you can re-use the implementation provided by this service.
-
-Injection tokens are available for the configuration of the side menu components used to save and load
-filters (`NAE_SAVE_FILTER_COMPONENT` and `NAE_LOAD_FILTER_COMPONENT`). An injection token is also available for
-configuring the filter that filters the filter process instances displayed when loading a filter (`NAE_FILTERS_FILTER`).
-
-### SearchComponent
-
-The [SearchComponent](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/classes/AbstractSearchComponent.html)
-is a wrapper for the two search mode components (fulltext and advanced)
-these components can be used independently of each other and the search component combines them into one and adds
-various control elements to them.
-
-The search component has two outputs - `filterLoaded` and `filterSaved`. These emit data related to either the saved
-filter, or the selected filter in the load pop-up. The developers must process these outputs in order to implement fully
-functional filter management.
-
-The component also has na input - `additionalFilterData`. This input can be used to modify the content of the set data
-request that is sent by the `UserFiltersService`, when a new filter is created. This way a less invasive option is
-available when overriding the default filter process and adding additional metadata necessary for your implementation of
-filter management.
-
-An injection token is available for the configuration of the search component - `NAE_SEARCH_COMPONENT_CONFIGURATION`. It
-provides
-a [SearchComponentConfiguration](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/interfaces/SearchComponentConfiguration.html)
-object that can be used to hide many elements of the search component (buttons mostly). This way you can remove the save
-and/or load filter buttons and therefore not allow the users to persist filters in some specific views.
-
-### SearchService
-
-The [SearchService](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/injectables/SearchService.html)
-provides two complementary methods - `createPredicateMetadata` and `loadFromMetadata`. These can be used to populate the
-search service with a predicate stored inside a filter field (`filterMetadata` attribute). If an advanced search
-component is connected to the search service, then it will automatically display the loaded predicate.
-
-### FilterExtractionService
-
-The [FilterExtractionService](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/injectables/FilterExtractionService.html)
-contains the functionality of extracting a frontend [Filter](https://developer.netgrif.com/projects/engine-frontend/latest/nae/docs/classes/Filter.html)
-instance from the data of a configurable navigation entry task.
\ No newline at end of file
diff --git a/docs/search/filter_import_export.md b/docs/search/filter_import_export.md
deleted file mode 100644
index cd98e559025..00000000000
--- a/docs/search/filter_import_export.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# Filter import/export
-
-## User guide
-
-This guide is aimed at application users that wish to know, how to import/export selected filters from/to application.
-If you are developer that wishes to know more about the implementation and various ways the system can be interacted
-with, you can find this information in the Developer guide of filter import/export.
-
-### Overview
-
-Every user in the application have option to import/export filters. For every user in application case for import
-filters and also case for export filters exists as shown on the next image.
-
-
-
-
-
-
-
-### Processes
-
-#### Export filters
-
-After user opens **Export filters USERNAME** case, task to export filters is displayed. The task consists of three
-fields, from which only two are initially displayed to user.
-
-
-
-
-
-
-
-First field is multi choice field, which allows user to select multiple filters, which he wants to export. Filters that
-are displayed in this multi choice are all **public** filters in the application (filters which can be created by other
-users) and also all **private** filters of currently logged user. After user selects filters which he wants to export
-and clicks on the **Export filters** button, the third field is displayed.
-
-
-
-This field holds exported xml file, which contains exported filters in the custom xml format. User can download this
-file by clicking on its name. This file can be later used to import filters into the application. Users should never
-create or modify these files on their own, because of the complexity of filters format inside xml file.
-
-#### Import filters
-
-After user opens **Import filters USERNAME** case, task to import filters is displayed. The task consists of two fields,
-which are initially displayed to user and after these two fields there is one section for every imported filter.
-
-
-
-
-
-
-
-First field is file field, where user should upload xml file, that contains exported filters. After user uploads this
-xml file and clicks on **Import filters** button, application validate uploaded file against schema, which doesn't allow
-upload of files in incorrect format. If format of uploaded file is correct, application initialize creation of filters
-and displays these filters under initially displayed fields.
-
-
-
-Each section after horizontal line represents one imported filter. User can change two properties for each imported
-filter. First one is filter name and the second one is visibility of the filter. User can also see type of the filter (
-if the filter is for tasks or for cases) and also filter preview, so he can know what it is filtering.
-
-In some situation, imported filter have dependency on some processes. To correctly display preview of filter and further
-correct function of filter, all these processes need to be uploaded in application. If some of these processes are
-missing, list of missing processes is displayed inside textarea as shown on next image.
-
-
-
-When user is satisfied with imported filters, their names and visibility, he can confirm creation of these filters by
-pressing **FINISH** button on the bottom right. If the imported filters are not according to his ideas, he can abort
-further creation of these filters by pressing **CANCEL** button or deleting uploaded xml file from file field.
-
-## Developer guide
-
-This guide is aimed at developers that wish to know the implementation detail of filter import/export, so they can
-interact with or override its implementation. If you are application user and wish to know how to use the options
-available to you read the User guide of filter import/export. Please read **Filter process guide** before u start
-reading this one, because of some references on this process.
-
-### Overview
-
-The filter import and export consists of two processes, which are independent of each other. Names of processes are
-self-explaining:
-
-- **Export of filters** - serves for export of filters into xml file `export_filters.xml`
-- **Import of filters** - serves for import of filters from xml file `import_filters.xml`
-
-New schema, which describes format of xml file for exported filters, were introduced
-`filter_export_schema.xml`. All imported xml files containing filters are validated against this schema to ensure
-filters will be correctly created and working. Also, new exception class for incorrect xml file format of imported
-filters was created to throw error after validating xml file against schema `IllegalFilterFileException`.
-
-Multiple classes were added to support serialization and deserialization of filters. These classes are:
-
-- Configuration
-- FilterImportExport
-- FilterImportExportList
-- FilterMetadataExport
-- Predicate
-- PredicateArray
-- PredicateValue
-- CustomFilterDeserializer
-
-`ActionDelegate` have two new functions, which support filters import/export:
-`exportFilters(Collection filtersToExport)` and `importFilters()`. Also, new service `FilterImportExportService`
-which is called by these two functions was created and which performs most functionality in importing and exporting
-functions.
-
-When creating new user, case for importing filters and also case for exporting filters is created for this user.
-
-Also, new property was added into `application.properties`, with name `nae.filter.export.file-name`, which defines name
-of xml file that contains exported filters. This property has default string value of **filters.xml**.
-
-When filters are exported, all of their ancestors are exported as well. If the exported filters have common ancestors,
-the ancestors are only exported once. The relationships between filters are reconstructed on import based on the
-exported filters case Ids. It is assumed that parents precede their children in the imported file
-(the exported file is generated in this way). If a filter cannot find its parent on import the connection will
-be severed and an error will be logged.
-
-### Processes
-
-#### Export filters
-
-Export process consists of one task `exportFilter`, which carry out whole exporting of filters. Process and also the
-only task consist of three data fields. First one is `exportable_filters`, which is **multichoice_map** that displays
-all filters, that are exportable by currently logged user. List is loaded from `ActionDelegate`
-method `findAllFilters()` and returns all filter cases, that are public and created by any user or that are private and
-created by currently logged user.
-
-After selecting filters to export and clicking on second data field **button** with id `export_btn`, the
-method `exportFilters()` from `ActionDelegate`
-is called. This method serialize selected filters into xml file and put this file into third **file** field with
-id `export_file`.
-
-Export filters can be run any number of times so the user can export any number of filters in selected combinations into
-any number of xml files.
-
-#### Import filters
-
-Import process also consists of one task `importFilter`, which carry out whole importing of filters. There are also just
-three data fields in this process. First one is **file** field `upload_file` that serves for upload of xml file with
-filters. After uploading this file and clicking on the next **button** field `import_file`, the `importFilters()` method
-from `ActionDelegate` is called, that validates uploaded xml file against xsd schema.
-
-- If file is valid, method creates **Filter** cases via **Automated new filter** task and move them into **Import
- filter** task. Method returns list of **Import filter** task ids, that are set as value for **taskRef**
- field `imported_filters`, so the user can update some values of these filters.
-- If file is not valid, error message is thrown.
-
-Pressing **FINISH** button in this task moves imported filters into state, where they can be added into group
-navigation.
-
-Pressing **CANCEL** button or changing value to `upload_file` deletes created filters, so there will not be unworkable
-filters saved in the application database.
-
-As well as export filters, import filters can be run any number of times, so user can upload how many filters as he
-wants.
diff --git a/pom.xml b/pom.xml
index d9f4b8ffac1..bcf3c0317e6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.netgrifapplication-engine
- 6.4.2
+ 6.5.0jarNETGRIF Application Engine
@@ -64,6 +64,7 @@
7.70.0.Finalnetgrif-osshttps://sonarcloud.io
+ 4.13.12.15.0-rc1
@@ -573,6 +574,12 @@
minio8.5.12
+
+
+ org.antlr
+ antlr4-runtime
+ ${antlr4.version}
+
@@ -838,6 +845,22 @@
license-maven-plugin2.0.0
+
+ org.antlr
+ antlr4-maven-plugin
+ ${antlr4.version}
+
+ src/main/java
+ ${project.build.directory}/generated-sources/java/
+
+
+
+
+ antlr4
+
+
+
+
diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/CaseFilterField.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/CaseFilterField.groovy
new file mode 100644
index 00000000000..38e32db0ee1
--- /dev/null
+++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/CaseFilterField.groovy
@@ -0,0 +1,23 @@
+package com.netgrif.application.engine.petrinet.domain.dataset
+
+import org.springframework.data.mongodb.core.mapping.Document
+
+@Document
+class CaseFilterField extends Field {
+
+ CaseFilterField() {
+ super()
+ }
+
+ @Override
+ FieldType getType() {
+ return FieldType.CASE_FILTER
+ }
+
+ @Override
+ Field clone() {
+ CaseFilterField clone = new CaseFilterField()
+ super.clone(clone)
+ return clone
+ }
+}
diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FieldType.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FieldType.groovy
index 0b96783f52c..53ed4407918 100644
--- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FieldType.groovy
+++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FieldType.groovy
@@ -21,7 +21,9 @@ enum FieldType {
DATETIME("dateTime"),
BUTTON("button"),
TASK_REF("taskRef"),
- FILTER("filter"),
+ CASE_FILTER("caseFilter"),
+ TASK_FILTER("taskFilter"),
+ PROCESS_FILTER("processFilter"),
I18N("i18n"),
STRING_COLLECTION("stringCollection")
diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FilterField.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FilterField.groovy
deleted file mode 100644
index a0102f21cc5..00000000000
--- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FilterField.groovy
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.netgrif.application.engine.petrinet.domain.dataset
-
-import org.springframework.data.mongodb.core.mapping.Document
-
-@Document
-class FilterField extends FieldWithAllowedNets {
-
- /**
- * Serialized information necessary for the restoration of the advanced search frontend GUI.
- *
- * Backend shouldn't need to interact with this attribute
- */
- private Map filterMetadata
-
- FilterField() {
- super()
- allowedNets = new ArrayList<>()
- filterMetadata = new HashMap<>()
- }
-
- FilterField(List allowedNets) {
- super(allowedNets)
- filterMetadata = new HashMap<>()
- }
-
- @Override
- FieldType getType() {
- return FieldType.FILTER
- }
-
- @Override
- Field clone() {
- FilterField clone = new FilterField()
- super.clone(clone)
- clone.filterMetadata = this.filterMetadata
-
- return clone
- }
-
- Map getFilterMetadata() {
- return filterMetadata
- }
-
- void setFilterMetadata(Map filterMetadata) {
- this.filterMetadata = filterMetadata
- }
-}
diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/MapOptionsField.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/MapOptionsField.groovy
index 1a26e8db413..f0acc8e978a 100644
--- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/MapOptionsField.groovy
+++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/MapOptionsField.groovy
@@ -40,4 +40,8 @@ abstract class MapOptionsField extends Field {
boolean isDynamic() {
return this.optionsExpression != null
}
+
+ T getOption(String key) {
+ return this.options.get(key)
+ }
}
diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/ProcessFilterField.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/ProcessFilterField.groovy
new file mode 100644
index 00000000000..35d238becf4
--- /dev/null
+++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/ProcessFilterField.groovy
@@ -0,0 +1,23 @@
+package com.netgrif.application.engine.petrinet.domain.dataset
+
+import org.springframework.data.mongodb.core.mapping.Document
+
+@Document
+class ProcessFilterField extends Field {
+
+ ProcessFilterField() {
+ super()
+ }
+
+ @Override
+ FieldType getType() {
+ return FieldType.PROCESS_FILTER
+ }
+
+ @Override
+ Field clone() {
+ ProcessFilterField clone = new ProcessFilterField()
+ super.clone(clone)
+ return clone
+ }
+}
diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/TaskFilterField.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/TaskFilterField.groovy
new file mode 100644
index 00000000000..a54c5421b59
--- /dev/null
+++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/TaskFilterField.groovy
@@ -0,0 +1,23 @@
+package com.netgrif.application.engine.petrinet.domain.dataset
+
+import org.springframework.data.mongodb.core.mapping.Document
+
+@Document
+class TaskFilterField extends Field {
+
+ TaskFilterField() {
+ super()
+ }
+
+ @Override
+ FieldType getType() {
+ return FieldType.TASK_FILTER
+ }
+
+ @Override
+ Field clone() {
+ TaskFilterField clone = new TaskFilterField()
+ super.clone(clone)
+ return clone
+ }
+}
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..24278dfb82d 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,26 @@ 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.files.IStorageResolverService
+import com.netgrif.application.engine.files.StorageResolverService
+import com.netgrif.application.engine.files.interfaces.IStorageService
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.CaseViewBody
+import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody
+import com.netgrif.application.engine.menu.domain.configurations.ViewBody
+import com.netgrif.application.engine.menu.domain.dashboard.DashboardItemBody
+import com.netgrif.application.engine.menu.domain.dashboard.DashboardManagementBody
+import com.netgrif.application.engine.menu.service.interfaces.DashboardItemService
+import com.netgrif.application.engine.menu.service.interfaces.DashboardManagementService
+import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService
import com.netgrif.application.engine.orgstructure.groups.interfaces.INextGroupService
import com.netgrif.application.engine.pdf.generator.config.PdfResource
import com.netgrif.application.engine.pdf.generator.service.interfaces.IPdfGenerator
@@ -37,23 +51,20 @@ import com.netgrif.application.engine.petrinet.domain.version.Version
import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService
import com.netgrif.application.engine.petrinet.service.interfaces.IProcessRoleService
import com.netgrif.application.engine.petrinet.service.interfaces.IUriService
+import com.netgrif.application.engine.pfql.service.IResourceSearchService
+import com.netgrif.application.engine.pfql.service.ISearchService
import com.netgrif.application.engine.rules.domain.RuleRepository
-import com.netgrif.application.engine.startup.DefaultFiltersRunner
-import com.netgrif.application.engine.startup.FilterRunner
import com.netgrif.application.engine.startup.ImportHelper
import com.netgrif.application.engine.utils.FullPageRequest
-import com.netgrif.application.engine.workflow.domain.Case
-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.*
import com.netgrif.application.engine.workflow.domain.eventoutcomes.EventOutcome
import com.netgrif.application.engine.workflow.domain.eventoutcomes.caseoutcomes.CreateCaseEventOutcome
import com.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes.GetDataEventOutcome
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.CancelTaskEventOutcome
+import com.netgrif.application.engine.workflow.domain.eventoutcomes.taskoutcomes.FinishTaskEventOutcome
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 +85,9 @@ import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
-import java.text.Normalizer
+import java.nio.file.Files
import java.time.ZoneId
import java.util.stream.Collectors
-
/**
* ActionDelegate class contains Actions API methods.
*/
@@ -154,18 +164,9 @@ class ActionDelegate {
@Autowired
Scheduler scheduler
- @Autowired
- IUserFilterSearchService filterSearchService
-
@Autowired
IConfigurableMenuService configurableMenuService
- @Autowired
- IMenuImportExportService menuImportExportService
-
- @Autowired
- IFilterImportExportService filterImportExportService
-
@Autowired
IExportService exportService
@@ -190,6 +191,33 @@ class ActionDelegate {
@Autowired
PublicViewProperties publicViewProperties
+ @Autowired
+ IMenuItemService menuItemService
+
+ @Autowired
+ DashboardManagementService dashboardManagementService
+
+ @Autowired
+ DashboardItemService dashboardItemService
+
+ @Autowired
+ IStorageResolverService storageResolver
+
+ @Autowired
+ ISearchService searchService
+
+ @Autowired
+ IResourceSearchService caseSearchService
+
+ @Autowired
+ IResourceSearchService taskSearchService
+
+ @Autowired
+ IResourceSearchService processSearchService
+
+ @Autowired
+ IResourceSearchService userSearchService
+
FrontendActionOutcome Frontend
/**
@@ -580,7 +608,7 @@ class ActionDelegate {
},
where: { Closure closure ->
[with: { Map dataSet ->
- executeTasks(dataSet, taskId, closure)
+ executeTasks(dataSet, task.stringId, closure)
}]
}]
}
@@ -655,7 +683,7 @@ class ActionDelegate {
saveChangedChoices(field, targetCase, targetTask)
},
allowedNets : { cl ->
- if (!(field instanceof CaseField)) // TODO make this work with FilterField as well
+ if (!(field instanceof CaseField))
return
def allowedNets = cl()
@@ -898,11 +926,194 @@ class ActionDelegate {
return result.content
}
+ /**
+ * Finds cases referenced by a field in its value.
+ *
+ * Use this overload when working on a case from the current action context. For working with fields from out of the
+ * current action context see other overloads of this action.
+ *
+ *
If the field value is {@code null}, this method returns an empty list.
+ *
If the value cannot be converted to case IDs, this method returns an empty list.
+ *
+ * @param caseRef field whose value contains case IDs, may be of types
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#CASE_REF},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#ENUMERATION_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#STRING_COLLECTION},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#TEXT},
+ * @return list of matching cases, or an empty list when the field value is {@code null}
+ * @see ActionDelegate#findCases(DataField)
+ * @see ActionDelegate#findCases(List)
+ * @see ActionDelegate#findCases(Closure)
+ * @see ActionDelegate#findCases(Closure, Pageable)
+ */
+ List findCases(Field caseRef) {
+ if(caseRef.value == null) {
+ log.debug("[findCases(Field)]: Value of field with id [${caseRef.importId}] is null, returning empty list.")
+ return []
+ }
+ try {
+ return this.findCases([caseRef.value].flatten() as List)
+ } catch (ClassCastException e) {
+ log.error("Method cannot be used with field with id [${caseRef.importId}].", e)
+ return []
+ }
+ }
+
+ /**
+ * Finds cases referenced by a dataField in its value.
+ *
+ * Use this overload when working on a case not from the current action context. For working with fields from the current
+ * action context see other overloads of this action.
+ *
+ *
If the field value is {@code null}, this method returns an empty list.
+ *
If the value cannot be converted to case IDs, this method returns an empty list.
+ *
+ * @param caseRef field whose value contains case IDs, may be of types
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#CASE_REF},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#ENUMERATION_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#STRING_COLLECTION},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#TEXT},
+ * @return list of matching cases, or an empty list when the field value is {@code null}
+ * @see ActionDelegate#findCases(Field)
+ * @see ActionDelegate#findCases(List)
+ * @see ActionDelegate#findCases(Closure)
+ * @see ActionDelegate#findCases(Closure, Pageable)
+ */
+ List findCases(DataField caseRef) {
+ if(caseRef.value == null) {
+ log.debug("[findCases(DataField]: Value of field is null, returning empty list.")
+ return []
+ }
+ try {
+ return this.findCases([caseRef.value].flatten() as List)
+ } catch (ClassCastException e) {
+ log.error("Method cannot be used with field.", e)
+ return []
+ }
+ }
+
+
+ /**
+ * Finds cases by their MongoDB IDs.
+ *
+ * @param mongoIds list of case IDs
+ * @return list of matching cases, or an empty list when the input is {@code null} or {@code empty}
+ * @see ActionDelegate#findCases(Field)
+ * @see ActionDelegate#findCases(DataField)
+ * @see ActionDelegate#findCases(Closure)
+ * @see ActionDelegate#findCases(Closure, Pageable)
+ */
+ List findCases(List mongoIds) {
+ if(mongoIds == null || mongoIds.empty) {
+ log.debug("[findCases(List)]: Null value detected, returning empty list.")
+ return []
+ }
+ return workflowService.findAllById(mongoIds)
+ }
+
Case findCase(Closure predicate) {
QCase qCase = new QCase("case")
return workflowService.searchOne(predicate(qCase))
}
+
+
+ /**
+ * Finds the first case referenced by a field in its value.
+ *
+ * Use this overload when working on a case from current action context. For working with fields from out of the
+ * current action context see other overloads of this action.
+ *
+ *
If the field value is {@code null}, this method returns {@code null}.
+ *
If the value cannot be converted to case IDs, this method returns {@code null}.
+ *
+ * @param caseRef field whose value contains case IDs, may be of types
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#CASE_REF},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#ENUMERATION_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#STRING_COLLECTION},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#TEXT},
+ * @return referenced case, or {@code null} when the field value is invalid
+ * @see ActionDelegate#findCase(DataField)
+ * @see ActionDelegate#findCase(String)
+ * @see ActionDelegate#findCase(Closure)
+ */
+ Case findCase(Field caseRef) {
+ if(caseRef.value == null) {
+ log.debug("[findCase(Field]: Value of field with id [${caseRef.importId}] is null, returning null.")
+ return null
+ }
+ try {
+ List castValue = [caseRef.value].flatten() as List
+ if(castValue.size() == 0) {
+ log.debug("[findCase(Field]: Value of field with id [${caseRef.importId}] does not contain at least one element, returning null.")
+ return null
+ }
+ return this.findCase(castValue[0])
+ } catch (ClassCastException e) {
+ log.error("Method cannot be used with field with id [${caseRef.importId}].", e)
+ return null
+ }
+ }
+
+
+ /**
+ * Finds the first case referenced by a dataField in its value.
+ *
+ * Use this overload when working on a case from out of current action context. For working with fields from the current
+ * action context see other overloads of this action.
+ *
+ *
If the field value is {@code null}, this method returns {@code null}.
+ *
If the value cannot be converted to case IDs, this method returns {@code null}.
+ *
+ * @param caseRef field whose value contains case IDs, may be of types
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#CASE_REF},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#ENUMERATION_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#STRING_COLLECTION},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#TEXT},
+ * @return referenced case, or {@code null} when the dataField value is invalid
+ * @see ActionDelegate#findCase(Field)
+ * @see ActionDelegate#findCase(String)
+ * @see ActionDelegate#findCase(Closure)
+ */
+ Case findCase(DataField caseRef) {
+ if(caseRef.value == null) {
+ log.debug("[findCase(DataField)]: Value of field is null, returning null.")
+ return null
+ }
+ try {
+ List castValue = [caseRef.value].flatten() as List
+ if(castValue.size() == 0) {
+ log.debug("[findCase(DataField)]: Value of field does not contain at least one element, returning null.")
+ return null
+ }
+ return this.findCase(castValue[0])
+ } catch (ClassCastException e) {
+ log.error("Method cannot be used with field.", e)
+ return null
+ }
+ }
+
+ /**
+ * Finds case by its MongoDB ID.
+ *
+ * @param mongoId case IDs
+ * @return resulting case
+ * @see ActionDelegate#findCase(Field)
+ * @see ActionDelegate#findCase(DataField)
+ * @see ActionDelegate#findCase(Closure)
+ */
+ Case findCase(String mongoId) {
+ return workflowService.findOne(mongoId)
+ }
+
Case createCase(String identifier, String title = null, String color = "", IUser author = userService.loggedOrSystem, Locale locale = LocaleContextHolder.getLocale(), Map params = [:]) {
return workflowService.createCaseByIdentifier(identifier, title, color, author.transformToLoggedUser(), locale, params).getCase()
}
@@ -913,19 +1124,70 @@ class ActionDelegate {
return outcome.getCase()
}
+ /**
+ * Deletes a case by its MongoDB ID.
+ *
+ * @param mongoId case identifier
+ * @return deleted case, or {@code null} when the input is {@code null}
+ */
+ Case deleteCase(String mongoId) {
+ if(mongoId == null){
+ log.debug("[deleteCase(String)]: Null value detected, returning null.")
+ return null
+ }
+ return this.deleteCase(workflowService.findOne(mongoId))
+ }
+
+ /**
+ * Deletes the provided case.
+ *
+ * @param toDelete case to delete
+ * @return deleted case, or {@code null} when the input is {@code null}
+ */
+ Case deleteCase(Case toDelete) {
+ if(toDelete == null){
+ log.debug("[deleteCase(Case)]: Null value detected, returning null.")
+ return null
+ }
+ return workflowService.deleteCase(toDelete).case
+ }
+
Task assignTask(String transitionId, Case aCase = useCase, IUser user = userService.loggedOrSystem, Map params = [:]) {
String taskId = getTaskId(transitionId, aCase)
- AssignTaskEventOutcome outcome = taskService.assignTask(user.transformToLoggedUser(), taskId, params)
- this.outcomes.add(outcome)
- return outcome.getTask()
+ return addTaskOutcomeAndReturnTask(taskService.assignTask(user.transformToLoggedUser(), taskId, params))
}
Task assignTask(Task task, IUser user = userService.loggedOrSystem, Map params = [:]) {
return addTaskOutcomeAndReturnTask(taskService.assignTask(task, user, params))
}
- void assignTasks(List tasks, IUser assignee = userService.loggedOrSystem, Map params = [:]) {
- this.outcomes.addAll(taskService.assignTasks(tasks, assignee, params))
+ /**
+ * Assigns tasks for all transitions in the provided list and returns the assigned tasks.
+ *
+ * @param transitionIds transition identifiers whose tasks should be assigned
+ * @param aCase case used to resolve the tasks, defaults to the current case
+ * @param user user to assign the tasks to, defaults to the logged or system user
+ * @param params additional parameters
+ * @return assigned tasks
+ */
+ List assignTasksByTransitions(List transitionIds, Case aCase = useCase, IUser user = userService.loggedOrSystem, Map params = [:]) {
+ List taskIds = getTaskIds(transitionIds, aCase)
+ List tasks = taskService.findAllById(taskIds)
+ return assignTasks(tasks, user, params)
+ }
+
+ /**
+ * Assigns the provided tasks and returns the assigned tasks.
+ *
+ * @param tasks tasks to assign
+ * @param assignee user to assign the tasks to, defaults to the logged or system user
+ * @param params additional parameters
+ * @return assigned tasks
+ */
+ List assignTasks(List tasks, IUser assignee = userService.loggedOrSystem, Map params = [:]) {
+ List outcomes = taskService.assignTasks(tasks, assignee, params)
+ this.outcomes.addAll(outcomes)
+ return outcomes.collect { it.task }
}
Task cancelTask(String transitionId, Case aCase = useCase, IUser user = userService.loggedOrSystem, Map params = [:]) {
@@ -937,8 +1199,34 @@ class ActionDelegate {
return addTaskOutcomeAndReturnTask(taskService.cancelTask(task, user, params))
}
- void cancelTasks(List tasks, IUser user = userService.loggedOrSystem, Map params = [:]) {
- this.outcomes.addAll(taskService.cancelTasks(tasks, user, params))
+
+ /**
+ * Cancels tasks for all transitions in the provided list and returns the canceled tasks.
+ *
+ * @param transitionIds transition identifiers whose tasks should be canceled
+ * @param aCase case used to resolve the tasks, defaults to the current case
+ * @param user user performing the cancellation, defaults to the logged or system user
+ * @param params additional parameters
+ * @return canceled tasks
+ */
+ List cancelTasksByTransitions(List transitionIds, Case aCase = useCase, IUser user = userService.loggedOrSystem, Map params = [:]) {
+ List taskIds = getTaskIds(transitionIds, aCase)
+ List tasks = taskService.findAllById(taskIds)
+ return cancelTasks(tasks, user, params)
+ }
+
+ /**
+ * Cancels the provided tasks and returns the canceled tasks.
+ *
+ * @param tasks tasks to cancel
+ * @param user user performing the cancellation, defaults to the logged or system user
+ * @param params additional parameters
+ * @return canceled tasks
+ */
+ List cancelTasks(List tasks, IUser user = userService.loggedOrSystem, Map params = [:]) {
+ List outcomes = taskService.cancelTasks(tasks, user, params)
+ this.outcomes.addAll(outcomes)
+ return outcomes.collect { it.task }
}
private Task addTaskOutcomeAndReturnTask(TaskEventOutcome outcome) {
@@ -946,17 +1234,42 @@ class ActionDelegate {
return outcome.getTask()
}
- void finishTask(String transitionId, Case aCase = useCase, IUser user = userService.loggedOrSystem, Map params = [:]) {
+ Task finishTask(String transitionId, Case aCase = useCase, IUser user = userService.loggedOrSystem, Map params = [:]) {
String taskId = getTaskId(transitionId, aCase)
- addTaskOutcomeAndReturnTask(taskService.finishTask(user.transformToLoggedUser(), taskId, params))
+ return addTaskOutcomeAndReturnTask(taskService.finishTask(user.transformToLoggedUser(), taskId, params))
+ }
+
+ Task finishTask(Task task, IUser user = userService.loggedOrSystem, Map params = [:]) {
+ return addTaskOutcomeAndReturnTask(taskService.finishTask(task, user, params))
}
- void finishTask(Task task, IUser user = userService.loggedOrSystem, Map params = [:]) {
- addTaskOutcomeAndReturnTask(taskService.finishTask(task, user, params))
+ /**
+ * Finishes tasks for all transitions in the provided list and returns the finished tasks.
+ *
+ * @param transitionIds transition identifiers whose tasks should be finished
+ * @param aCase case used to resolve the tasks, defaults to the current case
+ * @param user user performing the finish operation, defaults to the logged or system user
+ * @param params additional parameters
+ * @return finished tasks
+ */
+ List finishTasksByTransitions(List transitionIds, Case aCase = useCase, IUser user = userService.loggedOrSystem, Map params = [:]) {
+ List taskIds = getTaskIds(transitionIds, aCase)
+ List tasks = taskService.findAllById(taskIds)
+ return finishTasks(tasks, user, params)
}
- void finishTasks(List tasks, IUser finisher = userService.loggedOrSystem, Map params = [:]) {
- this.outcomes.addAll(taskService.finishTasks(tasks, finisher, params))
+ /**
+ * Finishes the provided tasks and returns the finished tasks.
+ *
+ * @param tasks tasks to finish
+ * @param finisher user performing the finish operation, defaults to the logged or system user
+ * @param params additional parameters
+ * @return finished tasks
+ */
+ List finishTasks(List tasks, IUser finisher = userService.loggedOrSystem, Map params = [:]) {
+ List outcomes = taskService.finishTasks(tasks, finisher, params)
+ this.outcomes.addAll(outcomes)
+ return outcomes.collect { it.task }
}
List findTasks(Closure predicate) {
@@ -971,18 +1284,279 @@ class ActionDelegate {
return result.content
}
+ /**
+ * Finds tasks referenced by a field in its value.
+ *
+ * Use this overload when working on a case from current action context. For working with fields from out of the
+ * current action context see other overloads of this action.
+ *
+ *
If the field value is {@code null}, this method returns an empty list.
+ *
If the value cannot be converted to task IDs, this method returns an empty list.
+ *
+ * @param taskRef field whose value contains task IDs
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#TASK_REF},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#ENUMERATION_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#STRING_COLLECTION},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#TEXT},
+ * @return list of matching tasks, or an empty list when the field value is {@code null}
+ * @see ActionDelegate#findTasks(DataField)
+ * @see ActionDelegate#findTasks(List)
+ */
+ List findTasks(Field taskRef) {
+ if(taskRef.value == null) {
+ log.debug("[findTasks(Field)]: Value of field with id [${taskRef.importId}] is null, returning empty list.")
+ return []
+ }
+ try {
+ return this.findTasks([taskRef.value].flatten() as List)
+ } catch (ClassCastException e) {
+ log.error("Method cannot be used with field with id [${taskRef.importId}].", e)
+ return []
+ }
+ }
+
+ /**
+ * Finds tasks referenced by a dataField in its value.
+ *
+ * Use this overload when working on a case not from the current action context. For working with fields from out of the
+ * current action context see other overloads of this action.
+ *
+ *
If the field value is {@code null}, this method returns an empty list.
+ *
If the value cannot be converted to task IDs, this method returns an empty list.
+ *
+ * @param taskRef field whose value contains task IDs
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#TASK_REF},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#ENUMERATION_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#STRING_COLLECTION},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#TEXT},
+ * @return list of matching tasks, or an empty list when the field value is {@code null}
+ * @see ActionDelegate#findTasks(Field)
+ * @see ActionDelegate#findTasks(List)
+ */
+ List findTasks(DataField taskRef) {
+ if(taskRef.value == null) {
+ log.debug("[findTasks(DataField)]: Value of field is null, returning empty list.")
+ return []
+ }
+ try {
+ return this.findTasks([taskRef.value].flatten() as List)
+ } catch (ClassCastException e) {
+ log.error("Method cannot be used with field.", e)
+ return []
+ }
+ }
+
+ /**
+ * Finds tasks by their MongoDB IDs.
+ *
+ * @param mongoIds task identifiers
+ * @return list of matching tasks, or an empty list when the input is {@code null} or {@code empty}
+ * @see ActionDelegate#findTasks(Field)
+ * @see ActionDelegate#findTasks(DataField)
+ */
+ List findTasks(List mongoIds) {
+ if(mongoIds == null || mongoIds.empty) {
+ log.debug("[findTasks(List)]: Null value detected, returning empty list.")
+ return []
+ }
+ return taskService.findAllById(mongoIds)
+ }
+
Task findTask(Closure predicate) {
QTask qTask = new QTask("task")
return taskService.searchOne(predicate(qTask))
}
Task findTask(String mongoId) {
- return taskService.searchOne(QTask.task._id.eq(new ObjectId(mongoId)))
+ return taskService.findOne(mongoId)
+ }
+
+ /**
+ * Finds the first task referenced by a field in its value.
+ *
+ * Use this overload when working on a case from the current action context. For working with fields from out of the
+ * current action context see other overloads of this action.
+ *
+ *
If the field value is {@code null}, this method returns {@code null}.
+ *
If the field contains no value or the value cannot be converted to a task ID, this method returns
+ * {@code null}.
+ *
+ * @param taskRef field whose value contains a task ID
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#TASK_REF},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#ENUMERATION_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#STRING_COLLECTION},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#TEXT},
+ * @return referenced task, or {@code null} when the field value is invalid
+ * @see ActionDelegate#findTask(DataField)
+ * @see ActionDelegate#findTask(String)
+ * @see ActionDelegate#findTask(Closure)
+ */
+ Task findTask(Field taskRef) {
+ if(taskRef.value == null) {
+ log.debug("[findTask(Field)]: Value of field with id [${taskRef.importId}] is null, returning null")
+ return null
+ }
+ try {
+ List castValue = [taskRef.value].flatten() as List
+ if(castValue.size() == 0) {
+ log.debug("[findTask(Field)]: Value of field with id [${taskRef.importId}] does not contain at least one element, returning null.")
+ return null
+ }
+ return this.findTask(castValue[0])
+ } catch (ClassCastException e) {
+ log.error("Method cannot be used with field with id [${taskRef.importId}].", e)
+ return null
+ }
+ }
+
+ /**
+ * Finds the first task referenced by a dataField in its value.
+ *
+ * Use this overload when working on a case not from the current action context. For working with fields from out of the
+ * current action context see other overloads of this action.
+ *
+ *
If the field value is {@code null}, this method returns {@code null}.
+ *
If the field contains no value or the value cannot be converted to a task ID, this method returns
+ * {@code null}.
+ *
+ * @param taskRef field whose value contains a task ID
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#TASK_REF},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#MULTICHOICE_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#ENUMERATION_MAP},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#STRING_COLLECTION},
+ * {@link com.netgrif.application.engine.petrinet.domain.dataset.FieldType#TEXT},
+ * @return referenced task, or {@code null} when the field value is invalid
+ * @see ActionDelegate#findTask(Field)
+ * @see ActionDelegate#findTask(String)
+ * @see ActionDelegate#findTask(Closure)
+ */
+ Task findTask(DataField taskRef) {
+ if(taskRef.value == null) {
+ log.debug("[findTask(DataField)]: Value of field is null, returning null")
+ return null
+ }
+ try {
+ List castValue = [taskRef.value].flatten() as List
+ if(castValue.size() == 0) {
+ log.debug("[findTask(DataField)]: Value of field does not contain at least one element, returning null.")
+ return null
+ }
+ return this.findTask(castValue[0])
+ } catch (ClassCastException e) {
+ log.error("Method cannot be used with field.", e)
+ return null
+ }
+ }
+
+ /**
+ * Finds a Petri net by its MongoDB ID.
+ *
+ * @param mongoId Petri net identifier
+ * @return matching Petri net, or {@code null} when the input is {@code null}
+ */
+ PetriNet findPetriNet(String mongoId) {
+ if(mongoId == null){
+ log.debug("[findPetriNet(String)]: Null value detected, returning null.")
+ return null
+ }
+ return petriNetService.getPetriNet(mongoId)
+ }
+
+ /**
+ * Finds a Petri net by its {@link ObjectId}.
+ *
+ * @param objectId Petri net object identifier
+ * @return matching Petri net, or {@code null} when the input is {@code null}
+ */
+ PetriNet findPetriNet(ObjectId objectId) {
+ if(objectId == null){
+ log.debug("[findPetriNet(ObjectId)]: Null value detected, returning null.")
+ return null
+ }
+ return petriNetService.get(objectId)
+ }
+
+ /**
+ * Finds Petri nets by their MongoDB IDs.
+ *
+ * @param mongoIds list of Petri net identifiers
+ * @return matching Petri nets, or an empty list when the input is {@code null} or {@code empty}
+ */
+ List findPetriNets(List mongoIds) {
+ if(mongoIds == null || mongoIds.empty){
+ log.debug("[findPetriNets(List)]: Null value detected, returning empty list.")
+ return []
+ }
+ return petriNetService.findAllById(mongoIds)
+ }
+
+ /**
+ * Finds Petri nets by their {@link ObjectId} values.
+ *
+ * @param objectIds list of Petri net object identifiers
+ * @return matching Petri nets, or an empty list when the input is {@code null} or {@code empty}
+ */
+ List findPetriNetsByObjectIds(List objectIds) {
+ if(objectIds == null || objectIds.empty){
+ log.debug("[findPetriNetsByObjectIds(List)]: Null value detected, returning empty list.")
+ return []
+ }
+ return petriNetService.get(objectIds as Collection)
+ }
+
+ /**
+ * Finds a Petri net by its identifier and optional version.
+ *
+ * If the version is not provided, the newest available version is returned.
+ *
+ * @param identifier Petri net identifier
+ * @param version requested version, or {@code null} for the newest version
+ * @return matching Petri net, or {@code null} when the identifier is {@code null}
+ */
+ PetriNet findPetriNetByIdentifier(String identifier, Version version = null) {
+ if(identifier == null) {
+ log.debug("[findPetriNetByIdentifier(String, Version)]: Null identifier value detected, returning null.")
+ return null
+ }
+ return version == null ? petriNetService.getNewestVersionByIdentifier(identifier) : petriNetService.getPetriNet(identifier, version)
+ }
+
+ /**
+ * Converts cases to a map of option keys and translated option values.
+ *
+ * @param casesToTransform cases to convert
+ * @param valueTransformation transformation used to derive the option label from a case, case title is used if not specified otherwise
+ * @param keyTransformation transformation used to derive the option key from a case, case stringId is used if not specified otherwise
+ * @return map of option keys and translated values
+ */
+ Map casesToOptions(List casesToTransform, Closure valueTransformation = { return it.title }, Closure keyTransformation = { return it.stringId }) {
+ return casesToTransform.collectEntries {
+ [(keyTransformation(it)): new I18nString(valueTransformation(it))]
+ }
}
String getTaskId(String transitionId, Case aCase = useCase) {
List refs = taskService.findAllByCase(aCase.stringId, null)
- refs.find { it.transitionId == transitionId }.stringId
+ return refs.find { it.transitionId == transitionId }.stringId
+ }
+
+ /**
+ * Returns task identifiers for tasks belonging to the provided transitions in the given case.
+ *
+ * @param transitionIds transition identifiers
+ * @param aCase case whose tasks should be inspected, defaults to the current case
+ * @return list of matching task identifiers
+ */
+ List getTaskIds(List transitionIds, Case aCase = useCase) {
+ List refs = taskService.findAllByCase(aCase.stringId, null)
+ return refs.findAll { transitionIds.contains(it.transitionId) }.collect { it.stringId}
}
IUser assignRole(String roleMongoId, IUser user = userService.loggedUser) {
@@ -1040,8 +1614,7 @@ class ActionDelegate {
}
SetDataEventOutcome setData(String transitionId, Case useCase, Map dataSet, Map params = [:]) {
- def predicate = QTask.task.caseId.eq(useCase.stringId) & QTask.task.transitionId.eq(transitionId)
- def task = taskService.searchOne(predicate)
+ Task task = taskService.findOne(useCase.tasks.find { it.transition == transitionId }.task)
return addSetDataOutcomeToOutcomes(dataService.setData(task.stringId, ImportHelper.populateDataset(dataSet), params))
}
@@ -1378,25 +1951,6 @@ class ActionDelegate {
return new DynamicValidation(rule, message)
}
- List findFilters(String userInput) {
- return filterSearchService.autocompleteFindFilters(userInput)
- }
-
- List findAllFilters() {
- return filterSearchService.autocompleteFindFilters("")
- }
-
- FileFieldValue exportFilters(Collection filtersToExport) {
- if (filtersToExport.isEmpty()) {
- return null
- }
- return filterImportExportService.exportFiltersToFile(filtersToExport)
- }
-
- List importFilters() {
- return filterImportExportService.importFilters()
- }
-
File exportCasesToFile(Closure predicate, String pathName, ExportDataConfig config = null,
int pageSize = exportConfiguration.getMongoPageSize()) {
File exportFile = new File(pathName)
@@ -1509,8 +2063,8 @@ class ActionDelegate {
}
/**
- * Action API case search function using Elasticsearch database
- * @param requests the CaseSearchRequest list
+ * Action API task search function using Elasticsearch database
+ * @param requests the @link{ElasticTaskSearchRequest} list
* @param loggedUser the user who is searching for the requests
* @param page the order of page to return. by default it returns the first page
* @param pageable the page configuration that will contain the requests
@@ -1518,13 +2072,13 @@ class ActionDelegate {
* @param isIntersection to decide null query handling
* @return page of cases
* */
- Page findTasks(List requests, LoggedUser loggedUser = userService.loggedOrSystem.transformToLoggedUser(),
+ Page findTasksElastic(List requests, LoggedUser loggedUser = userService.loggedOrSystem.transformToLoggedUser(),
int page = 1, int pageSize = 25, Locale locale = Locale.default, boolean isIntersection = false) {
return elasticTaskService.search(requests, loggedUser, PageRequest.of(page, pageSize), locale, isIntersection)
}
/**
- * Action API case search function using Elasticsearch database
+ * Action API task search function using Elasticsearch database
* @param request case search request
* @param loggedUser the user who is searching for the requests
* @param page the order of page to return. by default it returns the first page
@@ -1533,384 +2087,33 @@ class ActionDelegate {
* @param isIntersection to decide null query handling
* @return page of cases
* */
- Page findTasks(Map request, LoggedUser loggedUser = userService.loggedOrSystem.transformToLoggedUser(),
+ Page findTasksElastic(Map request, LoggedUser loggedUser = userService.loggedOrSystem.transformToLoggedUser(),
int page = 1, int pageSize = 25, Locale locale = Locale.default, boolean isIntersection = false) {
List requests = Collections.singletonList(new ElasticTaskSearchRequest(request))
- return findTasks(requests, loggedUser, page, pageSize, locale, isIntersection)
- }
-
- List findDefaultFilters() {
- if (!createDefaultFilters) {
- return []
- }
- return findCases({ it.processIdentifier.eq(FilterRunner.FILTER_PETRI_NET_IDENTIFIER).and(it.author.id.eq(userService.system.stringId)) })
+ return findTasksElastic(requests, loggedUser, page, pageSize, locale, isIntersection)
}
/**
- * Creates filter instance of type {@value DefaultFiltersRunner#FILTER_TYPE_CASE}
- *
- * @param title filter case title
- * @param query elastic query for the view
- * @param icon filter case icon
- * @param allowedNets List of process identifiers
- * @param visibility Possible values: {@value DefaultFiltersRunner#FILTER_VISIBILITY_PRIVATE} or {@value DefaultFiltersRunner#FILTER_VISIBILITY_PUBLIC}
- * @param filterMetadata metadata for filter. If no value is provided, then default value is used: {@link #defaultFilterMetadata(String)}
- *
- * @return created {@link Case} instance of filter
- */
- @NamedVariant
- Case createCaseFilter(def title, String query, List allowedNets,
- String icon = "", String visibility = DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE, def filterMetadata = null) {
- return createFilter(title, query, DefaultFiltersRunner.FILTER_TYPE_CASE, allowedNets, icon, visibility, filterMetadata)
- }
-
- /**
- * Creates filter instance of type {@value DefaultFiltersRunner#FILTER_TYPE_TASK}
- *
- * @param title filter case title
- * @param query elastic query for the view
- * @param icon filter case icon
- * @param allowedNets List of process identifiers
- * @param visibility Possible values: {@value DefaultFiltersRunner#FILTER_VISIBILITY_PRIVATE} or {@value DefaultFiltersRunner#FILTER_VISIBILITY_PUBLIC}
- * @param filterMetadata metadata for filter. If no value is provided, then default value is used: {@link #defaultFilterMetadata(String)}
- *
- * @return created {@link Case} instance of filter
- */
- @NamedVariant
- Case createTaskFilter(def title, String query, List allowedNets,
- String icon = "", String visibility = DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE, def filterMetadata = null) {
- return createFilter(title, query, DefaultFiltersRunner.FILTER_TYPE_TASK, allowedNets, icon, visibility, filterMetadata)
- }
-
- /**
- * Creates filter instance.
- *
- * @param title filter case title
- * @param query elastic query for the view
- * @param type Filter type. Possible values: {@value DefaultFiltersRunner#FILTER_TYPE_CASE} or {@value DefaultFiltersRunner#FILTER_TYPE_TASK}
- * @param icon filter case icon
- * @param allowedNets List of process identifiers
- * @param visibility Possible values: {@value DefaultFiltersRunner#FILTER_VISIBILITY_PRIVATE} or {@value DefaultFiltersRunner#FILTER_VISIBILITY_PUBLIC}
- * @param filterMetadata metadata for filter. If no value is provided, then default value is used: {@link #defaultFilterMetadata(String)}
- *
- * @return created {@link Case} instance of filter
- */
- @NamedVariant
- Case createFilter(def title, String query, String type, List allowedNets,
- String icon, String visibility, def filterMetadata) {
- 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)
- }
-
- /**
- * Changes data of provided filter instance. These attributes can be changed:
- *
changeMenuItem item uri { "/my_node1/my_node2" }
*
changeMenuItem item title { new I18nString("New title") }
*
changeMenuItem item title { "New title" }
*
changeMenuItem item menuIcon { "filter_alt" }
*
changeMenuItem item tabIcon { "filter_none" }
*
- * @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)
+ [allowedRoles : { cl ->
+ updateMenuItemRoles(item, cl as Closure, MenuItemConstants.FIELD_ALLOWED_ROLES)
},
- bannedRoles : { cl ->
- updateMenuItemRoles(item, cl as Closure, MenuItemConstants.PREFERENCE_ITEM_FIELD_BANNED_ROLES.attributeId)
+ bannedRoles : { cl ->
+ updateMenuItemRoles(item, cl as Closure, MenuItemConstants.FIELD_BANNED_ROLES)
},
- 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]
- ])
- },
- uri : { cl ->
+ uri : { cl ->
def uri = cl() as String
def aCase = useCase
if (useCase == null || item.stringId != useCase.stringId) {
@@ -1918,41 +2121,35 @@ class ActionDelegate {
}
moveMenuItem(aCase, uri)
},
- title : { cl ->
+ 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_SYNC_ID, item, [
+ (MenuItemConstants.FIELD_MENU_NAME): ["type": "i18n", "value": newName]
])
},
- menuIcon : { cl ->
+ 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_SYNC_ID, item, [
+ (MenuItemConstants.FIELD_MENU_ICON): ["type": "text", "value": value]
])
},
- tabIcon : { cl ->
+ 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]
+ setData(MenuItemConstants.TRANS_SYNC_ID, item, [
+ (MenuItemConstants.FIELD_TAB_ICON): ["type": "text", "value": value]
])
},
- requireTitleInCreation: { cl ->
+ useCustomView : { 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_SYNC_ID, item, [
+ (MenuItemConstants.FIELD_USE_CUSTOM_VIEW): ["type": "boolean", "value": value]
])
},
- useCustomView : { cl ->
+ customViewSelector: { 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]
- ])
- },
- 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_SYNC_ID, item, [
+ (MenuItemConstants.FIELD_CUSTOM_VIEW_SELECTOR): ["type": "text", "value": value]
])
}]
@@ -1981,202 +2178,30 @@ class ActionDelegate {
}
}
- /**
- * simplifies the process of creating a filter, menu item
- * @param uri
- * @param identifier - unique identifier of menu item
- * @param title
- * @param query
- * @param icon
- * @param type - "Case" or "Task"
- * @param allowedNets
- * @param groupName - name of group to add menu item to
- * @param allowedRoles
- * @param bannedRoles
- * @param visibility - "private" or "public"
- * @return
- */
- @Deprecated
- Case createFilterInMenu(String uri, String identifier, def title, String query, String type,
- List allowedNets,
- String groupName,
- Map allowedRoles = [:],
- Map bannedRoles = [:],
- List defaultHeaders = [],
- String icon = "",
- String visibility = DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE) {
- Case filter = createFilter(title, query, type, allowedNets, icon, visibility, null)
- Case menuItem = createMenuItem(uri, identifier, filter, groupName, allowedRoles, bannedRoles, defaultHeaders)
- return menuItem
- }
-
- /**
- * simplifies the process of creating a filter, menu item
- * @param uri
- * @param identifier - unique identifier of menu item
- * @param title
- * @param query
- * @param icon
- * @param type - "Case" or "Task"
- * @param allowedNets
- * @param allowedRoles
- * @param bannedRoles
- * @param visibility - "private" or "public"
- * @param orgGroup - group to add item to, if null default group is used
- * @return
- */
- @Deprecated
- Case createFilterInMenu(String uri, String identifier, def title, String query, String type, List allowedNets,
- Map allowedRoles = [:],
- Map bannedRoles = [:],
- List defaultHeaders,
- String icon = "",
- String visibility = DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE,
- Case orgGroup = null) {
- Case filter = createFilter(title, query, type, allowedNets, icon, visibility, null)
- Case menuItem = createMenuItem(uri, identifier, filter, allowedRoles, bannedRoles, orgGroup, defaultHeaders)
- return menuItem
- }
-
- /**
- * Creates filter and preference_item instances with given parameters.
- *
- * @param uri resource where the item is located in
- * @param itemIdentifier unique identifier of item
- * @param itemAndFilterName displayed label in menu and tab
- * @param filterQuery elastic query for filter
- * @param filterType type of filter. Possible values: {@value DefaultFiltersRunner#FILTER_TYPE_CASE} or
- * {@value DefaultFiltersRunner#FILTER_TYPE_TASK}
- * @param filterVisibility possible values: {@value DefaultFiltersRunner#FILTER_VISIBILITY_PRIVATE} or
- * {@value DefaultFiltersRunner#FILTER_VISIBILITY_PUBLIC}
- * @param filterAllowedNets List of allowed nets. Element of list is process identifier
- * @param itemAndFilterIcon displayed icon in menu and tab
- * @param itemAllowedRoles Map of roles, which have access to the item. Key is role_id in XML and value is process
- * identifier where the role exists
- * @param itemBannedRoles Map of roles, which don't have access to the item. Key is role_id in XML and value is process
- * identifier where the role exists
- * @param itemCaseDefaultHeaders List of headers displayed in case view
- * @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
- * */
- @NamedVariant
- Case createFilterInMenu(String uri, String itemIdentifier, def itemAndFilterName, String filterQuery,
- String filterType, String filterVisibility, List filterAllowedNets = [],
- 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)
- return menuItem
- }
-
- /**
- * Creates filter and preference_item instances with given parameters.
- *
- * @param body configuration class for menu item creation
- * @param filterQuery elastic query for filter
- * @param filterType type of filter. Possible values: {@value DefaultFiltersRunner#FILTER_TYPE_CASE} or
- * {@value DefaultFiltersRunner#FILTER_TYPE_TASK}
- * @param filterVisibility possible values: {@value DefaultFiltersRunner#FILTER_VISIBILITY_PRIVATE} or
- * {@value DefaultFiltersRunner#FILTER_VISIBILITY_PUBLIC}
- * @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
- * */
- 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
- 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
- }
- }
-
- 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 menuItemService.createMenuItem(body)
+ }
+
+ protected ViewBody createLegacyMenuItemViews(FilterBody filterBody, List caseDefaultHeaders = null,
+ List taskDefaultHeaders = null) {
+ if (filterBody.getType() == "Case") {
+ ViewBody caseView = new CaseViewBody()
+ caseView.setFilterBody(filterBody)
+ caseView.setDefaultHeaders(caseDefaultHeaders)
+ caseView.setRequireTitleInCreation(true)
+
+ ViewBody taskView = new TaskViewBody()
+ taskView.setDefaultHeaders(taskDefaultHeaders)
+ caseView.setChainedView(taskView)
+
+ return caseView
+ } else if (filterBody.getType() == "Task") {
+ ViewBody taskView = new TaskViewBody()
+ taskView.setFilterBody(filterBody)
+ taskView.setDefaultHeaders(taskDefaultHeaders)
+ return taskView
}
-
- return folder
+ return null
}
/**
@@ -2184,219 +2209,49 @@ 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)
}
/**
- * Finds filter by name
+ * Finds menu item by unique identifier
*
- * @param name Title of the filter
+ * @param menuItemIdentifier unique menu item identifier
*
- * @return found filter instance. Can be null
+ * @return found menu_item instance. Can be null
*/
- Case findFilter(String name) {
- return findCaseElastic("processIdentifier:$FilterRunner.FILTER_PETRI_NET_IDENTIFIER AND title.keyword:\"$name\"" as String)
+ Case findMenuItem(String menuItemIdentifier) {
+ return menuItemService.findMenuItem(menuItemIdentifier)
}
/**
- * Finds menu item by unique identifier
+ * @param node uri node
*
- * @param menuItemIdentifier unique menu item identifier
- *
- * @return found preference_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 folder menu item case by provided UriNode
+ * */
+ Case findFolderCase(UriNode node) {
+ return menuItemService.findFolderCase(node)
}
/**
@@ -2407,7 +2262,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 +2272,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 +2299,55 @@ 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
+ Case createDashboardManagement(DashboardManagementBody body) {
+ return dashboardManagementService.createDashboardManagement(body)
+ }
+
+ Case createDashboardItem(DashboardItemBody body) {
+ return dashboardItemService.getOrCreate(body)
+ }
+
+ Case findDashboardManagement(String identifier) {
+ return dashboardManagementService.findDashboardManagement(identifier)
+ }
+
+ Case findDashboardItem(String identifier) {
+ return dashboardItemService.findById(identifier)
+ }
+
+ Case updateDashboardManagement(Case managementCase, DashboardManagementBody body) {
+ return dashboardManagementService.updateDashboardManagement(managementCase, body)
+ }
+
+ Case updateDashboardItem(Case itemCase, DashboardItemBody body) {
+ return dashboardItemService.update(itemCase, body)
}
/**
* 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 +2356,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 +2386,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
@@ -2530,148 +2407,10 @@ class ActionDelegate {
} as Map
}
- private void updateFilter(Case filter, Map dataSet) {
- setData(DefaultFiltersRunner.DETAILS_TRANSITION, filter, dataSet)
- }
-
I18nString i18n(String value, Map translations) {
return new I18nString(value, translations)
}
- @Deprecated
- Map createMenuItem(String id, String uri, String query, String icon, String title, List allowedNets, Map roles, Map bannedRoles = [:], Case group = null, List defaultHeaders = []) {
- if (existsMenuItem(id)) {
- log.info("$id menu exists")
- return null
- }
- Case filter = createCaseFilter(title, query, allowedNets, icon, DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE)
- Case menu = createMenuItem(uri, id, filter, roles, bannedRoles, group, defaultHeaders)
- return [
- "filter" : filter,
- "menuItem": menu
- ]
- }
-
- @Deprecated
- Map createTaskMenuItem(String id, String uri, String query, String icon, String title, List allowedNets, Map roles, Case group = null, List defaultHeaders = []) {
- if (existsMenuItem(id)) {
- log.info("$id menu exists")
- return null
- }
- Case filter = createTaskFilter(title, query, allowedNets, icon, DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE)
- Case menu = createMenuItem(uri, id, filter, roles, [:], group, defaultHeaders)
- return [
- "filter" : filter,
- "menuItem": menu
- ]
- }
-
- @Deprecated
- Case createOrUpdateCaseMenuItem(String id, String uri, String query, String icon, String title, List allowedNets, Map roles = [:], Map bannedRoles = [:], Case group = null, List defaultHeaders = []) {
- return createOrUpdateMenuItemAndFilter(uri, id, title, query, DefaultFiltersRunner.FILTER_TYPE_CASE,
- DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE, allowedNets, icon, roles, bannedRoles, defaultHeaders)
- }
-
- @Deprecated
- Case createOrUpdateTaskMenuItem(String id, String uri, String query, String icon, String title, List allowedNets, Map roles = [:], Map bannedRoles = [:], Case group = null, List defaultHeaders = []) {
- return createOrUpdateMenuItemAndFilter(uri, id, title, query, DefaultFiltersRunner.FILTER_TYPE_TASK,
- DefaultFiltersRunner.FILTER_VISIBILITY_PRIVATE, allowedNets, icon, roles, bannedRoles, defaultHeaders)
- }
-
- @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)
-
- 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)
- }
- }
-
- /**
- * Creates or updates menu item with given identifier.
- *
- * @param uri resource where the item is located in
- * @param identifier unique identifier of item
- * @param name displayed label in menu and tab
- * @param icon displayed icon in menu and tab
- * @param filter Case instance of filter.xml
- * @param allowedRoles Map of roles, which have access to the item. Key is role_id in XML and value is process
- * identifier where the role exists
- * @param bannedRoles Map of roles, which don't have access to the item. Key is role_id in XML and value is process
- * identifier where the role exists
- * @param caseDefaultHeaders List of headers displayed in case view
- * @param taskDefaultHeaders List of headers displayed in task view
- *
- * @return created or updated menu item instance
- * */
- 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)
-
- return createOrUpdateMenuItem(body)
- }
-
- /**
- * Creates or updates menu item with given identifier along with the filter instance. It's safe to use on existing
- * menu item instance, that doesn't contain filter. In such case, missing filter will be created with provided
- * parameters.
- *
- * @param uri resource where the item is located in
- * @param itemIdentifier unique identifier of item
- * @param itemAndFilterName displayed label in menu and tab
- * @param filterQuery elastic query for filter
- * @param filterType type of filter. Possible values: {@value DefaultFiltersRunner#FILTER_TYPE_CASE} or
- * {@value DefaultFiltersRunner#FILTER_TYPE_TASK}
- * @param filterVisibility possible values: {@value DefaultFiltersRunner#FILTER_VISIBILITY_PRIVATE} or
- * {@value DefaultFiltersRunner#FILTER_VISIBILITY_PUBLIC}
- * @param filterAllowedNets List of allowed nets. Element of list is process identifier
- * @param itemAndFilterIcon displayed icon in menu and tab
- * @param itemAllowedRoles Map of roles, which have access to the item. Key is role_id in XML and value is process
- * identifier where the role exists
- * @param itemBannedRoles Map of roles, which don't have access to the item. Key is role_id in XML and value is process
- * identifier where the role exists
- * @param itemCaseDefaultHeaders List of headers displayed in case view
- * @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 or updated menu item instance along with the actual filter
- * */
- Case createOrUpdateMenuItemAndFilter(String uri, String itemIdentifier, def itemAndFilterName, String filterQuery,
- String filterType, String filterVisibility, List filterAllowedNets = [],
- String itemAndFilterIcon = "filter_none", Map itemAllowedRoles = [:],
- Map itemBannedRoles = [:], List itemCaseDefaultHeaders = [],
- List itemTaskDefaultHeaders = [], def filterMetadata = null) {
- MenuItemBody body = new MenuItemBody(uri, itemIdentifier, itemAndFilterName, itemAndFilterIcon)
- body.allowedRoles = collectRolesForPreferenceItem(itemAllowedRoles)
- body.bannedRoles = collectRolesForPreferenceItem(itemBannedRoles)
- body.caseDefaultHeaders = itemCaseDefaultHeaders
- body.taskDefaultHeaders = itemTaskDefaultHeaders
-
- return createOrUpdateMenuItemAndFilter(body, filterQuery, filterType, filterVisibility, filterAllowedNets,
- filterMetadata)
- }
-
/**
* Creates or updates menu item with given identifier.
*
@@ -2680,51 +2419,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)
- }
- }
-
- /**
- * Creates or updates menu item with given identifier along with the filter instance. It's safe to use on existing
- * menu item instance, that doesn't contain filter. In such case, missing filter will be created with provided
- * parameters.
- *
- * @param body data for menu item
- * @param filterQuery elastic query for filter
- * @param filterType type of filter. Possible values: {@value DefaultFiltersRunner#FILTER_TYPE_CASE} or
- * {@value DefaultFiltersRunner#FILTER_TYPE_TASK}
- * @param filterVisibility possible values: {@value DefaultFiltersRunner#FILTER_VISIBILITY_PRIVATE} or
- * {@value DefaultFiltersRunner#FILTER_VISIBILITY_PUBLIC}
- * @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 or updated menu item instance along with the actual filter
- * */
- 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)
- }
-
- return updateMenuItem(item, body)
- } else {
- return createFilterInMenu(body, filterQuery, filterType, filterVisibility, filterAllowedNets, filterMetadata)
- }
+ return menuItemService.createOrUpdateMenuItem(body)
}
/**
@@ -2735,38 +2430,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
- }
- }
-
- /**
- * Creates menu item or ignores it if already exists. If existing item does not contain filter, the filter instance
- * is created by provided parameters.
- *
- * @param body configuration class for menu item
- *
- * @return created or existing menu item instance
- * */
- 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
- }
- }
+ return menuItemService.createOrIgnoreMenuItem(body)
}
/**
@@ -2778,18 +2442,11 @@ 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
- ]
+ void removeChildItemFromParent(String folderId, Case childItem) {
+ menuItemService.removeChildItemFromParent(folderId, childItem)
}
String makeUrl(String publicViewUrl = publicViewProperties.url, String identifier) {
@@ -2864,4 +2521,443 @@ class ActionDelegate {
Case taskCase = workflowService.findOne(task.caseId)
return taskCase.getPetriNet().getDataSet().get(fieldId)
}
+
+ PetriNet importPetriNet(String xmlText) {
+ InputStream xmlStream = new ByteArrayInputStream(xmlText.bytes)
+ def outcome = petriNetService.importPetriNet(xmlStream, loggedUser().transformToLoggedUser())
+ return outcome.getNet()
+ }
+
+ /**
+ * Searches for a single {@link Case} matching the given query.
+ *
+ * The query must start with the resource keyword {@code case} (singular).
+ *
+ * Example:
+ *
+ * searchCase("case: processIdentifier eq 'query_test' and data.number_0.value == 3")
+ * searchCase("case: id eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
+ *
+ *
+ * @param query query language string starting with {@code case:}
+ * @return matching {@link Case} or {@code null} if none is found
+ */
+ Case searchCase(String query) {
+ return caseSearchService.searchOne(query)
+ }
+
+ /**
+ * Searches for all {@link Case} instances matching the given query and returns a paged result.
+ *
+ * The query must start with the resource keyword {@code cases} (plural) and may contain
+ * paging and sorting clauses.
+ *
+ * Example:
+ *
+ * pagedSearchCases("cases: processIdentifier eq 'query_test' page 1 size 5 sort by title desc")
+ * pagedSearchCases("cases: author eq 'user@mail.com' and creationDate gt 2020-03-03")
+ *
+ *
+ * @param query query language string starting with {@code cases:}
+ * @return {@link Page} of matching cases
+ */
+ Page pagedSearchCases(String query) {
+ return caseSearchService.searchAll(query)
+ }
+
+ /**
+ * Searches for all {@link Case} instances matching the given query and returns them as a list.
+ *
+ * The query must start with the resource keyword {@code cases} (plural). This is a convenience
+ * method returning only the content of {@link #pagedSearchCases(String)}.
+ *
+ * Example:
+ *
+ * searchCases("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true")
+ * searchCases("cases: title contains 'Test' sort by creationDate desc")
+ *
+ *
+ * @param query query language string starting with {@code cases:}
+ * @return list of matching cases
+ */
+ List searchCases(String query) {
+ return pagedSearchCases(query).content
+ }
+
+ /**
+ * Counts the number of {@link Case} instances matching the given query.
+ *
+ * The query must start with the resource keyword {@code cases} (plural).
+ *
+ *
+ * @param query query language string starting with {@code cases:}
+ * @return number of matching cases
+ */
+ long countCases(String query) {
+ return caseSearchService.count(query)
+ }
+
+ /**
+ * Checks whether at least one {@link Case} matching the given query exists.
+ *
+ * The query must start with the resource keyword {@code cases} (plural).
+ *
+ * Example:
+ *
+ * existsCase("cases: processIdentifier eq 'query_test'")
+ * existsCase("cases: id in ('5f9b1c2d3e4f5a6b7c8d9e0f', '5f9b1c2d3e4f5a6b7c8d9e10')")
+ *
+ *
+ * @param query query language string starting with {@code cases:}
+ * @return {@code true} if a matching case exists, {@code false} otherwise
+ */
+ boolean existsCase(String query) {
+ return caseSearchService.exists(query)
+ }
+
+ /**
+ * Searches for a single {@link Task} matching the given query.
+ *
+ * The query must start with the resource keyword {@code task} (singular).
+ *
+ * Example:
+ *
+ * searchTask("task: transitionId eq 't1' and caseId eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
+ * searchTask("task: id eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
+ *
+ *
+ * @param query query language string starting with {@code task:}
+ * @return matching {@link Task} or {@code null} if none is found
+ */
+ Task searchTask(String query) {
+ return taskSearchService.searchOne(query)
+ }
+
+ /**
+ * Searches for all {@link Task} instances matching the given query and returns a paged result.
+ *
+ * The query must start with the resource keyword {@code tasks} (plural) and may contain
+ * paging and sorting clauses.
+ *
+ * Example:
+ *
+ * pagedSearchTasks("tasks: title eq 'test' page 0 size 10 sort by lastFinish desc")
+ * pagedSearchTasks("tasks: userId eq 'user1' and state eq enabled")
+ *
+ *
+ * @param query query language string starting with {@code tasks:}
+ * @return {@link Page} of matching tasks
+ */
+ Page pagedSearchTasks(String query) {
+ return taskSearchService.searchAll(query)
+ }
+
+ /**
+ * Searches for all {@link Task} instances matching the given query and returns them as a list.
+ *
+ * The query must start with the resource keyword {@code tasks} (plural). This is a convenience
+ * method returning only the content of {@link #pagedSearchTasks(String)}.
+ *
+ * Example:
+ *
+ * searchTasks("tasks: processId eq 'my_process' and userId in ('user1', 'user2')")
+ * searchTasks("tasks: title contains 'Approve' sort by title asc")
+ *
+ *
+ * @param query query language string starting with {@code tasks:}
+ * @return list of matching tasks
+ */
+ List searchTasks(String query) {
+ return pagedSearchTasks(query).content
+ }
+
+ /**
+ * Counts the number of {@link Task} instances matching the given query.
+ *
+ * The query must start with the resource keyword {@code tasks} (plural).
+ *
+ *
+ * @param query query language string starting with {@code tasks:}
+ * @return number of matching tasks
+ */
+ long countTasks(String query) {
+ return taskSearchService.count(query)
+ }
+
+ /**
+ * Checks whether at least one {@link Task} matching the given query exists.
+ *
+ * The query must start with the resource keyword {@code tasks} (plural).
+ *
+ * Example:
+ *
+ * existsTask("tasks: caseId eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
+ * existsTask("tasks: transitionId eq 't1' and userId not eq 'user1'")
+ *
+ *
+ * @param query query language string starting with {@code tasks:}
+ * @return {@code true} if a matching task exists, {@code false} otherwise
+ */
+ boolean existsTask(String query) {
+ return taskSearchService.exists(query)
+ }
+
+ /**
+ * Searches for a single {@link PetriNet} (process) matching the given query.
+ *
+ * The query must start with the resource keyword {@code process} (singular).
+ *
+ * Example:
+ *
+ * searchProcess("process: identifier == 'query_test'")
+ * searchProcess("process: identifier eq 'my_process' and version eq 1.0.0")
+ *
+ *
+ * @param query query language string starting with {@code process:}
+ * @return matching {@link PetriNet} or {@code null} if none is found
+ */
+ PetriNet searchProcess(String query) {
+ return processSearchService.searchOne(query)
+ }
+
+ /**
+ * Searches for all {@link PetriNet} (process) instances matching the given query and returns a paged result.
+ *
+ * The query must start with the resource keyword {@code processes} (plural) and may contain
+ * paging and sorting clauses.
+ *
+ * Example:
+ *
+ * pagedSearchProcesses("processes: identifier eq 'my_process' page 0 size 10 sort by version desc")
+ * pagedSearchProcesses("processes: version in (1.0.0 : 2.0.0)")
+ *
+ *
+ * @param query query language string starting with {@code processes:}
+ * @return {@link Page} of matching processes
+ */
+ Page pagedSearchProcesses(String query) {
+ return processSearchService.searchAll(query)
+ }
+
+ /**
+ * Searches for all {@link PetriNet} (process) instances matching the given query and returns them as a list.
+ *
+ * The query must start with the resource keyword {@code processes} (plural). This is a convenience
+ * method returning only the content of {@link #pagedSearchProcesses(String)}.
+ *
+ * Example:
+ *
+ * searchProcesses("processes: title contains 'Test' sort by identifier asc")
+ * searchProcesses("processes: identifier in ('process_a', 'process_b')")
+ *
+ *
+ * @param query query language string starting with {@code processes:}
+ * @return list of matching processes
+ */
+ List searchProcesses(String query) {
+ return pagedSearchProcesses(query).content
+ }
+
+ /**
+ * Counts the number of {@link PetriNet} (process) instances matching the given query.
+ *
+ * The query must start with the resource keyword {@code processes} (plural).
+ *
+ *
+ * @param query query language string starting with {@code processes:}
+ * @return {@code true} if a matching process exists, {@code false} otherwise
+ */
+ boolean existsProcess(String query) {
+ return processSearchService.exists(query)
+ }
+
+ /**
+ * Searches for a single {@link IUser} matching the given query.
+ *
+ * The query must start with the resource keyword {@code user} (singular).
+ *
+ * Example:
+ *
+ * searchUser("user: email eq 'user@mail.com'")
+ * searchUser("user: name eq 'John' and surname eq 'Doe'")
+ *
+ *
+ * @param query query language string starting with {@code user:}
+ * @return matching {@link IUser} or {@code null} if none is found
+ */
+ IUser searchUser(String query) {
+ return userSearchService.searchOne(query)
+ }
+
+ /**
+ * Searches for all {@link IUser} instances matching the given query and returns a paged result.
+ *
+ * The query must start with the resource keyword {@code users} (plural) and may contain
+ * paging and sorting clauses.
+ *
+ *
+ * @param query query language string starting with {@code users:}
+ * @return {@link Page} of matching users
+ */
+ Page pagedSearchUsers(String query) {
+ return userSearchService.searchAll(query)
+ }
+
+ /**
+ * Searches for all {@link IUser} instances matching the given query and returns them as a list.
+ *
+ * The query must start with the resource keyword {@code users} (plural). This is a convenience
+ * method returning only the content of {@link #pagedSearchUsers(String)}.
+ *
+ * Example:
+ *
+ * searchUsers("users: surname eq 'Doe' sort by name asc")
+ * searchUsers("users: email in ('a@mail.com', 'b@mail.com')")
+ *
+ *
+ * @param query query language string starting with {@code users:}
+ * @return list of matching users
+ */
+ List searchUsers(String query) {
+ return pagedSearchUsers(query).content
+ }
+
+ /**
+ * Counts the number of {@link IUser} instances matching the given query.
+ *
+ * The query must start with the resource keyword {@code users} (plural).
+ *
+ *
+ * @param query query language string starting with {@code users:}
+ * @return number of matching users
+ */
+ long countUsers(String query) {
+ return userSearchService.count(query)
+ }
+
+ /**
+ * Checks whether at least one {@link IUser} matching the given query exists.
+ *
+ * The query must start with the resource keyword {@code users} (plural).
+ *
+ * Example:
+ *
+ * existsUser("users: email eq 'user@mail.com'")
+ * existsUser("users: name eq 'John' and surname eq 'Doe'")
+ *
+ *
+ * @param query query language string starting with {@code users:}
+ * @return {@code true} if a matching user exists, {@code false} otherwise
+ */
+ boolean existsUser(String query) {
+ return userSearchService.exists(query)
+ }
+
+ /**
+ * Generic search that resolves the resource type from the query itself and executes the search.
+ *
+ * The query must start with one of the resource keywords: {@code process}/{@code processes},
+ * {@code case}/{@code cases}, {@code task}/{@code tasks} or {@code user}/{@code users}.
+ * When the singular form is used, a single matching instance is returned. When the plural form
+ * is used, the content (a {@link List}) of the resulting {@link Page} is returned.
+ *
+ *
+ * @param query query language string starting with a resource keyword
+ * @return a single resource instance (singular form), a {@link List} of instances (plural form),
+ * or {@code null} if nothing matches
+ */
+ Object search(String query) {
+ Object result = searchService.search(query)
+ if (result instanceof Page>) {
+ return result.content
+ }
+ return result
+ }
+
+ /**
+ * Generic count that resolves the resource type from the query itself and counts matching instances.
+ *
+ * The query must start with one of the resource keywords: {@code processes}, {@code cases},
+ * {@code tasks} or {@code users} (plural form).
+ *
+ *
+ * @param query query language string starting with a resource keyword
+ * @return number of matching instances
+ */
+ long count(String query) {
+ return searchService.count(query)
+ }
+
+ /**
+ * Generic existence check that resolves the resource type from the query itself.
+ *
+ * The query must start with one of the resource keywords: {@code processes}, {@code cases},
+ * {@code tasks} or {@code users} (plural form).
+ *