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. - -![Filter field editable](../_media/search/Filter-processes-guide-field-example-editable.png) - -*an editable filter field* - -![Filter field visible](../_media/search/Filter-processes-guide-field-example-visible.png) - -*a visible filter field* - -![Filter field immediate](../_media/search/Filter-processes-guide-field-example-immediate.png) - -*an immediate filter field displayed on a panel* - -## Filter process - -The engine filter process is located under `resources/petriNets/engine-processes/filter.xml` - -![Filter process](../_media/search/Filter-processes-guide-filter-process.png) - -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. - -
- -![alt text](../_media/search/import_export_cases.png "Import and Export cases") - -
- -### 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. - -
- -![alt text](../_media/search/export_filters_1.png "Export of filters initial") - -
- -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. - -
- -![alt text](../_media/search/export_filters_2.png "Exported filters file") - -
- -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. - -
- -![alt text](../_media/search/import_filters_1.png "Import of filters initial") - -
- -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. - -
- -![alt text](../_media/search/import_filters_2.png "Imported filters displayed") - -
- -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. - -
- -![alt text](../_media/search/import_filters_3.png "Missing process") - -
- -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.netgrif application-engine - 6.4.2 + 6.5.0 jar NETGRIF Application Engine @@ -64,6 +64,7 @@ 7.70.0.Final netgrif-oss https://sonarcloud.io + 4.13.1 2.15.0-rc1 @@ -573,6 +574,12 @@ minio 8.5.12 + + + org.antlr + antlr4-runtime + ${antlr4.version} + @@ -838,6 +845,22 @@ license-maven-plugin 2.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: - *
    - *
  • changeFilter filter query { "processIdentifier:"my_process_id" } - *
  • changeFilter filter visibility { "private" } - *
  • changeFilter filter allowedNets { ["my_process_id1","my_process_id2"] } - *
  • changeFilter filter filterMetadata { [
    -     "searchCategories"       : [],
    -     "predicateMetadata"      : [],
    -     "filterType"             : "Case",
    -     "defaultSearchCategories": true,
    -     "inheritAllowedNets"     : false
    -     ] }
    - *
  • changeFilter filter title { new I18nString("New title") } - *
  • changeFilter filter title { "New title" } - *
  • changeFilter filter icon { "filter_alt" } - *
  • changeFilter filter uri { "/my_node1/my_node2" } - *
- * @param filter {@link Case} instance of filter - */ - def changeFilter(Case filter) { - [query : { cl -> - updateFilter(filter, [ - (DefaultFiltersRunner.FILTER_FIELD_ID): [ - "type" : "enumeration_map", - "value": cl() as String - ] - ]) - }, - visibility : { cl -> - updateFilter(filter, [ - (DefaultFiltersRunner.FILTER_VISIBILITY_FIELD_ID): [ - "type" : "enumeration_map", - "value": cl() as String - ] - ]) - }, - allowedNets : { cl -> - String currentQuery = workflowService.findOne(filter.stringId).dataSet[DefaultFiltersRunner.FILTER_FIELD_ID].value - updateFilter(filter, [ - (DefaultFiltersRunner.FILTER_FIELD_ID): [ - "type" : "filter", - "value" : currentQuery, - "allowedNets": cl() as List - ] - ]) - }, - filterMetadata: { cl -> - String currentQuery = workflowService.findOne(filter.stringId).dataSet[DefaultFiltersRunner.FILTER_FIELD_ID].value - updateFilter(filter, [ - (DefaultFiltersRunner.FILTER_FIELD_ID): [ - "type" : "filter", - "value" : currentQuery, - "filterMetadata": cl() as Map - ] - ]) - }, - title : { cl -> - filter = workflowService.findOne(filter.stringId) - def value = cl() - filter.setTitle(value as String) - filter.dataSet[DefaultFiltersRunner.FILTER_I18N_TITLE_FIELD_ID].value = (value instanceof I18nString) ? value : new I18nString(value as String) - workflowService.save(filter) - }, - icon : { cl -> - filter = workflowService.findOne(filter.stringId) - def icon = cl() as String - filter.setIcon(icon) - workflowService.save(filter) - }, - uri : { cl -> - filter = workflowService.findOne(filter.stringId) - def uri = cl() as String - filter.setUriNodeId(uriService.findByUri(uri).stringId) - workflowService.save(filter) - }] - } - - /** - * deletes filter instance - * Note: do not call this method if given instance is referenced in any preference_item instance - * @param filter - * @return - */ - def deleteFilter(Case filter) { - workflowService.deleteCase(filter.stringId) - } - - /** - * create menu item for given filter instance - * @param uri - * @param identifier - unique item identifier - * @param filter - * @param groupName - * @param allowedRoles ["role_import_id": "net_import_id"] - * @param bannedRoles ["role_import_id": "net_import_id"] - * @return - */ - @Deprecated - Case createMenuItem(String uri, String identifier, Case filter, String groupName, Map allowedRoles, Map bannedRoles = [:], List caseDefaultHeaders = [], List taskDefaultHeaders = []) { - MenuItemBody body = new MenuItemBody( - uri, - identifier, - filter.dataSet[FILTER_FIELD_I18N_FILTER_NAME].value as I18nString, - null - ) - body.setFilter(filter) - body.setCaseDefaultHeaders(caseDefaultHeaders) - body.setTaskDefaultHeaders(taskDefaultHeaders) - body.setAllowedRoles(collectRolesForPreferenceItem(allowedRoles)) - body.setBannedRoles(collectRolesForPreferenceItem(bannedRoles)) - body.setUseCustomView(false) - body.setCaseRequireTitleInCreation(true) - - return createMenuItem(body) - } - - /** - * create menu item for given filter instance - * @param uri - * @param identifier - unique item identifier - * @param filter - * @param groupName - * @param allowedRoles - * @param bannedRoles - * @return - */ - @Deprecated - Case createMenuItem(String uri, String identifier, Case filter, String groupName, List allowedRoles, List bannedRoles = [], List caseDefaultHeaders = [], List taskDefaultHeaders = []) { - MenuItemBody body = new MenuItemBody( - uri, - identifier, - filter.dataSet[FILTER_FIELD_I18N_FILTER_NAME].value as I18nString, - null - ) - body.setFilter(filter) - body.setCaseDefaultHeaders(caseDefaultHeaders) - body.setTaskDefaultHeaders(taskDefaultHeaders) - body.setAllowedRoles(collectRolesForPreferenceItem(allowedRoles)) - body.setBannedRoles(collectRolesForPreferenceItem(bannedRoles)) - body.setUseCustomView(false) - body.setCaseRequireTitleInCreation(true) - - return createMenuItem(body) - } - - /** - * create menu item for given filter instance - * @param uri - * @param identifier - unique item identifier - * @param filter - * @param groupName - * @param allowedRoles ["role_import_id": "net_import_id"] - * @param bannedRoles ["role_import_id": "net_import_id"] - * @param group - if null, default group is used - * @return - */ - @Deprecated - Case createMenuItem(String uri, String identifier, Case filter, Map allowedRoles, Map bannedRoles = [:], Case group = null, List caseDefaultHeaders = [], List taskDefaultHeaders = []) { - MenuItemBody body = new MenuItemBody( - uri, - identifier, - filter.dataSet[FILTER_FIELD_I18N_FILTER_NAME].value as I18nString, - null - ) - body.setFilter(filter) - body.setCaseDefaultHeaders(caseDefaultHeaders) - body.setTaskDefaultHeaders(taskDefaultHeaders) - body.setAllowedRoles(collectRolesForPreferenceItem(allowedRoles)) - body.setBannedRoles(collectRolesForPreferenceItem(bannedRoles)) - body.setUseCustomView(false) - body.setCaseRequireTitleInCreation(true) - - return createMenuItem(body) - } - - /** - * create menu item for given filter instance - * @param uri - * @param identifier - unique item identifier - * @param filter - * @param allowedRoles - * @param bannedRoles - * @param group - if null, default group is used - * @return - */ - @Deprecated - Case createMenuItem(String uri, String identifier, Case filter, List allowedRoles, List bannedRoles = [], Case group = null, List caseDefaultHeaders = [], List taskDefaultHeaders = []) { - MenuItemBody body = new MenuItemBody( - uri, - identifier, - filter.dataSet[FILTER_FIELD_I18N_FILTER_NAME].value as I18nString, - null - ) - body.setFilter(filter) - body.setCaseDefaultHeaders(caseDefaultHeaders) - body.setTaskDefaultHeaders(taskDefaultHeaders) - body.setAllowedRoles(collectRolesForPreferenceItem(allowedRoles)) - body.setBannedRoles(collectRolesForPreferenceItem(bannedRoles)) - body.setUseCustomView(false) - body.setCaseRequireTitleInCreation(true) - - return createMenuItem(body) - } - - /** - * Creates item in menu with given parameters - * - * @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 Case of preference_item - * */ - @NamedVariant - Case createMenuItem(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 instanceof I18nString) ? name : new I18nString(name as String), - icon - ) - body.setFilter(filter) - body.setCaseDefaultHeaders(caseDefaultHeaders) - body.setTaskDefaultHeaders(taskDefaultHeaders) - body.setAllowedRoles(collectRolesForPreferenceItem(allowedRoles)) - body.setBannedRoles(collectRolesForPreferenceItem(bannedRoles)) - body.setUseCustomView(false) - body.setCaseRequireTitleInCreation(true) - - return createMenuItem(body) - } - - /** - * Changes data of provided preference_item instance. These attributes can be changed: + * Changes data of provided menu_item instance. These attributes can be changed: *
    *
  • changeMenuItem item allowedRoles { ["role_1":"my_process_id"] } *
  • changeMenuItem item bannedRoles { ["role_1":"my_process_id"] } - *
  • changeMenuItem item caseDefaultHeaders { ["meta-title","meta-visualId"] } - *
  • changeMenuItem item taskDefaultHeaders { ["meta-title","meta-caseId"] } - *
  • changeMenuItem item filter { filterCase } *
  • 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). + *

+ * Example: + *
+     *     countCases("cases: processIdentifier eq 'query_test'")
+     *     countCases("cases: data.boolean_0.value == true and data.text_0.value != '4'")
+     * 
+ * + * @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). + *

+ * Example: + *
+     *     countTasks("tasks: caseId eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
+     *     countTasks("tasks: transitionId eq 't1' and userId eq 'user1'")
+     * 
+ * + * @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). + *

+ * Example: + *
+     *     countProcesses("processes: identifier eq 'my_process'")
+     *     countProcesses("processes: version gte 1.0.0")
+     * 
+ * + * @param query query language string starting with {@code processes:} + * @return number of matching processes + */ + long countProcesses(String query) { + return processSearchService.count(query) + } + + /** + * Checks whether at least one {@link PetriNet} (process) matching the given query exists. + *

+ * The query must start with the resource keyword {@code processes} (plural). + *

+ * Example: + *
+     *     existsProcess("processes: identifier eq 'my_process'")
+     *     existsProcess("processes: version eq 1.0.0")
+     * 
+ * + * @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. + *

+ * Example: + *
+     *     pagedSearchUsers("users: name eq 'John' page 0 size 25 sort by surname asc")
+     *     pagedSearchUsers("users: email contains '@company.com'")
+     * 
+ * + * @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). + *

+ * Example: + *
+     *     countUsers("users: email contains '@company.com'")
+     *     countUsers("users: name eq 'John'")
+     * 
+ * + * @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. + *

+ * Example: + *
+     *     search("case: processIdentifier eq 'query_test' and data.number_0.value == 3")
+     *     search("cases: processIdentifier eq 'query_test' page 1 size 5 sort by title desc")
+     *     search("process: identifier == 'query_test'")
+     * 
+ * + * @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). + *

+ * Example: + *
+     *     count("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true")
+     *     count("users: email contains '@company.com'")
+     * 
+ * + * @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). + *

+ * Example: + *
+     *     exists("cases: processIdentifier eq 'query_test'")
+     *     exists("tasks: transitionId eq 't1' and userId eq 'user1'")
+     * 
+ * + * @param query query language string starting with a resource keyword + * @return {@code true} if a matching instance exists, {@code false} otherwise + */ + boolean exists(String query) { + return searchService.exists(query) + } } \ No newline at end of file diff --git a/src/main/groovy/com/netgrif/application/engine/startup/DashboardManagementRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/DashboardManagementRunner.groovy new file mode 100644 index 00000000000..a95e79215ce --- /dev/null +++ b/src/main/groovy/com/netgrif/application/engine/startup/DashboardManagementRunner.groovy @@ -0,0 +1,27 @@ +package com.netgrif.application.engine.startup + +import groovy.util.logging.Slf4j +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.stereotype.Component + +@Slf4j +@Component +@ConditionalOnProperty(value = "nae.dashboard-management.enabled", matchIfMissing = true) +class DashboardManagementRunner extends AbstractOrderedCommandLineRunner { + + @Autowired + private ImportHelper helper + + public static final String DASHBOARD_MANAGEMENT_NET_IDENTIFIER = "dashboard_management" + private static final String DASHBOARD_MANAGEMENT_FILE_NAME = "engine-processes/dashboard_management.xml" + + public static final String DASHBOARD_ITEM_NET_IDENTIFIER = "dashboard_item" + private static final String DASHBOARD_ITEM_FILE_NAME = "engine-processes/dashboard_item.xml" + + @Override + void run(String... args) throws Exception { + helper.importProcess("Petri net for filters", DASHBOARD_MANAGEMENT_NET_IDENTIFIER, DASHBOARD_MANAGEMENT_FILE_NAME) + helper.importProcess("Petri net for filter preferences", DASHBOARD_ITEM_NET_IDENTIFIER, DASHBOARD_ITEM_FILE_NAME) + } +} diff --git a/src/main/groovy/com/netgrif/application/engine/startup/DashboardRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/DashboardRunner.groovy index 4c3690da982..aeafd8a270b 100644 --- a/src/main/groovy/com/netgrif/application/engine/startup/DashboardRunner.groovy +++ b/src/main/groovy/com/netgrif/application/engine/startup/DashboardRunner.groovy @@ -1,8 +1,5 @@ package com.netgrif.application.engine.startup -import com.netgrif.application.engine.petrinet.domain.PetriNet -import com.netgrif.application.engine.petrinet.domain.VersionType -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService import groovy.util.logging.Slf4j import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty @@ -13,15 +10,9 @@ import org.springframework.stereotype.Component @ConditionalOnProperty(value = "nae.dashboard.enabled", matchIfMissing = false) class DashboardRunner extends AbstractOrderedCommandLineRunner { - @Autowired - private IPetriNetService petriNetService - @Autowired private ImportHelper helper - @Autowired - private SystemUserRunner systemCreator - public static final String DASHBOARD_NET_IDENTIFIER = "dashboard" private static final String DASHBOARD_FILE_NAME = "engine-processes/dashboard.xml" @@ -30,32 +21,7 @@ class DashboardRunner extends AbstractOrderedCommandLineRunner { @Override void run(String... args) throws Exception { - createDashboardNet() - createDashboardTileNet() - } - - Optional createDashboardNet() { - importProcess("Petri net for filters", DASHBOARD_NET_IDENTIFIER, DASHBOARD_FILE_NAME) - } - - Optional createDashboardTileNet() { - importProcess("Petri net for filter preferences", DASHBOARD_TILE_NET_IDENTIFIER, DASHBOARD_TILE_FILE_NAME) - } - - - Optional importProcess(String message, String netIdentifier, String netFileName) { - PetriNet filter = petriNetService.getNewestVersionByIdentifier(netIdentifier) - if (filter != null) { - log.info("${message} has already been imported.") - return Optional.of(filter) - } - - Optional filterNet = helper.createNet(netFileName, VersionType.MAJOR, systemCreator.loggedSystem) - - if (!filterNet.isPresent()) { - log.error("Import of ${message} failed!") - } - - return filterNet + helper.importProcess("Petri net for filters", DASHBOARD_NET_IDENTIFIER, DASHBOARD_FILE_NAME) + helper.importProcess("Petri net for filter preferences", DASHBOARD_TILE_NET_IDENTIFIER, DASHBOARD_TILE_FILE_NAME) } } diff --git a/src/main/groovy/com/netgrif/application/engine/startup/DefaultDashboardRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/DefaultDashboardRunner.groovy new file mode 100644 index 00000000000..1e580755668 --- /dev/null +++ b/src/main/groovy/com/netgrif/application/engine/startup/DefaultDashboardRunner.groovy @@ -0,0 +1,27 @@ +package com.netgrif.application.engine.startup + + +import com.netgrif.application.engine.menu.domain.dashboard.DashboardManagementBody +import com.netgrif.application.engine.menu.service.interfaces.DashboardManagementService +import com.netgrif.application.engine.petrinet.domain.I18nString +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component + +@Component +class DefaultDashboardRunner extends AbstractOrderedCommandLineRunner { + + @Autowired + private DashboardManagementService dashboardManagementService + + @Override + void run(String... args) throws Exception { + createMainDashboardManagementItem() + } + + def createMainDashboardManagementItem() { + def dashboardItemBody = new DashboardManagementBody("main_dashboard", new I18nString("Main Dashboard",Map.of("sk","Hlavný Dashboard","de","Haupt-Dashboard"))) + dashboardItemBody.setLogoutDashboard(true) + + return dashboardManagementService.createDashboardManagement(dashboardItemBody) + } +} diff --git a/src/main/groovy/com/netgrif/application/engine/startup/DefaultFiltersRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/DefaultFiltersRunner.groovy deleted file mode 100644 index 3f2f1e2b371..00000000000 --- a/src/main/groovy/com/netgrif/application/engine/startup/DefaultFiltersRunner.groovy +++ /dev/null @@ -1,314 +0,0 @@ -package com.netgrif.application.engine.startup - -import com.netgrif.application.engine.auth.service.interfaces.IUserService -import com.netgrif.application.engine.petrinet.domain.I18nString -import com.netgrif.application.engine.petrinet.domain.PetriNet -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService -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.service.interfaces.IDataService -import com.netgrif.application.engine.workflow.service.interfaces.ITaskService -import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService -import lombok.extern.slf4j.Slf4j -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component -import org.springframework.beans.factory.annotation.Value - -@Slf4j -@Component -class DefaultFiltersRunner extends AbstractOrderedCommandLineRunner { - - public static final String AUTO_CREATE_TRANSITION = "auto_create" - public static final String DETAILS_TRANSITION = "t2" - - public static final String FILTER_TYPE_FIELD_ID = "filter_type" - public static final String FILTER_ORIGIN_VIEW_ID_FIELD_ID = "origin_view_id" - public static final String FILTER_PARENT_CASE_ID_FIELD_ID = "parent_filter_id" - public static final String FILTER_VISIBILITY_FIELD_ID = "visibility" - public static final String FILTER_FIELD_ID = "filter" - public static final String FILTER_I18N_TITLE_FIELD_ID = "i18n_filter_name" - public static final String GERMAN_ISO_3166_CODE = "de" - public static final String SLOVAK_ISO_3166_CODE = "sk" - public static final String IS_IMPORTED = "is_imported" - - public static final String FILTER_TYPE_CASE = "Case" - public static final String FILTER_TYPE_TASK = "Task" - - public static final String FILTER_VISIBILITY_PRIVATE = "private" - public static final String FILTER_VISIBILITY_PUBLIC = "public" - - @Value('${nae.create.default.filters:false}') - private Boolean createDefaultFilters - - @Autowired - private IPetriNetService petriNetService - - @Autowired - private IWorkflowService workflowService - - @Autowired - private IUserService userService - - @Autowired - private ITaskService taskService - - @Autowired - private IDataService dataService - - @Override - void run(String... args) throws Exception { - if (createDefaultFilters) { - createCaseFilter("All cases", "assignment", FILTER_VISIBILITY_PUBLIC, "", [], [ - "predicateMetadata": [], - "searchCategories" : [] - ], [ - (GERMAN_ISO_3166_CODE): "Alle Fälle", - (SLOVAK_ISO_3166_CODE): "Všetky prípady" - ]) - createCaseFilter("My cases", "assignment_ind", FILTER_VISIBILITY_PUBLIC, "(author:<>)", [], [ - "predicateMetadata": [[["category": "case_author", "configuration": ["operator": "equals"], "values": [["text": "search.category.userMe", value: ["<>"]]]]]], - "searchCategories" : ["case_author"] - ], [ - (GERMAN_ISO_3166_CODE): "Meine Fälle", - (SLOVAK_ISO_3166_CODE): "Moje prípady" - ]) - - createTaskFilter("All tasks", "library_add_check", FILTER_VISIBILITY_PUBLIC, "", [], [ - "predicateMetadata": [], - "searchCategories" : [] - ], [ - (GERMAN_ISO_3166_CODE): "Alle Aufgaben", - (SLOVAK_ISO_3166_CODE): "Všetky úlohy" - ]) - createTaskFilter("My tasks", "account_box", FILTER_VISIBILITY_PUBLIC, "(userId:<>)", [], [ - "predicateMetadata": [[["category": "task_assignee", "configuration": ["operator": "equals"], "values": [["text": "search.category.userMe", value: ["<>"]]]]]], - "searchCategories" : ["task_assignee"] - ], [ - (GERMAN_ISO_3166_CODE): "Meine Aufgaben", - (SLOVAK_ISO_3166_CODE): "Moje úlohy" - ]) - } - } - - /** - * Creates a new case filter filter process instance - * @param title unique title of the default filter - * @param icon material icon identifier of the default filter - * @param filterVisibility filter visibility - * @param filterQuery the elastic query string query used by the filter - * @param allowedNets list of process identifiers allowed for search categories metadata generation - * @param filterMetadata metadata of the serialised filter as generated by the frontend - * @param titleTranslations a map of locale codes to translated strings for the filter title - * @param withDefaultCategories whether the default search categories should be merged with the search categories specified in the metadata - * @param inheritBaseAllowedNets whether the base allowed nets should be merged with the allowed nets specified in the filter field - * @param originId the ID of the parent if any - * @param viewOrigin true if the parent was a view. false if the parent was another filter - * @param isImported whether the filter is being created by importing it from na XML file - * @return an empty Optional if the filter process does not exist. An existing filter process instance if a filter process instance with the same name already exists. A new filter process instance if not. - */ - Optional createCaseFilter( - String title, - String icon, - String filterVisibility, - String filterQuery, - List allowedNets, - Map filterMetadata, - Map titleTranslations, - boolean withDefaultCategories = true, - boolean inheritBaseAllowedNets = true, - String originId = null, - boolean viewOrigin = false, - boolean isImported = false - ) { - return createFilter( - title, - icon, - FILTER_TYPE_CASE, - filterVisibility, - filterQuery, - allowedNets, - filterMetadata, - titleTranslations, - withDefaultCategories, - inheritBaseAllowedNets, - originId, - viewOrigin, - isImported - ) - } - - /** - * Creates a new task filter filter process instance - * @param title unique title of the default filter - * @param icon material icon identifier of the default filter - * @param filterVisibility filter visibility - * @param filterQuery the elastic query string query used by the filter - * @param allowedNets list of process identifiers allowed for search categories metadata generation - * @param filterMetadata metadata of the serialised filter as generated by the frontend - * @param titleTranslations a map of locale codes to translated strings for the filter title - * @param withDefaultCategories whether the default search categories should be merged with the search categories specified in the metadata - * @param inheritBaseAllowedNets whether the base allowed nets should be merged with the allowed nets specified in the filter field - * @param originId the ID of the parent if any - * @param viewOrigin true if the parent was a view. false if the parent was another filter - * @param isImported whether the filter is being created by importing it from na XML file - * @return an empty Optional if the filter process does not exist. An existing filter process instance if a filter process instance with the same name already exists. A new filter process instance if not. - */ - Optional createTaskFilter( - String title, - String icon, - String filterVisibility, - String filterQuery, - List allowedNets, - Map filterMetadata, - Map titleTranslations, - boolean withDefaultCategories = true, - boolean inheritBaseAllowedNets = true, - String originId = null, - boolean viewOrigin = false, - boolean isImported = false - ) { - return createFilter( - title, - icon, - FILTER_TYPE_TASK, - filterVisibility, - filterQuery, - allowedNets, - filterMetadata, - titleTranslations, - withDefaultCategories, - inheritBaseAllowedNets, - originId, - viewOrigin, - isImported - ) - } - - /** - * Creates a new filter process instance of the provided type - * @param title unique title of the default filter - * @param icon material icon identifier of the default filter - * @param filterType the type of the filter - * @param filterVisibility filter visibility - * @param filterQuery the elastic query string query used by the filter - * @param allowedNets list of process identifiers allowed for search categories metadata generation - * @param filterMetadata metadata of the serialised filter as generated by the frontend - * @param titleTranslations a map of locale codes to translated strings for the filter title - * @param withDefaultCategories whether the default search categories should be merged with the search categories specified in the metadata - * @param inheritBaseAllowedNets whether the base allowed nets should be merged with the allowed nets specified in the filter field - * @param originId the ID of the parent if any - * @param viewOrigin true if the parent was a view. false if the parent was another filter - * @param isImported whether the filter is being created by importing it from na XML file - * @return an empty Optional if the filter process does not exist. An existing filter process instance if a filter process instance with the same name already exists. A new filter process instance if not. - */ - Optional createFilter( - String title, - String icon, - String filterType, - String filterVisibility, - String filterQuery, - List allowedNets, - Map filterMetadata, - Map titleTranslations, - boolean withDefaultCategories, - boolean inheritBaseAllowedNets, - String originId = null, - boolean viewOrigin = false, - boolean isImported = false - ) { - return createFilterCase( - title, - icon, - filterType, - filterVisibility, - filterQuery, - allowedNets, - filterMetadata << ["filterType": filterType, "defaultSearchCategories": withDefaultCategories, "inheritAllowedNets": inheritBaseAllowedNets], - titleTranslations, - originId, - viewOrigin, - isImported - ) - } - - private Optional createFilterCase( - String title, - String icon, - String filterType, - String filterVisibility, - String filterQuery, - List allowedNets, - Map filterMetadata, - Map titleTranslations, - String originId, - boolean viewOrigin, - boolean isImported - ) { - PetriNet filterNet = this.petriNetService.getNewestVersionByIdentifier('filter') - if (filterNet == null) { - return Optional.empty() - } - - def loggedUser = this.userService.getLoggedOrSystem() - - if (loggedUser.getStringId() == this.userService.getSystem().getStringId()) { - Case filterCase = this.workflowService.searchOne(QCase.case$.processIdentifier.eq("filter").and(QCase.case$.title.eq(title)).and(QCase.case$.author.id.eq(userService.getSystem().getStringId()))) - if (filterCase != null) { - return Optional.of(filterCase) - } - } - - Case filterCase = this.workflowService.createCase(filterNet.getStringId(), title, null, loggedUser.transformToLoggedUser()).getCase() - filterCase.setIcon(icon) - filterCase = this.workflowService.save(filterCase) - Task newFilterTask = this.taskService.searchOne(QTask.task.transitionId.eq(AUTO_CREATE_TRANSITION).and(QTask.task.caseId.eq(filterCase.getStringId()))) - this.taskService.assignTask(newFilterTask, this.userService.getLoggedOrSystem()) - - def setDataMap = [ - (FILTER_TYPE_FIELD_ID): [ - "type": "enumeration_map", - "value": filterType - ], - (FILTER_VISIBILITY_FIELD_ID): [ - "type": "enumeration_map", - "value": filterVisibility - ], - (FILTER_FIELD_ID): [ - "type": "filter", - "value": filterQuery, - "allowedNets": allowedNets, - "filterMetadata": filterMetadata - ] - ] - - if (originId != null) { - setDataMap.put(viewOrigin ? FILTER_ORIGIN_VIEW_ID_FIELD_ID : FILTER_PARENT_CASE_ID_FIELD_ID, [ - "type": "text", - "value": originId - ]) - } - - - this.dataService.setData(newFilterTask, ImportHelper.populateDataset(setDataMap)) - if (isImported) { - this.dataService.setData(newFilterTask, ImportHelper.populateDataset([ - (IS_IMPORTED): [ - "type": "number", - "value": 1 - ] - ])) - } - - I18nString translatedTitle = new I18nString(title) - titleTranslations.forEach({locale, translation -> translatedTitle.addTranslation(locale, translation)}) - - filterCase = this.workflowService.findOne(filterCase.getStringId()) - filterCase.dataSet[FILTER_I18N_TITLE_FIELD_ID].value = translatedTitle - workflowService.save(filterCase) - - this.taskService.finishTask(newFilterTask, this.userService.getLoggedOrSystem()) - return Optional.of(this.workflowService.findOne(filterCase.getStringId())) - } -} diff --git a/src/main/groovy/com/netgrif/application/engine/startup/FilterRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/FilterRunner.groovy deleted file mode 100644 index 0e1608c6da4..00000000000 --- a/src/main/groovy/com/netgrif/application/engine/startup/FilterRunner.groovy +++ /dev/null @@ -1,75 +0,0 @@ -package com.netgrif.application.engine.startup - -import com.netgrif.application.engine.petrinet.domain.PetriNet -import com.netgrif.application.engine.petrinet.domain.VersionType -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService -import com.netgrif.application.engine.workflow.domain.eventoutcomes.petrinetoutcomes.ImportPetriNetEventOutcome -import groovy.util.logging.Slf4j -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component - -@Slf4j -@Component -class FilterRunner extends AbstractOrderedCommandLineRunner { - - @Autowired - private IPetriNetService petriNetService - - @Autowired - private ImportHelper helper - - @Autowired - private SystemUserRunner systemCreator - - private static final String FILTER_FILE_NAME = "engine-processes/filter.xml" - public static final String FILTER_PETRI_NET_IDENTIFIER = "filter" - - private static final String PREFERRED_ITEM_FILE_NAME = "engine-processes/preference_item.xml" - public static final String PREFERRED_ITEM_NET_IDENTIFIER = "preference_item" - - private static final String EXPORT_FILTER_FILE_NAME = "engine-processes/export_filters.xml" - private static final String EXPORT_NET_IDENTIFIER = "export_filters" - - private static final String IMPORT_FILTER_FILE_NAME = "engine-processes/import_filters.xml" - private static final String IMPORT_NET_IDENTIFIER = "import_filters" - - @Override - void run(String... args) throws Exception { - createFilterNet() - createPreferenceItemNet() - createImportFiltersNet() - createExportFiltersNet() - } - - Optional createFilterNet() { - importProcess("Petri net for filters", FILTER_PETRI_NET_IDENTIFIER, FILTER_FILE_NAME) - } - - Optional createPreferenceItemNet() { - importProcess("Petri net for filter preferences", PREFERRED_ITEM_NET_IDENTIFIER, PREFERRED_ITEM_FILE_NAME) - } - - Optional createImportFiltersNet() { - importProcess("Petri net for importing filters", IMPORT_NET_IDENTIFIER, IMPORT_FILTER_FILE_NAME) - } - - Optional createExportFiltersNet() { - importProcess("Petri net for exporting filters", EXPORT_NET_IDENTIFIER, EXPORT_FILTER_FILE_NAME) - } - - Optional importProcess(String message, String netIdentifier, String netFileName) { - PetriNet filter = petriNetService.getNewestVersionByIdentifier(netIdentifier) - if (filter != null) { - log.info("${message} has already been imported.") - return Optional.of(filter) - } - - Optional filterNet = helper.createNet(netFileName, VersionType.MAJOR, systemCreator.loggedSystem) - - if (!filterNet.isPresent()) { - log.error("Import of ${message} failed!") - } - - return filterNet - } -} diff --git a/src/main/groovy/com/netgrif/application/engine/startup/ImpersonationRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/ImpersonationRunner.groovy index d2766a4e413..93e07d36550 100644 --- a/src/main/groovy/com/netgrif/application/engine/startup/ImpersonationRunner.groovy +++ b/src/main/groovy/com/netgrif/application/engine/startup/ImpersonationRunner.groovy @@ -1,8 +1,5 @@ package com.netgrif.application.engine.startup -import com.netgrif.application.engine.petrinet.domain.PetriNet -import com.netgrif.application.engine.petrinet.domain.VersionType -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService import groovy.util.logging.Slf4j import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -11,14 +8,9 @@ import org.springframework.stereotype.Component @Component class ImpersonationRunner extends AbstractOrderedCommandLineRunner { - @Autowired - protected IPetriNetService petriNetService - @Autowired protected ImportHelper helper - @Autowired - protected SystemUserRunner systemCreator protected static final String IMPERSONATION_CONFIG_FILE_NAME = "engine-processes/impersonation_config.xml" public static final String IMPERSONATION_CONFIG_PETRI_NET_IDENTIFIER = "impersonation_config" @@ -28,26 +20,7 @@ class ImpersonationRunner extends AbstractOrderedCommandLineRunner { @Override void run(String... args) throws Exception { - createConfigNets() - } - - void createConfigNets() { - importProcess("Petri net for impersonation config", IMPERSONATION_CONFIG_PETRI_NET_IDENTIFIER, IMPERSONATION_CONFIG_FILE_NAME) - importProcess("Petri net for impersonation user select", IMPERSONATION_CONFIG_USER_SELECT_PETRI_NET_IDENTIFIER, IMPERSONATION_CONFIG_USER_SELECT_FILE_NAME) - } - - Optional importProcess(String message, String netIdentifier, String netFileName) { - PetriNet foundNet = petriNetService.getNewestVersionByIdentifier(netIdentifier) - if (foundNet != null) { - log.info("${message} has already been imported.") - return Optional.of(foundNet) - } - - Optional net = helper.createNet(netFileName, VersionType.MAJOR, systemCreator.loggedSystem) - if (!net.isPresent()) { - log.error("Import of ${message} failed!") - } - - return net + helper.importProcess("Petri net for impersonation config", IMPERSONATION_CONFIG_PETRI_NET_IDENTIFIER, IMPERSONATION_CONFIG_FILE_NAME) + helper.importProcess("Petri net for impersonation user select", IMPERSONATION_CONFIG_USER_SELECT_PETRI_NET_IDENTIFIER, IMPERSONATION_CONFIG_USER_SELECT_FILE_NAME) } } diff --git a/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy b/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy index 8b7de95626b..ba5cde7d804 100644 --- a/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy +++ b/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy @@ -90,6 +90,9 @@ class ImportHelper { @Autowired private IUriService uriService + @Autowired + private SystemUserRunner systemCreator + private final ClassLoader loader = ImportHelper.getClassLoader() @@ -177,12 +180,12 @@ class ImportHelper { return user } - Case createCase(String title, PetriNet net, LoggedUser user) { - return workflowService.createCase(net.getStringId(), title, "", user).getCase() + Case createCase(String title, PetriNet net, LoggedUser user, Map params = [:]) { + return workflowService.createCase(net.getStringId(), title, "", user, params).getCase() } - Case createCase(String title, PetriNet net) { - return createCase(title, net, userService.getSystem().transformToLoggedUser()) + Case createCase(String title, PetriNet net, Map params = [:]) { + return createCase(title, net, userService.getSystem().transformToLoggedUser(), params) } Case createCaseAsSuper(String title, PetriNet net) { @@ -239,6 +242,22 @@ class ImportHelper { superCreator.setAllToSuperUser(); } + Optional importProcess(String message, String netIdentifier, String netFileName) { + PetriNet filter = petriNetService.getNewestVersionByIdentifier(netIdentifier) + if (filter != null) { + log.info("${message} has already been imported.") + return Optional.of(filter) + } + + Optional filterNet = this.createNet(netFileName, VersionType.MAJOR, systemCreator.loggedSystem) + + if (!filterNet.isPresent()) { + log.error("Import of ${message} failed!") + } + + return filterNet + } + static ObjectNode populateDataset(Map> data) { ObjectMapper mapper = new ObjectMapper() String json = mapper.writeValueAsString(data) diff --git a/src/main/groovy/com/netgrif/application/engine/startup/MenuRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/MenuRunner.groovy new file mode 100644 index 00000000000..472d9bb5a79 --- /dev/null +++ b/src/main/groovy/com/netgrif/application/engine/startup/MenuRunner.groovy @@ -0,0 +1,39 @@ +package com.netgrif.application.engine.startup + +import com.netgrif.application.engine.menu.domain.MenuItemConstants +import com.netgrif.application.engine.menu.domain.MenuItemViewType +import com.netgrif.application.engine.petrinet.domain.PetriNet +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService +import groovy.util.logging.Slf4j +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component + +@Slf4j +@Component +class MenuRunner extends AbstractOrderedCommandLineRunner { + + private static final String MENU_ITEM_FILE_NAME = "engine-processes/menu/menu_item.xml" + + @Autowired + private ImportHelper helper + + @Autowired + private IPetriNetService petriNetService + + @Autowired + private SystemUserRunner systemCreator + + @Override + void run(String... args) throws Exception { + createConfigurationNets() + helper.importProcess("Petri net for menu item", MenuItemConstants.PROCESS_IDENTIFIER, MENU_ITEM_FILE_NAME) + } + + private void createConfigurationNets() { + MenuItemViewType.values().each { view -> + String processIdentifier = view.getIdentifier() + "_configuration" + String filePath = String.format("engine-processes/menu/%s.xml", processIdentifier) + helper.importProcess(String.format("Petri net for %s", processIdentifier), processIdentifier, filePath) + }.collect() + } +} diff --git a/src/main/groovy/com/netgrif/application/engine/startup/MongoDbRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/MongoDbRunner.groovy index 4fac0dc985e..670b97b214a 100644 --- a/src/main/groovy/com/netgrif/application/engine/startup/MongoDbRunner.groovy +++ b/src/main/groovy/com/netgrif/application/engine/startup/MongoDbRunner.groovy @@ -1,10 +1,11 @@ package com.netgrif.application.engine.startup + +import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value -import org.springframework.context.annotation.Profile import org.springframework.data.mapping.context.MappingContext import org.springframework.data.mongodb.core.MongoTemplate import org.springframework.data.mongodb.core.index.IndexOperations @@ -16,7 +17,6 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty import org.springframework.stereotype.Component @Component -@Profile("!test") class MongoDbRunner extends AbstractOrderedCommandLineRunner { private static final Logger log = LoggerFactory.getLogger(MongoDbRunner) @@ -24,6 +24,9 @@ class MongoDbRunner extends AbstractOrderedCommandLineRunner { @Autowired private MongoTemplate mongoTemplate + @Autowired + private IMenuItemService menuItemService + @Value('${spring.data.mongodb.database}') private String name @@ -48,23 +51,25 @@ class MongoDbRunner extends AbstractOrderedCommandLineRunner { if (host != null && port != null) log.info("Dropping Mongo database ${host}:${port}/${name}") else if (uri != null) - log.info("Droppiung Mongo database ${uri}") + log.info("Dropping Mongo database ${uri}") mongoTemplate.getDb().drop() } if (resolveIndexesOnStartup) { + log.info("Ensuring Mongo indexes") resolveIndexes() } } - void resolveIndexes() { + private void resolveIndexes() { MappingContext, MongoPersistentProperty> mappingContext = mongoTemplate.getConverter().getMappingContext() IndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext) mappingContext.getPersistentEntities() .stream() .filter(it -> it.isAnnotationPresent(Document.class)) .forEach(it -> { - IndexOperations indexOps = mongoTemplate.indexOps(it.getType()); - resolver.resolveIndexFor(it.getType()).forEach(indexOps::ensureIndex); + IndexOperations indexOps = mongoTemplate.indexOps(it.getType()) + resolver.resolveIndexFor(it.getType()).forEach(indexOps::ensureIndex) }) + menuItemService.ensureDatabaseIndexes() } } \ No newline at end of file diff --git a/src/main/groovy/com/netgrif/application/engine/startup/ProcessRunner.groovy b/src/main/groovy/com/netgrif/application/engine/startup/ProcessRunner.groovy new file mode 100644 index 00000000000..0b1774dee79 --- /dev/null +++ b/src/main/groovy/com/netgrif/application/engine/startup/ProcessRunner.groovy @@ -0,0 +1,42 @@ +package com.netgrif.application.engine.startup + +import com.netgrif.application.engine.petrinet.service.PetriNetExistsException +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService +import groovy.util.logging.Slf4j +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.core.io.ClassPathResource +import org.springframework.stereotype.Component + +@Slf4j +@Component +class ProcessRunner extends AbstractOrderedCommandLineRunner { + + private final ImportHelper helper + private final IPetriNetService petriNetService + private SystemUserRunner systemUserRunner + + static final String PROCESS_XML_FILENAME = 'process.xml' + + ProcessRunner(ImportHelper helper, IPetriNetService petriNetService) { + this.helper = helper + this.petriNetService = petriNetService + } + + @Override + void run(String... args) throws Exception { + log.info("Import of system process Process started") + try(InputStream netStream = new ClassPathResource("petriNets/engine-processes/$PROCESS_XML_FILENAME" as String).inputStream) { + def outcome = petriNetService.importPetriNet(netStream, systemUserRunner.loggedSystem) + assert outcome.net + log.info("Process Process imported") + } catch (PetriNetExistsException ignored) { + log.info("Process Process already exists") + } + log.info("Import of system process Process ended") + } + + @Autowired + void setSystemUserRunner(SystemUserRunner systemUserRunner) { + this.systemUserRunner = systemUserRunner + } +} diff --git a/src/main/groovy/com/netgrif/application/engine/startup/RunnerController.groovy b/src/main/groovy/com/netgrif/application/engine/startup/RunnerController.groovy index d653c4c4d30..6194c5d7e26 100644 --- a/src/main/groovy/com/netgrif/application/engine/startup/RunnerController.groovy +++ b/src/main/groovy/com/netgrif/application/engine/startup/RunnerController.groovy @@ -16,15 +16,17 @@ class RunnerController { SystemUserRunner, UriRunner, FunctionsCacheRunner, - FilterRunner, + ProcessRunner, + MenuRunner, GroupRunner, - DefaultFiltersRunner, ImpersonationRunner, DashboardRunner, + DashboardManagementRunner, SuperCreator, FlushSessionsRunner, MailRunner, PostalCodeImporter, + DefaultDashboardRunner, DemoRunner, QuartzSchedulerRunner, PdfRunner, diff --git a/src/main/java/com/netgrif/application/engine/auth/service/AbstractUserService.java b/src/main/java/com/netgrif/application/engine/auth/service/AbstractUserService.java index d9e68491cf2..3074b6427d9 100644 --- a/src/main/java/com/netgrif/application/engine/auth/service/AbstractUserService.java +++ b/src/main/java/com/netgrif/application/engine/auth/service/AbstractUserService.java @@ -9,9 +9,11 @@ import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.petrinet.service.interfaces.IProcessRoleService; import com.netgrif.application.engine.security.service.ISecurityContextService; +import com.querydsl.core.types.Predicate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.security.core.context.SecurityContextHolder; @@ -123,6 +125,45 @@ public IUser createSystemUser() { return system; } + @Override + public Page search(Predicate predicate, Pageable pageable) { + if (predicate == null) { + return Page.empty(); + } + if (pageable == null) { + pageable = Pageable.unpaged(); + } + return repository.findAll(predicate, pageable).map(IUser.class::cast); + } + + @Override + public IUser searchOne(Predicate predicate) { + if (predicate == null) { + return null; + } + Page userAsPage = repository.findAll(predicate, PageRequest.of(0, 1)); + if (userAsPage.getTotalElements() > 0) { + return userAsPage.getContent().get(0); + } + return null; + } + + @Override + public long count(Predicate predicate) { + if (predicate == null) { + return 0; + } + return repository.count(predicate); + } + + @Override + public boolean exists(Predicate predicate) { + if (predicate == null) { + return false; + } + return repository.exists(predicate); + } + public Page changeType(Page users, Pageable pageable) { return new PageImpl<>(changeType(users.getContent()), pageable, users.getTotalElements()); } diff --git a/src/main/java/com/netgrif/application/engine/auth/service/UserService.java b/src/main/java/com/netgrif/application/engine/auth/service/UserService.java index b31836c11b9..cf74d3c5d43 100755 --- a/src/main/java/com/netgrif/application/engine/auth/service/UserService.java +++ b/src/main/java/com/netgrif/application/engine/auth/service/UserService.java @@ -10,7 +10,6 @@ import com.netgrif.application.engine.orgstructure.groups.interfaces.INextGroupService; import com.netgrif.application.engine.petrinet.service.interfaces.IProcessRoleService; import com.netgrif.application.engine.startup.SystemUserRunner; -import com.netgrif.application.engine.workflow.service.interfaces.IFilterImportExportService; import com.querydsl.core.types.dsl.BooleanExpression; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; @@ -50,9 +49,6 @@ public class UserService extends AbstractUserService { @Autowired private GroupConfigurationProperties groupProperties; - @Autowired - private IFilterImportExportService filterImportExportService; - @Override public IUser saveNewAndAuthenticate(IUser user) { return saveNew(user, true); @@ -70,8 +66,6 @@ private IUser saveNew(IUser user, boolean login) { addDefaultAuthorities(user); User savedUser = userRepository.save((User) user); - filterImportExportService.createFilterImport(user); - filterImportExportService.createFilterExport(user); if (groupProperties.isDefaultEnabled()) groupService.createGroup(user); diff --git a/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserService.java b/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserService.java index 989062f7585..6eb08971899 100644 --- a/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserService.java +++ b/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserService.java @@ -5,6 +5,7 @@ import com.netgrif.application.engine.auth.domain.LoggedUser; import com.netgrif.application.engine.auth.web.requestbodies.UpdateUserRequest; import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.querydsl.core.types.Predicate; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -77,4 +78,11 @@ public interface IUserService { IUser createSystemUser(); + Page search(Predicate predicate, Pageable pageable); + + IUser searchOne(Predicate predicate); + + long count(Predicate predicate); + + boolean exists(Predicate predicate); } diff --git a/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticPetriNet.java b/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticPetriNet.java index 48637e4c236..537dc92f369 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticPetriNet.java +++ b/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticPetriNet.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.netgrif.application.engine.elastic.domain.I18nField; import com.netgrif.application.engine.petrinet.domain.I18nString; import com.netgrif.application.engine.petrinet.domain.PetriNet; import com.netgrif.application.engine.petrinet.domain.version.Version; @@ -17,6 +18,8 @@ import org.springframework.data.elasticsearch.annotations.FieldType; import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; import static org.springframework.data.elasticsearch.annotations.FieldType.Keyword; @@ -40,7 +43,7 @@ public class ElasticPetriNet { @Field(type = Keyword) private String stringId; - private I18nString title; + private I18nField title; @Field(type = Keyword) private String initials; @@ -55,7 +58,7 @@ public ElasticPetriNet(PetriNet net) { this.version = net.getVersion(); this.uriNodeId = net.getUriNodeId(); this.stringId = net.getStringId(); - this.title = net.getTitle(); + this.title = this.transformToField(net.getTitle()); this.initials = net.getInitials(); this.creationDate = net.getCreationDate(); } @@ -68,4 +71,11 @@ public void update(ElasticPetriNet net) { this.title = net.getTitle(); this.initials = net.getInitials(); } + + protected I18nField transformToField(I18nString field) { + Set keys = field.getTranslations().keySet(); + Set values = new HashSet<>(field.getTranslations().values()); + values.add(field.getDefaultValue()); + return new I18nField(keys, values); + } } diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java index d352d4a41fa..b15a936daaa 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java @@ -174,14 +174,16 @@ public String findUriNodeId(Case aCase) { } public NativeSearchQuery buildQuery(List requests, LoggedUser user, Pageable pageable, Locale locale, Boolean isIntersection) { - List singleQueries = requests.stream().map(request -> buildSingleQuery(request, user, locale)).collect(Collectors.toList()); + List singleQueries = requests.stream() + .map(request -> buildSingleQuery(request, user, locale)) + .collect(Collectors.toList()); if (isIntersection && !singleQueries.stream().allMatch(Objects::nonNull)) { // one of the queries evaluates to empty set => the entire result is an empty set return null; } else if (!isIntersection) { singleQueries = singleQueries.stream().filter(Objects::nonNull).collect(Collectors.toList()); - if (singleQueries.size() == 0) { + if (singleQueries.isEmpty()) { // all queries result in an empty set => the entire result is an empty set return null; } @@ -215,10 +217,7 @@ protected BoolQueryBuilder buildSingleQuery(CaseSearchRequest request, LoggedUse // TODO: filtered query https://stackoverflow.com/questions/28116404/filtered-query-using-nativesearchquerybuilder-in-spring-data-elasticsearch - if (resultAlwaysEmpty) - return null; - else - return query; + return resultAlwaysEmpty ? null : query; } protected void buildPetriNetQuery(CaseSearchRequest request, LoggedUser user, BoolQueryBuilder query) { @@ -226,17 +225,25 @@ protected void buildPetriNetQuery(CaseSearchRequest request, LoggedUser user, Bo return; } - BoolQueryBuilder petriNetQuery = boolQuery(); + Set identifiers = new HashSet<>(); + Set processIds = new HashSet<>(); - for (CaseSearchRequest.PetriNet process : request.process) { - if (process.identifier != null) { - petriNetQuery.should(termQuery("processIdentifier", process.identifier)); + request.process.forEach(p -> { + if (p.identifier != null) { + identifiers.add(p.identifier); } - if (process.processId != null) { - petriNetQuery.should(termQuery("processId", process.processId)); + if (p.processId != null) { + processIds.add(p.processId); } - } + }); + BoolQueryBuilder petriNetQuery = boolQuery(); + if (!identifiers.isEmpty()) { + petriNetQuery.should(termsQuery("processIdentifier", identifiers)); + } + if (!processIds.isEmpty()) { + petriNetQuery.should(termsQuery("processId", processIds)); + } query.filter(petriNetQuery); } @@ -338,13 +345,7 @@ protected void buildRoleQuery(CaseSearchRequest request, BoolQueryBuilder query) if (request.role == null || request.role.isEmpty()) { return; } - - BoolQueryBuilder roleQuery = boolQuery(); - for (String roleId : request.role) { - roleQuery.should(termQuery("enabledRoles", roleId)); - } - - query.filter(roleQuery); + query.filter(termsQuery("enabledRoles", request.role)); } /** @@ -434,20 +435,14 @@ protected void buildCaseIdQuery(CaseSearchRequest request, BoolQueryBuilder quer if (request.stringId == null || request.stringId.isEmpty()) { return; } - - BoolQueryBuilder caseIdQuery = boolQuery(); - request.stringId.forEach(caseId -> caseIdQuery.should(termQuery("stringId", caseId))); - query.filter(caseIdQuery); + query.filter(termsQuery("stringId", request.stringId)); } protected void buildUriNodeIdQuery(CaseSearchRequest request, BoolQueryBuilder query) { if (request.uriNodeId == null || request.uriNodeId.isEmpty()) { return; } - - BoolQueryBuilder caseIdQuery = boolQuery(); - caseIdQuery.should(termQuery("uriNodeId", request.uriNodeId)); - query.filter(caseIdQuery); + query.filter(termQuery("uriNodeId", request.uriNodeId)); } /** @@ -476,14 +471,13 @@ protected boolean buildGroupQuery(CaseSearchRequest request, LoggedUser user, Lo PetriNetSearch processQuery = new PetriNetSearch(); processQuery.setGroup(request.group); List groupProcesses = this.petriNetService.search(processQuery, user, new FullPageRequest(), locale).getContent(); - if (groupProcesses.size() == 0) + if (groupProcesses.isEmpty()) { return true; - - BoolQueryBuilder groupQuery = boolQuery(); - groupProcesses.stream().map(PetriNetReference::getIdentifier) - .map(netIdentifier -> termQuery("processIdentifier", netIdentifier)) - .forEach(groupQuery::should); - query.filter(groupQuery); + } + List identifiers = groupProcesses.stream() + .map(PetriNetReference::getIdentifier) + .collect(Collectors.toList()); + query.filter(termsQuery("processIdentifier", identifiers)); return false; } diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java index 3777f1ddb86..dbaf4ee85c4 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java @@ -1,20 +1,40 @@ package com.netgrif.application.engine.elastic.service; +import com.netgrif.application.engine.auth.domain.LoggedUser; +import com.netgrif.application.engine.configuration.ElasticsearchConfiguration; import com.netgrif.application.engine.elastic.domain.ElasticPetriNet; import com.netgrif.application.engine.elastic.domain.ElasticPetriNetRepository; + import com.netgrif.application.engine.elastic.service.executors.Executor; import com.netgrif.application.engine.elastic.service.interfaces.IElasticPetriNetService; import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.PetriNetSearch; import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; +import com.netgrif.application.engine.petrinet.web.responsebodies.PetriNetReference; +import com.netgrif.application.engine.utils.FullPageRequest; import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.SearchHitSupport; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.stereotype.Service; -import java.util.List; +import java.util.*; +import java.util.function.BinaryOperator; import java.util.stream.Collectors; +import static org.elasticsearch.index.query.QueryBuilders.*; + @Service @Slf4j public class ElasticPetriNetService implements IElasticPetriNetService { @@ -25,9 +45,15 @@ public class ElasticPetriNetService implements IElasticPetriNetService { private IPetriNetService petriNetService; - public ElasticPetriNetService(ElasticPetriNetRepository repository, Executor executors) { + private final ElasticsearchRestTemplate template; + + protected ElasticsearchConfiguration elasticsearchConfiguration; + + public ElasticPetriNetService(ElasticPetriNetRepository repository, Executor executors, ElasticsearchRestTemplate template, ElasticsearchConfiguration elasticsearchConfiguration) { this.repository = repository; this.executors = executors; + this.template = template; + this.elasticsearchConfiguration = elasticsearchConfiguration; } @Lazy @@ -89,4 +115,105 @@ public List findAllByUriNodeId(String uriNodeId) { List elasticPetriNets = repository.findAllByUriNodeId(uriNodeId); return petriNetService.findAllById(elasticPetriNets.stream().map(ElasticPetriNet::getStringId).collect(Collectors.toList())); } + + /** + * Method for search of PetriNets in Elastic + * @param requests - search body, for now only title working + * @param user - logged user + * @param pageable - pageable for paging + * @param locale - internacionalization + * @param isIntersection - property for merging filter, not implemented now, use false + * @return Page - page of PetriNetReferences + */ + @Override + public Page search(PetriNetSearch requests, LoggedUser user, Pageable pageable, Locale locale, Boolean isIntersection) { + if (requests == null) { + throw new IllegalArgumentException("Request can not be null!"); + } + log.debug("Searching for PetriNet query with logged user [{}]", user.getId()); + LoggedUser loggedOrImpersonated = user.getSelfOrImpersonated(); + NativeSearchQuery query = buildQuery(requests, loggedOrImpersonated, pageable, locale, isIntersection); + List netPage; + long total; + if (query != null) { + SearchHits hits = template.search(query, ElasticPetriNet.class, IndexCoordinates.of(elasticsearchConfiguration.elasticPetriNetIndex())); + Page indexedNets = (Page) SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(hits, query.getPageable())); + netPage = petriNetService.findAllById(indexedNets.get().map(ElasticPetriNet::getStringId).collect(Collectors.toList())); + total = indexedNets.getTotalElements(); + log.debug("Found [{}] total elements of page [{}]", netPage.size(), pageable.getPageNumber()); + } else { + netPage = Collections.emptyList(); + total = 0; + } + + return new PageImpl<>(netPage.stream().map(net -> new PetriNetReference(net, locale)).collect(Collectors.toList()), pageable, total); + } + + protected NativeSearchQuery buildQuery(PetriNetSearch request, LoggedUser user, Pageable pageable, Locale locale, Boolean isIntersection) { + List singleQueries = new LinkedList<>(); + singleQueries.add(buildSingleQuery(request, user, locale)); + + if (isIntersection && !singleQueries.stream().allMatch(Objects::nonNull)) { + // one of the queries evaluates to empty set => the entire result is an empty set + return null; + } else if (!isIntersection) { + singleQueries = singleQueries.stream().filter(Objects::nonNull).collect(Collectors.toList()); + if (singleQueries.isEmpty()) { + // all queries result in an empty set => the entire result is an empty set + return null; + } + } + + BinaryOperator reductionOperator = isIntersection ? BoolQueryBuilder::must : BoolQueryBuilder::should; + BoolQueryBuilder query = singleQueries.stream().reduce(new BoolQueryBuilder(), reductionOperator); + + NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder(); + return builder + .withQuery(query) + .withPageable(pageable) + .build(); + } + + protected BoolQueryBuilder buildSingleQuery(PetriNetSearch request, LoggedUser user, Locale locale) { + BoolQueryBuilder query = boolQuery(); + + buildFullTextQuery(request, query); + boolean resultAlwaysEmpty = buildGroupQuery(request, user, locale, query); + if (resultAlwaysEmpty) { + return null; + } + return query; + } + + protected void buildFullTextQuery(PetriNetSearch request, BoolQueryBuilder query) { + if (request.getTitle() == null || request.getTitle().isEmpty()) { + return; + } + + String searchText = "*" + request.getTitle() + "*"; + Map fields = new HashMap<>(); + fields.put("title.textValue", 2f); + fields.put("identifier", 1f); + QueryBuilder fullTextQuery = queryStringQuery(searchText).fields(fields); + query.must(fullTextQuery); + } + + protected boolean buildGroupQuery(PetriNetSearch request, LoggedUser user, Locale locale, BoolQueryBuilder query) { + if (request.getGroup() == null || request.getGroup().isEmpty()) { + return false; + } + + PetriNetSearch processQuery = new PetriNetSearch(); + processQuery.setGroup(request.getGroup()); + List groupProcesses = this.petriNetService.search(processQuery, user, new FullPageRequest(), locale).getContent(); + if (groupProcesses.isEmpty()) + return true; + + BoolQueryBuilder groupQuery = boolQuery(); + groupProcesses.stream().map(PetriNetReference::getIdentifier) + .map(netIdentifier -> termQuery("identifier", netIdentifier)) + .forEach(groupQuery::should); + query.filter(groupQuery); + return false; + } } diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticTaskService.java b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticTaskService.java index cbb88b52feb..6eb471472c2 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticTaskService.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticTaskService.java @@ -178,7 +178,6 @@ protected BoolQueryBuilder buildSingleQuery(ElasticTaskSearchRequest request, Lo if (request == null) { throw new IllegalArgumentException("Request can not be null!"); } - addRolesQueryConstraint(request, user); BoolQueryBuilder query = boolQuery(); buildViewPermissionQuery(query, user); @@ -198,17 +197,6 @@ protected BoolQueryBuilder buildSingleQuery(ElasticTaskSearchRequest request, Lo return query; } - protected void addRolesQueryConstraint(ElasticTaskSearchRequest request, LoggedUser user) { - if (request.role != null && !request.role.isEmpty()) { - Set roles = new HashSet<>(request.role); - roles.addAll(user.getProcessRoles()); - request.role = new ArrayList<>(roles); - } else { - request.role = new ArrayList<>(user.getProcessRoles()); - } - } - - /** * Tasks of case with id "5cb07b6ff05be15f0b972c4d" * { diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticViewPermissionService.java b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticViewPermissionService.java index b3c707b6403..578b63c5634 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticViewPermissionService.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticViewPermissionService.java @@ -8,85 +8,64 @@ public abstract class ElasticViewPermissionService { protected void buildViewPermissionQuery(BoolQueryBuilder query, LoggedUser user) { - BoolQueryBuilder viewPermsExists = boolQuery(); - BoolQueryBuilder viewPermNotExists = boolQuery(); - viewPermsExists.should(existsQuery("viewRoles")); - viewPermsExists.should(existsQuery("viewUserRefs")); - viewPermNotExists.mustNot(viewPermsExists); - /* Build positive view role query */ - BoolQueryBuilder positiveViewRole = buildPositiveViewRoleQuery(viewPermNotExists, user); +// (Rp!=0 & Rn = 0) + BoolQueryBuilder roleViewQuery = boolQuery() + .filter(buildPositiveViewRoleQuery(user)) + .mustNot(buildNegativeViewRoleQuery(user)); - /* Build negative view role query */ - BoolQueryBuilder negativeViewRole = buildNegativeViewRoleQuery(user); +// ((Rp!=0 & Rn = 0) or Up!=0) + BoolQueryBuilder roleOrPositiveUserQuery = boolQuery().should(roleViewQuery) + .should(buildPositiveViewUser(user)) + .minimumShouldMatch(1); - /* Positive view role set-minus negative view role */ - BoolQueryBuilder positiveRoleSetMinusNegativeRole = setMinus(positiveViewRole, negativeViewRole); - - /* Build positive view userList query */ - BoolQueryBuilder positiveViewUser = buildPositiveViewUser(viewPermNotExists, user); - - /* Role query union positive view userList */ - BoolQueryBuilder roleSetMinusPositiveUserList = union(positiveRoleSetMinusNegativeRole, positiveViewUser); - - /* Build negative view userList query */ - BoolQueryBuilder negativeViewUser = buildNegativeViewUser(user); - - /* Role-UserListPositive set-minus negative view userList */ - BoolQueryBuilder permissionQuery = setMinus(roleSetMinusPositiveUserList, negativeViewUser); - - query.filter(permissionQuery); +// (((Rp!=0 & Rn = 0) or Up!=0) & Un=0) == 1 + query.filter(roleOrPositiveUserQuery) + .mustNot(buildNegativeViewUser(user)); } - private BoolQueryBuilder buildPositiveViewRoleQuery(BoolQueryBuilder viewPermNotExists, LoggedUser user) { + /** + * Build a positive view role query using termsQuery for efficiency. + * This reduces the number of clauses by sending all roles at once. + */ + private BoolQueryBuilder buildPositiveViewRoleQuery(LoggedUser user) { BoolQueryBuilder positiveViewRole = boolQuery(); - BoolQueryBuilder positiveViewRoleQuery = boolQuery(); - for (String roleId : user.getProcessRoles()) { - positiveViewRoleQuery.should(termQuery("viewRoles", roleId)); + if (!user.getProcessRoles().isEmpty()) { + positiveViewRole.should(termsQuery("viewRoles", user.getProcessRoles())); } - positiveViewRole.should(viewPermNotExists); - positiveViewRole.should(positiveViewRoleQuery); + positiveViewRole.minimumShouldMatch(1); return positiveViewRole; } + /** + * Build a negative view role query. + */ private BoolQueryBuilder buildNegativeViewRoleQuery(LoggedUser user) { BoolQueryBuilder negativeViewRole = boolQuery(); - BoolQueryBuilder negativeViewRoleQuery = boolQuery(); - for (String roleId : user.getProcessRoles()) { - negativeViewRoleQuery.should(termQuery("negativeViewRoles", roleId)); + if (!user.getProcessRoles().isEmpty()) { + negativeViewRole.should(termsQuery("negativeViewRoles", user.getProcessRoles())); } - negativeViewRole.mustNot(negativeViewRoleQuery); + negativeViewRole.minimumShouldMatch(1); return negativeViewRole; } - private BoolQueryBuilder buildPositiveViewUser(BoolQueryBuilder viewPermNotExists, LoggedUser user) { + /** + * Build a positive view user query. + */ + private BoolQueryBuilder buildPositiveViewUser(LoggedUser user) { BoolQueryBuilder positiveViewUser = boolQuery(); - BoolQueryBuilder positiveViewUserQuery = boolQuery(); - positiveViewUserQuery.must(termQuery("viewUsers", user.getId())); - positiveViewUser.should(viewPermNotExists); - positiveViewUser.should(positiveViewUserQuery); + positiveViewUser.should(termQuery("viewUsers", user.getId())); + positiveViewUser.minimumShouldMatch(1); return positiveViewUser; } + /** + * Build a negative view user query. + */ private BoolQueryBuilder buildNegativeViewUser(LoggedUser user) { BoolQueryBuilder negativeViewUser = boolQuery(); - BoolQueryBuilder negativeViewUserQuery = boolQuery(); - negativeViewUserQuery.should(termQuery("negativeViewUsers", user.getId())); - negativeViewUser.mustNot(negativeViewUserQuery); + negativeViewUser.should(termQuery("negativeViewUsers", user.getId())); + negativeViewUser.minimumShouldMatch(1); return negativeViewUser; } - - private BoolQueryBuilder setMinus(BoolQueryBuilder positiveSet, BoolQueryBuilder negativeSet) { - BoolQueryBuilder positiveSetMinusNegativeSet = boolQuery(); - positiveSetMinusNegativeSet.must(positiveSet); - positiveSetMinusNegativeSet.must(negativeSet); - return positiveSetMinusNegativeSet; - } - - private BoolQueryBuilder union(BoolQueryBuilder setA, BoolQueryBuilder setB) { - BoolQueryBuilder unionSet = boolQuery(); - unionSet.should(setA); - unionSet.should(setB); - return unionSet; - } } diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/interfaces/IElasticPetriNetService.java b/src/main/java/com/netgrif/application/engine/elastic/service/interfaces/IElasticPetriNetService.java index 6be7daaf0a3..54a2c96abe9 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/interfaces/IElasticPetriNetService.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/interfaces/IElasticPetriNetService.java @@ -1,10 +1,16 @@ package com.netgrif.application.engine.elastic.service.interfaces; +import com.netgrif.application.engine.auth.domain.LoggedUser; import com.netgrif.application.engine.elastic.domain.ElasticPetriNet; import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.PetriNetSearch; +import com.netgrif.application.engine.petrinet.web.responsebodies.PetriNetReference; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.scheduling.annotation.Async; import java.util.List; +import java.util.Locale; public interface IElasticPetriNetService { @@ -19,4 +25,6 @@ public interface IElasticPetriNetService { List findAllByUriNodeId(String uriNodeId); + Page search(PetriNetSearch requests, LoggedUser user, Pageable pageable, Locale locale, Boolean isIntersection); + } diff --git a/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java b/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java index c50c37df036..41cdd8a6c80 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java +++ b/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java @@ -4,7 +4,7 @@ import com.netgrif.application.engine.elastic.service.ReindexingTask; import com.netgrif.application.engine.elastic.service.interfaces.IElasticIndexService; import com.netgrif.application.engine.elastic.web.requestbodies.IndexParams; -import com.netgrif.application.engine.workflow.service.CaseSearchService; +import com.netgrif.application.engine.workflow.service.LegacyCaseSearchService; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.netgrif.application.engine.workflow.web.responsebodies.MessageResource; import com.querydsl.core.types.Predicate; @@ -43,7 +43,7 @@ public class ElasticController { private IWorkflowService workflowService; @Autowired - private CaseSearchService searchService; + private LegacyCaseSearchService searchService; @Autowired private ReindexingTask reindexingTask; diff --git a/src/main/java/com/netgrif/application/engine/elastic/web/requestbodies/ElasticTaskSearchRequest.java b/src/main/java/com/netgrif/application/engine/elastic/web/requestbodies/ElasticTaskSearchRequest.java index 39fc80d1504..fb83664014d 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/web/requestbodies/ElasticTaskSearchRequest.java +++ b/src/main/java/com/netgrif/application/engine/elastic/web/requestbodies/ElasticTaskSearchRequest.java @@ -12,9 +12,7 @@ import java.util.stream.Collectors; @NoArgsConstructor -@AllArgsConstructor public class ElasticTaskSearchRequest extends TaskSearchRequest { - public String query; public ElasticTaskSearchRequest(Map request) { if (request.containsKey("role") && request.get("role") instanceof List) { diff --git a/src/main/java/com/netgrif/application/engine/files/interfaces/IStorageService.java b/src/main/java/com/netgrif/application/engine/files/interfaces/IStorageService.java index 0b6a0b3098e..2c3553fb800 100644 --- a/src/main/java/com/netgrif/application/engine/files/interfaces/IStorageService.java +++ b/src/main/java/com/netgrif/application/engine/files/interfaces/IStorageService.java @@ -1,6 +1,6 @@ package com.netgrif.application.engine.files.interfaces; -import com.netgrif.application.engine.files.throwable.BadRequestException; +import com.netgrif.application.engine.utils.throwable.BadRequestException; import com.netgrif.application.engine.files.throwable.ServiceErrorException; import com.netgrif.application.engine.files.throwable.StorageException; import com.netgrif.application.engine.importer.model.Data; diff --git a/src/main/java/com/netgrif/application/engine/files/local/LocalStorageService.java b/src/main/java/com/netgrif/application/engine/files/local/LocalStorageService.java index c9567678a08..da90ada602e 100644 --- a/src/main/java/com/netgrif/application/engine/files/local/LocalStorageService.java +++ b/src/main/java/com/netgrif/application/engine/files/local/LocalStorageService.java @@ -1,7 +1,7 @@ package com.netgrif.application.engine.files.local; import com.netgrif.application.engine.files.interfaces.IStorageService; -import com.netgrif.application.engine.files.throwable.BadRequestException; +import com.netgrif.application.engine.utils.throwable.BadRequestException; import com.netgrif.application.engine.files.throwable.ServiceErrorException; import com.netgrif.application.engine.files.throwable.StorageException; import com.netgrif.application.engine.importer.model.Data; diff --git a/src/main/java/com/netgrif/application/engine/files/minio/MinIoStorageService.java b/src/main/java/com/netgrif/application/engine/files/minio/MinIoStorageService.java index 2a6c648017a..33222093d36 100644 --- a/src/main/java/com/netgrif/application/engine/files/minio/MinIoStorageService.java +++ b/src/main/java/com/netgrif/application/engine/files/minio/MinIoStorageService.java @@ -1,7 +1,7 @@ package com.netgrif.application.engine.files.minio; import com.netgrif.application.engine.files.interfaces.IStorageService; -import com.netgrif.application.engine.files.throwable.BadRequestException; +import com.netgrif.application.engine.utils.throwable.BadRequestException; import com.netgrif.application.engine.files.throwable.ServiceErrorException; import com.netgrif.application.engine.files.throwable.StorageException; import com.netgrif.application.engine.files.throwable.StorageNotEnabledException; diff --git a/src/main/java/com/netgrif/application/engine/importer/service/FieldFactory.java b/src/main/java/com/netgrif/application/engine/importer/service/FieldFactory.java index 4ac4a03c32c..850e4dbe0be 100644 --- a/src/main/java/com/netgrif/application/engine/importer/service/FieldFactory.java +++ b/src/main/java/com/netgrif/application/engine/importer/service/FieldFactory.java @@ -197,8 +197,8 @@ public static String parseEnumerationMapValue(Case useCase, String fieldId) { } // TODO: refactor this shit - Field getField(Data data, Importer importer) throws IllegalArgumentException, MissingIconKeyException { - Field field; + Field getField(Data data, Importer importer) throws IllegalArgumentException, MissingIconKeyException { + Field field; switch (data.getType()) { case TEXT: field = buildTextField(data); @@ -248,8 +248,15 @@ Field getField(Data data, Importer importer) throws IllegalArgumentException, Mi case MULTICHOICE_MAP: field = buildMultichoiceMapField(data, importer); break; - case FILTER: - field = buildFilterField(data); + case FILTER: // "FILTER" is deprecated + case CASE_FILTER: + field = buildCaseFilterField(data); + break; + case TASK_FILTER: + field = buildTaskFilterField(data); + break; + case PROCESS_FILTER: + field = buildProcessFilterField(data); break; case I_18_N: field = buildI18nField(data, importer); @@ -549,13 +556,22 @@ private FileListField buildFileListField(Data data) { return fileListField; } - private FilterField buildFilterField(Data data) { - AllowedNets nets = data.getAllowedNets(); - if (nets == null) { - return new FilterField(); - } else { - return new FilterField(new ArrayList<>(nets.getAllowedNet())); - } + private CaseFilterField buildCaseFilterField(Data data) { + CaseFilterField field = new CaseFilterField(); + setDefaultValue(field, data, field::setDefaultValue); + return field; + } + + private TaskFilterField buildTaskFilterField(Data data) { + TaskFilterField field = new TaskFilterField(); + setDefaultValue(field, data, field::setDefaultValue); + return field; + } + + private ProcessFilterField buildProcessFilterField(Data data) { + ProcessFilterField field = new ProcessFilterField(); + setDefaultValue(field, data, field::setDefaultValue); + return field; } private I18nField buildI18nField(Data data, Importer importer) { @@ -611,8 +627,6 @@ private Field buildField(Case useCase, String fieldId, boolean withValidation, S resolveMapOptions((MapOptionsField) field, useCase); if (field instanceof FieldWithAllowedNets) resolveAllowedNets((FieldWithAllowedNets) field, useCase); - if (field instanceof FilterField) - resolveFilterMetadata((FilterField) field, useCase); if (withValidation) resolveValidations(field, useCase); return field; @@ -661,13 +675,6 @@ private void resolveAllowedNets(FieldWithAllowedNets field, Case useCase) { field.setAllowedNets(allowedNets); } - private void resolveFilterMetadata(FilterField field, Case useCase) { - Map metadata = useCase.getDataField(field.getImportId()).getFilterMetadata(); - if (metadata == null) - return; - field.setFilterMetadata(metadata); - } - public Field buildImmediateField(Case useCase, String fieldId) { Field field = useCase.getPetriNet().getDataSet().get(fieldId).clone(); resolveDataValues(field, useCase, fieldId); @@ -788,14 +795,10 @@ private void parseFileListValue(FileListField field, Case useCase, String fieldI private void resolveAttributeValues(Field field, Case useCase, String fieldId) { DataField dataField = useCase.getDataSet().get(fieldId); - if (field.getType().equals(FieldType.CASE_REF) || field.getType().equals(FieldType.FILTER)) { + if (field.getType().equals(FieldType.CASE_REF)) { List allowedNets = new ArrayList<>(dataField.getAllowedNets()); ((FieldWithAllowedNets) field).setAllowedNets(allowedNets); } - if (field.getType().equals(FieldType.FILTER)) { - Map filterMetadata = new HashMap<>(dataField.getFilterMetadata()); - ((FilterField) field).setFilterMetadata(filterMetadata); - } } private void setDefaultValue(Field field, Data data, Consumer setDefault) { diff --git a/src/main/java/com/netgrif/application/engine/importer/service/Importer.java b/src/main/java/com/netgrif/application/engine/importer/service/Importer.java index 596db2e301f..b274d20f2b2 100644 --- a/src/main/java/com/netgrif/application/engine/importer/service/Importer.java +++ b/src/main/java/com/netgrif/application/engine/importer/service/Importer.java @@ -29,6 +29,7 @@ import com.netgrif.application.engine.petrinet.domain.policies.FinishPolicy; import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.petrinet.domain.throwable.MissingPetriNetMetaDataException; +import com.netgrif.application.engine.petrinet.domain.version.Version; import com.netgrif.application.engine.petrinet.service.ArcFactory; import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; import com.netgrif.application.engine.petrinet.service.interfaces.IProcessRoleService; @@ -39,7 +40,6 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; @@ -123,7 +123,6 @@ public class Importer { @Autowired private ILogicValidator logicValidator; - @Transactional public Optional importPetriNet(InputStream xml) throws MissingPetriNetMetaDataException, MissingIconKeyException { try { initialize(); @@ -135,7 +134,6 @@ public Optional importPetriNet(InputStream xml) throws MissingPetriNet return Optional.empty(); } - @Transactional public Optional importPetriNet(File xml) throws MissingPetriNetMetaDataException, MissingIconKeyException { try { return importPetriNet(new FileInputStream(xml)); @@ -159,7 +157,6 @@ protected void initialize() { this.functions = new LinkedList<>(); } - @Transactional protected void unmarshallXml(InputStream xml) throws JAXBException { JAXBContext jaxbContext = JAXBContext.newInstance(Document.class); @@ -167,7 +164,6 @@ protected void unmarshallXml(InputStream xml) throws JAXBException { document = (Document) jaxbUnmarshaller.unmarshal(xml); } - @Transactional public Path saveNetFile(PetriNet net, InputStream xmlFile) throws IOException { File savedFile = new File(fileStorageConfiguration.getStorageArchived() + net.getStringId() + "-" + net.getTitle() + FILE_EXTENSION); savedFile.getParentFile().mkdirs(); @@ -176,7 +172,6 @@ public Path saveNetFile(PetriNet net, InputStream xmlFile) throws IOException { return savedFile.toPath(); } - @Transactional protected Optional createPetriNet() throws MissingPetriNetMetaDataException, MissingIconKeyException { net = new PetriNet(); @@ -225,7 +220,6 @@ protected Optional createPetriNet() throws MissingPetriNetMetaDataExce return Optional.of(net); } - @Transactional protected void resolveRoleRef(CaseRoleRef roleRef) { CaseLogic logic = roleRef.getCaseLogic(); String roleId = getRole(roleRef.getId()).getStringId(); @@ -240,7 +234,6 @@ protected void resolveRoleRef(CaseRoleRef roleRef) { net.addPermission(roleId, roleFactory.getProcessPermissions(logic)); } - @Transactional protected void createFunction(com.netgrif.application.engine.importer.model.Function function) { com.netgrif.application.engine.petrinet.domain.Function fun = functionFactory.getFunction(function); @@ -248,8 +241,7 @@ protected void createFunction(com.netgrif.application.engine.importer.model.Func functions.add(fun); } - @Transactional - protected void resolveUserRef(CaseUserRef userRef) { + protected void resolveUserRef(CaseActorRef userRef) { CaseLogic logic = userRef.getCaseLogic(); String usersId = userRef.getId(); @@ -260,21 +252,18 @@ protected void resolveUserRef(CaseUserRef userRef) { net.addUserPermission(usersId, roleFactory.getProcessPermissions(logic)); } - @Transactional protected void resolveProcessEvents(ProcessEvents processEvents) { if (processEvents != null && processEvents.getEvent() != null) { net.setProcessEvents(createProcessEventsMap(processEvents.getEvent())); } } - @Transactional protected void resolveCaseEvents(CaseEvents caseEvents) { if (caseEvents != null && caseEvents.getEvent() != null) { net.setCaseEvents(createCaseEventsMap(caseEvents.getEvent())); } } - @Transactional protected void evaluateFunctions() { try { actionsCacheService.evaluateFunctions(functions); @@ -283,7 +272,6 @@ protected void evaluateFunctions() { } } - @Transactional protected void evaluateActions(String s, Action action) { try { actionsRunner.getActionCode(action, functions, true); @@ -292,7 +280,6 @@ protected void evaluateActions(String s, Action action) { } } - @Transactional protected void resolveActionRefs(String actionId, Action action) { Action referenced = actions.get(actionId); if (referenced == null) { @@ -302,13 +289,11 @@ protected void resolveActionRefs(String actionId, Action action) { action.setTrigger(referenced.getTrigger()); } - @Transactional protected void addI18N(I18N importI18N) { String locale = importI18N.getLocale(); importI18N.getI18NString().forEach(translation -> addTranslation(translation, locale)); } - @Transactional protected void addTranslation(I18NStringType i18NStringType, String locale) { String name = i18NStringType.getName(); I18nString translation = getI18n(name); @@ -319,7 +304,6 @@ protected void addTranslation(I18NStringType i18NStringType, String locale) { translation.addTranslation(locale, i18NStringType.getValue()); } - @Transactional protected void applyMapping(Mapping mapping) throws MissingIconKeyException { Transition transition = getTransition(mapping.getTransitionRef()); mapping.getRoleRef().forEach(roleRef -> addRoleLogic(transition, roleRef)); @@ -330,7 +314,6 @@ protected void applyMapping(Mapping mapping) throws MissingIconKeyException { mapping.getTrigger().forEach(trigger -> addTrigger(transition, trigger)); } - @Transactional protected void resolveDataActions(Data data) { String fieldId = data.getId(); if (data.getEvent() != null && !data.getEvent().isEmpty()) { @@ -368,7 +351,6 @@ private void addActionsToEvent(List actions, DataEventType type, Map actions = buildActionRefs(data.getActionRef()); @@ -388,7 +370,6 @@ protected Action fromActionRef(ActionRef actionRef) { return placeholder; } - @Transactional protected void resolveTransitionActions(com.netgrif.application.engine.importer.model.Transition trans) { if (trans.getDataRef() != null) { resolveDataRefActions(trans.getDataRef(), trans); @@ -402,7 +383,6 @@ protected void resolveTransitionActions(com.netgrif.application.engine.importer. } } - @Transactional protected void resolveDataRefActions(List dataRef, com.netgrif.application.engine.importer.model.Transition trans) { dataRef.forEach(ref -> { String fieldId = getField(ref.getId()).getStringId(); @@ -449,7 +429,6 @@ protected DataEvent createDefaultEvent(List actions, DataEventType type) return event; } - @Transactional protected void createArc(com.netgrif.application.engine.importer.model.Arc importArc) { Arc arc = arcFactory.getArc(importArc); arc.setImportId(importArc.getId()); @@ -477,15 +456,13 @@ protected void createArc(com.netgrif.application.engine.importer.model.Arc impor net.addArc(arc); } - @Transactional protected void createDataSet(Data importData) throws MissingIconKeyException { - Field field = fieldFactory.getField(importData, this); + Field field = fieldFactory.getField(importData, this); net.addDataSetField(field); fields.put(importData.getId(), field); } - @Transactional protected void createTransition(com.netgrif.application.engine.importer.model.Transition importTransition) throws MissingIconKeyException { transitionValidator.checkConflictingAttributes(importTransition, importTransition.getUsersRef(), importTransition.getUserRef(), "usersRef", "userRef"); transitionValidator.checkDeprecatedAttributes(importTransition); @@ -558,7 +535,6 @@ protected void createTransition(com.netgrif.application.engine.importer.model.Tr transitions.put(importTransition.getId(), transition); } - @Transactional protected void addAssignedUserPolicy(com.netgrif.application.engine.importer.model.Transition importTransition, Transition transition) { if (importTransition.getAssignedUser().isCancel() != null) { transition.getAssignedUserPolicy().put("cancel", importTransition.getAssignedUser().isCancel()); @@ -568,7 +544,6 @@ protected void addAssignedUserPolicy(com.netgrif.application.engine.importer.mod } } - @Transactional protected com.netgrif.application.engine.petrinet.domain.events.Event addEvent(String transitionId, com.netgrif.application.engine.importer.model.Event imported) { com.netgrif.application.engine.petrinet.domain.events.Event event = new com.netgrif.application.engine.petrinet.domain.events.Event(); event.setImportId(imported.getId()); @@ -581,7 +556,6 @@ protected com.netgrif.application.engine.petrinet.domain.events.Event addEvent(S return event; } - @Transactional protected com.netgrif.application.engine.petrinet.domain.events.ProcessEvent addProcessEvent(com.netgrif.application.engine.importer.model.ProcessEvent imported) { com.netgrif.application.engine.petrinet.domain.events.ProcessEvent event = new com.netgrif.application.engine.petrinet.domain.events.ProcessEvent(); event.setMessage(toI18NString(imported.getMessage())); @@ -593,7 +567,6 @@ protected com.netgrif.application.engine.petrinet.domain.events.ProcessEvent add return event; } - @Transactional protected com.netgrif.application.engine.petrinet.domain.events.CaseEvent addCaseEvent(com.netgrif.application.engine.importer.model.CaseEvent imported) { com.netgrif.application.engine.petrinet.domain.events.CaseEvent event = new com.netgrif.application.engine.petrinet.domain.events.CaseEvent(); event.setMessage(toI18NString(imported.getMessage())); @@ -644,7 +617,6 @@ protected List parsePhaseActions(String fieldId, EventPhaseType phase, D return actionList; } - @Transactional protected void addDefaultRole(Transition transition) { if (!net.isDefaultRoleEnabled() || isDefaultRoleReferenced(transition)) { return; @@ -656,7 +628,6 @@ protected void addDefaultRole(Transition transition) { transition.addRole(defaultRole.getStringId(), roleFactory.getPermissions(logic)); } - @Transactional protected void addAnonymousRole(Transition transition) { if (!net.isAnonymousRoleEnabled() || isAnonymousRoleReferenced(transition)) { return; @@ -667,7 +638,6 @@ protected void addAnonymousRole(Transition transition) { transition.addRole(anonymousRole.getStringId(), roleFactory.getPermissions(logic)); } - @Transactional protected void addDefaultPermissions() { if (!net.isDefaultRoleEnabled() || isDefaultRoleReferencedOnNet()) { return; @@ -680,7 +650,6 @@ protected void addDefaultPermissions() { net.addPermission(defaultRole.getStringId(), roleFactory.getProcessPermissions(logic)); } - @Transactional protected void addAnonymousPermissions() { if (!net.isAnonymousRoleEnabled() || isAnonymousRoleReferencedOnNet()) { return; @@ -692,7 +661,6 @@ protected void addAnonymousPermissions() { net.addPermission(anonymousRole.getStringId(), roleFactory.getProcessPermissions(logic)); } - @Transactional protected void addDataWithDefaultGroup(Transition transition, DataRef dataRef) throws MissingIconKeyException { DataGroup dataGroup = new DataGroup(); dataGroup.setImportId(transition.getImportId() + "_" + dataRef.getId() + "_" + System.currentTimeMillis()); @@ -709,7 +677,6 @@ protected void addDataWithDefaultGroup(Transition transition, DataRef dataRef) t addDataComponent(transition, dataRef); } - @Transactional protected void addDataGroup(Transition transition, com.netgrif.application.engine.importer.model.DataGroup importDataGroup, int index) throws MissingIconKeyException { String alignment = importDataGroup.getAlignment() != null ? importDataGroup.getAlignment().value() : ""; DataGroup dataGroup = new DataGroup(); @@ -738,7 +705,6 @@ protected void addDataGroup(Transition transition, com.netgrif.application.engin } } - @Transactional protected void addToTransaction(Transition transition, TransactionRef transactionRef) { Transaction transaction = getTransaction(transactionRef.getId()); if (transaction == null) { @@ -747,7 +713,6 @@ protected void addToTransaction(Transition transition, TransactionRef transactio transaction.addTransition(transition); } - @Transactional protected void addRoleLogic(Transition transition, RoleRef roleRef) { Logic logic = roleRef.getLogic(); String roleId = getRole(roleRef.getId()).getStringId(); @@ -765,8 +730,7 @@ protected void addRoleLogic(Transition transition, RoleRef roleRef) { transition.addRole(roleId, roleFactory.getPermissions(logic)); } - @Transactional - protected void addUserLogic(Transition transition, UserRef userRef) { + protected void addUserLogic(Transition transition, ActorRef userRef) { Logic logic = userRef.getLogic(); String userRefId = userRef.getId(); @@ -780,7 +744,6 @@ protected void addUserLogic(Transition transition, UserRef userRef) { transition.addUserRef(userRefId, roleFactory.getPermissions(logic)); } - @Transactional protected void addDataLogic(Transition transition, DataRef dataRef) { Logic logic = dataRef.getLogic(); try { @@ -800,7 +763,6 @@ protected void addDataLogic(Transition transition, DataRef dataRef) { } } - @Transactional protected void addDataLayout(Transition transition, DataRef dataRef) { Layout layout = dataRef.getLayout(); try { @@ -831,7 +793,6 @@ protected void addDataLayout(Transition transition, DataRef dataRef) { } } - @Transactional protected void addDataComponent(Transition transition, DataRef dataRef) throws MissingIconKeyException { String fieldId = getField(dataRef.getId()).getStringId(); Component component = null; @@ -841,7 +802,6 @@ protected void addDataComponent(Transition transition, DataRef dataRef) throws M transition.addDataSet(fieldId, null, null, null, component); } - @Transactional protected Map buildEvents(String fieldId, List events, String transitionId) { Map parsedEvents = new HashMap<>(); @@ -897,7 +857,6 @@ protected com.netgrif.application.engine.petrinet.domain.events.DataEvent create return dataEvent; } - @Transactional protected List buildActions(List imported, String fieldId, String transitionId) { return imported.stream() .map(action -> parseAction(fieldId, transitionId, action)) @@ -964,7 +923,6 @@ protected boolean containsParams(String definition) { return definition.matches("[\\W\\w\\s]*[\\w]*:[\\s]*[ft].[\\w]+;[\\w\\W\\s]*"); } - @Transactional protected void parseObjectIds(Action action, String fieldId, String transitionId, String definition) { try { Map ids = parseParams(definition); @@ -1020,14 +978,12 @@ protected String getFieldId(String importId) { } } - @Transactional protected void addTrigger(Transition transition, com.netgrif.application.engine.importer.model.Trigger importTrigger) { Trigger trigger = triggerFactory.buildTrigger(importTrigger); transition.addTrigger(trigger); } - @Transactional protected void createPlace(com.netgrif.application.engine.importer.model.Place importPlace) { Place place = new Place(); place.setImportId(importPlace.getId()); @@ -1044,7 +1000,6 @@ protected void createPlace(com.netgrif.application.engine.importer.model.Place i places.put(importPlace.getId(), place); } - @Transactional protected void createRole(Role importRole) { if (importRole.getId().equals(ProcessRole.DEFAULT_ROLE)) { throw new IllegalArgumentException("Role ID '" + ProcessRole.DEFAULT_ROLE + "' is a reserved identifier, roles with this ID cannot be defined!"); @@ -1115,7 +1070,6 @@ protected Map perms.containsValue(true)) - || net.getUserRefs().values().stream().anyMatch(perms -> perms.containsValue(true))) { + if (!net.getPermissions().isEmpty() || !net.getUserRefs().isEmpty()) { return; } @@ -1317,8 +1258,14 @@ protected void setMetaData() throws MissingPetriNetMetaDataException { } else { missingMetaData.add(""); } - if (!missingMetaData.isEmpty()) - throw new MissingPetriNetMetaDataException(missingMetaData); + if (document.getVersion() != null) { + net.setVersion(Version.from(document.getVersion())); + } else { + missingMetaData.add(""); + } + if (!missingMetaData.isEmpty()) { + throw new MissingPetriNetMetaDataException(missingMetaData, net.getIdentifier()); + } } protected Map buildTagsMap(List tagsList) { diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java b/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java new file mode 100644 index 00000000000..2b1447fbd93 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/ConfigurationTemplateOutcome.java @@ -0,0 +1,27 @@ +package com.netgrif.application.engine.menu.domain; + +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; + +public class ConfigurationTemplateOutcome { + /** + * Map of field data where the key is field ID and the value is field value. + */ + @Getter + private final Map mapping; + + public ConfigurationTemplateOutcome() { + this.mapping = new HashMap<>(); + } + + public ConfigurationTemplateOutcome(ToDataSetOutcome toDataSetOutcome) { + this(); + if (toDataSetOutcome != null) { + toDataSetOutcome.getDataSet() + .forEach((fieldId, fieldMap) -> this.mapping.put(fieldId, fieldMap.get("value"))); + } + } + +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java new file mode 100644 index 00000000000..bfa7f29634c --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/FilterBody.java @@ -0,0 +1,33 @@ +package com.netgrif.application.engine.menu.domain; + +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import com.netgrif.application.engine.workflow.service.interfaces.IDataService; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +@Data +@NoArgsConstructor +public class FilterBody { + private String query; + private FieldType type; + + /** + * Transforms attributes into dataSet for {@link IDataService#setData} + * + * @return {@link ToDataSetOutcome} object with dataSet + * */ + public ToDataSetOutcome toDataSet(ToDataSetOutcome viewDataSetOutcome, String filterFieldId) { + Map dataSetValues = new HashMap<>(); + if (this.type == null) { + throw new IllegalArgumentException("Filter type is not provided"); + } + dataSetValues.put("type", this.type.getName()); + dataSetValues.put("value", this.query); + viewDataSetOutcome.getDataSet().put(filterFieldId, dataSetValues); + + return viewDataSetOutcome; + } +} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/Menu.java b/src/main/java/com/netgrif/application/engine/menu/domain/Menu.java similarity index 96% rename from src/main/java/com/netgrif/application/engine/workflow/domain/menu/Menu.java rename to src/main/java/com/netgrif/application/engine/menu/domain/Menu.java index 62e75dcae2c..cce9388ba21 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/Menu.java +++ b/src/main/java/com/netgrif/application/engine/menu/domain/Menu.java @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.workflow.domain.menu; +package com.netgrif.application.engine.menu.domain; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuAndFilters.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuAndFilters.java similarity index 96% rename from src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuAndFilters.java rename to src/main/java/com/netgrif/application/engine/menu/domain/MenuAndFilters.java index 13b77dbc185..e12f019fabe 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuAndFilters.java +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuAndFilters.java @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.workflow.domain.menu; +package com.netgrif.application.engine.menu.domain; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuEntry.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntry.java similarity index 96% rename from src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuEntry.java rename to src/main/java/com/netgrif/application/engine/menu/domain/MenuEntry.java index 40bb87973aa..c3900ddf511 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuEntry.java +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntry.java @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.workflow.domain.menu; +package com.netgrif.application.engine.menu.domain; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuEntryRole.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntryRole.java similarity index 96% rename from src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuEntryRole.java rename to src/main/java/com/netgrif/application/engine/menu/domain/MenuEntryRole.java index 66d6aab50e3..dd39ea86e8d 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuEntryRole.java +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntryRole.java @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.workflow.domain.menu; +package com.netgrif.application.engine.menu.domain; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.netgrif.application.engine.workflow.domain.AuthorizationType; diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java new file mode 100644 index 00000000000..02dde589073 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemBody.java @@ -0,0 +1,222 @@ +package com.netgrif.application.engine.menu.domain; + +import com.netgrif.application.engine.menu.domain.configurations.ViewBody; +import com.netgrif.application.engine.menu.domain.configurations.ViewConstants; +import com.netgrif.application.engine.menu.utils.MenuItemUtils; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.service.interfaces.IDataService; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Class, that holds configurable attributes of menu item. + */ +@Data +@NoArgsConstructor +public class MenuItemBody { + private String uri; + private String identifier; + private String configurationTemplateIdentifier; + + private String menuIcon = "filter_none"; + private I18nString menuName; + private Map allowedRoles = new HashMap<>(); + private Map bannedRoles = new HashMap<>(); + private boolean useCustomView = false; + private String customViewSelector; + private boolean isAutoSelect = false; + + private boolean useTabbedView = true; + private String tabIcon; + private boolean useTabIcon = true; + private I18nString tabName; + private ViewBody view; + + public MenuItemBody(I18nString name, String icon) { + this.menuName = name; + this.tabName = name; + this.menuIcon = icon; + this.tabIcon = icon; + } + + public MenuItemBody(I18nString menuName, I18nString tabName, String menuIcon, String tabIcon) { + this.menuName = menuName; + this.tabName = tabName; + this.menuIcon = menuIcon; + this.tabIcon = tabIcon; + } + + public MenuItemBody(String uri, String identifier, I18nString name, String icon) { + this.uri = uri; + this.identifier = identifier; + this.menuName = name; + this.tabName = name; + this.menuIcon = icon; + this.tabIcon = icon; + } + + public MenuItemBody(String uri, String identifier, I18nString menuName, I18nString tabName, String menuIcon, String tabIcon) { + this.uri = uri; + this.identifier = identifier; + this.menuName = menuName; + this.tabName = tabName; + this.menuIcon = menuIcon; + this.tabIcon = tabIcon; + } + + public MenuItemBody(String uri, String identifier, String name, String icon) { + this.uri = uri; + this.identifier = identifier; + this.menuName = new I18nString(name); + this.tabName = new I18nString(name); + this.menuIcon = icon; + this.tabIcon = icon; + } + + public MenuItemBody(String uri, String identifier, String menuName, String tabName, String menuIcon, String tabIcon) { + this.uri = uri; + this.identifier = identifier; + this.menuName = new I18nString(menuName); + this.tabName = new I18nString(tabName); + this.menuIcon = menuIcon; + this.tabIcon = tabIcon; + } + + public String getIdentifier() { + return MenuItemUtils.sanitize(this.identifier); + } + + public void setMenuName(I18nString name) { + this.menuName = name; + } + + public void setMenuName(String name) { + this.menuName = new I18nString(name); + } + + public void setTabName(I18nString name) { + this.tabName = name; + } + + public void setTabName(String name) { + this.tabName = new I18nString(name); + } + + public void setView(ViewBody viewBody) { + if (viewBody != null) { + this.view = viewBody; + MenuItemViewType viewType = viewBody.getViewType(); + if (viewType.isTabbed() != viewType.isUntabbed()) { + // if isTabbed == isUntabbed we cannot determine the result value + this.useTabbedView = viewType.isTabbed(); + } + } + } + + /** + * @return true if the menu item contains view + * */ + public boolean hasView() { + return this.view != null; + } + + /** + * Transforms attributes into dataSet for {@link IDataService#setData} + * + * @return {@link ToDataSetOutcome} object with dataSet + */ + public ToDataSetOutcome toDataSet() { + return toDataSet(null, null, null); + } + + /** + * Transforms attributes into dataSet for {@link IDataService#setData} + * + * @param viewCase case instance of view. If provided, caseRef and taskRef are initialized + * + * @return {@link ToDataSetOutcome} object with dataSet + */ + public ToDataSetOutcome toDataSet(Case viewCase) { + return toDataSet(null, null, viewCase); + } + + /** + * Transforms attributes into dataSet for {@link IDataService#setData} + * + * @param parentId identifier of parent menu item instance + * @param nodePath uri, that represents the menu item (f.e.: "/myItem1/myItem2") + * @param viewCase case instance of view. If provided, caseRef and taskRef are initialized + * + * @return {@link ToDataSetOutcome} object with dataSet + */ + public ToDataSetOutcome toDataSet(String parentId, String nodePath, Case viewCase) { + ToDataSetOutcome outcome = new ToDataSetOutcome(); + + if (parentId != null) { + outcome.putDataSetEntry(MenuItemConstants.FIELD_PARENT_ID, FieldType.CASE_REF, List.of(parentId)); + } + if (nodePath != null) { + outcome.putDataSetEntry(MenuItemConstants.FIELD_NODE_PATH, FieldType.TEXT, nodePath); + } + if (this.menuName == null) { + outcome.putDataSetEntry(MenuItemConstants.FIELD_MENU_NAME, FieldType.I18N, new I18nString(this.identifier)); + } else { + outcome.putDataSetEntry(MenuItemConstants.FIELD_MENU_NAME, FieldType.I18N, this.menuName); + } + outcome.putDataSetEntry(MenuItemConstants.FIELD_MENU_ICON, FieldType.TEXT, this.menuIcon); + outcome.putDataSetEntry(MenuItemConstants.FIELD_USE_TABBED_VIEW, FieldType.BOOLEAN, this.useTabbedView); + if (this.tabName != null) { + outcome.putDataSetEntry(MenuItemConstants.FIELD_TAB_NAME, FieldType.I18N, this.tabName); + } + outcome.putDataSetEntry(MenuItemConstants.FIELD_TAB_ICON, FieldType.TEXT, this.tabIcon); + if (this.identifier != null) { + outcome.putDataSetEntry(MenuItemConstants.FIELD_IDENTIFIER, FieldType.TEXT, this.getIdentifier()); + } + outcome.putDataSetEntry(MenuItemConstants.FIELD_USE_TAB_ICON, FieldType.BOOLEAN, this.useTabIcon); + outcome.putDataSetEntry(MenuItemConstants.FIELD_USE_CUSTOM_VIEW, FieldType.BOOLEAN, + this.useCustomView); + outcome.putDataSetEntry(MenuItemConstants.FIELD_CUSTOM_VIEW_SELECTOR, FieldType.TEXT, + this.customViewSelector); + outcome.putDataSetEntry(MenuItemConstants.FIELD_IS_AUTO_SELECT, FieldType.BOOLEAN, this.isAutoSelect); + outcome.putDataSetEntryOptions(MenuItemConstants.FIELD_ALLOWED_ROLES, FieldType.MULTICHOICE_MAP, this.allowedRoles); + outcome.putDataSetEntryOptions(MenuItemConstants.FIELD_BANNED_ROLES, FieldType.MULTICHOICE_MAP, this.bannedRoles); + outcome.putDataSetEntry(MenuItemConstants.FIELD_CONFIGURATION_TEMPLATES, FieldType.ENUMERATION_MAP, this.configurationTemplateIdentifier); + + outcome = toDataSetWithView(viewCase, outcome); + + return outcome; + } + + /** + * Transforms minimal attributes into dataSet for view configuration by template. + * + * @param viewCase case instance of view + * @return {@link ToDataSetOutcome} object with dataSet containing only view-related fields + */ + public ToDataSetOutcome toDataSetByConfigTemplate(Case viewCase) { + ToDataSetOutcome outcome = new ToDataSetOutcome(); + outcome.putDataSetEntry(MenuItemConstants.FIELD_USE_TABBED_VIEW, FieldType.BOOLEAN, this.useTabbedView); + outcome.putDataSetEntry(MenuItemConstants.FIELD_USE_CUSTOM_VIEW, FieldType.BOOLEAN, this.useCustomView); + return toDataSetWithView(viewCase, outcome); + } + + protected ToDataSetOutcome toDataSetWithView(Case viewCase, ToDataSetOutcome outcome) { + if (viewCase != null) { + outcome.putDataSetEntry(MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE, FieldType.ENUMERATION_MAP, + this.view.getViewType().getIdentifier()); + outcome.putDataSetEntry(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID, FieldType.CASE_REF, + List.of(viewCase.getStringId())); + String taskId = MenuItemUtils.findTaskIdInCase(viewCase, ViewConstants.TRANS_SETTINGS_ID); + outcome.putDataSetEntry(MenuItemConstants.FIELD_VIEW_CONFIGURATION_FORM, FieldType.TASK_REF, List.of(taskId)); + String allDataTaskId = MenuItemUtils.findTaskIdInCase(viewCase, ViewConstants.TRANS_ALL_MENU_DATA_ID); + outcome.putDataSetEntry(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM, FieldType.TASK_REF, List.of(allDataTaskId)); + } + return outcome; + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java new file mode 100644 index 00000000000..60992401417 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemConstants.java @@ -0,0 +1,44 @@ +package com.netgrif.application.engine.menu.domain; + +/** + * Here are declared constants of process menu_item.xml. + */ +public class MenuItemConstants { + public static final String FIELD_PARENT_ID = "parentId"; + public static final String FIELD_CHILD_ITEM_IDS = "childItemIds"; + public static final String FIELD_HAS_CHILDREN = "hasChildren"; + public static final String FIELD_IDENTIFIER = "menu_item_identifier"; + public static final String FIELD_APPEND_MENU_ITEM = "append_menu_item_stringId"; + public static final String FIELD_ALLOWED_ROLES = "allowed_roles"; + public static final String FIELD_BANNED_ROLES = "banned_roles"; + public static final String FIELD_MENU_NAME = "menu_name"; + public static final String FIELD_MENU_ICON = "menu_icon"; + public static final String FIELD_TAB_NAME = "tab_name"; + public static final String FIELD_USE_TABBED_VIEW = "use_tabbed_view"; + public static final String FIELD_USE_TAB_ICON = "use_tab_icon"; + public static final String FIELD_TAB_ICON = "tab_icon"; + public static final String FIELD_VIEW_CONFIGURATION_TYPE = "view_configuration_type"; + public static final String FIELD_NODE_PATH = "nodePath"; + public static final String FIELD_NODE_NAME = "nodeName"; + public static final String FIELD_DUPLICATE_TITLE = "duplicate_new_title"; + public static final String FIELD_DUPLICATE_IDENTIFIER = "duplicate_view_identifier"; + public static final String FIELD_DUPLICATE_RESET_CHILD_ITEM_IDS = "duplicate_reset_childItemIds"; + public static final String FIELD_USE_CUSTOM_VIEW = "use_custom_view"; + public static final String FIELD_CUSTOM_VIEW_SELECTOR = "custom_view_selector"; + public static final String FIELD_IS_AUTO_SELECT = "is_auto_select"; + public static final String FIELD_VIEW_CONFIGURATION_ID = "view_configuration_id"; + public static final String FIELD_VIEW_CONFIGURATION_FORM = "view_configuration_form"; + public static final String FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM = "view_configuration_all_data_form"; + public static final String FIELD_CONFIGURATION_TEMPLATES = "configuration_templates"; + + public static final String TRANS_SETTINGS_ID = "item_settings"; + public static final String TRANS_SYS_INIT_ID = "system_initialize"; + public static final String TRANS_INIT_ID = "initialize"; + public static final String TRANS_SYNC_ID = "data_sync"; + public static final String TRANS_ALL_MENU_DATA = "all_menu_data"; + + public static final String PROCESS_IDENTIFIER = "menu_item"; + + public static final String IDENTIFIER_INDEX_NAME = "menuItemIdentifierIdx"; + public static final String NODE_PATH_INDEX_NAME = "menuItemNodePathIdx"; +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemViewType.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemViewType.java new file mode 100644 index 00000000000..bdf38961bee --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuItemViewType.java @@ -0,0 +1,95 @@ +package com.netgrif.application.engine.menu.domain; + +import com.netgrif.application.engine.petrinet.domain.I18nString; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + + +/** + * Here is listed and configured every configuration process available for menu items. + * */ +@Getter +public enum MenuItemViewType { + CASE_VIEW(new I18nString("Case view", + Map.of("sk", "Zobrazenie prípadov", "de", "Fallansicht")), + "case_view", List.of("task_view"), true, true, true), + TASK_VIEW(new I18nString("Task view", + Map.of("sk", "Zobrazenie úloh", "de", "Aufgabenansicht")), + "task_view", List.of(), true, true, true), + TABBED_TICKET_VIEW(new I18nString("Tabbed ticket view", + Map.of("sk", "Tiketové zobrazenie v taboch", "de", "Ticketansicht mit Registerkarten")), + "tabbed_ticket_view", List.of("single_task_view"), true, false, true), + SINGLE_TASK_VIEW(new I18nString("Single task view", + Map.of("sk", "Zobrazenie jednej úlohy", "de", "Einzelaufgabenansicht")), + "single_task_view", List.of(), true, true, true); + + private final I18nString name; + private final String identifier; + /** + * List of view identifiers of views, that can be associated with the view + * */ + private final List allowedAssociatedViews; + private final boolean isTabbed; + private final boolean isUntabbed; + /** + * if false, the view cannot be used as first configuration of the menu_item, but can be used as secondary + * (associated to another view) + * */ + private final boolean isPrimary; + + MenuItemViewType(I18nString name, String identifier, List allowedAssociatedViews, boolean isTabbed, + boolean isUntabbed, boolean isPrimary) { + this.name = name; + this.identifier = identifier; + this.allowedAssociatedViews = allowedAssociatedViews; + this.isTabbed = isTabbed; + this.isUntabbed = isUntabbed; + this.isPrimary = isPrimary; + } + + /** + * Builds enum value by the view identifier + * */ + public static MenuItemViewType fromIdentifier(String identifier) { + for (MenuItemViewType view : MenuItemViewType.values()) { + if (view.identifier.equals(identifier)) { + return view; + } + } + throw new IllegalArgumentException(identifier); + } + + /** + * Finds all enum values, that are tabbed or non-tabbed + * + * @param isTabbed if true, only tabbed values will be returned + * @param isPrimary if true, only views accessible directly from the menu_item will be returned + * + * @return List of views based on {@link #isTabbed} + * */ + public static List findAllByIsTabbedAndIsPrimary(boolean isTabbed, boolean isPrimary) { + return Arrays.stream(MenuItemViewType.values()) + .filter(view -> (view.isTabbed == isTabbed || view.isUntabbed != isTabbed) && view.isPrimary == isPrimary) + .collect(Collectors.toList()); + } + + /** + * Finds all enum values, that are tabbed or non-tabbed and are defined in parent view as {@link #allowedAssociatedViews} + * + * @param isTabbed if true, set of views is reduced to only tabbed views + * @param parentIdentifier identifier of the view, that contains returned views in {@link #allowedAssociatedViews} + * + * @return List of views based on {@link #isTabbed} and {@link #allowedAssociatedViews} + * */ + public static List findAllByIsTabbedAndParentIdentifier(boolean isTabbed, String parentIdentifier) { + MenuItemViewType parentView = fromIdentifier(parentIdentifier); + return Arrays.stream(MenuItemViewType.values()) + .filter(view -> (view.isTabbed == isTabbed || view.isUntabbed != isTabbed) + && parentView.getAllowedAssociatedViews().contains(view.identifier)) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuList.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuList.java similarity index 95% rename from src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuList.java rename to src/main/java/com/netgrif/application/engine/menu/domain/MenuList.java index 9ee55560ed9..e76f0f7e40e 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuList.java +++ b/src/main/java/com/netgrif/application/engine/menu/domain/MenuList.java @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.workflow.domain.menu; +package com.netgrif.application.engine.menu.domain; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/ToDataSetOutcome.java b/src/main/java/com/netgrif/application/engine/menu/domain/ToDataSetOutcome.java new file mode 100644 index 00000000000..7fdba433994 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/ToDataSetOutcome.java @@ -0,0 +1,53 @@ +package com.netgrif.application.engine.menu.domain; + +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import lombok.AllArgsConstructor; +import lombok.Data; + +import javax.annotation.Nullable; +import java.util.*; + +@Data +@AllArgsConstructor +public class ToDataSetOutcome { + private Map> dataSet; + + public ToDataSetOutcome() { + this.dataSet = new HashMap<>(); + } + + /** + * Puts provided value into {@link #dataSet} according to dataSet rules. + * + * @param fieldId importId of the field + * @param fieldType type of the field + * @param fieldValue new value of the field + * */ + public void putDataSetEntry(String fieldId, FieldType fieldType, @Nullable Object fieldValue) { + Map fieldMap = new LinkedHashMap<>(); + fieldMap.put("type", fieldType.getName()); + fieldMap.put("value", fieldValue); + this.dataSet.put(fieldId, fieldMap); + } + + /** + * Puts provided options into {@link #dataSet} according to dataSet rules. + * + * @param fieldId importId of the field + * @param fieldType type of the field + * @param options new options of the field + * */ + public void putDataSetEntryOptions(String fieldId, FieldType fieldType, @Nullable Map options) { + Map fieldMap = new LinkedHashMap<>(); + fieldMap.put("type", fieldType.getName()); + if (fieldType.equals(FieldType.MULTICHOICE_MAP)) { + fieldMap.put("value", Set.of()); + } + if (options == null) { + options = new HashMap<>(); + } + fieldMap.put("options", options); + this.dataSet.put(fieldId, fieldMap); + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java new file mode 100644 index 00000000000..6a47b3b45b3 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewBody.java @@ -0,0 +1,114 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +import com.netgrif.application.engine.menu.domain.MenuItemViewType; +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CaseViewBody extends ViewBody { + private String viewSearchType = "fulltext_advanced"; + private I18nString createCaseButtonTitle; + private String createCaseButtonIcon = "add"; + private boolean requireTitleInCreation = true; + private boolean showCreateCaseButton = true; + private String bannedNetsInCreation; + private boolean showMoreMenu = false; + private boolean allowHeaderTableMode = true; + private List headersMode = new ArrayList<>(List.of("sort", "edit", "search")); + private String headersDefaultMode = "sort"; + private List defaultHeaders; + private boolean headerModeChangeable = true; + private boolean useDefaultHeaders = true; + private I18nString emptyContentText; + private String emptyContentIcon; + private boolean allowExport; + private boolean allAllowedNets = true; + private List allowedNets; + private boolean inheritAllowedNets = true; + + private ViewBody chainedView; + + @Override + public ViewBody getAssociatedViewBody() { + return this.chainedView; + } + + @Override + public MenuItemViewType getViewType() { + return MenuItemViewType.CASE_VIEW; + } + + @Override + public String getFilterFieldId() { + return CaseViewConstants.FIELD_FILTER; + } + + @Override + public FieldType getFilterType() { + return FieldType.CASE_FILTER; + } + + @Override + protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) { + + outcome.putDataSetEntry(CaseViewConstants.FIELD_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP, + this.viewSearchType); + if (this.createCaseButtonTitle != null) { + outcome.putDataSetEntry(CaseViewConstants.FIELD_CREATE_CASE_BUTTON_TITLE, FieldType.I18N, + this.createCaseButtonTitle); + } + outcome.putDataSetEntry(CaseViewConstants.FIELD_CREATE_CASE_BUTTON_ICON, FieldType.TEXT, + this.createCaseButtonIcon); + outcome.putDataSetEntry(CaseViewConstants.FIELD_REQUIRE_TITLE_IN_CREATION, FieldType.BOOLEAN, + this.requireTitleInCreation); + outcome.putDataSetEntry(CaseViewConstants.FIELD_SHOW_CREATE_CASE_BUTTON, FieldType.BOOLEAN, + this.showCreateCaseButton); + outcome.putDataSetEntry(CaseViewConstants.FIELD_BANNED_NETS_IN_CREATION, FieldType.TEXT, + this.bannedNetsInCreation); + outcome.putDataSetEntry(CaseViewConstants.FIELD_SHOW_MORE_MENU, FieldType.BOOLEAN, + this.showMoreMenu); + outcome.putDataSetEntry(CaseViewConstants.FIELD_ALLOW_HEADER_TABLE_MODE, FieldType.BOOLEAN, + this.allowHeaderTableMode); + outcome.putDataSetEntry(CaseViewConstants.FIELD_HEADERS_MODE, FieldType.MULTICHOICE_MAP, + this.headersMode == null ? new ArrayList<>() : this.headersMode); + outcome.putDataSetEntry(CaseViewConstants.FIELD_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP, + this.headersDefaultMode); + if (this.defaultHeaders != null) { + outcome.putDataSetEntry(CaseViewConstants.FIELD_DEFAULT_HEADERS, FieldType.STRING_COLLECTION, + this.defaultHeaders); + } + outcome.putDataSetEntry(CaseViewConstants.FIELD_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN, + this.headerModeChangeable); + outcome.putDataSetEntry(CaseViewConstants.FIELD_USE_DEFAULT_HEADERS, FieldType.BOOLEAN, + this.useDefaultHeaders); + if (this.emptyContentText != null) { + outcome.putDataSetEntry(CaseViewConstants.FIELD_EMPTY_CONTENT_TEXT, FieldType.I18N, + this.emptyContentText); + } + outcome.putDataSetEntry(CaseViewConstants.FIELD_EMPTY_CONTENT_ICON, FieldType.TEXT, + this.emptyContentIcon); + outcome.putDataSetEntry(CaseViewConstants.FIELD_ALLOW_EXPORT, FieldType.BOOLEAN, + this.allowExport); + + outcome.putDataSetEntry(CaseViewConstants.FIELD_ALL_ALLOWED_NETS, FieldType.BOOLEAN, + this.allAllowedNets); + if (this.allowedNets != null) { + outcome.putDataSetEntry(CaseViewConstants.FIELD_ALLOWED_NETS, FieldType.STRING_COLLECTION, + this.allowedNets); + } + outcome.putDataSetEntry(CaseViewConstants.FIELD_INHERIT_ALLOWED_NETS, FieldType.BOOLEAN, + this.inheritAllowedNets); + + return outcome; + } +} + diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java new file mode 100644 index 00000000000..44db426e1b5 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/CaseViewConstants.java @@ -0,0 +1,27 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +/** + * Here are declared constants of process case_view_configuration.xml. + */ +public class CaseViewConstants extends ViewConstants { + public static final String FIELD_DEFAULT_HEADERS = "case_default_headers"; + public static final String FIELD_REQUIRE_TITLE_IN_CREATION = "require_title_in_creation"; + public static final String FIELD_VIEW_SEARCH_TYPE = "case_view_search_type"; + public static final String FIELD_CREATE_CASE_BUTTON_TITLE = "create_case_button_title"; + public static final String FIELD_CREATE_CASE_BUTTON_ICON = "create_case_button_icon"; + public static final String FIELD_BANNED_NETS_IN_CREATION = "banned_nets_in_creation"; + public static final String FIELD_SHOW_CREATE_CASE_BUTTON = "show_create_case_button"; + public static final String FIELD_SHOW_MORE_MENU = "case_show_more_menu"; + public static final String FIELD_ALLOW_HEADER_TABLE_MODE = "case_allow_header_table_mode"; + public static final String FIELD_HEADERS_MODE = "case_headers_mode"; + public static final String FIELD_HEADERS_DEFAULT_MODE = "case_headers_default_mode"; + public static final String FIELD_IS_HEADER_MODE_CHANGEABLE = "case_is_header_mode_changeable"; + public static final String FIELD_USE_DEFAULT_HEADERS = "use_case_default_headers"; + public static final String FIELD_EMPTY_CONTENT_TEXT = "case_empty_content_text"; + public static final String FIELD_EMPTY_CONTENT_ICON = "case_empty_content_icon"; + public static final String FIELD_FILTER = "case_filter"; + public static final String FIELD_ALLOW_EXPORT = "case_allow_export"; + public static final String FIELD_ALL_ALLOWED_NETS = "case_all_allowed_nets"; + public static final String FIELD_ALLOWED_NETS = "case_allowed_nets"; + public static final String FIELD_INHERIT_ALLOWED_NETS = "case_inherit_allowed_nets"; +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewBody.java new file mode 100644 index 00000000000..1b4e3531adb --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewBody.java @@ -0,0 +1,43 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +import com.netgrif.application.engine.menu.domain.MenuItemViewType; +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SingleTaskViewBody extends ViewBody { + private boolean showPageHeader = true; + private boolean showPageFooter = false; + + @Override + public ViewBody getAssociatedViewBody() { + return null; + } + + @Override + public MenuItemViewType getViewType() { + return MenuItemViewType.SINGLE_TASK_VIEW; + } + + @Override + public String getFilterFieldId() { + return SingleTaskViewConstants.FIELD_FILTER; + } + + @Override + public FieldType getFilterType() { + return FieldType.TASK_FILTER; + } + + @Override + protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) { + outcome.putDataSetEntry(SingleTaskViewConstants.FIELD_SHOW_PAGE_HEADER, FieldType.BOOLEAN, this.showPageHeader); + outcome.putDataSetEntry(SingleTaskViewConstants.FIELD_SHOW_PAGE_FOOTER, FieldType.BOOLEAN, this.showPageFooter); + return outcome; + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewConstants.java new file mode 100644 index 00000000000..d251523ae8f --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/SingleTaskViewConstants.java @@ -0,0 +1,7 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +public class SingleTaskViewConstants extends ViewConstants { + public static final String FIELD_SHOW_PAGE_HEADER = "show_page_header"; + public static final String FIELD_SHOW_PAGE_FOOTER = "show_page_footer"; + public static final String FIELD_FILTER = "task_filter"; +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTicketViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTicketViewBody.java new file mode 100644 index 00000000000..7f898695be2 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTicketViewBody.java @@ -0,0 +1,41 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +import com.netgrif.application.engine.menu.domain.MenuItemViewType; +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TabbedTicketViewBody extends ViewBody { + + private ViewBody chainedView; + + @Override + public ViewBody getAssociatedViewBody() { + return this.chainedView; + } + + @Override + public MenuItemViewType getViewType() { + return MenuItemViewType.TABBED_TICKET_VIEW; + } + + @Override + public String getFilterFieldId() { + return ""; + } + + @Override + public FieldType getFilterType() { + return null; + } + + @Override + protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) { + return outcome; + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTicketViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTicketViewConstants.java new file mode 100644 index 00000000000..af308eba20c --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TabbedTicketViewConstants.java @@ -0,0 +1,4 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +public class TabbedTicketViewConstants extends ViewConstants { +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java new file mode 100644 index 00000000000..4e8e09456bd --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewBody.java @@ -0,0 +1,94 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +import com.netgrif.application.engine.menu.domain.MenuItemViewType; +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TaskViewBody extends ViewBody { + private boolean mergeFilters = true; + private String viewSearchType = "fulltext_advanced"; + private List headersMode = new ArrayList<>(List.of("sort", "edit")); + private String headersDefaultMode = "sort"; + private boolean isHeaderModeChangeable = true; + private boolean allowHeaderTableMode = true; + private boolean useDefaultHeaders = true; + private List defaultHeaders; + private boolean showMoreMenu = true; + private I18nString emptyContentText; + private String emptyContentIcon; + private boolean allAllowedNets = true; + private List allowedNets; + private boolean inheritAllowedNets = true; + + @Override + public ViewBody getAssociatedViewBody() { + return null; + } + + @Override + public MenuItemViewType getViewType() { + return MenuItemViewType.TASK_VIEW; + } + + @Override + public String getFilterFieldId() { + return TaskViewConstants.FIELD_FILTER; + } + + @Override + public FieldType getFilterType() { + return FieldType.TASK_FILTER; + } + + @Override + protected ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome) { + + outcome.putDataSetEntry(TaskViewConstants.FIELD_MERGE_FILTERS, FieldType.BOOLEAN, + this.mergeFilters); + outcome.putDataSetEntry(TaskViewConstants.FIELD_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP, + this.viewSearchType); + outcome.putDataSetEntry(TaskViewConstants.FIELD_HEADERS_MODE, FieldType.MULTICHOICE_MAP, + this.headersMode == null ? new ArrayList<>() : this.headersMode); + outcome.putDataSetEntry(TaskViewConstants.FIELD_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP, + this.headersDefaultMode); + outcome.putDataSetEntry(TaskViewConstants.FIELD_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN, + this.isHeaderModeChangeable); + outcome.putDataSetEntry(TaskViewConstants.FIELD_ALLOW_HEADER_TABLE_MODE, FieldType.BOOLEAN, + this.allowHeaderTableMode); + outcome.putDataSetEntry(TaskViewConstants.FIELD_USE_DEFAULT_HEADERS, FieldType.BOOLEAN, + this.useDefaultHeaders); + if (this.defaultHeaders != null) { + outcome.putDataSetEntry(TaskViewConstants.FIELD_DEFAULT_HEADERS, FieldType.STRING_COLLECTION, + this.defaultHeaders); + } + outcome.putDataSetEntry(TaskViewConstants.FIELD_SHOW_MORE_MENU, FieldType.BOOLEAN, + this.showMoreMenu); + if (this.emptyContentText != null) { + outcome.putDataSetEntry(TaskViewConstants.FIELD_EMPTY_CONTENT_TEXT, FieldType.I18N, + this.emptyContentText); + } + outcome.putDataSetEntry(TaskViewConstants.FIELD_EMPTY_CONTENT_ICON, FieldType.TEXT, + this.emptyContentIcon); + + outcome.putDataSetEntry(TaskViewConstants.FIELD_ALL_ALLOWED_NETS, FieldType.BOOLEAN, + this.allAllowedNets); + if (this.allowedNets != null) { + outcome.putDataSetEntry(TaskViewConstants.FIELD_ALLOWED_NETS, FieldType.STRING_COLLECTION, + this.allowedNets); + } + outcome.putDataSetEntry(TaskViewConstants.FIELD_INHERIT_ALLOWED_NETS, FieldType.BOOLEAN, + this.inheritAllowedNets); + + return outcome; + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java new file mode 100644 index 00000000000..c9acf6f9e54 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/TaskViewConstants.java @@ -0,0 +1,22 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +/** + * Here are declared constants of process task_view_configuration.xml. + */ +public class TaskViewConstants extends ViewConstants { + public static final String FIELD_MERGE_FILTERS = "merge_filters"; + public static final String FIELD_FILTER = "task_filter"; + public static final String FIELD_VIEW_SEARCH_TYPE = "task_view_search_type"; + public static final String FIELD_DEFAULT_HEADERS = "task_default_headers"; + public static final String FIELD_HEADERS_MODE = "task_headers_mode"; + public static final String FIELD_HEADERS_DEFAULT_MODE = "task_headers_default_mode"; + public static final String FIELD_IS_HEADER_MODE_CHANGEABLE = "task_is_header_mode_changeable"; + public static final String FIELD_ALLOW_HEADER_TABLE_MODE = "task_allow_header_table_mode"; + public static final String FIELD_USE_DEFAULT_HEADERS = "use_task_default_headers"; + public static final String FIELD_SHOW_MORE_MENU = "task_show_more_menu"; + public static final String FIELD_EMPTY_CONTENT_TEXT = "task_empty_content_text"; + public static final String FIELD_EMPTY_CONTENT_ICON = "task_empty_content_icon"; + public static final String FIELD_ALL_ALLOWED_NETS = "task_all_allowed_nets"; + public static final String FIELD_ALLOWED_NETS = "task_allowed_nets"; + public static final String FIELD_INHERIT_ALLOWED_NETS = "task_inherit_allowed_nets"; +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewBody.java new file mode 100644 index 00000000000..58c8edf0c0f --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewBody.java @@ -0,0 +1,87 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +import com.netgrif.application.engine.menu.domain.FilterBody; +import com.netgrif.application.engine.menu.domain.MenuItemViewType; +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.menu.utils.MenuItemUtils; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.service.interfaces.IDataService; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.annotation.Nullable; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public abstract class ViewBody { + + @Nullable + protected FilterBody filterBody; + + public abstract ViewBody getAssociatedViewBody(); + public abstract MenuItemViewType getViewType(); + public abstract String getFilterFieldId(); + public abstract FieldType getFilterType(); + /** + * Internal method, that must transform data in concrete class and add them into received outcome. Method must return + * the updated outcome. + * */ + protected abstract ToDataSetOutcome toDataSetInternal(ToDataSetOutcome outcome); + + /** + * Checks if the view has associated view + * + * @return true if this view has associated view + * */ + public boolean hasAssociatedView() { + return this.getAssociatedViewBody() != null; + } + + /** + * @return returns process identifier for this view + * */ + public String getViewProcessIdentifier() { + return getViewType().getIdentifier() + "_configuration"; + } + + /** + * Transforms data of this class into {@link ToDataSetOutcome}, which contains prepared data for the {@link IDataService#setData} + * + * @return {@link ToDataSetOutcome} object containing dataSet + * */ + public ToDataSetOutcome toDataSet() { + return toDataSet(null); + }; + + /** + * Transforms data of this class into {@link ToDataSetOutcome}, which contains prepared data for the {@link IDataService#setData} + * + * @param associatedViewCase case instance of associated view. If provided, caseRef and taskRef are initialized. + * + * @return {@link ToDataSetOutcome} object containing dataSet + * */ + public ToDataSetOutcome toDataSet(Case associatedViewCase) { + ToDataSetOutcome outcome = new ToDataSetOutcome(); + + if (associatedViewCase != null) { + outcome.putDataSetEntry(ViewConstants.FIELD_CONFIGURATION_TYPE, FieldType.ENUMERATION_MAP, + this.getAssociatedViewBody().getViewType().getIdentifier()); + outcome.putDataSetEntry(ViewConstants.FIELD_VIEW_CONFIGURATION_ID, FieldType.CASE_REF, + List.of(associatedViewCase.getStringId())); + String taskId = MenuItemUtils.findTaskIdInCase(associatedViewCase, ViewConstants.TRANS_SETTINGS_ID); + outcome.putDataSetEntry(ViewConstants.FIELD_VIEW_CONFIGURATION_FORM, FieldType.TASK_REF, List.of(taskId)); + String allDataTaskId = MenuItemUtils.findTaskIdInCase(associatedViewCase, ViewConstants.TRANS_ALL_MENU_DATA_ID); + outcome.putDataSetEntry(ViewConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM, FieldType.TASK_REF, List.of(allDataTaskId)); + } + if (filterBody != null) { + filterBody.setType(getFilterType()); + outcome = filterBody.toDataSet(outcome, getFilterFieldId()); + } + + return toDataSetInternal(outcome); + }; +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewConstants.java new file mode 100644 index 00000000000..d27bfa83e03 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/configurations/ViewConstants.java @@ -0,0 +1,16 @@ +package com.netgrif.application.engine.menu.domain.configurations; + +/** + * Here are declared general constants of menu item configuration processes. + */ +public class ViewConstants { + public static final String FIELD_CONFIGURATION_TYPE = "view_configuration_type"; + public static final String FIELD_VIEW_CONFIGURATION_ID = "view_configuration_id"; + public static final String FIELD_VIEW_CONFIGURATION_FORM = "view_configuration_form"; + public static final String FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM = "view_configuration_all_data_form"; + + public static final String TRANS_INIT_ID = "initialize"; + public static final String TRANS_SETTINGS_ID = "settings"; + public static final String TRANS_ALL_MENU_DATA_ID = "all_menu_data"; + public static final String TRANS_SYNC_ID = "data_sync"; +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardItemBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardItemBody.java new file mode 100644 index 00000000000..3d08716d758 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardItemBody.java @@ -0,0 +1,54 @@ +package com.netgrif.application.engine.menu.domain.dashboard; + +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@AllArgsConstructor +public class DashboardItemBody { + + private String id; + private String menuItems; + private boolean isInternal; + private String externalUrl; + private String externalIcon; + private String iconColor; + private String itemIcon; + private String fontColor; + private String fontWeight; + private I18nString name; + private boolean inheritIcon; + private boolean inheritName; + private boolean isActive; + + public DashboardItemBody(String id, String menuItems, String itemIcon, I18nString name, boolean isInternal) { + this.id = id; + this.menuItems = menuItems; + this.isInternal = isInternal; + this.itemIcon = itemIcon; + this.name = name; + } + + public ToDataSetOutcome toDataSet() { + ToDataSetOutcome outcome = new ToDataSetOutcome(); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_ID, FieldType.TEXT, this.id); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_MENU_ITEM_LIST, FieldType.ENUMERATION_MAP, this.menuItems); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_IS_INTERNAL, FieldType.BOOLEAN, this.isInternal); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_EXTERNAL_URL, FieldType.TEXT, this.externalUrl); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_EXTERNAL_ICON, FieldType.BOOLEAN, this.externalIcon); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_ICON_COLOR, FieldType.TEXT, this.iconColor); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_ITEM_ICON, FieldType.TEXT, this.itemIcon); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_FONT_COLOR, FieldType.TEXT, this.fontColor); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_FONT_WEIGHT, FieldType.TEXT, this.fontWeight); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_ITEM_NAME, FieldType.I18N, this.name); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_INHERIT_ICON, FieldType.BOOLEAN, this.inheritIcon); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_INHERIT_NAME, FieldType.BOOLEAN, this.inheritName); + outcome.putDataSetEntry(DashboardItemConstants.FIELD_IS_ACTIVE, FieldType.BOOLEAN, this.isActive); + return outcome; + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardItemConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardItemConstants.java new file mode 100644 index 00000000000..de9c3b9bcbf --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardItemConstants.java @@ -0,0 +1,23 @@ +package com.netgrif.application.engine.menu.domain.dashboard; + +/** + * Here are declared constants of process dashboard_management.xml. + */ +public class DashboardItemConstants { + public static final String PROCESS_IDENTIFIER = "dashboard_item"; + public static final String TASK_CONFIGURE = "configuration"; + public static final String FIELD_ID= "item_id"; + public static final String FIELD_MENU_ITEM_LIST = "menu_items_list"; + public static final String FIELD_IS_INTERNAL= "is_internal"; + public static final String FIELD_EXTERNAL_URL= "external_url"; + public static final String FIELD_EXTERNAL_ICON= "external_icon"; + public static final String FIELD_ICON_COLOR= "icon_color"; + public static final String FIELD_ITEM_ICON= "item_icon"; + public static final String FIELD_FONT_COLOR= "font_color"; + public static final String FIELD_FONT_WEIGHT= "font_weight"; + public static final String FIELD_ITEM_NAME= "item_name"; + public static final String FIELD_INHERIT_ICON= "inherit_icon"; + public static final String FIELD_INHERIT_NAME= "inherit_name"; + public static final String FIELD_IS_ACTIVE= "is_active"; + +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardManagementBody.java b/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardManagementBody.java new file mode 100644 index 00000000000..8f8aa25da75 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardManagementBody.java @@ -0,0 +1,84 @@ +package com.netgrif.application.engine.menu.domain.dashboard; + +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class DashboardManagementBody { + /** + * id + */ + private String id; + + /** + * name with translation + */ + private I18nString name; + /** + * logo from assets on frontend + */ + private String logo; + /** + * dashboardItems + */ + private HashMap dashboardItems; + /** + * mapping for menuItems to DashboardItems + */ + private HashMap menuItemsToDashboardItems; + /** + * should dashboard toolbar contains menu with options. + */ + private boolean simpleDashboard = false; + /** + * should dashboard toolbar menu with profile. + */ + private boolean profileDashboard = false; + /** + * should dashboard toolbar contains menu with language selection + */ + private boolean languageDashboard = false; + /** + * should dashboard toolbar contains logout button. + */ + private boolean logoutDashboard = false; + + + public DashboardManagementBody(String id, I18nString name) { + this.id = id; + this.name = name; + } + + public ToDataSetOutcome toDataSet() { + DashboardToDataSetOutcome outcome = new DashboardToDataSetOutcome(); + if (this.id != null) { + outcome.putDataSetEntry(DashboardManagementConstants.FIELD_ID, FieldType.TEXT, this.id); + } + if (this.id != null) { + outcome.putDataSetEntry(DashboardManagementConstants.FIELD_NAME, FieldType.I18N, this.name); + } + if (this.logo != null) { + outcome.putDataSetEntry(DashboardManagementConstants.FIELD_LOGO, FieldType.TEXT, this.logo); + } + outcome.putDataSetEntry(DashboardManagementConstants.FIELD_SIMPLE_DASHBOARD, FieldType.BOOLEAN, this.simpleDashboard); + outcome.putDataSetEntry(DashboardManagementConstants.FIELD_PROFILE_DASHBOARD, FieldType.BOOLEAN, this.profileDashboard); + outcome.putDataSetEntry(DashboardManagementConstants.FIELD_LANGUAGE_DASHBOARD, FieldType.BOOLEAN, this.profileDashboard); + outcome.putDataSetEntry(DashboardManagementConstants.FIELD_LOGOUT_DASHBOARD, FieldType.BOOLEAN, this.logoutDashboard); + if (this.dashboardItems != null) { + outcome.putDataSetEntryWithOptions(DashboardManagementConstants.FIELD_DASHBOARD_ITEM_LIST, FieldType.ENUMERATION_MAP, this.dashboardItems, ""); + outcome.putDataSetEntryWithOptions(DashboardManagementConstants.FIELD_DASHBOARD_ITEM_TO_MENU_ITEM_LIST, FieldType.ENUMERATION_MAP, this.menuItemsToDashboardItems, ""); + outcome.putDataSetEntry(DashboardManagementConstants.FIELD_ITEMS_ORDER, FieldType.TEXT, String.join(",", this.dashboardItems.keySet())); + } + return outcome; + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardManagementConstants.java b/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardManagementConstants.java new file mode 100644 index 00000000000..0b922261721 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardManagementConstants.java @@ -0,0 +1,20 @@ +package com.netgrif.application.engine.menu.domain.dashboard; + +/** + * Here are declared constants of process dashboard_management.xml. + */ +public class DashboardManagementConstants { + public static final String PROCESS_IDENTIFIER = "dashboard_management"; + public static final String TASK_CONFIGURE = "configure"; + public static final String FIELD_ID = "dashboard_id"; + public static final String FIELD_NAME = "dashboard_name"; + public static final String FIELD_LOGO = "dashboard_logo"; + public static final String FIELD_DASHBOARD_ITEM_LIST = "dashboard_item_list"; + public static final String FIELD_DASHBOARD_ITEM_TO_MENU_ITEM_LIST = "dashboard_item_to_menu_item"; + public static final String FIELD_SIMPLE_DASHBOARD = "simple_dashboard_toolbar"; + public static final String FIELD_PROFILE_DASHBOARD = "profile_dashboard_toolbar"; + public static final String FIELD_LANGUAGE_DASHBOARD = "language_dashboard_toolbar"; + public static final String FIELD_LOGOUT_DASHBOARD = "logout_dashboard_toolbar"; + public static final String FIELD_ITEMS_ORDER = "items_order"; + public static final String FIELD_ADD_NEW_ITEM = "add_new_item"; +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardToDataSetOutcome.java b/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardToDataSetOutcome.java new file mode 100644 index 00000000000..b02c972122e --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/dashboard/DashboardToDataSetOutcome.java @@ -0,0 +1,34 @@ +package com.netgrif.application.engine.menu.domain.dashboard; + +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +public class DashboardToDataSetOutcome extends ToDataSetOutcome { + public void putDataSetEntryWithOptions(String fieldId, FieldType fieldType, @Nullable Map options, @Nullable Object fieldValue){ + Map fieldMap = new LinkedHashMap<>(); + + fieldMap.put("type", fieldType.getName()); + if (fieldType.equals(FieldType.MULTICHOICE_MAP)) { + fieldMap.put("value", Set.of()); + } + if (options == null) { + options = new HashMap<>(); + } + fieldMap.put("options", options); + fieldMap.put("value", fieldValue); + this.getDataSet().put(fieldId, fieldMap); + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java new file mode 100644 index 00000000000..ac5970868b7 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/CustomViewTemplate.java @@ -0,0 +1,37 @@ +package com.netgrif.application.engine.menu.domain.templates; + +import com.netgrif.application.engine.menu.domain.MenuItemBody; +import com.netgrif.application.engine.petrinet.domain.I18nString; + +import java.util.Map; + +public class CustomViewTemplate implements Template { + public static final String IDENTIFIER = "custom_view"; + + private static final I18nString NAME = new I18nString("Custom view", + Map.of("sk", "Vlastné zobrazenie", "de", "Benutzerdefinierte Ansicht")); + + private static MenuItemBody buildTemplate() { + MenuItemBody menuItemBody = new MenuItemBody(); + menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER); + menuItemBody.setUseTabbedView(false); + menuItemBody.setUseCustomView(true); + + return menuItemBody; + } + + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @Override + public I18nString getName() { + return NAME; + } + + @Override + public MenuItemBody getTemplate() { + return buildTemplate(); + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/README.md b/src/main/java/com/netgrif/application/engine/menu/domain/templates/README.md new file mode 100644 index 00000000000..7dd8af166fa --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/README.md @@ -0,0 +1,48 @@ +# Menu Item Template Creation Guide + +## Overview + +Templates provide predefined configurations for menu items that can be reused throughout the application. Each template +encapsulates a complete menu item structure with its associated views and filters. + +## Template Structure + +A template must implement the `Template` interface with three core components: + +- **Identifier**: A unique string constant that identifies the template +- **Name**: An internationalized string (I18nString) providing the display name in multiple languages +- **Template Body**: A MenuItemBody instance containing the complete menu item configuration + +## Creation Steps + +1. **Create Template Class** + - Implement the `Template` interface + - Define a unique public static IDENTIFIER constant + - Create a private static I18nString NAME with translations + - Declare a private static MenuItemBody TEMPLATE field + +2. **Build Template Configuration** + - Implement a private static `buildTemplate()` method + - Instantiate MenuItemBody and configure its properties + - Create and configure the appropriate view body (CaseViewBody, TaskViewBody, ...) + - Set up FilterBody for data filtering requirements + - Configure chained views if needed (e.g., TaskViewBody within CaseViewBody) + - Wire all components together through setter methods + +3. **Implement Interface Methods** + - Return IDENTIFIER from `getIdentifier()` + - Return NAME from `getName()` + - Return TEMPLATE from `getTemplate()` + +4. **Register Template** + - Add the template to the `MenuItemTemplateHolder.templates` map + - Use the template's IDENTIFIER as the key + - Instantiate the template class as the value + +## Best Practices + +- Keep identifiers lowercase with underscores for consistency +- Provide translations for all supported languages in the NAME field +- Build template configuration statically to ensure immutability +- Ensure all required view and filter configurations are properly initialized +- Use descriptive identifiers that reflect the template's purpose diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java new file mode 100644 index 00000000000..59a99ffd44d --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleCaseViewTemplate.java @@ -0,0 +1,42 @@ +package com.netgrif.application.engine.menu.domain.templates; + +import com.netgrif.application.engine.menu.domain.MenuItemBody; +import com.netgrif.application.engine.menu.domain.configurations.CaseViewBody; +import com.netgrif.application.engine.petrinet.domain.I18nString; + +import java.util.Map; + +public class SimpleCaseViewTemplate implements Template { + + public static final String IDENTIFIER = "simple_case_view"; + + private static final I18nString NAME = new I18nString("Simple case view", + Map.of("sk", "Zobrazenie prípadov", "de", "Fallansicht")); + + private static MenuItemBody buildTemplate() { + MenuItemBody menuItemBody = new MenuItemBody(); + menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER); + menuItemBody.setUseTabbedView(false); + + CaseViewBody caseViewBody = new CaseViewBody(); + caseViewBody.setFilterBody(Template.defaultCaseFilterBody()); + menuItemBody.setView(caseViewBody); + + return menuItemBody; + } + + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @Override + public I18nString getName() { + return NAME; + } + + @Override + public MenuItemBody getTemplate() { + return buildTemplate(); + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java new file mode 100644 index 00000000000..e636d1fc466 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SimpleTaskViewTemplate.java @@ -0,0 +1,42 @@ +package com.netgrif.application.engine.menu.domain.templates; + +import com.netgrif.application.engine.menu.domain.MenuItemBody; +import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody; +import com.netgrif.application.engine.petrinet.domain.I18nString; + +import java.util.Map; + +public class SimpleTaskViewTemplate implements Template { + + public static final String IDENTIFIER = "simple_task_view"; + + private static final I18nString NAME = new I18nString("Simple task view", + Map.of("sk", "Zobrazenie úloh", "de", "Aufgabenansicht")); + + private static MenuItemBody buildTemplate() { + MenuItemBody menuItemBody = new MenuItemBody(); + menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER); + menuItemBody.setUseTabbedView(false); + + TaskViewBody taskViewBody = new TaskViewBody(); + taskViewBody.setFilterBody(Template.defaultTaskFilterBody()); + menuItemBody.setView(taskViewBody); + + return menuItemBody; + } + + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @Override + public I18nString getName() { + return NAME; + } + + @Override + public MenuItemBody getTemplate() { + return buildTemplate(); + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java new file mode 100644 index 00000000000..a2254755a19 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/SingleTaskViewTemplate.java @@ -0,0 +1,42 @@ +package com.netgrif.application.engine.menu.domain.templates; + +import com.netgrif.application.engine.menu.domain.MenuItemBody; +import com.netgrif.application.engine.menu.domain.configurations.SingleTaskViewBody; +import com.netgrif.application.engine.petrinet.domain.I18nString; + +import java.util.Map; + +public class SingleTaskViewTemplate implements Template { + + public static final String IDENTIFIER = "single_task_view"; + + private static final I18nString NAME = new I18nString("Single task view", + Map.of("sk", "Zobrazenie jednej úlohy", "de", "Anzeige einer Aufgabe")); + + private static MenuItemBody buildTemplate() { + MenuItemBody menuItemBody = new MenuItemBody(); + menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER); + menuItemBody.setUseTabbedView(false); + + SingleTaskViewBody singleTaskViewBody = new SingleTaskViewBody(); + singleTaskViewBody.setFilterBody(Template.defaultSingleTaskFilterBody()); + menuItemBody.setView(singleTaskViewBody); + + return menuItemBody; + } + + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @Override + public I18nString getName() { + return NAME; + } + + @Override + public MenuItemBody getTemplate() { + return buildTemplate(); + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java new file mode 100644 index 00000000000..ecfc1d93f16 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedCaseViewTemplate.java @@ -0,0 +1,45 @@ +package com.netgrif.application.engine.menu.domain.templates; + +import com.netgrif.application.engine.menu.domain.MenuItemBody; +import com.netgrif.application.engine.menu.domain.configurations.CaseViewBody; +import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody; +import com.netgrif.application.engine.petrinet.domain.I18nString; + +import java.util.Map; + +public class TabbedCaseViewTemplate implements Template { + + public static final String IDENTIFIER = "tabbed_case_view"; + + private static final I18nString NAME = new I18nString("Tabbed case view", + Map.of("sk", "Zobrazenie prípadov v záložkách", "de", "Registerkartenansicht für Fälle")); + + private static MenuItemBody buildTemplate() { + MenuItemBody menuItemBody = new MenuItemBody(); + menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER); + menuItemBody.setUseTabbedView(true); + + CaseViewBody caseViewBody = new CaseViewBody(); + + caseViewBody.setFilterBody(Template.defaultCaseFilterBody()); + caseViewBody.setChainedView(new TaskViewBody()); + menuItemBody.setView(caseViewBody); + + return menuItemBody; + } + + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @Override + public I18nString getName() { + return NAME; + } + + @Override + public MenuItemBody getTemplate() { + return buildTemplate(); + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java new file mode 100644 index 00000000000..cc3135c3b27 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTaskViewTemplate.java @@ -0,0 +1,43 @@ +package com.netgrif.application.engine.menu.domain.templates; + +import com.netgrif.application.engine.menu.domain.MenuItemBody; +import com.netgrif.application.engine.menu.domain.configurations.TaskViewBody; +import com.netgrif.application.engine.petrinet.domain.I18nString; + +import java.util.Map; + +public class TabbedTaskViewTemplate implements Template { + + public static final String IDENTIFIER = "tabbed_task_view"; + + private static final I18nString NAME = new I18nString("Tabbed task view", + Map.of("sk", "Zobrazenie úloh v záložkách", "de", "Aufgabenansicht in Registerkarten")); + + private static MenuItemBody buildTemplate() { + MenuItemBody menuItemBody = new MenuItemBody(); + menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER); + menuItemBody.setUseTabbedView(true); + + TaskViewBody taskViewBody = new TaskViewBody(); + + taskViewBody.setFilterBody(Template.defaultTaskFilterBody()); + menuItemBody.setView(taskViewBody); + + return menuItemBody; + } + + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @Override + public I18nString getName() { + return NAME; + } + + @Override + public MenuItemBody getTemplate() { + return buildTemplate(); + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java new file mode 100644 index 00000000000..4221d963dbf --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/TabbedTicketViewTemplate.java @@ -0,0 +1,46 @@ +package com.netgrif.application.engine.menu.domain.templates; + +import com.netgrif.application.engine.menu.domain.MenuItemBody; +import com.netgrif.application.engine.menu.domain.configurations.SingleTaskViewBody; +import com.netgrif.application.engine.menu.domain.configurations.TabbedTicketViewBody; +import com.netgrif.application.engine.petrinet.domain.I18nString; + +import java.util.Map; + +public class TabbedTicketViewTemplate implements Template { + + public static final String IDENTIFIER = "tabbed_ticket_view"; + + private static final I18nString NAME = new I18nString("Tabbed ticket view", + Map.of("sk", "Tiketové zobrazenie", "de", "Ticketansicht")); + + private static MenuItemBody buildTemplate() { + MenuItemBody menuItemBody = new MenuItemBody(); + menuItemBody.setConfigurationTemplateIdentifier(IDENTIFIER); + menuItemBody.setUseTabbedView(true); + + TabbedTicketViewBody tabbedTicketViewBody = new TabbedTicketViewBody(); + + SingleTaskViewBody singleTaskViewBody = new SingleTaskViewBody(); + singleTaskViewBody.setFilterBody(Template.defaultTaskFilterBody()); + tabbedTicketViewBody.setChainedView(singleTaskViewBody); + menuItemBody.setView(tabbedTicketViewBody); + + return menuItemBody; + } + + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @Override + public I18nString getName() { + return NAME; + } + + @Override + public MenuItemBody getTemplate() { + return buildTemplate(); + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java b/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java new file mode 100644 index 00000000000..978f3b475d2 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/domain/templates/Template.java @@ -0,0 +1,33 @@ +package com.netgrif.application.engine.menu.domain.templates; + +import com.netgrif.application.engine.menu.domain.FilterBody; +import com.netgrif.application.engine.menu.domain.MenuItemBody; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; + +public interface Template { + String getIdentifier(); + I18nString getName(); + MenuItemBody getTemplate(); + + static FilterBody defaultTaskFilterBody() { + FilterBody filterBody = new FilterBody(); + filterBody.setType(FieldType.TASK_FILTER); + filterBody.setQuery("tasks"); + return filterBody; + } + + static FilterBody defaultSingleTaskFilterBody() { + FilterBody filterBody = new FilterBody(); + filterBody.setType(FieldType.TASK_FILTER); + filterBody.setQuery("task"); + return filterBody; + } + + static FilterBody defaultCaseFilterBody() { + FilterBody filterBody = new FilterBody(); + filterBody.setType(FieldType.CASE_FILTER); + filterBody.setQuery("cases"); + return filterBody; + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/service/DashboardItemServiceImpl.java b/src/main/java/com/netgrif/application/engine/menu/service/DashboardItemServiceImpl.java new file mode 100644 index 00000000000..20dbb8dbe18 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/service/DashboardItemServiceImpl.java @@ -0,0 +1,124 @@ +package com.netgrif.application.engine.menu.service; + +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.auth.domain.LoggedUser; +import com.netgrif.application.engine.auth.service.interfaces.IUserService; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; +import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.menu.domain.dashboard.DashboardItemBody; +import com.netgrif.application.engine.menu.domain.dashboard.DashboardItemConstants; +import com.netgrif.application.engine.menu.service.interfaces.DashboardItemService; +import com.netgrif.application.engine.menu.utils.MenuItemUtils; +import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; +import com.netgrif.application.engine.startup.ImportHelper; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.Task; +import com.netgrif.application.engine.workflow.service.interfaces.IDataService; +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.Locale; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DashboardItemServiceImpl implements DashboardItemService { + + protected final IWorkflowService workflowService; + protected final IUserService userService; + protected final ITaskService taskService; + protected final IDataService dataService; + protected final IPetriNetService petriNetService; + protected final IElasticCaseService elasticCaseService; + + /** + * Creates a new dashboard item case if it does not already exist. + * If a case with the same ID is found, it is returned instead of creating a new one. + * + * @param body The {@link DashboardItemBody} containing data for the dashboard item. + * @return The created or existing {@link Case} representing the dashboard item. + * @throws TransitionNotExecutableException if the task transition is not executable. + */ + @Override + public Case getOrCreate(DashboardItemBody body) throws TransitionNotExecutableException { + MenuItemUtils.sanitize(body.getId()); + Case itemCase; + + itemCase = findById(body.getId()); + if (itemCase != null) { + log.info("Dashboard item with id:{} already exists", body.getId()); + return itemCase; + } + + LoggedUser loggedUser = userService.getLoggedOrSystem().transformToLoggedUser(); + itemCase = workflowService.createCase(petriNetService.getNewestVersionByIdentifier(DashboardItemConstants.PROCESS_IDENTIFIER).getStringId(), body.getName().getDefaultValue(), "", loggedUser).getCase(); + ToDataSetOutcome outcome = body.toDataSet(); + itemCase = setDataWithExecute(itemCase, DashboardItemConstants.TASK_CONFIGURE, outcome.getDataSet()); + return itemCase; + } + + + /** + * Updates an existing dashboard item case with new data. + * + * @param itemCase The existing {@link Case} to update. + * @param body The {@link DashboardItemBody} containing updated data. + * @return The updated {@link Case} representing the dashboard item. + * @throws TransitionNotExecutableException if the task transition is not executable. + */ + @Override + public Case update(Case itemCase, DashboardItemBody body) throws TransitionNotExecutableException { + MenuItemUtils.sanitize(body.getId()); + ToDataSetOutcome outcome = body.toDataSet(); + itemCase = setDataWithExecute(itemCase, DashboardItemConstants.TASK_CONFIGURE, outcome.getDataSet()); + return itemCase; + } + + /** + * Finds an existing dashboard item case by its identifier. + * + * @param identifier The unique identifier of the dashboard item. + * @return The {@link Case} representing the dashboard item, or null if not found. + */ + @Override + public Case findById(String identifier) { + String query = String.format("processIdentifier:%s AND dataSet.%s.textValue.keyword:\"%s\"", + DashboardItemConstants.PROCESS_IDENTIFIER, DashboardItemConstants.FIELD_ID, identifier); + return findCase(DashboardItemConstants.PROCESS_IDENTIFIER, query); + } + + protected Case setData(Case useCase, String transId, Map> dataSet) { + String taskId = MenuItemUtils.findTaskIdInCase(useCase, transId); + return dataService.setData(taskId, ImportHelper.populateDataset((Map) dataSet)).getCase(); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + protected Case setDataWithExecute(Case useCase, String transId, Map> dataSet) throws TransitionNotExecutableException { + IUser loggedUser = userService.getLoggedOrSystem(); + String taskId = MenuItemUtils.findTaskIdInCase(useCase, transId); + Task task = taskService.findOne(taskId); + task = taskService.assignTask(task, loggedUser).getTask(); + task = dataService.setData(task, ImportHelper.populateDataset((Map) dataSet)).getTask(); + return taskService.finishTask(task, loggedUser).getCase(); + } + + protected Case findCase(String processIdentifier, String query) { + CaseSearchRequest request = CaseSearchRequest.builder() + .process(Collections.singletonList(new CaseSearchRequest.PetriNet(processIdentifier))) + .query(query) + .build(); + Page resultPage = elasticCaseService.search(java.util.List.of(request), userService.getLoggedOrSystem().transformToLoggedUser(), + PageRequest.of(0, 1), Locale.getDefault(), false); + + return resultPage.hasContent() ? resultPage.getContent().get(0) : null; + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/service/DashboardManagementServiceImpl.java b/src/main/java/com/netgrif/application/engine/menu/service/DashboardManagementServiceImpl.java new file mode 100644 index 00000000000..944270d33e5 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/service/DashboardManagementServiceImpl.java @@ -0,0 +1,138 @@ +package com.netgrif.application.engine.menu.service; + +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.auth.domain.LoggedUser; +import com.netgrif.application.engine.auth.service.interfaces.IUserService; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; +import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; +import com.netgrif.application.engine.menu.domain.ToDataSetOutcome; +import com.netgrif.application.engine.menu.domain.dashboard.DashboardItemConstants; +import com.netgrif.application.engine.menu.domain.dashboard.DashboardManagementBody; +import com.netgrif.application.engine.menu.domain.dashboard.DashboardManagementConstants; +import com.netgrif.application.engine.menu.service.interfaces.DashboardManagementService; +import com.netgrif.application.engine.menu.utils.MenuItemUtils; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; +import com.netgrif.application.engine.startup.ImportHelper; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.Task; +import com.netgrif.application.engine.workflow.service.interfaces.IDataService; +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DashboardManagementServiceImpl implements DashboardManagementService { + + protected final IWorkflowService workflowService; + protected final IUserService userService; + protected final ITaskService taskService; + protected final IDataService dataService; + protected final IPetriNetService petriNetService; + protected final IElasticCaseService elasticCaseService; + + + /** + * Creates a new dashboard management case if it does not already exist. + * If a case with the same ID is found, it is returned instead of creating a new one. + * + * @param body The {@link DashboardManagementBody} containing data for the dashboard management. + * @return The created or existing {@link Case} representing the dashboard management. + */ + @Override + public Case createDashboardManagement(DashboardManagementBody body) throws TransitionNotExecutableException { + Case managementCase; + MenuItemUtils.sanitize(body.getId()); + + managementCase = findDashboardManagement(body.getId()); + if (managementCase != null) { + log.info("Dashboard management with id:{} already exists", body.getId()); + return managementCase; + } + addReferencedMenuItems(body); + LoggedUser loggedUser = userService.getLoggedOrSystem().transformToLoggedUser(); + managementCase = workflowService.createCase(petriNetService.getNewestVersionByIdentifier(DashboardManagementConstants.PROCESS_IDENTIFIER).getStringId(), body.getName().getDefaultValue(), "", loggedUser).getCase(); + ToDataSetOutcome outcome = body.toDataSet(); + managementCase = setDataWithExecute(managementCase, DashboardItemConstants.TASK_CONFIGURE, outcome.getDataSet()); + return managementCase; + } + + /** + * Updates an existing dashboard management case with new data. + * + * @param managementCase The existing {@link Case} to update. + * @param body The {@link DashboardManagementBody} containing updated data. + * @return The updated {@link Case} representing the dashboard management. + */ + @Override + public Case updateDashboardManagement(Case managementCase, DashboardManagementBody body) throws TransitionNotExecutableException { + MenuItemUtils.sanitize(body.getId()); + addReferencedMenuItems(body); + ToDataSetOutcome outcome = body.toDataSet(); + managementCase = setDataWithExecute(managementCase, DashboardItemConstants.TASK_CONFIGURE, outcome.getDataSet()); + return managementCase; + } + + /** + * Finds an existing dashboard management case by its identifier. + * + * @param identifier The unique identifier of the dashboard management. + * @return The {@link Case} representing the dashboard management, or null if not found. + */ + @Override + public Case findDashboardManagement(String identifier) { + String query = String.format("processIdentifier:%s AND dataSet.%s.textValue.keyword:\"%s\"", + DashboardManagementConstants.PROCESS_IDENTIFIER, DashboardManagementConstants.FIELD_ID, identifier); + return findCase(DashboardManagementConstants.PROCESS_IDENTIFIER, query); + } + + protected Case setData(Case useCase, String transId, Map> dataSet) { + String taskId = MenuItemUtils.findTaskIdInCase(useCase, transId); + return dataService.setData(taskId, ImportHelper.populateDataset((Map) dataSet)).getCase(); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + protected Case setDataWithExecute(Case useCase, String transId, Map> dataSet) throws TransitionNotExecutableException { + IUser loggedUser = userService.getLoggedOrSystem(); + String taskId = MenuItemUtils.findTaskIdInCase(useCase, transId); + Task task = taskService.findOne(taskId); + task = taskService.assignTask(task, loggedUser).getTask(); + task = dataService.setData(task, ImportHelper.populateDataset((Map) dataSet)).getTask(); + return taskService.finishTask(task, loggedUser).getCase(); + } + + protected Case findCase(String processIdentifier, String query) { + CaseSearchRequest request = CaseSearchRequest.builder() + .process(Collections.singletonList(new CaseSearchRequest.PetriNet(processIdentifier))) + .query(query) + .build(); + Page resultPage = elasticCaseService.search(java.util.List.of(request), userService.getLoggedOrSystem().transformToLoggedUser(), + PageRequest.of(0, 1), Locale.getDefault(), false); + + return resultPage.hasContent() ? resultPage.getContent().get(0) : null; + } + + private void addReferencedMenuItems(DashboardManagementBody body) { + if (body.getDashboardItems() == null || body.getDashboardItems().isEmpty()) { + return; + } + HashMap menuItemToDashboardItem = new HashMap<>(); + body.getDashboardItems().keySet().forEach(dashboardItemId -> { + Case dashboardItem = workflowService.findOne(dashboardItemId); + menuItemToDashboardItem.put(dashboardItemId, new I18nString(String.valueOf(dashboardItem.getFieldValue(DashboardItemConstants.FIELD_MENU_ITEM_LIST)))); + }); + body.setMenuItemsToDashboardItems(menuItemToDashboardItem); + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java new file mode 100644 index 00000000000..07010a59171 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemService.java @@ -0,0 +1,752 @@ +package com.netgrif.application.engine.menu.service; + +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.auth.domain.LoggedUser; +import com.netgrif.application.engine.auth.service.interfaces.IUserService; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; +import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; +import com.netgrif.application.engine.menu.domain.*; +import com.netgrif.application.engine.menu.domain.configurations.ViewBody; +import com.netgrif.application.engine.menu.domain.configurations.ViewConstants; +import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService; +import com.netgrif.application.engine.menu.utils.MenuItemUtils; +import com.netgrif.application.engine.petrinet.domain.*; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; +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.startup.ImportHelper; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.Task; +import com.netgrif.application.engine.workflow.service.interfaces.IDataService; +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.springframework.data.domain.*; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.index.CompoundIndexDefinition; +import org.springframework.data.mongodb.core.index.IndexDefinition; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Slf4j +@Service +@RequiredArgsConstructor +public class MenuItemService implements IMenuItemService { + protected final IWorkflowService workflowService; + protected final ITaskService taskService; + protected final IDataService dataService; + protected final IUserService userService; + protected final IUriService uriService; + protected final IElasticCaseService elasticCaseService; + protected final MongoTemplate mongoTemplate; + protected final IPetriNetService petriNetService; + protected final IProcessRoleService processRoleService; + + protected Boolean existDatabaseIndexes; + + protected static final String DEFAULT_FOLDER_ICON = "folder"; + protected static final String GLOBAL_ROLE = "GLOBAL_ROLE"; + protected static final Map CUSTOM_MENU_ITEM_INDEXES = Map.of( + String.format("dataSet.%s.value", MenuItemConstants.FIELD_IDENTIFIER), MenuItemConstants.IDENTIFIER_INDEX_NAME, + String.format("dataSet.%s.value", MenuItemConstants.FIELD_NODE_PATH), MenuItemConstants.NODE_PATH_INDEX_NAME + ); + + + /** + * Ensures custom MongoDB compound indexes for menu items are created on the Case collection. + * Creates background indexes combining processIdentifier with menu item identifier + * and node path fields to optimize menu item queries. + * + * @see #CUSTOM_MENU_ITEM_INDEXES + */ + @Override + public void ensureDatabaseIndexes() { + log.info("Ensuring Mongo database menu item indexes"); + CUSTOM_MENU_ITEM_INDEXES.forEach( (indexKey, indexName) -> { + org.bson.Document keys = new org.bson.Document() + .append("processIdentifier", 1) + .append(indexKey, 1); + IndexDefinition index = new CompoundIndexDefinition(keys) + .named(indexName) + .background(); + mongoTemplate.indexOps(Case.class).ensureIndex(index); + }); + existDatabaseIndexes = Boolean.TRUE; + } + + /** + * Creates menu item case and it's configuration cases + * + * @param body data used for creation + * + * @return initialized menu item instance with the provided data + * + * @throws IllegalArgumentException if the provided menu identifier already exists + * */ + @Override + public Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableException { + validateMenuItemBody(body); + + log.debug("Creation of menu item case with identifier [{}] started.", body.getIdentifier()); + IUser loggedUser = userService.getLoggedOrSystem(); + + if (existsMenuItem(body.getIdentifier())) { + throw new IllegalArgumentException(String.format("Menu item identifier %s is not unique!", body.getIdentifier())); + } + + Case parentItemCase = getOrCreateFolderItem(body.getUri()); + I18nString newName = body.getMenuName(); + if (newName == null) { + newName = new I18nString(body.getIdentifier()); + } + Case menuItemCase = createCase(MenuItemConstants.PROCESS_IDENTIFIER, newName.getDefaultValue(), + loggedUser.transformToLoggedUser()); + menuItemCase.setUriNodeId(uriService.findByUri(body.getUri()).getStringId()); + menuItemCase = workflowService.save(menuItemCase); + + parentItemCase = appendChildCaseIdAndSave(parentItemCase, menuItemCase.getStringId()); + + String nodePath = createNodePath(body.getUri(), body.getIdentifier()); + uriService.getOrCreate(nodePath, UriContentType.CASE); + + Case viewCase = null; + if (body.hasView()) { + viewCase = createView(body.getView(), body.isUseTabbedView()); + } + ToDataSetOutcome dataSetOutcome = body.toDataSet(parentItemCase.getStringId(), nodePath, viewCase); + menuItemCase = setDataWithExecute(menuItemCase, MenuItemConstants.TRANS_SYS_INIT_ID, dataSetOutcome.getDataSet()); + log.debug("Created menu item case [{}] with identifier [{}].", menuItemCase.getStringId(), body.getIdentifier()); + return menuItemCase; + } + + /** + * Updates menu item case and it's configuration cases (recreates) + * + * @param itemCase menu item case to be updated + * @param body data used for update + * + * @return recreated menu item case (configuration cases are recreated, but not returned) + * */ + @Override + public Case updateMenuItem(Case itemCase, MenuItemBody body) throws TransitionNotExecutableException { + validateMenuItemBody(body); + if (itemCase == null) { + throw new IllegalArgumentException("Menu item case is null. Cannot update"); + } + + log.debug("Update of menu item case with identifier [{}] started.", body.getIdentifier()); + workflowService.deleteCase(itemCase); + itemCase = createMenuItem(body); + log.debug("Updated menu item case [{}] with identifier [{}].", itemCase.getStringId(), body.getIdentifier()); + return itemCase; + } + + /** + * Creates or updates menu item. At first menu item is searched by identifier. If found, then menu item will be + * updated. If not, menu item will be created + * + * @param body data used for the update or creation + * + * @return updated or created menu item case + * */ + @Override + public Case createOrUpdateMenuItem(MenuItemBody body) throws TransitionNotExecutableException { + if (body == null) { + throw new IllegalArgumentException("Menu item body cannot be null"); + } + Case itemCase = findMenuItem(MenuItemUtils.sanitize(body.getIdentifier())); + if (itemCase != null) { + return updateMenuItem(itemCase, body); + } else { + return createMenuItem(body); + } + } + + /** + * Creates or ignore menu item. At first menu item is searched by identifier. If found, then nothing will happen. + * If not, menu item will be created + * + * @param body data used for the creation + * + * @return ignored or created menu item case + * */ + @Override + public Case createOrIgnoreMenuItem(MenuItemBody body) throws TransitionNotExecutableException { + if (body == null) { + throw new IllegalArgumentException("Menu item body cannot be null"); + } + String sanitizedIdentifier = MenuItemUtils.sanitize(body.getIdentifier()); + Case itemCase = findMenuItem(sanitizedIdentifier); + if (itemCase != null) { + log.debug("Ignored creation or update of menu item case [{}] with identifier [{}].", itemCase.getStringId(), + sanitizedIdentifier); + return itemCase; + } else { + return createMenuItem(body); + } + } + + /** + * Finds menu item by identifier. + * + * @param identifier identifier of the menu item + * + * @return Found menu item case. If not found, null will be returned + * */ + @Override + public Case findMenuItem(String identifier) { + Query query = Query.query( + Criteria.where("processIdentifier").is(MenuItemConstants.PROCESS_IDENTIFIER) + .and(String.format("dataSet.%s.value", MenuItemConstants.FIELD_IDENTIFIER)).is(identifier) + ); + if (existDatabaseIndexes == null || !existDatabaseIndexes) { + ensureDatabaseIndexes(); + } + query.withHint(MenuItemConstants.IDENTIFIER_INDEX_NAME); + List caseAsList = mongoTemplate.find(query, Case.class); + Optional caseOptional = caseAsList.stream().findFirst(); + return caseOptional.map(aCase -> workflowService.findOne(aCase.getStringId())).orElse(null); + } + + /** + * Finds menu item by uri and name. + * + * @param uri string id of UriNode where the item exists + * @param name name of the menu item + * + * @return Found menu item case. If not found, null will be returned + * */ + @Override + public Case findMenuItem(String uri, String name) { + UriNode uriNode = uriService.findByUri(uri); + String query = String.format("processIdentifier:%s AND title.keyword:\"%s\" AND uriNodeId:\"%s\"", + MenuItemConstants.PROCESS_IDENTIFIER, name, uriNode.getStringId()); + return findCase(MenuItemConstants.PROCESS_IDENTIFIER, query); + } + + /** + * Finds folder case by UriNode + * + * @param node UriNode, which folder case represents + * + * @return Found folder menu item case. If not found, null will be returned + * */ + @Override + public Case findFolderCase(UriNode node) { + if (node == null) { + throw new IllegalArgumentException("Node cannot be null"); + } + Query query = Query.query( + Criteria.where("processIdentifier").is(MenuItemConstants.PROCESS_IDENTIFIER) + .and(String.format("dataSet.%s.value", MenuItemConstants.FIELD_NODE_PATH)).is(node.getUriPath()) + ); + if (existDatabaseIndexes == null || !existDatabaseIndexes) { + ensureDatabaseIndexes(); + } + query.withHint(MenuItemConstants.NODE_PATH_INDEX_NAME); + List caseAsList = mongoTemplate.find(query, Case.class); + Optional caseOptional = caseAsList.stream().findFirst(); + return caseOptional.map(aCase -> workflowService.findOne(aCase.getStringId())).orElse(null); + } + + /** + * Checks if the menu item exists + * + * @param identifier identifier of the menu item + * + * @return true if the menu item exists + * */ + @Override + public boolean existsMenuItem(String identifier) { + return findMenuItem(identifier) != null; + } + + /** + * Changes location of menu item. If non-existing location is provided, the new location is created and then the + * item is moved. Cyclic destination path is forbidden (f.e. from "/my_node" to + * "/my_node/my_node2" + * + * @param itemCase Instance of menu_item to be moved + * @param destUri destination path where the item will be moved. F.e. "/my_new_node" + * + * @throws IllegalArgumentException if the path is forbidden + * */ + @Override + public void moveItem(Case itemCase, String destUri) throws TransitionNotExecutableException { + if (destUri == null) { + throw new IllegalArgumentException("Destination path cannot be null"); + } + if (itemCase == null) { + throw new IllegalArgumentException("Item case cannot be null"); + } + log.debug("Move of menu item case [{}] started. Destination path [{}]", itemCase.getStringId(), destUri); + if (MenuItemUtils.isCyclicNodePath(itemCase, destUri)) { + throw new IllegalArgumentException(String.format("Cyclic path not supported. Destination path: %s", destUri)); + } + List casesToSave = new ArrayList<>(); + + List oldParentIdAsList = MenuItemUtils.getCaseIdsFromCaseRef(itemCase, MenuItemConstants.FIELD_PARENT_ID); + + UriNode destNode = uriService.getOrCreate(destUri, UriContentType.CASE); + Case newParent = getOrCreateFolderItem(destNode.getUriPath()); + + if (oldParentIdAsList != null && !oldParentIdAsList.isEmpty()) { + Case oldParent = removeChildItemFromParent(oldParentIdAsList.get(0), itemCase); + casesToSave.add(oldParent); + } + + if (newParent != null) { + itemCase.getDataField(MenuItemConstants.FIELD_PARENT_ID).setValue(List.of(newParent.getStringId())); + appendChildCaseIdInMemory(newParent, itemCase.getStringId()); + casesToSave.add(newParent); + } else { + itemCase.getDataField(MenuItemConstants.FIELD_PARENT_ID).setValue(null); + } + + itemCase.setUriNodeId(destNode.getStringId()); + resolveAndHandleNewNodePath(itemCase, destNode.getUriPath()); + casesToSave.add(itemCase); + + if (MenuItemUtils.hasFolderChildren(itemCase)) { + List childrenToSave = updateNodeInChildrenFoldersRecursive(itemCase); + casesToSave.addAll(childrenToSave); + } + + for (Case useCase : casesToSave) { + if (useCase != null) { + workflowService.save(useCase); + } + } + log.debug("Moved menu item case [{}]. Destination path was [{}]", itemCase.getStringId(), destUri); + } + + /** + * Duplicates menu item. It creates new menu_item instance with the same dataSet as the provided + * item instance. The only difference is in title, menu_item_identifier and associations. Configuration cases are + * duplicated as well. + * + * @param originItem Menu item instance, which is duplicated + * @param newTitle Title of menu item, that is displayed in menu and tab. Cannot be empty or null. + * @param newIdentifier unique menu item identifier + * + * @return duplicated {@link Case} instance of menu_item + * + * @throws IllegalArgumentException if the input data are invalid or the menu item of the new identifier already + * exists + * */ + @Override + public Case duplicateItem(Case originItem, I18nString newTitle, String newIdentifier) throws TransitionNotExecutableException { + log.debug("Duplication of menu item case [{}] started.", originItem.getStringId()); + if (newIdentifier == null || newIdentifier.isEmpty()) { + throw new IllegalArgumentException("View item identifier is null or empty!"); + } + if (newTitle == null || newTitle.getDefaultValue().isEmpty()) { + throw new IllegalArgumentException("Default title is null or empty"); + } + String sanitizedIdentifier = MenuItemUtils.sanitize(newIdentifier); + if (existsMenuItem(sanitizedIdentifier)) { + throw new IllegalArgumentException(String.format("View item identifier %s is not unique!", sanitizedIdentifier)); + } + + Case duplicatedViewCase = null; + if (MenuItemUtils.hasView(originItem)) { + String originViewId = MenuItemUtils.getCaseIdFromCaseRef(originItem, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID); + Case originViewCase = workflowService.findOne(originViewId); + duplicatedViewCase = duplicateView(originViewCase); + } + + Case duplicated = createCase(MenuItemConstants.PROCESS_IDENTIFIER, newTitle.getDefaultValue(), + userService.getLoggedOrSystem().transformToLoggedUser()); + duplicated.setUriNodeId(originItem.getUriNodeId()); + duplicated.setDataSet(originItem.getDataSet()); + duplicated.setTitle(newTitle.getDefaultValue()); + duplicated = workflowService.save(duplicated); + + UriNode node = uriService.findById(originItem.getUriNodeId()); + String newNodePath = createNodePath(node.getUriPath(), sanitizedIdentifier); + uriService.getOrCreate(newNodePath, UriContentType.CASE); + + Map> dataSet = new HashMap<>(); + dataSet.put(MenuItemConstants.FIELD_DUPLICATE_TITLE, Map.of("type", FieldType.I18N.getName(), "value", + new I18nString(""))); + dataSet.put(MenuItemConstants.FIELD_DUPLICATE_IDENTIFIER, Map.of("type", FieldType.TEXT.getName(), + "value", "")); + dataSet.put(MenuItemConstants.FIELD_MENU_NAME, Map.of("type", FieldType.I18N.getName(), + "value", newTitle)); + dataSet.put(MenuItemConstants.FIELD_TAB_NAME, Map.of("type", FieldType.I18N.getName(), + "value", newTitle)); + dataSet.put(MenuItemConstants.FIELD_NODE_PATH, Map.of("type", FieldType.TEXT.getName(), + "value", newNodePath)); + // Must be reset by button, because we have the same dataSet reference between originItem and duplicated + dataSet.put(MenuItemConstants.FIELD_DUPLICATE_RESET_CHILD_ITEM_IDS, Map.of("type", FieldType.BUTTON.getName(), + "value", 0)); + if (duplicatedViewCase != null) { + addConfigurationIntoDataSet(duplicatedViewCase, dataSet); + } + + setDataWithExecute(duplicated, MenuItemConstants.TRANS_SYS_INIT_ID, dataSet); + + List parentIdAsList = MenuItemUtils.getCaseIdsFromCaseRef(originItem, MenuItemConstants.FIELD_PARENT_ID); + if (parentIdAsList != null && !parentIdAsList.isEmpty()) { + Case parent = workflowService.findOne(parentIdAsList.get(0)); + appendChildCaseIdAndSave(parent, duplicated.getStringId()); + } + log.debug("Duplicated menu item case [{}]. New title [{}] and new identifier [{}].", originItem.getStringId(), + newTitle.getDefaultValue(), newIdentifier); + return workflowService.findOne(duplicated.getStringId()); + } + + /** + * Removes child menu item from the dataSet of the folder menu item case + * + * @param folderId menu item identifier of the folder case + * @param childItem menu item case of the child item to be removed + * + * @return updated folder menu item case + * */ + @Override + public Case removeChildItemFromParent(String folderId, Case childItem) { + Case parentFolder = workflowService.findOne(folderId); + List childIds = MenuItemUtils.getCaseIdsFromCaseRef(parentFolder, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + if (childIds == null || childIds.isEmpty()) { + return parentFolder; + } + childIds.remove(childItem.getStringId()); + parentFolder.getDataField(MenuItemConstants.FIELD_CHILD_ITEM_IDS).setValue(childIds); + parentFolder.getDataField(MenuItemConstants.FIELD_HAS_CHILDREN).setValue(MenuItemUtils.hasFolderChildren(parentFolder)); + return workflowService.save(parentFolder); + } + + /** + * Handles the application of a configuration template to a menu item case. + *

+ * This method retrieves the selected configuration template from the menu item case, + * loads the corresponding template definition, and applies it by creating or updating + * the associated view configuration. If no template is selected, the method returns + * without making any changes. + *

+ * + * @param menuItemCase the menu item case to which the configuration template should be applied + * @return a ConfigurationTemplateOutcome containing the dataSet outcome from applying the template, + * or an empty outcome if no template was selected + * @throws TransitionNotExecutableException if the workflow transition required for applying the template configuration cannot be executed + * @throws IllegalArgumentException if the selected template identifier does not correspond to any registered template + */ + @Override + public ConfigurationTemplateOutcome handleConfigurationTemplate(Case menuItemCase) throws TransitionNotExecutableException { + String selectedTemplate = (String) menuItemCase.getFieldValue(MenuItemConstants.FIELD_CONFIGURATION_TEMPLATES); + if (selectedTemplate == null || selectedTemplate.isEmpty()) { + return new ConfigurationTemplateOutcome(); + } + + String menuItemIdentifier = (String) menuItemCase.getFieldValue(MenuItemConstants.FIELD_IDENTIFIER); + log.debug("Handling configuration template selection for menu item: [{}, {}] and configuration template: {}", + menuItemCase.getStringId(), menuItemIdentifier, selectedTemplate); + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(selectedTemplate); + if (menuItemBodyOpt.isEmpty()) { + throw new IllegalArgumentException(String.format("No configuration template found with name: %s", selectedTemplate)); + } + + MenuItemBody menuItemBody = menuItemBodyOpt.get(); + Case viewCase = null; + if (menuItemBody.hasView()) { + viewCase = createView(menuItemBody.getView(), menuItemBody.isUseTabbedView()); + } + ToDataSetOutcome dataSetOutcome = menuItemBody.toDataSetByConfigTemplate(viewCase); + log.debug("For menu item: [{}. {}] was used configuration template: {}", menuItemCase.getStringId(), + menuItemIdentifier, selectedTemplate); + return new ConfigurationTemplateOutcome(dataSetOutcome); + } + + @Override + public Map collectRoles(List roles) { + // todo rework authorization + Map roleMap = new HashMap<>(); + for (ProcessRole role : roles) { + String key; + I18nString displayName; + + if (role.isGlobal()) { + key = role.getImportId() + ":" + GLOBAL_ROLE; + displayName = new I18nString(role.getName() + " (🌍 Global role)"); + } else { + PetriNet net = petriNetService.get(new ObjectId(role.getNetId())); + key = role.getImportId() + ":" + net.getIdentifier(); + displayName = new I18nString(role.getName() + " (" + net.getTitle() + ")"); + } + + roleMap.put(key, displayName); + } + return roleMap; + } + + @Override + public Map collectRoles(Map roles) { + // todo rework authorization + Map temp = new HashMap<>(); + Map result = new HashMap<>(); + + for (Map.Entry entry : roles.entrySet()) { + if (GLOBAL_ROLE.equals(entry.getValue())) { + Set findGlobalRole = processRoleService.findAllByImportId(ProcessRole.GLOBAL + entry.getKey()); + if (findGlobalRole == null || findGlobalRole.isEmpty()) { + continue; + } + Optional roleOpt = findGlobalRole.stream() + .filter(ProcessRole::isGlobal) + .findFirst(); + if (roleOpt.isEmpty()) { + continue; + } + result.put(roleOpt.get().getImportId() + ":" + GLOBAL_ROLE, + new I18nString(roleOpt.get().getName() + " (🌍 Global role)")); + } else { + if (!temp.containsKey(entry.getValue())) { + temp.put(entry.getValue(), petriNetService.getNewestVersionByIdentifier(entry.getValue())); + } + PetriNet net = temp.get(entry.getValue()); + Optional roleOpt = net.getRoles().values().stream() + .filter(r -> r.getImportId().equals(entry.getKey())) + .findFirst(); + roleOpt.ifPresent(processRole -> result.put(processRole.getImportId() + ":" + net.getIdentifier(), + new I18nString(processRole.getName() + " (" + net.getTitle() + ")"))); + } + } + + return result; + } + + protected void validateMenuItemBody(MenuItemBody body) { + if (body == null) { + throw new IllegalArgumentException("Input data cannot be null"); + } + if (body.getIdentifier() == null) { + throw new IllegalArgumentException("Identifier cannot be null"); + } + if (body.getUri() == null || body.getUri().isBlank()) { + throw new IllegalArgumentException("Uri cannot be null"); + } else { + body.setUri(MenuItemUtils.sanitizeUriSegments(body.getUri(), uriService)); + List uriSegments = List.of(body.getUri().split(uriService.getUriSeparator())); + if (uriSegments.contains(body.getIdentifier())) { + throw new IllegalArgumentException("Uri cannot contain this identifier"); + } + } + } + + protected Case findCase(String processIdentifier, String query) { + CaseSearchRequest request = CaseSearchRequest.builder() + .process(Collections.singletonList(new CaseSearchRequest.PetriNet(processIdentifier))) + .query(query) + .build(); + Page resultPage = elasticCaseService.search(List.of(request), userService.getLoggedOrSystem().transformToLoggedUser(), + PageRequest.of(0, 1), Locale.getDefault(), false); + + return resultPage.hasContent() ? resultPage.getContent().get(0) : null; + } + + protected Case duplicateView(Case viewCase) throws TransitionNotExecutableException { + Case duplicatedAssociatedViewCase = null; + if (MenuItemUtils.hasView(viewCase)) { + String originViewId = MenuItemUtils.getCaseIdFromCaseRef(viewCase, ViewConstants.FIELD_VIEW_CONFIGURATION_ID); + Case originViewCase = workflowService.findOne(originViewId); + duplicatedAssociatedViewCase = duplicateView(originViewCase); + } + + Case duplicatedViewCase = createCase(viewCase.getProcessIdentifier(), viewCase.getTitle(), + userService.getLoggedOrSystem().transformToLoggedUser()); + duplicatedViewCase.setDataSet(viewCase.getDataSet()); + workflowService.save(duplicatedViewCase); + + Map> dataSet = new HashMap<>(); + if (duplicatedAssociatedViewCase != null) { + addConfigurationIntoDataSet(duplicatedAssociatedViewCase, dataSet); + } + + return setDataWithExecute(duplicatedViewCase, ViewConstants.TRANS_INIT_ID, dataSet); + } + + protected Case createView(ViewBody body, boolean isTabbed) throws TransitionNotExecutableException { + IUser loggedUser = userService.getLoggedOrSystem(); + Case viewCase = createCase(body.getViewProcessIdentifier(), body.getViewProcessIdentifier(), + loggedUser.transformToLoggedUser(), isTabbed); + + Case associatedViewCase = null; + if (body.hasAssociatedView()) { + associatedViewCase = createView(body.getAssociatedViewBody(), isTabbed); + } + + ToDataSetOutcome dataSetOutcome = body.toDataSet(associatedViewCase); + viewCase = setDataWithExecute(viewCase, ViewConstants.TRANS_INIT_ID, dataSetOutcome.getDataSet()); + + log.trace("Created configuration view case [{}] of identifier [{}]", viewCase.getStringId(), + body.getViewProcessIdentifier()); + return viewCase; + } + + protected List updateNodeInChildrenFoldersRecursive(Case parentFolder) { + List childItemIds = MenuItemUtils.getCaseIdsFromCaseRef(parentFolder, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + if (childItemIds == null || childItemIds.isEmpty()) { + return new ArrayList<>(); + } + + List children = workflowService.findAllById(childItemIds); + + List casesToSave = new ArrayList<>(); + for (Case childCase : children) { + UriNode parentNode = uriService.getOrCreate((String) parentFolder.getFieldValue(MenuItemConstants.FIELD_NODE_PATH), + UriContentType.CASE); + childCase.setUriNodeId(parentNode.getStringId()); + resolveAndHandleNewNodePath(childCase, parentNode.getUriPath()); + + casesToSave.add(childCase); + casesToSave.addAll(updateNodeInChildrenFoldersRecursive(childCase)); + } + + return casesToSave; + } + + protected void resolveAndHandleNewNodePath(Case folderItem, String destUri) { + String newNodePath = resolveNewNodePath(folderItem, destUri); + if (newNodePath.startsWith("//")) { + newNodePath = newNodePath.replace("//", uriService.getUriSeparator()); + } + UriNode newNode = uriService.getOrCreate(newNodePath, UriContentType.CASE); + folderItem.getDataField(MenuItemConstants.FIELD_NODE_PATH).setValue(newNode.getUriPath()); + } + + protected String resolveNewNodePath(Case folderItem, String destUri) { + return destUri + uriService.getUriSeparator() + folderItem.getFieldValue(MenuItemConstants.FIELD_IDENTIFIER); + } + + protected String createNodePath(String uri, String identifier) { + if (Objects.equals(uri, uriService.getUriSeparator())) { + return uri + identifier; + } else { + return uri + uriService.getUriSeparator() + identifier; + } + } + + protected Case getOrCreateFolderItem(String uri) throws TransitionNotExecutableException { + UriNode node = uriService.getOrCreate(uri, UriContentType.CASE); + MenuItemBody body = new MenuItemBody(new I18nString(node.getName()), DEFAULT_FOLDER_ICON); + return getOrCreateFolderRecursive(node, body); + } + + protected Case getOrCreateFolderRecursive(UriNode node, MenuItemBody body) throws TransitionNotExecutableException { + return getOrCreateFolderRecursive(node, body, null); + } + + protected Case getOrCreateFolderRecursive(UriNode node, MenuItemBody body, Case childFolderCase) throws TransitionNotExecutableException { + IUser loggedUser = userService.getLoggedOrSystem(); + Case folderCase = findFolderCase(node); + if (folderCase != null) { + if (childFolderCase != null) { + appendChildCaseIdAndSave(folderCase, childFolderCase.getStringId()); + } + return folderCase; + } + + folderCase = createCase(MenuItemConstants.PROCESS_IDENTIFIER, body.getMenuName().getDefaultValue(), + loggedUser.transformToLoggedUser()); + folderCase.setUriNodeId(node.getParentId()); + folderCase = workflowService.save(folderCase); + + ToDataSetOutcome dataSetOutcome = body.toDataSet(null, node.getUriPath(), null); + if (childFolderCase != null) { + appendChildCaseIdInDataSet(folderCase, childFolderCase.getStringId(), dataSetOutcome.getDataSet()); + } + + if (node.getParentId() != null) { + UriNode parentNode = uriService.findById(node.getParentId()); + body = new MenuItemBody(new I18nString(parentNode.getName()), DEFAULT_FOLDER_ICON); + + Case parentFolderCase = getOrCreateFolderRecursive(parentNode, body, folderCase); + dataSetOutcome.putDataSetEntry(MenuItemConstants.FIELD_PARENT_ID, FieldType.CASE_REF, List.of(parentFolderCase.getStringId())); + } + folderCase = setDataWithExecute(folderCase, MenuItemConstants.TRANS_SYS_INIT_ID, dataSetOutcome.getDataSet()); + + log.trace("Created folder menu item [{}] with identifier [{}]", folderCase.getStringId(), body.getIdentifier()); + return folderCase; + } + + protected void appendChildCaseIdInDataSet(Case folderCase, String childItemCaseId, Map> dataSet) { + List childIds = MenuItemUtils.getCaseIdsFromCaseRef(folderCase, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + if (childIds == null || childIds.isEmpty()) { + childIds = List.of(childItemCaseId); + } else { + childIds.add(childItemCaseId); + } + + dataSet.put(MenuItemConstants.FIELD_CHILD_ITEM_IDS, Map.of("type", FieldType.CASE_REF.getName(), + "value", childIds)); + dataSet.put(MenuItemConstants.FIELD_HAS_CHILDREN, Map.of("type", FieldType.BOOLEAN.getName(), + "value", !childIds.isEmpty())); + } + + protected void appendChildCaseIdInMemory(Case folderCase, String childItemCaseId) { + List childIds = MenuItemUtils.getCaseIdsFromCaseRef(folderCase, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + if (childIds == null || childIds.isEmpty()) { + folderCase.getDataField(MenuItemConstants.FIELD_CHILD_ITEM_IDS).setValue(List.of(childItemCaseId)); + } else { + childIds.add(childItemCaseId); + folderCase.getDataField(MenuItemConstants.FIELD_CHILD_ITEM_IDS).setValue(childIds); + } + folderCase.getDataField(MenuItemConstants.FIELD_HAS_CHILDREN).setValue(MenuItemUtils.hasFolderChildren(folderCase)); + } + + protected Case appendChildCaseIdAndSave(Case folderCase, String childItemCaseId) { + Map> dataSet = new HashMap<>(); + appendChildCaseIdInDataSet(folderCase, childItemCaseId, dataSet); + return setData(folderCase, MenuItemConstants.TRANS_SYNC_ID, dataSet); + } + + protected void addConfigurationIntoDataSet(Case configurationCase, Map> dataSet) { + dataSet.put(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID, Map.of("type", FieldType.CASE_REF.getName(), + "value", List.of(configurationCase.getStringId()))); + String taskId = MenuItemUtils.findTaskIdInCase(configurationCase, ViewConstants.TRANS_SETTINGS_ID); + dataSet.put(MenuItemConstants.FIELD_VIEW_CONFIGURATION_FORM, Map.of("type", FieldType.TASK_REF.getName(), + "value", List.of(taskId))); + String allTaskId = MenuItemUtils.findTaskIdInCase(configurationCase, ViewConstants.TRANS_ALL_MENU_DATA_ID); + dataSet.put(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM, Map.of("type", FieldType.TASK_REF.getName(), + "value", List.of(allTaskId))); + } + + protected Case createCase(String identifier, String title, LoggedUser loggedUser) { + return workflowService.createCaseByIdentifier(identifier, title, "", loggedUser).getCase(); + } + + protected Case createCase(String identifier, String title, LoggedUser loggedUser, boolean isTabbed) { + return workflowService.createCaseByIdentifier(identifier, title, "", loggedUser, + Map.of("is_tabbed", String.valueOf(isTabbed))).getCase(); + } + + protected Case setData(Case useCase, String transId, Map> dataSet) { + String taskId = MenuItemUtils.findTaskIdInCase(useCase, transId); + return setData(taskId, dataSet); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + protected Case setData(String taskId, Map> dataSet) { + return dataService.setData(taskId, ImportHelper.populateDataset((Map) dataSet)).getCase(); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + protected Case setDataWithExecute(Case useCase, String transId, Map> dataSet) throws TransitionNotExecutableException { + IUser loggedUser = userService.getLoggedOrSystem(); + String taskId = MenuItemUtils.findTaskIdInCase(useCase, transId); + Task task = taskService.findOne(taskId); + task = taskService.assignTask(task, loggedUser).getTask(); + task = dataService.setData(task, ImportHelper.populateDataset((Map) dataSet)).getTask(); + return taskService.finishTask(task, loggedUser).getCase(); + } + +} diff --git a/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java new file mode 100644 index 00000000000..48f27d931ac --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/service/MenuItemTemplateHolder.java @@ -0,0 +1,121 @@ +package com.netgrif.application.engine.menu.service; + +import com.netgrif.application.engine.menu.domain.MenuItemBody; +import com.netgrif.application.engine.menu.domain.templates.*; +import com.netgrif.application.engine.menu.utils.MenuItemUtils; +import com.netgrif.application.engine.petrinet.domain.I18nString; + +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Holder class that manages predefined menu item templates. + *

+ * This class provides a central registry for menu item templates that can be used + * throughout the application. Templates are identified by unique string identifiers + * and can be retrieved or transformed into user-selectable options. + *

+ */ +public class MenuItemTemplateHolder { + /** + * Map of available menu item templates indexed by their unique identifiers. + *

+ * Each entry maps a template identifier to its corresponding Template instance. + * This map is immutable and initialized with predefined templates. + * The key is the template identifier (String), and the value is the Template instance. + *

+ */ + private final static Map templates = Map.of( + TabbedCaseViewTemplate.IDENTIFIER, new TabbedCaseViewTemplate(), + TabbedTaskViewTemplate.IDENTIFIER, new TabbedTaskViewTemplate(), + SimpleCaseViewTemplate.IDENTIFIER, new SimpleCaseViewTemplate(), + SimpleTaskViewTemplate.IDENTIFIER, new SimpleTaskViewTemplate(), + SingleTaskViewTemplate.IDENTIFIER, new SingleTaskViewTemplate(), + TabbedTicketViewTemplate.IDENTIFIER, new TabbedTicketViewTemplate(), + CustomViewTemplate.IDENTIFIER, new CustomViewTemplate() + ); + + + /** + * Retrieves a template by its unique identifier. + *

+ * This method looks up and returns the Template instance associated with the + * provided identifier from the templates registry. + *

+ * + * @param identifier the unique identifier of the template to retrieve + * @return an Optional containing the MenuItemBody instance if found, or empty Optional if no template exists for the given identifier + */ + public static Optional get(String identifier) { + return Optional.ofNullable(templates.get(identifier)).map(Template::getTemplate); + } + + /** + * Retrieves a template by its unique identifier and configures it with the provided URI and menu item identifier. + *

+ * This method looks up the Template instance associated with the provided identifier from the templates registry, + * and then configures the template's body with the specified URI and menu item identifier. + *

+ * + * @param identifier the unique identifier of the template to retrieve + * @param uri the URI to set on the template's body + * @param menuItemIdentifier the menu item identifier to set on the template's body + * @return an Optional containing the configured MenuItemBody instance if found, or empty Optional if no template exists for the given identifier + */ + public static Optional get(String identifier, String uri, String menuItemIdentifier) { + return get(identifier).map(body -> { + body.setUri(uri); + body.setIdentifier(menuItemIdentifier); + return body; + }); + } + + /** + * Retrieves a template by its unique identifier and configures it with the provided URI and menu name. + *

+ * This method looks up the Template instance associated with the provided identifier from the templates registry, + * and then configures the template's body with the specified URI and internationalized menu name. + * The menu item identifier is automatically generated by sanitizing the default menu name value. + * If the default value is null or blank, the first available translation is used instead. + *

+ * + * @param identifier the unique identifier of the template to retrieve + * @param uri the URI to set on the template's body + * @param menuName the internationalized menu name to set on the template's body + * @return an Optional containing the configured MenuItemBody instance if found, or empty Optional if no template exists for the given identifier + * @throws IllegalArgumentException if the menu name has no default value or translations + */ + public static Optional get(String identifier, String uri, I18nString menuName) { + return get(identifier).map(body -> { + body.setUri(uri); + body.setMenuName(menuName); + String defaultName = menuName.getDefaultValue(); + if (defaultName == null || defaultName.isBlank()) { + defaultName = menuName.getTranslations().values().stream() + .findAny() + .orElseThrow(() -> new IllegalArgumentException("Please provide a name for the menu item")); + } + body.setIdentifier(MenuItemUtils.sanitize(defaultName)); + return body; + }); + } + + /** + * Transforms the available templates into a map of selectable options. + *

+ * This method converts the templates map into a format suitable for displaying + * as user-selectable options, where the key is the template identifier and the + * value is the internationalized name of the template. + *

+ * + * @return a map where keys are template identifiers and values are internationalized template names + */ + public static Map transformToOptions() { + return templates.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().getName() + )); + } +} diff --git a/src/main/java/com/netgrif/application/engine/menu/service/interfaces/DashboardItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/DashboardItemService.java new file mode 100644 index 00000000000..3dd698d53c1 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/DashboardItemService.java @@ -0,0 +1,14 @@ +package com.netgrif.application.engine.menu.service.interfaces; + +import com.netgrif.application.engine.menu.domain.dashboard.DashboardItemBody; +import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; +import com.netgrif.application.engine.workflow.domain.Case; + +public interface DashboardItemService { + + Case getOrCreate(DashboardItemBody body) throws TransitionNotExecutableException; + + Case update(Case itemCase, DashboardItemBody body) throws TransitionNotExecutableException; + + Case findById(String identifier); +} diff --git a/src/main/java/com/netgrif/application/engine/menu/service/interfaces/DashboardManagementService.java b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/DashboardManagementService.java new file mode 100644 index 00000000000..e5765ddbf3b --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/DashboardManagementService.java @@ -0,0 +1,14 @@ +package com.netgrif.application.engine.menu.service.interfaces; + +import com.netgrif.application.engine.menu.domain.dashboard.DashboardManagementBody; +import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; +import com.netgrif.application.engine.workflow.domain.Case; + +public interface DashboardManagementService { + Case createDashboardManagement(DashboardManagementBody body) throws TransitionNotExecutableException; + + Case updateDashboardManagement(Case managementCase, DashboardManagementBody body) throws TransitionNotExecutableException; + + Case findDashboardManagement(String identifier); + +} diff --git a/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java new file mode 100644 index 00000000000..ad776b2a9eb --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/service/interfaces/IMenuItemService.java @@ -0,0 +1,67 @@ +package com.netgrif.application.engine.menu.service.interfaces; + +import com.netgrif.application.engine.menu.domain.ConfigurationTemplateOutcome; +import com.netgrif.application.engine.menu.domain.MenuItemBody; +import com.netgrif.application.engine.menu.domain.MenuItemViewType; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.UriNode; +import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.petrinet.domain.dataset.MapOptionsField; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public interface IMenuItemService { + + void ensureDatabaseIndexes(); + Case createMenuItem(MenuItemBody body) throws TransitionNotExecutableException; + Case updateMenuItem(Case itemCase, MenuItemBody body) throws TransitionNotExecutableException; + Case createOrUpdateMenuItem(MenuItemBody body) throws TransitionNotExecutableException; + Case createOrIgnoreMenuItem(MenuItemBody body) throws TransitionNotExecutableException; + Case findMenuItem(String identifier); + Case findMenuItem(String uri, String name); + Case findFolderCase(UriNode node); + boolean existsMenuItem(String identifier); + void moveItem(Case item, String destUri) throws TransitionNotExecutableException; + Case duplicateItem(Case originItem, I18nString newTitle, String newIdentifier) throws TransitionNotExecutableException; + Case removeChildItemFromParent(String folderId, Case childItem); + ConfigurationTemplateOutcome handleConfigurationTemplate(Case menuItemCase) throws TransitionNotExecutableException; + Map collectRoles(List roles); + Map collectRoles(Map roles); + + /** + * Gets all tabbed or non-tabbed views + * + * @param isTabbed if true, only tabbed views will be returned + * @param isPrimary if true, only views accessible directly from the menu_item will be returned + * + * @return All available views defined in {@link MenuItemViewType} in consideration of input value. Views are returned as + * options for {@link MapOptionsField} + * */ + default Map getAvailableViewsAsOptions(boolean isTabbed, boolean isPrimary) { + return MenuItemViewType.findAllByIsTabbedAndIsPrimary(isTabbed, isPrimary).stream() + .collect(Collectors.toMap(MenuItemViewType::getIdentifier, MenuItemViewType::getName)); + } + + /** + * Gets all tabbed or non-tabbed views + * + * @param isTabbed if true, only tabbed views will be returned + * @param viewIdentifier identifier of view (defined in {@link MenuItemViewType}), which is parent to returned views + * + * @return All available views defined in {@link MenuItemViewType} in consideration of input values. Views are returned as + * options for {@link MapOptionsField} + * */ + default Map getAvailableViewsAsOptions(boolean isTabbed, String viewIdentifier) { + int index = viewIdentifier.lastIndexOf("_configuration"); + if (index > 0) { + viewIdentifier = viewIdentifier.substring(0, index); + } + return MenuItemViewType.findAllByIsTabbedAndParentIdentifier(isTabbed, viewIdentifier).stream() + .collect(Collectors.toMap(MenuItemViewType::getIdentifier, MenuItemViewType::getName)); + } + +} diff --git a/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java b/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java new file mode 100644 index 00000000000..b7b3107a8b6 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/menu/utils/MenuItemUtils.java @@ -0,0 +1,147 @@ +package com.netgrif.application.engine.menu.utils; + +import com.netgrif.application.engine.menu.domain.MenuItemConstants; +import com.netgrif.application.engine.petrinet.service.interfaces.IUriService; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.TaskPair; +import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService; + +import java.text.Normalizer; +import java.util.List; + +public class MenuItemUtils { + + /** + * Sanitizes input. Removes any diacritical marks, replaces any special character with delimiter and lowers the + * characters + * + * @param input input string to be sanitized + * + * @return sanitized input string + * */ + public static String sanitize(String input) { + if (input == null) { + return null; + } + return Normalizer.normalize(input.trim(), Normalizer.Form.NFD) + .replaceAll("[^\\p{ASCII}]", "") + .replaceAll("\\p{InCombiningDiacriticalMarks}+", "") + .replaceAll("[\\W-]+", "-") + .toLowerCase(); + } + + /** + * Sanitizes each segment of a URI path by removing diacritical marks, replacing special characters with + * delimiters, and converting to lowercase. The URI is split by the separator defined in the provided + * {@link IUriService}, each segment is sanitized individually, and then the segments are concatenated back + * together. + * + * @param uri the URI string to be sanitized + * @param uriService the service providing the URI separator configuration + * + * @return sanitized URI string with all segments processed, or null if input URI is null + * */ + public static String sanitizeUriSegments(String uri, IUriService uriService) { + if (uri == null) { + return null; + } + String[] uriSegments = uri.split(uriService.getUriSeparator()); + if (uriSegments.length == 0) { + return uriService.getRoot().getUriPath(); + } + StringBuilder sanitizedUriBuilder = new StringBuilder(); + for (String uriSegment : uriSegments) { + sanitizedUriBuilder.append(uriService.getUriSeparator()); + sanitizedUriBuilder.append(MenuItemUtils.sanitize(uriSegment)); + } + return sanitizedUriBuilder.toString().replaceAll("//", uriService.getUriSeparator()); + } + + /** + * Finds task id in the provided case instance by transition id + * + * @param useCase case instance containing the task id to be found + * @param transId transition identifier of the task + * + * @return id of found task or null otherwise + */ + public static String findTaskIdInCase(Case useCase, String transId) { + if (useCase == null || transId == null) { + return null; + } + + TaskPair resultPair = useCase.getTasks().stream() + .filter(taskPair -> taskPair.getTransition().equals(transId)) + .findFirst().orElse(null); + + if (resultPair == null) { + return null; + } + + return resultPair.getTask(); + } + + /** + * This method is mainly used for {@link IMenuItemService#moveItem(Case, String)} + * + * @param folderItem case instance of folder menu item + * @param destUri path of the uri node + * + * @return true, if the nodePath would become cyclic to folderItem's current nodePath after item move. F.e. + * "/node1/node2" would be cyclic to "/node1/node2/node3" after move + * */ + public static boolean isCyclicNodePath(Case folderItem, String destUri) { + String oldNodePath = (String) folderItem.getFieldValue(MenuItemConstants.FIELD_NODE_PATH); + return oldNodePath != null && destUri.contains(oldNodePath); + } + + /** + * @param useCase case instance where the caseRef exists + * @param caseRefId id of caseRef field + * + * @return List of case ids inside caseRef field. Returns null if the field doesn't exist or the field's value is + * null. + * */ + @SuppressWarnings("unchecked") + public static List getCaseIdsFromCaseRef(Case useCase, String caseRefId) { + try { + return (List) useCase.getFieldValue(caseRefId); + } catch (NullPointerException ignore) { + return null; + } + } + + /** + * @param useCase case instance where the caseRef exists + * @param caseRefId id of caseRef field + * + * @return Case id inside caseRef field. Returns null if the field doesn't exist or the caseRef is empty. + * */ + public static String getCaseIdFromCaseRef(Case useCase, String caseRefId) { + List caseIds = getCaseIdsFromCaseRef(useCase, caseRefId); + if (caseIds == null || caseIds.isEmpty()) { + return null; + } + return caseIds.get(0); + } + + /** + * @param menuItemCase case instance of menu item + * + * @return true if the menu item contains view + * */ + public static boolean hasView(Case menuItemCase) { + return getCaseIdFromCaseRef(menuItemCase, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID) != null; + } + + /** + * @param folderCase case instance of folder menu item + * + * @return true if the folder case contains any child menu item cases + * */ + public static boolean hasFolderChildren(Case folderCase) { + List childIds = MenuItemUtils.getCaseIdsFromCaseRef(folderCase, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + return childIds != null && !childIds.isEmpty(); + } + +} diff --git a/src/main/java/com/netgrif/application/engine/petrinet/domain/DeploymentState.java b/src/main/java/com/netgrif/application/engine/petrinet/domain/DeploymentState.java new file mode 100644 index 00000000000..982ca4e78d3 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/petrinet/domain/DeploymentState.java @@ -0,0 +1,8 @@ +package com.netgrif.application.engine.petrinet.domain; + +public enum DeploymentState { + DRAFT, + DEPLOYED, + ARCHIVED, + TRASH +} diff --git a/src/main/java/com/netgrif/application/engine/petrinet/domain/PetriNet.java b/src/main/java/com/netgrif/application/engine/petrinet/domain/PetriNet.java index 42ccca6e134..5e8da3d7ad6 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/domain/PetriNet.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/domain/PetriNet.java @@ -28,7 +28,6 @@ @Document public class PetriNet extends PetriNetObject { - @Getter @Setter private String identifier; //combination of identifier and version must be unique ... maybe use @CompoundIndex? @@ -73,6 +72,10 @@ public class PetriNet extends PetriNetObject { @Setter private Version version; + @Getter + @Setter + private DeploymentState deploymentState; + @Getter @Setter private Author author; @@ -449,6 +452,11 @@ public PetriNet clone() { this.getNegativeViewRoles().forEach(clone::addNegativeViewRole); this.getFunctions().forEach(clone::addFunction); clone.setTags(new HashMap<>(this.tags)); + clone.setDeploymentState(this.deploymentState); return clone; } + + public boolean isDeployed() { + return DeploymentState.DEPLOYED.equals(this.deploymentState); + } } \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java b/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java index 7f7729cd4d1..0d479d8d99f 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java @@ -1,5 +1,6 @@ package com.netgrif.application.engine.petrinet.domain.repositories; +import com.netgrif.application.engine.petrinet.domain.DeploymentState; import com.netgrif.application.engine.petrinet.domain.PetriNet; import com.netgrif.application.engine.petrinet.domain.version.Version; import org.bson.types.ObjectId; @@ -20,6 +21,10 @@ public interface PetriNetRepository extends MongoRepository, Q PetriNet findByIdentifierAndVersion(String identifier, Version version); + boolean existsByIdentifierAndVersion(String identifier, Version version); + + boolean existsBy_idAndDeploymentState(ObjectId id, DeploymentState deploymentState); + Page findByIdentifier(String identifier, Pageable pageable); Page findByIdentifierIn(List identifier, Pageable pageable); diff --git a/src/main/java/com/netgrif/application/engine/petrinet/domain/throwable/MissingPetriNetMetaDataException.java b/src/main/java/com/netgrif/application/engine/petrinet/domain/throwable/MissingPetriNetMetaDataException.java index 01011a0db3a..0141f1a9664 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/domain/throwable/MissingPetriNetMetaDataException.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/domain/throwable/MissingPetriNetMetaDataException.java @@ -4,7 +4,7 @@ public class MissingPetriNetMetaDataException extends Exception { - public MissingPetriNetMetaDataException(List missingMetaData) { - super("Following properties from the imported net are missing: " + String.join(", ", missingMetaData)); + public MissingPetriNetMetaDataException(List missingMetaData, String processId) { + super("Following properties from the imported net '" + processId + "' are missing: " + String.join(", ", missingMetaData)); } } diff --git a/src/main/java/com/netgrif/application/engine/petrinet/domain/version/StringToVersionConverter.java b/src/main/java/com/netgrif/application/engine/petrinet/domain/version/StringToVersionConverter.java index 144b686ddf1..441185fb690 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/domain/version/StringToVersionConverter.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/domain/version/StringToVersionConverter.java @@ -20,17 +20,6 @@ public Version convert(String source) { if (NEWEST.equals(source) || LATEST.equals(source)) { return null; } - - Version version = new Version(); - try { - String[] split = source.split("\\."); - version.setMajor(Long.parseLong(split[0])); - version.setMinor(Long.parseLong(split[1])); - version.setPatch(Long.parseLong(split[2])); - } catch (Exception e) { - log.error("Could not parse version " + source + " caused by:", e); - } - - return version; + return Version.from(source); } } \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/petrinet/domain/version/Version.java b/src/main/java/com/netgrif/application/engine/petrinet/domain/version/Version.java index 463dbc4a0bc..80ff3f0f927 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/domain/version/Version.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/domain/version/Version.java @@ -3,12 +3,14 @@ import com.netgrif.application.engine.petrinet.domain.VersionType; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import java.io.Serializable; import static com.netgrif.application.engine.petrinet.domain.VersionType.MAJOR; import static com.netgrif.application.engine.petrinet.domain.VersionType.MINOR; +@Slf4j @Data @AllArgsConstructor public class Version implements Serializable { @@ -60,4 +62,18 @@ public Version clone() { clone.setPatch(this.patch); return clone; } + + public static Version from(String stringVersion) { + try { + Version version = new Version(); + String[] split = stringVersion.split("\\."); + version.setMajor(Long.parseLong(split[0])); + version.setMinor(Long.parseLong(split[1])); + version.setPatch(Long.parseLong(split[2])); + return version; + } catch (Exception e) { + log.error("Could not parse version {} caused by:", stringVersion, e); + throw new IllegalArgumentException("Could not parse version " + stringVersion, e); + } + } } \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetExistsException.java b/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetExistsException.java new file mode 100644 index 00000000000..3e4c1d439f2 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetExistsException.java @@ -0,0 +1,10 @@ +package com.netgrif.application.engine.petrinet.service; + +import com.netgrif.application.engine.petrinet.domain.PetriNet; + +public class PetriNetExistsException extends RuntimeException { + + public PetriNetExistsException(PetriNet net) { + super("Petri net with identifier " + net.getIdentifier() + " and version " + net.getVersion() + " already exists."); + } +} diff --git a/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index 9009183cdce..63d476a75c5 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -15,10 +15,7 @@ import com.netgrif.application.engine.importer.service.throwable.MissingIconKeyException; import com.netgrif.application.engine.ldap.service.interfaces.ILdapGroupRefService; import com.netgrif.application.engine.orgstructure.groups.interfaces.INextGroupService; -import com.netgrif.application.engine.petrinet.domain.PetriNet; -import com.netgrif.application.engine.petrinet.domain.PetriNetSearch; -import com.netgrif.application.engine.petrinet.domain.Transition; -import com.netgrif.application.engine.petrinet.domain.VersionType; +import com.netgrif.application.engine.petrinet.domain.*; import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.Action; import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.FieldActionsRunner; import com.netgrif.application.engine.petrinet.domain.events.EventPhase; @@ -35,9 +32,12 @@ import com.netgrif.application.engine.workflow.domain.Case; import com.netgrif.application.engine.workflow.domain.FileStorageConfiguration; import com.netgrif.application.engine.workflow.domain.eventoutcomes.petrinetoutcomes.ImportPetriNetEventOutcome; +import com.netgrif.application.engine.workflow.service.TaskService; +import com.netgrif.application.engine.workflow.service.interfaces.IDataService; import com.netgrif.application.engine.workflow.service.interfaces.IEventService; import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import com.querydsl.core.types.Predicate; import lombok.extern.slf4j.Slf4j; import org.apache.tomcat.util.http.fileupload.IOUtils; import org.bson.Document; @@ -62,8 +62,10 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -131,6 +133,10 @@ public class PetriNetService implements IPetriNetService { private IUriService uriService; private IElasticPetriNetService elasticPetriNetService; + @Autowired + private TaskService taskService; + @Autowired + private IDataService iDataService; @Autowired public void setElasticPetriNetService(IElasticPetriNetService elasticPetriNetService) { @@ -214,37 +220,15 @@ public ImportPetriNetEventOutcome importPetriNet(InputStream xmlFile, VersionTyp @Override public ImportPetriNetEventOutcome importPetriNet(InputStream xmlFile, VersionType releaseType, LoggedUser author, String uriNodeId, Map params) throws IOException, MissingPetriNetMetaDataException, MissingIconKeyException { - ImportPetriNetEventOutcome outcome = new ImportPetriNetEventOutcome(); - ByteArrayOutputStream xmlCopy = new ByteArrayOutputStream(); - IOUtils.copy(xmlFile, xmlCopy); - Optional imported = getImporter().importPetriNet(new ByteArrayInputStream(xmlCopy.toByteArray())); - if (imported.isEmpty()) { - return outcome; - } - PetriNet net = imported.get(); - net.setUriNodeId(uriNodeId); - - PetriNet existingNet = getNewestVersionByIdentifier(net.getIdentifier()); - if (existingNet != null) { - net.setVersion(existingNet.getVersion()); - net.incrementVersion(releaseType); - } - processRoleService.saveAll(net.getRoles().values()); - net.setAuthor(author.transformToAuthor()); - functionCacheService.cachePetriNetFunctions(net); - Path savedPath = getImporter().saveNetFile(net, new ByteArrayInputStream(xmlCopy.toByteArray())); - xmlCopy.close(); - log.info("Petri net " + net.getTitle() + " (" + net.getInitials() + " v" + net.getVersion() + ") imported successfully and saved in a folder: " + savedPath.toString()); - - outcome.setOutcomes(eventService.runActions(net.getPreUploadActions(), null, Optional.empty(), params)); - evaluateRules(net, EventPhase.PRE); - historyService.save(new ImportPetriNetEventLog(null, EventPhase.PRE, net.getObjectId())); - save(net); - outcome.setOutcomes(eventService.runActions(net.getPostUploadActions(), null, Optional.empty(), params)); - evaluateRules(net, EventPhase.POST); - historyService.save(new ImportPetriNetEventLog(null, EventPhase.POST, net.getObjectId())); - addMessageToOutcome(net, ProcessEventType.UPLOAD, outcome); - outcome.setNet(imported.get()); + ImportPetriNetEventOutcome outcome = importPetriNet(xmlFile, author, uriNodeId, params, net -> { + PetriNet existingNet = self.getNewestVersionByIdentifier(net.getIdentifier()); + if (existingNet != null) { + net.setVersion(existingNet.getVersion()); + net.incrementVersion(releaseType); + } + return net; + }); + createProcessCase(outcome, author); return outcome; } @@ -368,6 +352,51 @@ public PetriNetImportReference getNetFromCase(String caseId) { return pn; } + @Override + public ImportPetriNetEventOutcome importPetriNet(InputStream xmlFile, LoggedUser author) throws IOException, MissingPetriNetMetaDataException, MissingIconKeyException, PetriNetExistsException { + return importPetriNet(xmlFile, author, uriService.getRoot().getStringId(), new HashMap<>(), net -> { + if (exists(net.getIdentifier(), net.getVersion())) { + throw new PetriNetExistsException(net); + } + return net; + }); + } + + @Override + public boolean exists(String identifier, Version version) { + return repository.existsByIdentifierAndVersion(identifier, version); + } + + @Override + public PetriNet deploy(PetriNet net, LoggedUser user) { + return changeDeployment(net, DeploymentState.DEPLOYED, user); + } + + @Override + public PetriNet undeploy(PetriNet net, LoggedUser user) { + return changeDeployment(net, DeploymentState.DRAFT, user); + } + + @Override + public PetriNet archive(PetriNet net, LoggedUser user) { + return changeDeployment(net, DeploymentState.ARCHIVED, user); + } + + @Override + public boolean isDeployed(String netId) { + return repository.existsBy_idAndDeploymentState(new ObjectId(netId), DeploymentState.DEPLOYED); + } + + private PetriNet changeDeployment(PetriNet net, DeploymentState newState, LoggedUser user) { + net.setDeploymentState(newState); + Optional saved = save(net); + if (saved.isEmpty()) { + log.error("Petri net {} ({}) deployment could not be changed to {}", net.getIdentifier(), net.getVersion().toString(), newState); + throw new IllegalStateException("Petri net deployment could not be changed"); + } + return saved.get(); + } + @Override public List getAll() { List nets = repository.findAll(); @@ -522,6 +551,42 @@ public Page search(PetriNetSearch criteriaClass, LoggedUser u return new PageImpl<>(nets.stream().map(net -> new PetriNetReference(net, locale)).collect(Collectors.toList()), pageable, mongoTemplate.count(queryTotal, PetriNet.class)); } + @Override + public Page search(Predicate predicate, Pageable pageable) { + if (predicate == null) { + return Page.empty(); + } + if (pageable == null) { + pageable = Pageable.unpaged(); + } + return repository.findAll(predicate, pageable); + } + + @Override + public PetriNet searchOne(Predicate predicate) { + Page processAsPage = search(predicate, PageRequest.of(0, 1)); + if (processAsPage.getTotalElements() > 0) { + return processAsPage.getContent().get(0); + } + return null; + } + + @Override + public long count(Predicate predicate) { + if (predicate == null) { + return 0; + } + return repository.count(predicate); + } + + @Override + public boolean exists(Predicate predicate) { + if (predicate == null) { + return false; + } + return repository.exists(predicate); + } + private void addValueCriteria(Query query, Query queryTotal, Criteria criteria) { query.addCriteria(criteria); queryTotal.addCriteria(criteria); @@ -531,7 +596,7 @@ private void addValueCriteria(Query query, Query queryTotal, Criteria criteria) @Transactional public void deletePetriNet(String processId, LoggedUser loggedUser) { Optional petriNetOptional = repository.findById(processId); - if (!petriNetOptional.isPresent()) { + if (petriNetOptional.isEmpty()) { throw new IllegalArgumentException("Could not find process with id [" + processId + "]"); } @@ -549,7 +614,6 @@ public void deletePetriNet(String processId, LoggedUser loggedUser) { log.error("LdapGroup", ex); } - log.info("[" + processId + "]: User [" + userService.getLoggedOrSystem().getStringId() + "] is deleting Petri net " + petriNet.getIdentifier() + " version " + petriNet.getVersion().toString()); this.repository.deleteBy_id(petriNet.getObjectId()); this.evictCache(petriNet); @@ -581,4 +645,58 @@ protected T requireNonNull(T obj, Object... item) { } return obj; } + + /** + * Creates a case of the Process process and set it to deployed state. + */ + protected void createProcessCase(ImportPetriNetEventOutcome outcome, LoggedUser author) { + // TODO: NAE-2447 replace with case import in later releases + PetriNet net = outcome.getNet(); + Case procesCase = workflowService.createCaseByIdentifier("process", net.getTitle().getDefaultValue(), "", author).getCase(); + procesCase.getDataSet().get("state").setValue("deployed"); + procesCase.getDataSet().get("id").setValue(net.getIdentifier()); + procesCase.getDataSet().get("title").setValue(net.getTitle().getDefaultValue()); + procesCase.getDataSet().get("version").setValue(net.getVersion().toString()); + procesCase.getDataSet().get("icon").setValue(net.getIcon()); + procesCase.getDataSet().get("mongo_id").setValue(net.getStringId()); + procesCase.getDataSet().get("xml_text").setValue(outcome.getXmlContent()); + procesCase.setActivePlaces(Map.of("p2", 1, "p7", 1)); + workflowService.save(procesCase); + procesCase = workflowService.findOne(procesCase.getStringId()); + taskService.reloadTasks(procesCase); + } + + protected ImportPetriNetEventOutcome importPetriNet(InputStream xmlFile, LoggedUser author, String uriNodeId, Map params, Function existenceCheck) throws IOException, MissingPetriNetMetaDataException, MissingIconKeyException { + ImportPetriNetEventOutcome outcome = new ImportPetriNetEventOutcome(); + ByteArrayOutputStream xmlCopy = new ByteArrayOutputStream(); + IOUtils.copy(xmlFile, xmlCopy); + Optional imported = getImporter().importPetriNet(new ByteArrayInputStream(xmlCopy.toByteArray())); + if (imported.isEmpty()) { + return outcome; + } + PetriNet net = imported.get(); + net.setUriNodeId(uriNodeId); + net.setDeploymentState(DeploymentState.DEPLOYED); + + net = existenceCheck.apply(net); + + processRoleService.saveAll(net.getRoles().values()); + net.setAuthor(author.transformToAuthor()); + functionCacheService.cachePetriNetFunctions(net); + Path savedPath = getImporter().saveNetFile(net, new ByteArrayInputStream(xmlCopy.toByteArray())); + outcome.setXmlContent(xmlCopy.toString(StandardCharsets.UTF_8)); + xmlCopy.close(); + log.info("Petri net {} ({} v{}) imported successfully and saved in a folder: {}", net.getTitle(), net.getInitials(), net.getVersion(), savedPath.toString()); + + outcome.setOutcomes(eventService.runActions(net.getPreUploadActions(), null, Optional.empty(), params)); + evaluateRules(net, EventPhase.PRE); + historyService.save(new ImportPetriNetEventLog(null, EventPhase.PRE, net.getObjectId())); + save(net); + outcome.setOutcomes(eventService.runActions(net.getPostUploadActions(), null, Optional.empty(), params)); + evaluateRules(net, EventPhase.POST); + historyService.save(new ImportPetriNetEventLog(null, EventPhase.POST, net.getObjectId())); + addMessageToOutcome(net, ProcessEventType.UPLOAD, outcome); + outcome.setNet(imported.get()); + return outcome; + } } diff --git a/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java b/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java index 85a9b7c0a7a..520cbb0bbf4 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java @@ -10,11 +10,13 @@ import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.Action; import com.netgrif.application.engine.petrinet.domain.throwable.MissingPetriNetMetaDataException; import com.netgrif.application.engine.petrinet.domain.version.Version; +import com.netgrif.application.engine.petrinet.service.PetriNetExistsException; import com.netgrif.application.engine.petrinet.web.responsebodies.DataFieldReference; import com.netgrif.application.engine.petrinet.web.responsebodies.PetriNetImportReference; import com.netgrif.application.engine.petrinet.web.responsebodies.PetriNetReference; import com.netgrif.application.engine.petrinet.web.responsebodies.TransitionReference; import com.netgrif.application.engine.workflow.domain.eventoutcomes.petrinetoutcomes.ImportPetriNetEventOutcome; +import com.querydsl.core.types.Predicate; import org.bson.types.ObjectId; import org.springframework.core.io.FileSystemResource; import org.springframework.data.domain.Page; @@ -91,6 +93,14 @@ static DataFieldReference transformToReference(PetriNet net, Transition transiti Page search(PetriNetSearch criteria, LoggedUser user, Pageable pageable, Locale locale); + Page search(Predicate predicate, Pageable pageable); + + PetriNet searchOne(Predicate predicate); + + long count(Predicate predicate); + + boolean exists(Predicate predicate); + Optional findByImportId(String id); void evictAllCaches(); @@ -110,4 +120,16 @@ static DataFieldReference transformToReference(PetriNet net, Transition transiti List getExistingPetriNetIdentifiersFromIdentifiersList(List identifiers); PetriNetImportReference getNetFromCase(String caseId); + + ImportPetriNetEventOutcome importPetriNet(InputStream xmlFile, LoggedUser author) throws IOException, MissingPetriNetMetaDataException, MissingIconKeyException, PetriNetExistsException; + + boolean exists(String identifier, Version version); + + PetriNet deploy(PetriNet net, LoggedUser user); + + PetriNet undeploy(PetriNet net, LoggedUser user); + + PetriNet archive(PetriNet net, LoggedUser user); + + boolean isDeployed(String netId); } \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java b/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java index 4b983dcf1c1..ed29ce7f6a6 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java @@ -2,6 +2,7 @@ import com.netgrif.application.engine.AsyncRunner; import com.netgrif.application.engine.auth.domain.LoggedUser; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticPetriNetService; import com.netgrif.application.engine.eventoutcomes.LocalisedEventOutcomeFactory; import com.netgrif.application.engine.importer.service.Importer; import com.netgrif.application.engine.importer.service.throwable.MissingIconKeyException; @@ -71,6 +72,9 @@ public class PetriNetController { @Autowired private IPetriNetService service; + @Autowired + private IElasticPetriNetService elasticService; + @Autowired private IProcessRoleService roleService; @@ -197,6 +201,20 @@ PagedModel searchPetriNets(@RequestBody PetriNetSearc return resources; } + @Operation(summary = "Search elastic processes", security = {@SecurityRequirement(name = "BasicAuth")}) + @PostMapping(value = "/search_elastic", produces = MediaTypes.HAL_JSON_VALUE) + public @ResponseBody + PagedModel searchElasticPetriNets(@RequestBody PetriNetSearch criteria, Authentication auth, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { + LoggedUser user = (LoggedUser) auth.getPrincipal(); + // TODO: add Merge Filters and its operations + Page nets = elasticService.search(criteria, user, pageable, locale,false); + Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(PetriNetController.class) + .searchElasticPetriNets(criteria, auth, pageable, assembler, locale)).withRel("search_elastic"); + PagedModel resources = assembler.toModel(nets, new PetriNetReferenceResourceAssembler(), selfLink); + PetriNetReferenceResourceAssembler.buildLinks(resources); + return resources; + } + @PreAuthorize("@petriNetAuthorizationService.canCallProcessDelete(#auth.getPrincipal(), #processId)") @Operation(summary = "Delete process", description = "Caller must have the ADMIN role. Removes the specified process, along with it's cases, tasks and process roles.", diff --git a/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 new file mode 100644 index 00000000000..2c7918dfb5b --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 @@ -0,0 +1,380 @@ +// todo generate this with plugin +grammar QueryLang; + +query: resource=(PROCESS | PROCESSES) processConditionsAndPaging? EOF # processQuery + | resource=(CASE | CASES) caseConditionsAndPaging? EOF # caseQuery + | resource=(TASK | TASKS) taskConditionsAndPaging? EOF # taskQuery + | resource=(USER | USERS) userConditionsAndPaging? EOF # userQuery + ; + +processConditionsAndPaging: delimeter processConditions? paging? processSorting?; +caseConditionsAndPaging: delimeter caseConditions? paging? caseSorting?; +taskConditionsAndPaging: delimeter taskConditions? paging? taskSorting?; +userConditionsAndPaging: delimeter userConditions? paging? userSorting?; + +processConditions: processOrExpression ; +processOrExpression: processAndExpression (SPACE OR SPACE processAndExpression)* ; +processAndExpression: processConditionGroup (SPACE AND SPACE processConditionGroup)* ; +processConditionGroup: processCondition # processConditionGroupBasic + | (NOT SPACE?)? '(' SPACE? processConditions SPACE? ')' SPACE? # processConditionGroupParenthesis + ; +processCondition: processComparisons SPACE? ; + +caseConditions: caseOrExpression ; +caseOrExpression: caseAndExpression (SPACE OR SPACE caseAndExpression)* ; +caseAndExpression: caseConditionGroup (SPACE AND SPACE caseConditionGroup)* ; +caseConditionGroup: caseCondition # caseConditionGroupBasic + | (NOT SPACE?)? '(' SPACE? caseConditions SPACE? ')' SPACE? # caseConditionGroupParenthesis + ; +caseCondition: caseComparisons SPACE? ; + +taskConditions: taskOrExpression ; +taskOrExpression: taskAndExpression (SPACE OR SPACE taskAndExpression)* ; +taskAndExpression: taskConditionGroup (SPACE AND SPACE taskConditionGroup)* ; +taskConditionGroup: taskCondition # taskConditionGroupBasic + | (NOT SPACE?)? '(' SPACE? taskConditions SPACE? ')' SPACE? # taskConditionGroupParenthesis + ; +taskCondition: taskComparisons SPACE? ; + +userConditions: userOrExpression ; +userOrExpression: userAndExpression (SPACE OR SPACE userAndExpression)* ; +userAndExpression: userConditionGroup (SPACE AND SPACE userConditionGroup)* ; +userConditionGroup: userCondition # userConditionGroupBasic + | (NOT SPACE?)? '(' SPACE? userConditions SPACE? ')' SPACE? # userConditionGroupParenthesis + ; +userCondition: userComparisons SPACE? ; + +// delimeter +delimeter: SPACE WHERE SPACE | SPACE? ':' SPACE ; +WHERE: W H E R E ; + +// paging +paging: PAGE SPACE pageNum=INT (SPACE SIZE SPACE pageSize=INT)? SPACE?; + +// sorting +processSorting: SORT_BY SPACE processAttributeOrdering (',' SPACE? processAttributeOrdering)* SPACE?; +processAttributeOrdering: processAttribute (SPACE ordering=(ASC | DESC))? ; +processAttribute: ID + | IDENTIFIER + | VERSION + | TITLE + | CREATION_DATE + ; + +caseSorting: SORT_BY SPACE caseAttributeOrdering (',' SPACE? caseAttributeOrdering)* SPACE?; +caseAttributeOrdering: caseAttribute (SPACE ordering=(ASC | DESC))? ; +caseAttribute: ID + | PROCESS_ID + | PROCESS_IDENTIFIER + | TITLE + | CREATION_DATE + | AUTHOR + | places + | tasksUserId + | tasksState + | dataValue + | dataOptions + ; + +taskSorting: SORT_BY SPACE taskAttributeOrdering (',' SPACE? taskAttributeOrdering)* SPACE?; +taskAttributeOrdering: taskAttribute (SPACE ordering=(ASC | DESC))? ; +taskAttribute: ID + | TRANSITION_ID + | TITLE + | STATE + | USER_ID + | CASE_ID + | PROCESS_ID + | LAST_ASSIGN + | LAST_FINISH + ; + +userSorting: SORT_BY SPACE userAttributeOrdering (',' SPACE? userAttributeOrdering)* SPACE?; +userAttributeOrdering: userAttribute (SPACE ordering=(ASC | DESC))? ; +userAttribute: ID + | NAME + | SURNAME + | EMAIL + ; + +// resource comparisons +processComparisons: idComparison + | identifierComparison + | versionComparison + | titleComparison + | creationDateComparison + ; + +caseComparisons: idComparison + | processIdObjIdComparison + | processIdentifierComparison + | titleComparison + | creationDateComparison + | authorComparison + | placesComparison + | tasksStateComparison + | tasksUserIdComparison + | dataValueComparison + | dataOptionsComparison + ; + +taskComparisons: idComparison + | transitionIdComparison + | titleComparison + | stateComparison + | userIdComparison + | caseIdComparison + | processIdComparison + | lastAssignComparison + | lastFinishComparison + ; + +userComparisons: idComparison + | nameComparison + | surnameComparison + | emailComparison + ; + +// attribute comparisons +idComparison: ID SPACE objectIdComparison # idBasic + | ID SPACE inListStringComparison # idList + ; +titleComparison: TITLE SPACE stringComparison # titleBasic + | TITLE SPACE inListStringComparison # titleList + | TITLE SPACE inRangeStringComparison # titleRange + ; +identifierComparison: IDENTIFIER SPACE stringComparison # identifierBasic + | IDENTIFIER SPACE inListStringComparison # identifierList + | IDENTIFIER SPACE inRangeStringComparison # identifierRange + ; +versionComparison: VERSION SPACE (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER # versionBasic + | VERSION SPACE inListVersionComparison # versionListCmp + | VERSION SPACE inRangeVersionComparison # versionRangeCmp + ; +creationDateComparison: CREATION_DATE SPACE dateComparison # cdDateBasic + | CREATION_DATE SPACE dateTimeComparison # cdDateTimeBasic + | CREATION_DATE SPACE inListDateComparison # cdDateList + | CREATION_DATE SPACE inRangeDateComparison # cdDateRange + ; +processIdComparison: PROCESS_ID SPACE stringComparison # processIdBasic + | PROCESS_ID SPACE inListStringComparison # processIdList + ; +processIdObjIdComparison: PROCESS_ID SPACE objectIdComparison # processIdObjIdBasic + | PROCESS_ID SPACE inListStringComparison # processIdObjIdList + ; +processIdentifierComparison: PROCESS_IDENTIFIER SPACE stringComparison # processIdentifierBasic + | PROCESS_IDENTIFIER SPACE inListStringComparison # processIdentifierList + | PROCESS_IDENTIFIER SPACE inRangeStringComparison # processIdentifierRange + ; +authorComparison: AUTHOR SPACE stringComparison # authorBasic + | AUTHOR SPACE inListStringComparison # authorList + ; +transitionIdComparison: TRANSITION_ID SPACE stringComparison # transitionIdBasic + | TRANSITION_ID SPACE inListStringComparison # transitionIdList + | TRANSITION_ID SPACE inRangeStringComparison # transitionIdRange + ; +stateComparison: STATE SPACE EQ SPACE state=(ENABLED | DISABLED) ; +userIdComparison: USER_ID SPACE stringComparison # userIdBasic + | USER_ID SPACE inListStringComparison # userIdList + ; +caseIdComparison: CASE_ID SPACE stringComparison # caseIdBasic + | CASE_ID SPACE inListStringComparison # caseIdList + ; +lastAssignComparison: LAST_ASSIGN SPACE dateComparison # laDateBasic + | LAST_ASSIGN SPACE dateTimeComparison # laDateTimeBasic + | LAST_ASSIGN SPACE inListDateComparison # laDateList + | LAST_ASSIGN SPACE inRangeDateComparison # laDateRange + ; +lastFinishComparison: LAST_FINISH SPACE dateComparison # lfDateBasic + | LAST_FINISH SPACE dateTimeComparison # lfDateTimeBasic + | LAST_FINISH SPACE inListDateComparison # lfDateList + | LAST_FINISH SPACE inRangeDateComparison # lfDateRange + ; +nameComparison: NAME SPACE stringComparison # nameBasic + | NAME SPACE inListStringComparison # nameList + | NAME SPACE inRangeStringComparison # nameRange + ; +surnameComparison: SURNAME SPACE stringComparison # surnameBasic + | SURNAME SPACE inListStringComparison # surnameList + | SURNAME SPACE inRangeStringComparison # surnameRange + ; +emailComparison: EMAIL SPACE stringComparison # emailBasic + | EMAIL SPACE inListStringComparison # emailList + | EMAIL SPACE inRangeStringComparison # emailRange + ; +dataValueComparison: dataValue SPACE stringComparison # dataString + | dataValue SPACE numberComparison # dataNumber + | dataValue SPACE dateComparison # dataDate + | dataValue SPACE dateTimeComparison # dataDatetime + | dataValue SPACE booleanComparison # dataBoolean + | dataValue SPACE inListStringComparison # dataStringList + | dataValue SPACE inListNumberComparison # dataNumberList + | dataValue SPACE inListDateComparison # dataDateList + | dataValue SPACE inRangeStringComparison # dataStringRange + | dataValue SPACE inRangeNumberComparison # dataNumberRange + | dataValue SPACE inRangeDateComparison # dataDateRange + ; +dataOptionsComparison: dataOptions SPACE stringComparison # dataOptionsBasic + | dataOptions SPACE inListStringComparison # dataOptionsList + | dataOptions SPACE inRangeStringComparison # dataOptionsRange + ; +placesComparison: places SPACE numberComparison # placesBasic + | places SPACE inListNumberComparison # placesList + | places SPACE inRangeNumberComparison # placesRange + ; +tasksStateComparison: tasksState SPACE (NOT SPACE?)? op=EQ SPACE state=(ENABLED | DISABLED) ; +tasksUserIdComparison: tasksUserId SPACE stringComparison # tasksUserIdBasic + | tasksUserId SPACE inListStringComparison # tasksUserIdList + ; + +// basic comparisons +objectIdComparison: (NOT SPACE?)? op=(EQ | NEQ) SPACE STRING ; +stringComparison: (NOT SPACE?)? op=(EQ | NEQ | CONTAINS | LT | GT | LTE | GTE) SPACE STRING ; +numberComparison: (NOT SPACE?)? op=(EQ | NEQ | LT | GT | LTE | GTE) SPACE number=(INT | DOUBLE) ; +dateComparison: (NOT SPACE?)? op=(EQ | NEQ | LT | GT | LTE | GTE) SPACE DATE ; +dateTimeComparison: (NOT SPACE?)? op=(EQ | NEQ | LT | GT | LTE | GTE) SPACE DATETIME ; +booleanComparison: (NOT SPACE?)? op=(EQ | NEQ) SPACE BOOLEAN ; + +// in list/in range comparisons +inListStringComparison: (NOT SPACE?)? op=IN SPACE? stringList ; +inListNumberComparison: (NOT SPACE?)? op=IN SPACE? (intList | doubleList) ; +inListDateComparison: (NOT SPACE?)? op=IN SPACE? (dateList | dateTimeList) ; +inListVersionComparison: (NOT SPACE?)? op=IN SPACE? versionList ; +inRangeStringComparison: (NOT SPACE?)? op=IN SPACE? stringRange ; +inRangeNumberComparison: (NOT SPACE?)? op=IN SPACE? (intRange | doubleRange) ; +inRangeDateComparison: (NOT SPACE?)? op=IN SPACE? (dateRange | dateTimeRange) ; +inRangeVersionComparison: (NOT SPACE?)? op=IN SPACE? versionRange ; + +// special attribute rules +dataValue: DATA '.' fieldId=javaId '.' VALUE ; +dataOptions: DATA '.' fieldId=javaId '.' OPTIONS ; +places: PLACES '.' placeId=javaId '.' MARKING ; +tasksState: TASKS '.' taskId=javaId '.' STATE ; +tasksUserId: TASKS '.' taskId=javaId '.' USER_ID ; + +// identifier that also accepts reserved words as plain identifiers +javaId: JAVA_ID + | ID | TITLE | IDENTIFIER | VERSION | CREATION_DATE + | PROCESS_ID | PROCESS_IDENTIFIER | AUTHOR | PLACES + | TRANSITION_ID | STATE | USER_ID | CASE_ID + | LAST_ASSIGN | LAST_FINISH + | NAME | SURNAME | EMAIL + | DATA | VALUE | OPTIONS | MARKING + | ENABLED | DISABLED + | PAGE | SIZE | ASC | DESC + | CASE | CASES | TASK | TASKS + | USER | USERS | PROCESS | PROCESSES + ; + +// operators +AND: A N D | '&' ; +OR: O R | '|' ; +NOT: N O T | '!' ; +EQ: E Q | '==' ; +NEQ: N E Q | '!=' ; +LT: L T | '<' ; +GT: G T | '>' ; +LTE: L T E | '<=' ; +GTE: G T E | '>=' ; +CONTAINS: C O N T A I N S | '~'; +IN: I N ; + +// resurces +CASE: C A S E ; +CASES: C A S E S ; +TASK: T A S K ; +TASKS: T A S K S ; +USER: U S E R ; +USERS: U S E R S ; +PROCESS: P R O C E S S ; +PROCESSES: P R O C E S S E S ; + +// attributes +ID: I D ; +TITLE: T I T L E ; +IDENTIFIER: I D E N T I F I E R ; +VERSION: V E R S I O N ; +CREATION_DATE: C R E A T I O N D A T E ; +PROCESS_ID: P R O C E S S I D ; +PROCESS_IDENTIFIER: P R O C E S S I D E N T I F I E R ; +AUTHOR: A U T H O R ; +PLACES: P L A C E S ; +TRANSITION_ID: T R A N S I T I O N I D ; +STATE: S T A T E ; +USER_ID: U S E R I D ; +CASE_ID: C A S E I D ; +LAST_ASSIGN: L A S T A S S I G N ; +LAST_FINISH: L A S T F I N I S H ; +NAME: N A M E ; +SURNAME: S U R N A M E ; +EMAIL: E M A I L ; + +DATA: D A T A ; +VALUE: V A L U E ; +OPTIONS: O P T I O N S ; +MARKING: M A R K I N G ; +ENABLED: E N A B L E D ; +DISABLED: D I S A B L E D ; + +// paging +PAGE: P A G E ; +SIZE: S I Z E ; + +// sorting +SORT_BY: S O R T SPACE B Y ; +ASC: A S C ; +DESC: D E S C ; + +// basic types +stringList: '(' SPACE? (STRING SPACE? (',' SPACE? STRING SPACE? )* )? SPACE? ')' ; +intList: '(' SPACE? (INT SPACE? (',' SPACE? INT SPACE? )* )? SPACE? ')' ; +doubleList: '(' SPACE? (DOUBLE SPACE? (',' SPACE? DOUBLE SPACE? )* )? SPACE? ')' ; +dateList: '(' SPACE? (DATE SPACE? (',' SPACE? DATE SPACE? )* )? SPACE? ')' ; +dateTimeList: '(' SPACE? (DATETIME SPACE? (',' SPACE? DATETIME SPACE? )* )? SPACE? ')' ; +versionList: '(' SPACE? (VERSION_NUMBER SPACE? (',' SPACE? VERSION_NUMBER SPACE? )* )? SPACE? ')' ; +stringRange: leftEndpoint=('(' | '[') SPACE? STRING SPACE? ':' SPACE? STRING SPACE? rightEndpoint=(')' | ']') ; +intRange: leftEndpoint=('(' | '[') SPACE? INT SPACE? ':' SPACE? INT SPACE? rightEndpoint=(')' | ']') ; +doubleRange: leftEndpoint=('(' | '[') SPACE? DOUBLE SPACE? ':' SPACE? DOUBLE SPACE? rightEndpoint=(')' | ']') ; +dateRange: leftEndpoint=('(' | '[') SPACE? DATE SPACE? ':' SPACE? DATE SPACE? rightEndpoint=(')' | ']') ; +dateTimeRange: leftEndpoint=('(' | '[') SPACE? DATETIME SPACE? ':' SPACE? DATETIME SPACE? rightEndpoint=(')' | ']') ; +versionRange: leftEndpoint=('(' | '[') SPACE? VERSION_NUMBER SPACE? ':' SPACE? VERSION_NUMBER SPACE? rightEndpoint=(')' | ']') ; +STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; // todo NAE-1997: escape??? +INT: DIGIT+ ; +DOUBLE: DIGIT+ '.' DIGIT+ ; +DATETIME: DATE 'T' ([01] DIGIT | '2' [0-3]) ':' [0-5] DIGIT ':' [0-5] DIGIT ('.' DIGIT+)? ; // 2020-03-03T20:00:00.055 // todo NAE-1997: format +DATE: DIGIT DIGIT DIGIT DIGIT '-' ('0' [1-9] | '1' [0-2]) '-' ('0' [1-9] | [12] DIGIT | '3' [01]) ; // 2020-03-03 // todo NAE-1997: format +BOOLEAN: T R U E | F A L S E ; +VERSION_NUMBER: DIGIT+ '.' DIGIT+ '.' DIGIT+ ; +JAVA_ID: [a-zA-Z$_] [a-zA-Z0-9$_]* ; + +SPACE: [ ]+ ; +ANY: . ; + +// fragments +fragment A : [aA]; +fragment B : [bB]; +fragment C : [cC]; +fragment D : [dD]; +fragment E : [eE]; +fragment F : [fF]; +fragment G : [gG]; +fragment H : [hH]; +fragment I : [iI]; +fragment J : [jJ]; +fragment K : [kK]; +fragment L : [lL]; +fragment M : [mM]; +fragment N : [nN]; +fragment O : [oO]; +fragment P : [pP]; +fragment Q : [qQ]; +fragment R : [rR]; +fragment S : [sS]; +fragment T : [tT]; +fragment U : [uU]; +fragment V : [vV]; +fragment W : [wW]; +fragment X : [xX]; +fragment Y : [yY]; +fragment Z : [zZ]; +fragment DIGIT: [0-9]; diff --git a/src/main/java/com/netgrif/application/engine/pfql/domain/enums/ComparisonType.java b/src/main/java/com/netgrif/application/engine/pfql/domain/enums/ComparisonType.java new file mode 100644 index 00000000000..acb085c3933 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/domain/enums/ComparisonType.java @@ -0,0 +1,11 @@ +package com.netgrif.application.engine.pfql.domain.enums; + +public enum ComparisonType { + ID, + STRING, + NUMBER, + DATE, + DATETIME, + BOOLEAN, + OPTIONS +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/domain/enums/QueryType.java b/src/main/java/com/netgrif/application/engine/pfql/domain/enums/QueryType.java new file mode 100644 index 00000000000..50ad695c128 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/domain/enums/QueryType.java @@ -0,0 +1,8 @@ +package com.netgrif.application.engine.pfql.domain.enums; + +public enum QueryType { + PROCESS, + CASE, + TASK, + USER +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java new file mode 100644 index 00000000000..b097588e24f --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java @@ -0,0 +1,88 @@ +package com.netgrif.application.engine.pfql.service; + + +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import org.springframework.data.domain.Page; + + +/** + * Service interface for searching resources using query language expressions. + *

+ * Provides a unified contract for executing search operations on specific resource types + * using both string-based queries and pre-evaluated query expressions. Implementations + * of this interface should handle the translation of query language syntax into the + * appropriate search mechanism (e.g., MongoDB queries, Elasticsearch queries). + *

+ *

+ * This interface supports various search operations including single result retrieval, + * paginated searches, counting matches, and existence checks. Each operation can be + * performed using either a raw query string or a pre-evaluated {@link QueryLangEvaluator}. + *

+ * + * @param the type of resource this service searches for (e.g., Case, Task, User) + */ +public interface IResourceSearchService { + + QueryType getQueryResourceType(); + + Resource searchOne(String queryString); + Resource searchOne(QueryLangEvaluator evaluator); + + Page searchAll(String queryString); + Page searchAll(QueryLangEvaluator evaluator); + + long count(String queryString); + long count(QueryLangEvaluator evaluator); + + boolean exists(String queryString); + boolean exists(QueryLangEvaluator evaluator); + + /** + * Validates that the query evaluator is not null. + * + * @param evaluator the query evaluator to validate + * @throws IllegalArgumentException if the evaluator is null + */ + default void checkEvaluatorNotNull(QueryLangEvaluator evaluator) { + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + } + + /** + * Validates that the query evaluator expects multiple results. + * + * @param evaluator the query evaluator to validate + * @throws IllegalArgumentException if the evaluator expects a single result + */ + default void checkEvaluatorIsMultiple(QueryLangEvaluator evaluator) { + if (!evaluator.getMultiple()) { + throw new IllegalArgumentException("Cannot use searchAll() with a query that expects single result. Use searchOne() instead."); + } + } + + /** + * Validates that the query evaluator expects a single result. + * + * @param evaluator the query evaluator to validate + * @throws IllegalArgumentException if the evaluator expects multiple results + */ + default void checkEvaluatorIsSingle(QueryLangEvaluator evaluator) { + if (evaluator.getMultiple()) { + throw new IllegalArgumentException("Cannot use searchOne() with a query that expects multiple results. Use searchAll() instead."); + } + } + + /** + * Validates that the query evaluator's resource type matches the expected type. + * + * @param evaluator the query evaluator to validate + * @throws IllegalArgumentException if the resource type does not match + */ + default void checkEvaluatorResourceType(QueryLangEvaluator evaluator) { + if (evaluator.getResourceType() != getQueryResourceType()) { + throw new IllegalArgumentException(String.format("Wrong query resource type. Should be: %s, was: %s", + getQueryResourceType(), evaluator.getResourceType())); + } + } +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/ISearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/ISearchService.java new file mode 100644 index 00000000000..1ea796443f4 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/ISearchService.java @@ -0,0 +1,11 @@ +package com.netgrif.application.engine.pfql.service; + +public interface ISearchService { + String explainQuery(String query); + + Object search(String query); + + long count(String query); + + boolean exists(String query); +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangErrorListener.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangErrorListener.java new file mode 100644 index 00000000000..34f9455511c --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangErrorListener.java @@ -0,0 +1,39 @@ +package com.netgrif.application.engine.pfql.service; + +import lombok.Getter; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.ParseCancellationException; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class QueryLangErrorListener extends BaseErrorListener { + List errorMessages = new ArrayList<>(); + + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) + throws ParseCancellationException { + errorMessages.add(underlineError(recognizer, (Token) offendingSymbol, line, charPositionInLine, msg)); + } + + protected String underlineError(Recognizer recognizer, Token offendingToken, int line, int charPositionInLine, String msg) { + String underlineErrorMsg = msg + "\n"; + int start = offendingToken.getStartIndex(); + int stop = offendingToken.getStopIndex(); + if (start > stop) { + return underlineErrorMsg; + } + CommonTokenStream tokens = (CommonTokenStream) recognizer.getInputStream(); + String input = tokens.getTokenSource().getInputStream().toString(); + String[] lines = input.split("\n"); + if (line < 1 || line > lines.length) { + return underlineErrorMsg; + } + String errorLine = lines[line - 1]; + underlineErrorMsg += errorLine + "\n"; + underlineErrorMsg += " ".repeat(charPositionInLine) + "^".repeat(stop - start + 1) + "\n"; + + return underlineErrorMsg; + } +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java new file mode 100644 index 00000000000..27cf81a0421 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java @@ -0,0 +1,1463 @@ +package com.netgrif.application.engine.pfql.service; + +import com.netgrif.application.engine.auth.domain.QUser; +import com.netgrif.application.engine.petrinet.domain.QPetriNet; +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangBaseListener; +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangParser; +import com.netgrif.application.engine.pfql.domain.enums.ComparisonType; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.workflow.domain.QCase; +import com.netgrif.application.engine.workflow.domain.QTask; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.StringPath; +import lombok.Getter; +import lombok.Setter; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.ParseTreeProperty; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.bson.types.ObjectId; +import org.bson.types.QObjectId; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.*; + +public class QueryLangEvaluator extends QueryLangBaseListener { + + private final ParseTreeProperty elasticQuery = new ParseTreeProperty<>(); + private final ParseTreeProperty mongoQuery = new ParseTreeProperty<>(); + + @Getter + private QueryType resourceType; + @Getter + private Boolean multiple; + @Getter + @Setter + private Boolean searchWithElastic = false; + @Getter + private Predicate fullMongoQuery; + @Getter + private String fullElasticQuery; + @Getter + @Setter + private Pageable pageable; + + private int pageNumber = 0; + private int pageSize = 20; + private final List sortOrders = new ArrayList<>(); + + public void setElasticQuery(ParseTree node, String query) { + elasticQuery.put(node, query); + } + + public String getElasticQuery(ParseTree node) { + return elasticQuery.get(node); + } + + public void setMongoQuery(ParseTree node, Predicate predicate) { + mongoQuery.put(node, predicate); + } + + public Predicate getMongoQuery(ParseTree node) { + return mongoQuery.get(node); + } + + private void processBasicExpression(ParseTree child, ParseTree current) { + setMongoQuery(current, getMongoQuery(child)); + setElasticQuery(current, getElasticQuery(child)); + } + + private void processOrExpression(List children, ParseTree current) { + List predicates = children.stream() + .map(this::getMongoQuery) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + String elasticQuery = children.stream() + .map(this::getElasticQuery) + .filter(Objects::nonNull) + .collect(Collectors.joining(" OR ")); + + if (!predicates.isEmpty()) { + BooleanBuilder predicate = new BooleanBuilder(); + predicates.forEach(predicate::or); + setMongoQuery(current, predicate); + } + setElasticQuery(current, elasticQuery.isBlank() ? null : elasticQuery); + } + + private void processAndExpression(List children, ParseTree current) { + List predicates = children.stream() + .map(this::getMongoQuery) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + String elasticQuery = children.stream() + .map(this::getElasticQuery) + .filter(Objects::nonNull) + .collect(Collectors.joining(" AND ")); + + if (!predicates.isEmpty()) { + BooleanBuilder predicate = new BooleanBuilder(); + predicates.forEach(predicate::and); + setMongoQuery(current, predicate); + } + setElasticQuery(current, elasticQuery.isBlank() ? null : elasticQuery); + } + + private void processConditionGroup(ParseTree child, ParseTree current, Boolean not, Boolean parenthesis) { + Predicate predicate = getMongoQuery(child); + String elasticQuery = getElasticQuery(child); + + if (predicate != null) { + predicate = not ? (predicate).not() : (predicate); + } + + if (elasticQuery != null) { + if (parenthesis) { + elasticQuery = "(" + elasticQuery + ")"; + } + + if (not) { + elasticQuery = "NOT " + elasticQuery; + } + } + + setMongoQuery(current, predicate); + setElasticQuery(current, elasticQuery); + } + + private Predicate getEmptyMongoQuery() { + return new BooleanBuilder(); + } + + private String getEmptyElasticQuery() { + return "*"; + } + + private void handleNoneConditions() { + fullMongoQuery = getEmptyMongoQuery(); + fullElasticQuery = getEmptyElasticQuery(); + } + + @Override + public void enterProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + resourceType = QueryType.PROCESS; + multiple = ctx.resource.getType() == QueryLangParser.PROCESSES; + } + + @Override + public void exitProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + if (ctx.processConditionsAndPaging() == null || ctx.processConditionsAndPaging().processConditions() == null) { + handleNoneConditions(); + } else { + processBasicExpression(ctx.processConditionsAndPaging().processConditions(), ctx); + fullMongoQuery = getMongoQuery(ctx); + fullElasticQuery = getElasticQuery(ctx); + } + pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortOrders)); + } + + @Override + public void enterCaseQuery(QueryLangParser.CaseQueryContext ctx) { + resourceType = QueryType.CASE; + multiple = ctx.resource.getType() == QueryLangParser.CASES; + } + + @Override + public void exitCaseQuery(QueryLangParser.CaseQueryContext ctx) { + if (ctx.caseConditionsAndPaging() == null || ctx.caseConditionsAndPaging().caseConditions() == null) { + handleNoneConditions(); + } else { + processBasicExpression(ctx.caseConditionsAndPaging().caseConditions(), ctx); + fullMongoQuery = getMongoQuery(ctx); + fullElasticQuery = getElasticQuery(ctx); + } + pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortOrders)); + } + + @Override + public void enterTaskQuery(QueryLangParser.TaskQueryContext ctx) { + resourceType = QueryType.TASK; + multiple = ctx.resource.getType() == QueryLangParser.TASKS; + } + + @Override + public void exitTaskQuery(QueryLangParser.TaskQueryContext ctx) { + if (ctx.taskConditionsAndPaging() == null || ctx.taskConditionsAndPaging().taskConditions() == null) { + handleNoneConditions(); + } else { + processBasicExpression(ctx.taskConditionsAndPaging().taskConditions(), ctx); + fullMongoQuery = getMongoQuery(ctx); + fullElasticQuery = getElasticQuery(ctx); + } + pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortOrders)); + } + + @Override + public void enterUserQuery(QueryLangParser.UserQueryContext ctx) { + resourceType = QueryType.USER; + multiple = ctx.resource.getType() == QueryLangParser.USERS; + } + + @Override + public void exitUserQuery(QueryLangParser.UserQueryContext ctx) { + if (ctx.userConditionsAndPaging() == null || ctx.userConditionsAndPaging().userConditions() == null) { + handleNoneConditions(); + } else { + processBasicExpression(ctx.userConditionsAndPaging().userConditions(), ctx); + fullMongoQuery = getMongoQuery(ctx); + fullElasticQuery = getElasticQuery(ctx); + } + pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortOrders)); + } + + @Override + public void exitProcessConditions(QueryLangParser.ProcessConditionsContext ctx) { + processBasicExpression(ctx.processOrExpression(), ctx); + } + + @Override + public void exitProcessOrExpression(QueryLangParser.ProcessOrExpressionContext ctx) { + List children = ctx.processAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processOrExpression(children, ctx); + } + + @Override + public void exitProcessAndExpression(QueryLangParser.ProcessAndExpressionContext ctx) { + List children = ctx.processConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processAndExpression(children, ctx); + } + + @Override + public void exitProcessConditionGroupBasic(QueryLangParser.ProcessConditionGroupBasicContext ctx) { + processConditionGroup(ctx.processCondition(), ctx, false, false); + } + + @Override + public void exitProcessConditionGroupParenthesis(QueryLangParser.ProcessConditionGroupParenthesisContext ctx) { + processConditionGroup(ctx.processConditions(), ctx, ctx.NOT() != null, true); + } + + @Override + public void exitProcessCondition(QueryLangParser.ProcessConditionContext ctx) { + processBasicExpression(ctx.processComparisons(), ctx); + } + + @Override + public void exitCaseConditions(QueryLangParser.CaseConditionsContext ctx) { + processBasicExpression(ctx.caseOrExpression(), ctx); + } + + @Override + public void exitCaseOrExpression(QueryLangParser.CaseOrExpressionContext ctx) { + List children = ctx.caseAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processOrExpression(children, ctx); + } + + @Override + public void exitCaseAndExpression(QueryLangParser.CaseAndExpressionContext ctx) { + List children = ctx.caseConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processAndExpression(children, ctx); + } + + @Override + public void exitCaseConditionGroupBasic(QueryLangParser.CaseConditionGroupBasicContext ctx) { + processConditionGroup(ctx.caseCondition(), ctx, false, false); + } + + @Override + public void exitCaseConditionGroupParenthesis(QueryLangParser.CaseConditionGroupParenthesisContext ctx) { + processConditionGroup(ctx.caseConditions(), ctx, ctx.NOT() != null, true); + } + + @Override + public void exitCaseCondition(QueryLangParser.CaseConditionContext ctx) { + processBasicExpression(ctx.caseComparisons(), ctx); + } + + @Override + public void exitTaskConditions(QueryLangParser.TaskConditionsContext ctx) { + processBasicExpression(ctx.taskOrExpression(), ctx); + } + + @Override + public void exitTaskOrExpression(QueryLangParser.TaskOrExpressionContext ctx) { + List children = ctx.taskAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processOrExpression(children, ctx); + } + + @Override + public void exitTaskAndExpression(QueryLangParser.TaskAndExpressionContext ctx) { + List children = ctx.taskConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processAndExpression(children, ctx); + } + + @Override + public void exitTaskConditionGroupBasic(QueryLangParser.TaskConditionGroupBasicContext ctx) { + processConditionGroup(ctx.taskCondition(), ctx, false, false); + } + + @Override + public void exitTaskConditionGroupParenthesis(QueryLangParser.TaskConditionGroupParenthesisContext ctx) { + processConditionGroup(ctx.taskConditions(), ctx, ctx.NOT() != null, true); + } + + @Override + public void exitTaskCondition(QueryLangParser.TaskConditionContext ctx) { + processBasicExpression(ctx.taskComparisons(), ctx); + } + + @Override + public void exitUserConditions(QueryLangParser.UserConditionsContext ctx) { + processBasicExpression(ctx.userOrExpression(), ctx); + } + + @Override + public void exitUserOrExpression(QueryLangParser.UserOrExpressionContext ctx) { + List children = ctx.userAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processOrExpression(children, ctx); + } + + @Override + public void exitUserAndExpression(QueryLangParser.UserAndExpressionContext ctx) { + List children = ctx.userConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processAndExpression(children, ctx); + } + + @Override + public void exitUserConditionGroupBasic(QueryLangParser.UserConditionGroupBasicContext ctx) { + processConditionGroup(ctx.userCondition(), ctx, false, false); + } + + @Override + public void exitUserConditionGroupParenthesis(QueryLangParser.UserConditionGroupParenthesisContext ctx) { + processConditionGroup(ctx.userConditions(), ctx, ctx.NOT() != null, true); + } + + @Override + public void exitUserCondition(QueryLangParser.UserConditionContext ctx) { + processBasicExpression(ctx.userComparisons(), ctx); + } + + @Override + public void exitProcessComparisons(QueryLangParser.ProcessComparisonsContext ctx) { + processBasicExpression(ctx.children.get(0), ctx); + } + + @Override + public void exitCaseComparisons(QueryLangParser.CaseComparisonsContext ctx) { + processBasicExpression(ctx.children.get(0), ctx); + } + + @Override + public void exitTaskComparisons(QueryLangParser.TaskComparisonsContext ctx) { + processBasicExpression(ctx.children.get(0), ctx); + } + + @Override + public void exitUserComparisons(QueryLangParser.UserComparisonsContext ctx) { + processBasicExpression(ctx.children.get(0), ctx); + } + + @Override + public void exitIdBasic(QueryLangParser.IdBasicContext ctx) { + QObjectId qObjectId; + Token op = ctx.objectIdComparison().op; + boolean not = ctx.objectIdComparison().NOT() != null; + checkOp(ComparisonType.ID, op); + ObjectId objectId = getObjectIdValue(ctx.objectIdComparison().STRING().getText()); + + switch (resourceType) { + case PROCESS: + qObjectId = QPetriNet.petriNet._id; + break; + case CASE: + qObjectId = QCase.case$._id; + setElasticQuery(ctx, buildElasticQuery("stringId", op.getType(), objectId.toString(), not)); + break; + case TASK: + qObjectId = QTask.task._id; + break; + case USER: + qObjectId = QUser.user._id; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op.getType(), objectId, not)); + } + + @Override + public void exitIdList(QueryLangParser.IdListContext ctx) { + QObjectId qObjectId; + Token op = ctx.inListStringComparison().op; + boolean not = ctx.inListStringComparison().NOT() != null; + checkOp(ComparisonType.ID, op); + List objectIdList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getObjectIdValue(node.getText())) + .collect(Collectors.toList()); + List stringIdList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + switch (resourceType) { + case PROCESS: + qObjectId = QPetriNet.petriNet._id; + break; + case CASE: + qObjectId = QCase.case$._id; + setElasticQuery(ctx, buildElasticQueryInList("stringId", stringIdList, not)); + break; + case TASK: + qObjectId = QTask.task._id; + break; + case USER: + qObjectId = QUser.user._id; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + setMongoQuery(ctx, buildObjectIdPredicateInList(qObjectId, objectIdList, not)); + } + + @Override + public void exitTitleBasic(QueryLangParser.TitleBasicContext ctx) { + StringPath stringPath; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + switch (resourceType) { + case PROCESS: + stringPath = QPetriNet.petriNet.title.defaultValue; + break; + case CASE: + stringPath = QCase.case$.title; + setElasticQuery(ctx, buildElasticQuery("title", op.getType(), string, not)); + break; + case TASK: + stringPath = QTask.task.title.defaultValue; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitTitleList(QueryLangParser.TitleListContext ctx) { + StringPath stringPath; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + switch (resourceType) { + case PROCESS: + stringPath = QPetriNet.petriNet.title.defaultValue; + break; + case CASE: + stringPath = QCase.case$.title; + setElasticQuery(ctx, buildElasticQueryInList("title", stringList, not)); + break; + case TASK: + stringPath = QTask.task.title.defaultValue; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitTitleRange(QueryLangParser.TitleRangeContext ctx) { + StringPath stringPath; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + switch (resourceType) { + case PROCESS: + stringPath = QPetriNet.petriNet.title.defaultValue; + break; + case CASE: + stringPath = QCase.case$.title; + setElasticQuery(ctx, buildElasticQueryInRange("title", leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + break; + case TASK: + stringPath = QTask.task.title.defaultValue; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitIdentifierBasic(QueryLangParser.IdentifierBasicContext ctx) { + StringPath stringPath = QPetriNet.petriNet.identifier; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitIdentifierList(QueryLangParser.IdentifierListContext ctx) { + StringPath stringPath = QPetriNet.petriNet.identifier; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitIdentifierRange(QueryLangParser.IdentifierRangeContext ctx) { + StringPath stringPath = QPetriNet.petriNet.identifier; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitVersionBasic(QueryLangParser.VersionBasicContext ctx) { + Token op = ctx.op; + boolean not = ctx.NOT() != null; + String versionString = ctx.VERSION_NUMBER().getText(); + + setMongoQuery(ctx, buildVersionPredicate(op.getType(), versionString, not)); + } + + @Override + public void exitVersionListCmp(QueryLangParser.VersionListCmpContext ctx) { + boolean not = ctx.inListVersionComparison().NOT() != null; + List stringList = ctx.inListVersionComparison().versionList().VERSION_NUMBER().stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, buildVersionPredicateInList(stringList, not)); + } + + @Override + public void exitVersionRangeCmp(QueryLangParser.VersionRangeCmpContext ctx) { + boolean not = ctx.inRangeVersionComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeVersionComparison().versionRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeVersionComparison().versionRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeVersionComparison().versionRange().VERSION_NUMBER(0).getText()); + String rightString = getStringValue(ctx.inRangeVersionComparison().versionRange().VERSION_NUMBER(1).getText()); + + setMongoQuery(ctx, buildVersionPredicateInRange(leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitCdDateBasic(QueryLangParser.CdDateBasicContext ctx) { + DateTimePath dateTimePath; + Token op = ctx.dateComparison().op; + boolean not = ctx.dateComparison().NOT() != null; + LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); + + switch (resourceType) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; + case CASE: + dateTimePath = QCase.case$.creationDate; + setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitCdDateTimeBasic(QueryLangParser.CdDateTimeBasicContext ctx) { + DateTimePath dateTimePath; + Token op = ctx.dateTimeComparison().op; + boolean not = ctx.dateTimeComparison().NOT() != null; + LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); + + switch (resourceType) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; + case CASE: + dateTimePath = QCase.case$.creationDate; + setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitCdDateList(QueryLangParser.CdDateListContext ctx) { + DateTimePath dateTimePath; + boolean not = ctx.inListDateComparison().NOT() != null; + List terminalNodeList = ctx.inListDateComparison().dateList() != null ? ctx.inListDateComparison().dateList().DATE() : ctx.inListDateComparison().dateTimeList().DATETIME() ; + List stringDateList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); + + switch (resourceType) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; + case CASE: + dateTimePath = QCase.case$.creationDate; + List timestampStringList = stringDateList.stream().map(dateString -> { + LocalDateTime localDateTime = toDateTime(dateString); + return String.valueOf(Timestamp.valueOf(localDateTime).getTime()); + }).collect(Collectors.toList()); + setElasticQuery(ctx, buildElasticQueryInList("creationDateSortable", timestampStringList, not)); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + setMongoQuery(ctx, buildDateTimePredicateInList(dateTimePath, stringDateList, not)); + } + + @Override + public void exitCdDateRange(QueryLangParser.CdDateRangeContext ctx) { + DateTimePath dateTimePath; + boolean not = ctx.inRangeDateComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + LocalDateTime leftDateTime; + LocalDateTime rightDateTime; + if (ctx.inRangeDateComparison().dateRange() != null) { + leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); + } else { + leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); + } + + + switch (resourceType) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; + case CASE: + dateTimePath = QCase.case$.creationDate; + setElasticQuery(ctx, buildElasticQueryInRange("creationDateSortable", String.valueOf(Timestamp.valueOf(leftDateTime).getTime()), leftEndpointOpen, String.valueOf(Timestamp.valueOf(rightDateTime).getTime()), rightEndpointOpen, not)); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + setMongoQuery(ctx, buildDateTimePredicateInRange(dateTimePath, leftDateTime, leftEndpointOpen, rightDateTime, rightEndpointOpen, not)); + } + + @Override + public void exitProcessIdBasic(QueryLangParser.ProcessIdBasicContext ctx) { + StringPath stringPath = QTask.task.processId; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitProcessIdList(QueryLangParser.ProcessIdListContext ctx) { + StringPath stringPath = QTask.task.processId; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitProcessIdObjIdBasic(QueryLangParser.ProcessIdObjIdBasicContext ctx) { + QObjectId qObjectId = QCase.case$.petriNetObjectId; + Token op = ctx.objectIdComparison().op; + boolean not = ctx.objectIdComparison().NOT() != null; + ObjectId objectId = getObjectIdValue(ctx.objectIdComparison().STRING().getText()); + + setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op.getType(), objectId, not)); + setElasticQuery(ctx, buildElasticQuery("processId", op.getType(), objectId.toString(), not)); + } + + @Override + public void exitProcessIdObjIdList(QueryLangParser.ProcessIdObjIdListContext ctx) { + QObjectId qObjectId = QCase.case$.petriNetObjectId; + boolean not = ctx.inListStringComparison().NOT() != null; + List objectIdList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getObjectIdValue(node.getText())) + .collect(Collectors.toList()); + List stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildObjectIdPredicateInList(qObjectId, objectIdList, not)); + setElasticQuery(ctx, buildElasticQueryInList("processId", stringList, not)); + } + + @Override + public void exitProcessIdentifierBasic(QueryLangParser.ProcessIdentifierBasicContext ctx) { + StringPath stringPath = QCase.case$.processIdentifier; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + setElasticQuery(ctx, buildElasticQuery("processIdentifier", op.getType(), string, not)); + } + + @Override + public void exitProcessIdentifierList(QueryLangParser.ProcessIdentifierListContext ctx) { + StringPath stringPath = QCase.case$.processIdentifier; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + setElasticQuery(ctx, buildElasticQueryInList("processIdentifier", stringList, not)); + } + + @Override + public void exitProcessIdentifierRange(QueryLangParser.ProcessIdentifierRangeContext ctx) { + StringPath stringPath = QCase.case$.processIdentifier; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + setElasticQuery(ctx, buildElasticQueryInRange("processIdentifier", leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitAuthorBasic(QueryLangParser.AuthorBasicContext ctx) { + StringPath stringPath = QCase.case$.author.id; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + setElasticQuery(ctx, buildElasticQuery("author", op.getType(), string, not)); + } + + @Override + public void exitAuthorList(QueryLangParser.AuthorListContext ctx) { + StringPath stringPath = QCase.case$.author.id; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + setElasticQuery(ctx, buildElasticQueryInList("author", stringList, not)); + } + + @Override + public void exitTransitionIdBasic(QueryLangParser.TransitionIdBasicContext ctx) { + StringPath stringPath = QTask.task.transitionId; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitTransitionIdList(QueryLangParser.TransitionIdListContext ctx) { + StringPath stringPath = QTask.task.transitionId; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitTransitionIdRange(QueryLangParser.TransitionIdRangeContext ctx) { + StringPath stringPath = QTask.task.transitionId; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitStateComparison(QueryLangParser.StateComparisonContext ctx) { + // todo implement task states +// switch (ctx.state.getType()) { +// case QueryLangParser.ENABLED: +// setMongoQuery(ctx, QTask.task.state.eq(State.ENABLED)); +// break; +// case QueryLangParser.DISABLED: +// setMongoQuery(ctx, QTask.task.state.eq(State.DISABLED)); +// break; +// } + } + + @Override + public void exitUserIdBasic(QueryLangParser.UserIdBasicContext ctx) { + StringPath stringPath = QTask.task.userId; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitUserIdList(QueryLangParser.UserIdListContext ctx) { + StringPath stringPath = QTask.task.userId; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitCaseIdBasic(QueryLangParser.CaseIdBasicContext ctx) { + StringPath stringPath = QTask.task.caseId; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitCaseIdList(QueryLangParser.CaseIdListContext ctx) { + StringPath stringPath = QTask.task.caseId; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitLaDateBasic(QueryLangParser.LaDateBasicContext ctx) { + // todo implement lastAssigned +// DateTimePath dateTimePath = QTask.task.lastAssigned; +// Token op = ctx.dateComparison().op; +// boolean not = ctx.dateComparison().NOT() != null; +// LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); +// +// setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitLaDateTimeBasic(QueryLangParser.LaDateTimeBasicContext ctx) { + // todo implement lastAssigned +// DateTimePath dateTimePath = QTask.task.lastAssigned; +// Token op = ctx.dateTimeComparison().op; +// boolean not = ctx.dateTimeComparison().NOT() != null; +// LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); +// +// setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitLaDateList(QueryLangParser.LaDateListContext ctx) { + // todo implement lastAssigned +// DateTimePath dateTimePath = QTask.task.lastAssigned; +// boolean not = ctx.inListDateComparison().NOT() != null; +// List terminalNodeList = ctx.inListDateComparison().dateList() != null ? ctx.inListDateComparison().dateList().DATE() : ctx.inListDateComparison().dateTimeList().DATETIME() ; +// List stringDateList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); +// +// setMongoQuery(ctx, buildDateTimePredicateInList(dateTimePath, stringDateList, not)); + } + + @Override + public void exitLaDateRange(QueryLangParser.LaDateRangeContext ctx) { + // todo implement lastAssigned +// DateTimePath dateTimePath = QTask.task.lastAssigned; +// boolean not = ctx.inRangeDateComparison().NOT() != null; +// boolean leftEndpointOpen; +// boolean rightEndpointOpen; +// LocalDateTime leftDateTime; +// LocalDateTime rightDateTime; +// if (ctx.inRangeDateComparison().dateRange() != null) { +// leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); +// rightEndpointOpen = ctx.inRangeDateComparison().dateRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); +// leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); +// rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); +// } else { +// leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); +// rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); +// leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); +// rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); +// } +// +// setMongoQuery(ctx, buildDateTimePredicateInRange(dateTimePath, leftDateTime, leftEndpointOpen, rightDateTime, rightEndpointOpen, not)); + } + + @Override + public void exitLfDateBasic(QueryLangParser.LfDateBasicContext ctx) { + // todo implement lastFinished +// DateTimePath dateTimePath = QTask.task.lastFinished; +// Token op = ctx.dateComparison().op; +// boolean not = ctx.dateComparison().NOT() != null; +// LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); +// +// setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitLfDateTimeBasic(QueryLangParser.LfDateTimeBasicContext ctx) { + // todo implement lastFinished +// DateTimePath dateTimePath = QTask.task.lastFinished; +// Token op = ctx.dateTimeComparison().op; +// boolean not = ctx.dateTimeComparison().NOT() != null; +// LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); +// +// setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitLfDateList(QueryLangParser.LfDateListContext ctx) { + // todo implement lastFinished +// DateTimePath dateTimePath = QTask.task.lastFinished; +// boolean not = ctx.inListDateComparison().NOT() != null; +// List terminalNodeList = ctx.inListDateComparison().dateList() != null ? ctx.inListDateComparison().dateList().DATE() : ctx.inListDateComparison().dateTimeList().DATETIME() ; +// List stringDateList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); +// +// setMongoQuery(ctx, buildDateTimePredicateInList(dateTimePath, stringDateList, not)); + } + + @Override + public void exitLfDateRange(QueryLangParser.LfDateRangeContext ctx) { + // todo implement lastFinished +// DateTimePath dateTimePath = QTask.task.lastFinished; +// boolean not = ctx.inRangeDateComparison().NOT() != null; +// boolean leftEndpointOpen; +// boolean rightEndpointOpen; +// LocalDateTime leftDateTime; +// LocalDateTime rightDateTime; +// if (ctx.inRangeDateComparison().dateRange() != null) { +// leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); +// rightEndpointOpen = ctx.inRangeDateComparison().dateRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); +// leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); +// rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); +// } else { +// leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); +// rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); +// leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); +// rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); +// } +// +// setMongoQuery(ctx, buildDateTimePredicateInRange(dateTimePath, leftDateTime, leftEndpointOpen, rightDateTime, rightEndpointOpen, not)); + } + + @Override + public void exitNameBasic(QueryLangParser.NameBasicContext ctx) { + StringPath stringPath = QUser.user.name; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitNameList(QueryLangParser.NameListContext ctx) { + StringPath stringPath = QUser.user.name; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitNameRange(QueryLangParser.NameRangeContext ctx) { + StringPath stringPath = QUser.user.name; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitSurnameBasic(QueryLangParser.SurnameBasicContext ctx) { + StringPath stringPath = QUser.user.surname; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitSurnameList(QueryLangParser.SurnameListContext ctx) { + StringPath stringPath = QUser.user.surname; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitSurnameRange(QueryLangParser.SurnameRangeContext ctx) { + StringPath stringPath = QUser.user.surname; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitEmailBasic(QueryLangParser.EmailBasicContext ctx) { + StringPath stringPath = QUser.user.email; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitEmailList(QueryLangParser.EmailListContext ctx) { + StringPath stringPath = QUser.user.email; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitEmailRange(QueryLangParser.EmailRangeContext ctx) { + StringPath stringPath = QUser.user.email; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitDataString(QueryLangParser.DataStringContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + Token op = ctx.stringComparison().op; + checkOp(ComparisonType.STRING, op); + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".textValue", op.getType(), string, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataStringList(QueryLangParser.DataStringListContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("dataSet." + fieldId + ".textValue", stringList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataStringRange(QueryLangParser.DataStringRangeContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("dataSet." + fieldId + ".textValue", leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataNumber(QueryLangParser.DataNumberContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + Token op = ctx.numberComparison().op; + checkOp(ComparisonType.NUMBER, op); + boolean not = ctx.numberComparison().NOT() != null; + String number = ctx.numberComparison().number.getText(); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".numberValue", op.getType(), number, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataNumberList(QueryLangParser.DataNumberListContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inListNumberComparison().NOT() != null; + List terminalNodeList = ctx.inListNumberComparison().intList() != null ? ctx.inListNumberComparison().intList().INT() : ctx.inListNumberComparison().doubleList().DOUBLE(); + List stringNumberList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("dataSet." + fieldId + ".numberValue", stringNumberList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataNumberRange(QueryLangParser.DataNumberRangeContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inRangeNumberComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + String leftNumberAsString; + String rightNumberAsString; + if (ctx.inRangeNumberComparison().intRange() != null) { + leftEndpointOpen = ctx.inRangeNumberComparison().intRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().intRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftNumberAsString = ctx.inRangeNumberComparison().intRange().INT(0).getText(); + rightNumberAsString = ctx.inRangeNumberComparison().intRange().INT(1).getText(); + } else { + leftEndpointOpen = ctx.inRangeNumberComparison().doubleRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().doubleRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(0).getText(); + rightNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(1).getText(); + } + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("dataSet." + fieldId + ".numberValue", leftNumberAsString, leftEndpointOpen, rightNumberAsString, rightEndpointOpen, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataDate(QueryLangParser.DataDateContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + Token op = ctx.dateComparison().op; + checkOp(ComparisonType.DATE, op); + boolean not = ctx.dateComparison().NOT() != null; + LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataDatetime(QueryLangParser.DataDatetimeContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + Token op = ctx.dateTimeComparison().op; + checkOp(ComparisonType.DATETIME, op); + boolean not = ctx.dateTimeComparison().NOT() != null; + LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataDateList(QueryLangParser.DataDateListContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inListDateComparison().NOT() != null; + List terminalNodeList = ctx.inListDateComparison().dateList() != null ? ctx.inListDateComparison().dateList().DATE() : ctx.inListDateComparison().dateTimeList().DATETIME(); + List stringNumberList = terminalNodeList.stream().map(TerminalNode::getText).map(dateAsString -> { + LocalDateTime localDateTime = toDateTime(dateAsString); + return String.valueOf(Timestamp.valueOf(localDateTime).getTime()); + }).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("dataSet." + fieldId + ".timestampValue", stringNumberList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataDateRange(QueryLangParser.DataDateRangeContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inRangeDateComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + LocalDateTime leftDateTime; + LocalDateTime rightDateTime; + if (ctx.inRangeDateComparison().dateRange() != null) { + leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); + } else { + leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); + } + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("dataSet." + fieldId + ".timestampValue", String.valueOf(Timestamp.valueOf(leftDateTime).getTime()), leftEndpointOpen, String.valueOf(Timestamp.valueOf(rightDateTime).getTime()), rightEndpointOpen, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataBoolean(QueryLangParser.DataBooleanContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + Token op = ctx.booleanComparison().op; + checkOp(ComparisonType.BOOLEAN, op); + boolean not = ctx.booleanComparison().NOT() != null; + String booleanValue = ctx.booleanComparison().BOOLEAN().getText(); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".booleanValue", op.getType(), booleanValue, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataOptionsBasic(QueryLangParser.DataOptionsBasicContext ctx) { + String fieldId = ctx.dataOptions().fieldId.getText(); + Token op = ctx.stringComparison().op; + checkOp(ComparisonType.STRING, op); + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".options", op.getType(), string, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataOptionsList(QueryLangParser.DataOptionsListContext ctx) { + String fieldId = ctx.dataOptions().fieldId.getText(); + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("dataSet." + fieldId + ".options", stringList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataOptionsRange(QueryLangParser.DataOptionsRangeContext ctx) { + String fieldId = ctx.dataOptions().fieldId.getText(); + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("dataSet." + fieldId + ".options", leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + this.searchWithElastic = true; + } + + @Override + public void exitPlacesBasic(QueryLangParser.PlacesBasicContext ctx) { + String placeId = ctx.places().placeId.getText(); + Token op = ctx.numberComparison().op; + checkOp(ComparisonType.NUMBER, op); + boolean not = ctx.numberComparison().NOT() != null; + String numberValue = ctx.numberComparison().number.getText(); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("places." + placeId + ".marking", op.getType(), numberValue, not)); + this.searchWithElastic = true; + } + + @Override + public void exitPlacesList(QueryLangParser.PlacesListContext ctx) { + String placeId = ctx.places().placeId.getText(); + boolean not = ctx.inListNumberComparison().NOT() != null; + List terminalNodeList = ctx.inListNumberComparison().intList() != null ? ctx.inListNumberComparison().intList().INT() : ctx.inListNumberComparison().doubleList().DOUBLE(); + List stringNumberList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("places." + placeId + ".marking", stringNumberList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitPlacesRange(QueryLangParser.PlacesRangeContext ctx) { + String placeId = ctx.places().placeId.getText(); + boolean not = ctx.inRangeNumberComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + String leftNumberAsString; + String rightNumberAsString; + if (ctx.inRangeNumberComparison().intRange() != null) { + leftEndpointOpen = ctx.inRangeNumberComparison().intRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().intRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftNumberAsString = ctx.inRangeNumberComparison().intRange().INT(0).getText(); + rightNumberAsString = ctx.inRangeNumberComparison().intRange().INT(1).getText(); + } else { + leftEndpointOpen = ctx.inRangeNumberComparison().doubleRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().doubleRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(0).getText(); + rightNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(1).getText(); + } + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("places." + placeId + ".marking", leftNumberAsString, leftEndpointOpen, rightNumberAsString, rightEndpointOpen, not)); + this.searchWithElastic = true; + } + + @Override + public void exitTasksStateComparison(QueryLangParser.TasksStateComparisonContext ctx) { + // todo implement task states +// String taskId = ctx.tasksState().taskId.getText(); +// Token op = ctx.op; +// checkOp(ComparisonType.STRING, op); +// boolean not = ctx.NOT() != null; +// State state = ctx.state.getType() == QueryLangParser.ENABLED ? State.ENABLED : State.DISABLED; +// +// setMongoQuery(ctx, null); +// setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".state", op.getType(), state.toString(), not)); +// this.searchWithElastic = true; + } + + @Override + public void exitTasksUserIdBasic(QueryLangParser.TasksUserIdBasicContext ctx) { + String taskId = ctx.tasksUserId().taskId.getText(); + Token op = ctx.stringComparison().op; + checkOp(ComparisonType.STRING, op); + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".userId", op.getType(), string, not)); + this.searchWithElastic = true; + } + + @Override + public void exitTasksUserIdList(QueryLangParser.TasksUserIdListContext ctx) { + String taskId = ctx.tasksUserId().taskId.getText(); + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("tasks." + taskId + ".userId", stringList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitPaging(QueryLangParser.PagingContext ctx) { + pageNumber = Integer.parseInt(ctx.pageNum.getText()); + + if (ctx.pageSize != null) { + pageSize = Integer.parseInt(ctx.pageSize.getText()); + } + } + + @Override + public void exitCaseSorting(QueryLangParser.CaseSortingContext ctx) { + ctx.caseAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop; + if (searchWithElastic) { + // todo: sorting by data value, options + if (attrOrd.caseAttribute().places() != null) { + prop = "places." + attrOrd.caseAttribute().places().placeId.getText() + ".marking"; + } else if (attrOrd.caseAttribute().tasksState() != null) { + prop = "tasks." + attrOrd.caseAttribute().tasksState().taskId.getText() + ".state.keyword"; + } else if (attrOrd.caseAttribute().tasksUserId() != null) { + prop = "tasks." + attrOrd.caseAttribute().tasksUserId().taskId.getText() + ".userId.keyword"; + } else { + prop = caseAttrToSortPropElasticMapping.get(attrOrd.caseAttribute().getText().toLowerCase()); + } + } else { + prop = caseAttrToSortPropMapping.get(attrOrd.caseAttribute().getText().toLowerCase()); + } + + if (prop == null) { + return; + } + sortOrders.add(new Sort.Order(dir, prop)); + }); + } + + @Override + public void exitProcessSorting(QueryLangParser.ProcessSortingContext ctx) { + ctx.processAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = processAttrToSortPropMapping.get(attrOrd.processAttribute().getText().toLowerCase()); + if (prop == null) { + return; + } + sortOrders.add(new Sort.Order(dir, prop)); + }); + } + + @Override + public void exitTaskSorting(QueryLangParser.TaskSortingContext ctx) { + ctx.taskAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = taskAttrToSortPropMapping.get(attrOrd.taskAttribute().getText().toLowerCase()); + if (prop == null) { + return; + } + sortOrders.add(new Sort.Order(dir, prop)); + }); + } + + @Override + public void exitUserSorting(QueryLangParser.UserSortingContext ctx) { + ctx.userAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = userAttrToSortPropMapping.get(attrOrd.userAttribute().getText().toLowerCase()); + if (prop == null) { + return; + } + sortOrders.add(new Sort.Order(dir, prop)); + }); + } +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangExplainEvaluator.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangExplainEvaluator.java new file mode 100644 index 00000000000..f6a7e936818 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangExplainEvaluator.java @@ -0,0 +1,522 @@ +package com.netgrif.application.engine.pfql.service; + +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangBaseListener; +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangParser; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.utils.QueryLangTreeNode; +import lombok.Getter; +import org.antlr.v4.runtime.tree.ErrorNodeImpl; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.ParseTreeProperty; +import org.springframework.data.domain.Sort; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.*; + + +public class QueryLangExplainEvaluator extends QueryLangBaseListener { + + private final ParseTreeProperty node = new ParseTreeProperty<>(); + @Getter + private QueryLangTreeNode root = null; + + @Getter + private QueryType type; + @Getter + private boolean multiple; + @Getter + private boolean searchWithElastic = false; + + private int pageNumber = 0; + private int pageSize = 20; + private final List sortOrders = new ArrayList<>(); + + public String explain() { + StringBuilder result = new StringBuilder("Searching "); + + result.append(multiple ? "multiple instances" : "single instance") + .append(" of resource ") + .append(type != null ? type.name() : "invalid type") + .append(" with ") + .append(searchWithElastic ? "Elasticsearch" : "MongoDB") + .append(".\n") + .append("page number: ").append(pageNumber) + .append(", page size: ").append(pageSize) + .append("\n"); + + if (!sortOrders.isEmpty()) { + result.append("sort by ").append(String.join(";", sortOrders)).append("\n"); + } + + result.append(root != null ? root.toString() : "Tree visualisation not available."); + + return result.toString(); + } + + public void setQueryLangTreeNode(ParseTree node, QueryLangTreeNode queryLangTreeNode) { + if (queryLangTreeNode == null) { + queryLangTreeNode = new QueryLangTreeNode("error: " + node.getText()); + } + this.node.put(node, queryLangTreeNode); + } + + public QueryLangTreeNode getQueryLangTreeNode(ParseTree node) { + return this.node.get(node); + } + + private static QueryLangTreeNode createTreeNode(String name, List children, List errors) { + List combinedChildren = Stream.concat(children.stream(), errors.stream()).collect(Collectors.toList()); + return new QueryLangTreeNode(name, combinedChildren); + } + + private QueryLangTreeNode getErrorFromNode(ParseTree node) { + if (node instanceof ErrorNodeImpl) { + String errorMsg = "error: " + ((ErrorNodeImpl) node).symbol.getText(); + return new QueryLangTreeNode(errorMsg); + } + return null; + } + + private List getErrorsFromChildren(List children) { + List errors = new ArrayList<>(); + children.forEach(child -> { + if (child instanceof ErrorNodeImpl) { + errors.add(getErrorFromNode(child)); + } + }); + return errors; + } + + private List getErrorsRecursive(ParseTree node) { + List errors = new ArrayList<>(); + if (node.getChildCount() == 0) { + if (node instanceof ErrorNodeImpl) { + errors.add(getErrorFromNode(node)); + } + return errors; + } + + int numChildren = node.getChildCount(); + for (int i = 0; i < numChildren; i++) { + errors.addAll(getErrorsRecursive(node.getChild(i))); + } + return errors; + } + + private void processComplexExpression(String nodeName, List children, ParseTree current) { + if (children.size() == 1) { + setQueryLangTreeNode(current, getQueryLangTreeNode(children.get(0))); + return; + } + List errorNodes = getErrorsFromChildren(children); + + List childrenNodes = children.stream() + .map(child -> { + QueryLangTreeNode node = getQueryLangTreeNode(child); + if (node == null) { + errorNodes.addAll(getErrorsRecursive(child)); + } + return node; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + + setQueryLangTreeNode(current, createTreeNode(nodeName, childrenNodes, errorNodes)); + } + + @Override + public void enterProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + type = QueryType.PROCESS; + multiple = ctx.resource.getType() == QueryLangParser.PROCESSES; + } + + @Override + public void exitProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + List children = new ArrayList<>(); + if (ctx.processConditionsAndPaging() != null && ctx.processConditionsAndPaging().processConditions() != null) { + children.add(getQueryLangTreeNode(ctx.processConditionsAndPaging().processConditions())); + } + root = createTreeNode("process query", children, getErrorsFromChildren(ctx.children)); + } + + @Override + public void enterCaseQuery(QueryLangParser.CaseQueryContext ctx) { + type = QueryType.CASE; + multiple = ctx.resource.getType() == QueryLangParser.CASES; + } + + @Override + public void exitCaseQuery(QueryLangParser.CaseQueryContext ctx) { + List children = new ArrayList<>(); + if (ctx.caseConditionsAndPaging() != null && ctx.caseConditionsAndPaging().caseConditions() != null) { + children.add(getQueryLangTreeNode(ctx.caseConditionsAndPaging().caseConditions())); + } + root = createTreeNode("case query", children, getErrorsFromChildren(ctx.children)); + } + + @Override + public void enterTaskQuery(QueryLangParser.TaskQueryContext ctx) { + type = QueryType.TASK; + multiple = ctx.resource.getType() == QueryLangParser.TASKS; + } + + @Override + public void exitTaskQuery(QueryLangParser.TaskQueryContext ctx) { + List children = new ArrayList<>(); + if (ctx.taskConditionsAndPaging() != null && ctx.taskConditionsAndPaging().taskConditions() != null) { + children.add(getQueryLangTreeNode(ctx.taskConditionsAndPaging().taskConditions())); + } + root = createTreeNode("task query", children, getErrorsFromChildren(ctx.children)); + } + + @Override + public void enterUserQuery(QueryLangParser.UserQueryContext ctx) { + type = QueryType.USER; + multiple = ctx.resource.getType() == QueryLangParser.USERS; + } + + @Override + public void exitUserQuery(QueryLangParser.UserQueryContext ctx) { + List children = new ArrayList<>(); + if (ctx.userConditionsAndPaging() != null && ctx.userConditionsAndPaging().userConditions() != null) { + children.add(getQueryLangTreeNode(ctx.userConditionsAndPaging().userConditions())); + } + root = createTreeNode("user query", children, getErrorsFromChildren(ctx.children)); + } + + @Override + public void exitProcessConditions(QueryLangParser.ProcessConditionsContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.processOrExpression())); + } + + @Override + public void exitProcessOrExpression(QueryLangParser.ProcessOrExpressionContext ctx) { + List children = ctx.processAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processComplexExpression("OR", children, ctx); + } + + @Override + public void exitProcessAndExpression(QueryLangParser.ProcessAndExpressionContext ctx) { + List children = ctx.processConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processComplexExpression("AND", children, ctx); + } + + @Override + public void exitProcessConditionGroupBasic(QueryLangParser.ProcessConditionGroupBasicContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.processCondition())); + } + + @Override + public void exitProcessConditionGroupParenthesis(QueryLangParser.ProcessConditionGroupParenthesisContext ctx) { + setQueryLangTreeNode(ctx, createTreeNode("()", List.of(getQueryLangTreeNode(ctx.processConditions())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitCaseConditions(QueryLangParser.CaseConditionsContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.caseOrExpression())); + } + + @Override + public void exitCaseOrExpression(QueryLangParser.CaseOrExpressionContext ctx) { + List children = ctx.caseAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processComplexExpression("OR", children, ctx); + } + + @Override + public void exitCaseAndExpression(QueryLangParser.CaseAndExpressionContext ctx) { + List children = ctx.caseConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processComplexExpression("AND", children, ctx); + } + + @Override + public void exitCaseConditionGroupBasic(QueryLangParser.CaseConditionGroupBasicContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.caseCondition())); + } + + + @Override + public void exitCaseConditionGroupParenthesis(QueryLangParser.CaseConditionGroupParenthesisContext ctx) { + setQueryLangTreeNode(ctx, createTreeNode("()", List.of(getQueryLangTreeNode(ctx.caseConditions())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitTaskConditions(QueryLangParser.TaskConditionsContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.taskOrExpression())); + } + + @Override + public void exitTaskOrExpression(QueryLangParser.TaskOrExpressionContext ctx) { + List children = ctx.taskAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processComplexExpression("OR", children, ctx); + } + + @Override + public void exitTaskAndExpression(QueryLangParser.TaskAndExpressionContext ctx) { + List children = ctx.taskConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processComplexExpression("AND", children, ctx); + } + + @Override + public void exitTaskConditionGroupBasic(QueryLangParser.TaskConditionGroupBasicContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.taskCondition())); + } + + @Override + public void exitTaskConditionGroupParenthesis(QueryLangParser.TaskConditionGroupParenthesisContext ctx) { + setQueryLangTreeNode(ctx, createTreeNode("()", List.of(getQueryLangTreeNode(ctx.taskConditions())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitUserConditions(QueryLangParser.UserConditionsContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.userOrExpression())); + } + + @Override + public void exitUserOrExpression(QueryLangParser.UserOrExpressionContext ctx) { + List children = ctx.userAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processComplexExpression("OR", children, ctx); + } + + @Override + public void exitUserAndExpression(QueryLangParser.UserAndExpressionContext ctx) { + List children = ctx.userConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processComplexExpression("AND", children, ctx); + } + + @Override + public void exitUserConditionGroupBasic(QueryLangParser.UserConditionGroupBasicContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.userCondition())); + } + + @Override + public void exitUserConditionGroupParenthesis(QueryLangParser.UserConditionGroupParenthesisContext ctx) { + setQueryLangTreeNode(ctx, createTreeNode("()", List.of(getQueryLangTreeNode(ctx.userConditions())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitProcessCondition(QueryLangParser.ProcessConditionContext ctx) { + List errors = getErrorsRecursive(ctx); + setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); + } + + @Override + public void exitCaseCondition(QueryLangParser.CaseConditionContext ctx) { + List errors = getErrorsRecursive(ctx); + setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); + } + + @Override + public void exitTaskCondition(QueryLangParser.TaskConditionContext ctx) { + List errors = getErrorsRecursive(ctx); + setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); + } + + @Override + public void exitUserCondition(QueryLangParser.UserConditionContext ctx) { + List errors = getErrorsRecursive(ctx); + setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); + } + + @Override + public void exitPlacesBasic(QueryLangParser.PlacesBasicContext ctx) { + searchWithElastic = true; + } + + @Override + public void enterPlacesList(QueryLangParser.PlacesListContext ctx) { + searchWithElastic = true; + } + + @Override + public void enterPlacesRange(QueryLangParser.PlacesRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitTasksStateComparison(QueryLangParser.TasksStateComparisonContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitTasksUserIdBasic(QueryLangParser.TasksUserIdBasicContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitTasksUserIdList(QueryLangParser.TasksUserIdListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataString(QueryLangParser.DataStringContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataNumber(QueryLangParser.DataNumberContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataDate(QueryLangParser.DataDateContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataDatetime(QueryLangParser.DataDatetimeContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataBoolean(QueryLangParser.DataBooleanContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataDateList(QueryLangParser.DataDateListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataDateRange(QueryLangParser.DataDateRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataNumberList(QueryLangParser.DataNumberListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataNumberRange(QueryLangParser.DataNumberRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataStringList(QueryLangParser.DataStringListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataStringRange(QueryLangParser.DataStringRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataOptionsBasic(QueryLangParser.DataOptionsBasicContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataOptionsList(QueryLangParser.DataOptionsListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataOptionsRange(QueryLangParser.DataOptionsRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitPaging(QueryLangParser.PagingContext ctx) { + pageNumber = Integer.parseInt(ctx.pageNum.getText()); + + if (ctx.pageSize != null) { + pageSize = Integer.parseInt(ctx.pageSize.getText()); + } + } + + @Override + public void exitCaseSorting(QueryLangParser.CaseSortingContext ctx) { + ctx.caseAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop; + if (searchWithElastic) { + // todo: sorting by data value, options + if (attrOrd.caseAttribute().places() != null) { + prop = "places." + attrOrd.caseAttribute().places().placeId.getText() + ".marking"; + } else if (attrOrd.caseAttribute().tasksState() != null) { + prop = "tasks." + attrOrd.caseAttribute().tasksState().taskId.getText() + ".state.keyword"; + } else if (attrOrd.caseAttribute().tasksUserId() != null) { + prop = "tasks." + attrOrd.caseAttribute().tasksUserId().taskId.getText() + ".userId.keyword"; + } else { + prop = caseAttrToSortPropElasticMapping.get(attrOrd.caseAttribute().getText().toLowerCase()); + } + } else { + prop = caseAttrToSortPropMapping.get(attrOrd.caseAttribute().getText().toLowerCase()); + } + + if (prop == null) { + sortOrders.add("Invalid attribute: " + attrOrd.caseAttribute().getText()); + return; + } + sortOrders.add("attribute: " + prop + ", ordering: " + dir); + }); + } + + @Override + public void exitProcessSorting(QueryLangParser.ProcessSortingContext ctx) { + ctx.processAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = processAttrToSortPropMapping.get(attrOrd.processAttribute().getText().toLowerCase()); + if (prop == null) { + sortOrders.add("Invalid attribute: " + attrOrd.processAttribute().getText()); + } + sortOrders.add("attribute: " + prop + ", ordering: " + dir); + }); + } + + @Override + public void exitTaskSorting(QueryLangParser.TaskSortingContext ctx) { + ctx.taskAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = taskAttrToSortPropMapping.get(attrOrd.taskAttribute().getText().toLowerCase()); + if (prop == null) { + sortOrders.add("Invalid attribute: " + attrOrd.taskAttribute().getText()); + } + sortOrders.add("attribute: " + prop + ", ordering: " + dir); + }); + } + + @Override + public void exitUserSorting(QueryLangParser.UserSortingContext ctx) { + ctx.userAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = userAttrToSortPropMapping.get(attrOrd.userAttribute().getText().toLowerCase()); + if (prop == null) { + sortOrders.add("Invalid attribute: " + attrOrd.userAttribute().getText()); + } + sortOrders.add("attribute: " + prop + ", ordering: " + dir); + }); + } +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java new file mode 100644 index 00000000000..f9a27017013 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java @@ -0,0 +1,102 @@ +package com.netgrif.application.engine.pfql.service; + +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.utils.SearchUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; + +@Slf4j +@Service +public class SearchService implements ISearchService { + + private final Map> serviceRegistry; + + public SearchService(List> services) { + this.serviceRegistry = services.stream() + .collect(Collectors.toMap(IResourceSearchService::getQueryResourceType, Function.identity())); + + } + + /** + * Explains the provided query by parsing and describing its structure. + * + * @param input the query string to be explained + * @return a human-readable explanation of the query structure + */ + @Override + public String explainQuery(String input) { + log.debug("Explaining query: {}", input); + String explanation = SearchUtils.explainQuery(input); + log.trace("Query explanation result: {}", explanation); + return explanation; + } + + /** + * Executes a search operation based on the provided query string. + * Evaluates the query and delegates to the appropriate resource search service. + * Returns either a single result or multiple results based on the query specification. + * + * @param input the query string to be executed + * @return a single resource object or a page of resources depending on the query type + */ + @Override + public Object search(String input) { + log.debug("Executing search with query: {}", input); + QueryLangEvaluator evaluator = evaluateQuery(input); + log.trace("Evaluated query type: {}, multiple: {}", evaluator.getResourceType(), evaluator.getMultiple()); + IResourceSearchService service = this.serviceRegistry.get(evaluator.getResourceType()); + if (service == null) { + throw new IllegalStateException("No PFQL search service registered for query type: " + evaluator.getResourceType()); + } + Object result = evaluator.getMultiple() ? service.searchAll(evaluator) : service.searchOne(evaluator); + log.debug("Search completed, returning {} result", evaluator.getMultiple() ? "multiple" : "single"); + return result; + } + + /** + * Counts the number of resources that match the provided query string. + * + * @param input the query string to be evaluated + * @return the count of matching resources + */ + @Override + public long count(String input) { + log.debug("Counting resources with query: {}", input); + QueryLangEvaluator evaluator = evaluateQuery(input); + log.trace("Evaluated query type for count: {}", evaluator.getResourceType()); + IResourceSearchService service = this.serviceRegistry.get(evaluator.getResourceType()); + if (service == null) { + throw new IllegalStateException("No PFQL search service registered for query type: " + evaluator.getResourceType()); + } + long count = service.count(evaluator); + log.debug("Count completed, result: {}", count); + return count; + } + + /** + * Checks whether any resources exist that match the provided query string. + * + * @param input the query string to be evaluated + * @return true if at least one matching resource exists, false otherwise + */ + @Override + public boolean exists(String input) { + log.debug("Checking existence with query: {}", input); + QueryLangEvaluator evaluator = evaluateQuery(input); + log.trace("Evaluated query type for exists: {}", evaluator.getResourceType()); + IResourceSearchService service = this.serviceRegistry.get(evaluator.getResourceType()); + if (service == null) { + throw new IllegalStateException("No PFQL search service registered for query type: " + evaluator.getResourceType()); + } + boolean exists = service.exists(evaluator); + log.debug("Existence check completed, result: {}", exists); + return exists; + } +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java new file mode 100644 index 00000000000..1a480043c6b --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java @@ -0,0 +1,230 @@ +package com.netgrif.application.engine.pfql.service.caseresource; + +import com.netgrif.application.engine.auth.service.interfaces.IUserService; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; +import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.IResourceSearchService; +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; + +/** + * Service implementation for searching and querying Case resources. + * Supports both MongoDB and Elasticsearch-based searches depending on the query configuration. + * Provides methods to search for single or multiple cases, count matching cases, and check existence. + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class CaseSearchService implements IResourceSearchService { + + private final IWorkflowService workflowService; + private final IElasticCaseService elasticCaseService; + private final IUserService userService; + + /** + * Returns the query type handled by this service. + * + * @return the QueryType.CASE indicating this service handles case queries + */ + @Override + public QueryType getQueryResourceType() { + return QueryType.CASE; + } + + /** + * Searches for a single case matching the provided query string. + * The query string is evaluated and processed before execution. + * + * @param queryString the query string to be evaluated and executed + * @return the first matching Case, or null if no match is found + */ + @Override + public Case searchOne(String queryString) { + log.debug("Searching for single case with query: {}", queryString); + return searchOne(evaluateQuery(queryString)); + } + + /** + * Searches for a single case using a pre-evaluated query evaluator. + * Routes the search to either Elasticsearch or MongoDB based on the evaluator configuration. + * + * @param evaluator the query evaluator containing the parsed query and configuration + * @return the first matching Case, or null if no match is found + * @throws IllegalArgumentException if the evaluator is null or configured for multiple results + */ + @Override + public Case searchOne(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorIsSingle(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Searching for single case using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch query: {}", evaluator.getFullElasticQuery()); + Page caseInPage = findCasesElastic(evaluator.getFullElasticQuery(), PageRequest.of(0, 1)); + Case result = caseInPage.getContent().stream().findFirst().orElse(null); + log.trace("Elasticsearch search one result: {}", result != null ? result.getStringId() : "null"); + return result; + } else { + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Case result = workflowService.searchOne(evaluator.getFullMongoQuery()); + log.trace("MongoDB search one result: {}", result != null ? result.getStringId() : "null"); + return result; + } + } + + /** + * Searches for all cases matching the provided query string. + * The query string is evaluated and processed before execution. + * + * @param queryString the query string to be evaluated and executed + * @return a Page containing all matching Cases + */ + @Override + public Page searchAll(String queryString) { + log.debug("Searching for all cases with query: {}", queryString); + return searchAll(evaluateQuery(queryString)); + } + + /** + * Searches for all cases using a pre-evaluated query evaluator. + * Routes the search to either Elasticsearch or MongoDB based on the evaluator configuration. + * Supports pagination through the evaluator's pageable configuration. + * + * @param evaluator the query evaluator containing the parsed query, pagination, and configuration + * @return a Page containing all matching Cases + * @throws IllegalArgumentException if the evaluator is null or configured for single result + */ + @Override + public Page searchAll(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorIsMultiple(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Searching for all cases using {} with pagination: page={}, size={}", + evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB", + evaluator.getPageable().getPageNumber(), evaluator.getPageable().getPageSize()); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch query: {}", evaluator.getFullElasticQuery()); + Page result = findCasesElastic(evaluator.getFullElasticQuery(), evaluator.getPageable()); + log.trace("Elasticsearch search all result: page size={}, total elements={}", result.getNumberOfElements(), result.getTotalElements()); + return result; + } else { + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Page result = workflowService.search(evaluator.getFullMongoQuery(), evaluator.getPageable()); + log.trace("MongoDB search all result: page size={}, total elements={}", result.getNumberOfElements(), result.getTotalElements()); + return result; + } + } + + /** + * Counts the number of cases matching the provided query string. + * The query string is evaluated and processed before execution. + * + * @param queryString the query string to be evaluated and executed + * @return the count of matching cases + */ + @Override + public long count(String queryString) { + log.debug("Counting cases with query: {}", queryString); + return count(evaluateQuery(queryString)); + } + + /** + * Counts the number of cases using a pre-evaluated query evaluator. + * Routes the count operation to either Elasticsearch or MongoDB based on the evaluator configuration. + * + * @param evaluator the query evaluator containing the parsed query and configuration + * @return the count of matching cases + * @throws IllegalArgumentException if the evaluator is null + */ + @Override + public long count(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Counting cases using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch count query: {}", evaluator.getFullElasticQuery()); + long result = countCasesElastic(evaluator.getFullElasticQuery()); + log.trace("Elasticsearch count result: {}", result); + return result; + } else { + log.trace("Executing MongoDB count query: {}", evaluator.getFullMongoQuery()); + long result = workflowService.count(evaluator.getFullMongoQuery()); + log.trace("MongoDB count result: {}", result); + return result; + } + } + + /** + * Checks whether any cases exist that match the provided query string. + * The query string is evaluated and processed before execution. + * + * @param queryString the query string to be evaluated and executed + * @return true if at least one matching case exists, false otherwise + */ + @Override + public boolean exists(String queryString) { + log.debug("Checking existence of case with query: {}", queryString); + return exists(evaluateQuery(queryString)); + } + + /** + * Checks whether any cases exist using a pre-evaluated query evaluator. + * Routes the existence check to either Elasticsearch or MongoDB based on the evaluator configuration. + * + * @param evaluator the query evaluator containing the parsed query and configuration + * @return true if at least one matching case exists, false otherwise + * @throws IllegalArgumentException if the evaluator is null + */ + @Override + public boolean exists(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Checking existence of cases using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch exists query: {}", evaluator.getFullElasticQuery()); + boolean result = existsCasesElastic(evaluator.getFullElasticQuery()); + log.trace("Elasticsearch exists result: {}", result); + return result; + } else { + log.trace("Executing MongoDB exists query: {}", evaluator.getFullMongoQuery()); + boolean result = workflowService.exists(evaluator.getFullMongoQuery()); + log.trace("MongoDB exists result: {}", result); + return result; + } + } + + private long countCasesElastic(String elasticQuery) { + CaseSearchRequest caseSearchRequest = new CaseSearchRequest(); + caseSearchRequest.query = elasticQuery; + return elasticCaseService.count(List.of(caseSearchRequest), userService.getLoggedOrSystem().transformToLoggedUser(), + LocaleContextHolder.getLocale(), false); + } + + private Page findCasesElastic(String elasticQuery, Pageable pageable) { + CaseSearchRequest caseSearchRequest = new CaseSearchRequest(); + caseSearchRequest.query = elasticQuery; + return elasticCaseService.search(List.of(caseSearchRequest), userService.getLoggedOrSystem().transformToLoggedUser(), + pageable, LocaleContextHolder.getLocale(), false); + } + + private boolean existsCasesElastic(String elasticQuery) { + return countCasesElastic(elasticQuery) > 0; + } +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java new file mode 100644 index 00000000000..4f945338032 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java @@ -0,0 +1,207 @@ +package com.netgrif.application.engine.pfql.service.processresource; + +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.IResourceSearchService; +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ProcessSearchService implements IResourceSearchService { + + private final IPetriNetService petriNetService; + + /** + * Returns the query type handled by this service. + * + * @return {@link QueryType#PROCESS} indicating this service handles process queries + */ + @Override + public QueryType getQueryResourceType() { + return QueryType.PROCESS; + } + + /** + * Searches for a single process that matches the provided query string. + *

+ * This method parses the query string into an evaluator and delegates to + * {@link #searchOne(QueryLangEvaluator)} for execution. + *

+ * + * @param queryString the query string to be evaluated and executed + * @return the matching {@link PetriNet} process, or null if no match is found + * @throws IllegalArgumentException if the query string results in a multiple-results query + */ + @Override + public PetriNet searchOne(String queryString) { + log.debug("Searching for single process with query: {}", queryString); + return searchOne(evaluateQuery(queryString)); + } + + /** + * Searches for a single process using a pre-evaluated query expression. + *

+ * This method validates that the evaluator is configured for single-result queries + * and currently executes the search using MongoDB. Future implementations will + * support Elasticsearch as an alternative search backend. + *

+ * + * @param evaluator the query evaluator containing the parsed query and search configuration + * @return the matching {@link PetriNet} process, or null if no match is found + * @throws IllegalArgumentException if evaluator is null or configured for multiple results + */ + @Override + public PetriNet searchOne(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorIsSingle(evaluator); + checkEvaluatorResourceType(evaluator); + + // todo implement Elasticsearch search (service layer and evaluator layer) + + log.debug("Searching for single process using MongoDB"); + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + PetriNet result = petriNetService.searchOne(evaluator.getFullMongoQuery()); + log.trace("MongoDB search one result: {}", result != null ? result.getStringId() : "null"); + return result; + } + + /** + * Searches for all processes that match the provided query string. + *

+ * This method parses the query string into an evaluator and delegates to + * {@link #searchAll(QueryLangEvaluator)} for execution. + *

+ * + * @param queryString the query string to be evaluated and executed + * @return a page of matching {@link PetriNet} processes + * @throws IllegalArgumentException if the query string results in a single-result query + */ + @Override + public Page searchAll(String queryString) { + log.debug("Searching for all processes with query: {}", queryString); + return searchAll(evaluateQuery(queryString)); + } + + /** + * Searches for all processes using a pre-evaluated query expression. + *

+ * This method validates that the evaluator is configured for multiple-result queries + * and executes the search with pagination support. Currently uses MongoDB as the + * search backend. Future implementations will support Elasticsearch. + *

+ * + * @param evaluator the query evaluator containing the parsed query, pagination, and search configuration + * @return a page of matching {@link PetriNet} processes + * @throws IllegalArgumentException if evaluator is null or configured for single result + */ + @Override + public Page searchAll(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorIsMultiple(evaluator); + checkEvaluatorResourceType(evaluator); + + // todo implement Elasticsearch search (service layer and evaluator layer) + + log.debug("Searching for all processes using MongoDB"); + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Page result = petriNetService.search(evaluator.getFullMongoQuery(), evaluator.getPageable()); + log.trace("MongoDB search all result: page size={}, total elements={}", result.getNumberOfElements(), result.getTotalElements()); + return result; + } + + /** + * Counts the number of processes that match the provided query string. + *

+ * This method parses the query string into an evaluator and delegates to + * {@link #count(QueryLangEvaluator)} for execution. + *

+ * + * @param queryString the query string to be evaluated and executed + * @return the count of matching processes + */ + @Override + public long count(String queryString) { + log.debug("Counting processes with query: {}", queryString); + return count(evaluateQuery(queryString)); + } + + /** + * Counts the number of processes using a pre-evaluated query expression. + *

+ * This method executes a count operation without retrieving the actual process data. + * Currently uses MongoDB as the search backend. Future implementations will support + * Elasticsearch. + *

+ * + * @param evaluator the query evaluator containing the parsed query and search configuration + * @return the count of processes matching the query + * @throws IllegalArgumentException if evaluator is null + */ + @Override + public long count(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + + // todo implement Elasticsearch search (service layer and evaluator layer) + + log.debug("Counting processes using MongoDB"); + log.trace("Executing MongoDB count query: {}", evaluator.getFullMongoQuery()); + long result = petriNetService.count(evaluator.getFullMongoQuery()); + log.trace("MongoDB count result: {}", result); + return result; + } + + /** + * Checks whether any processes exist that match the provided query string. + *

+ * This method parses the query string into an evaluator and delegates to + * {@link #exists(QueryLangEvaluator)} for execution. + *

+ * + * @param queryString the query string to be evaluated and executed + * @return true if at least one matching process exists, false otherwise + */ + @Override + public boolean exists(String queryString) { + log.debug("Checking existence of process with query: {}", queryString); + return exists(evaluateQuery(queryString)); + } + + /** + * Checks whether any processes exist using a pre-evaluated query expression. + *

+ * This method performs an existence check without retrieving or counting the actual + * process data, making it more efficient than count or search operations when only + * existence needs to be verified. Currently uses MongoDB as the search backend. + * Future implementations will support Elasticsearch. + *

+ * + * @param evaluator the query evaluator containing the parsed query and search configuration + * @return true if at least one matching process exists, false otherwise + * @throws IllegalArgumentException if evaluator is null + */ + @Override + public boolean exists(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + + // todo implement Elasticsearch search (service layer and evaluator layer) + + log.debug("Checking existence of processes using MongoDB"); + log.trace("Executing MongoDB exists query: {}", evaluator.getFullMongoQuery()); + boolean result = petriNetService.exists(evaluator.getFullMongoQuery()); + log.trace("MongoDB exists result: {}", result); + return result; + } +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java new file mode 100644 index 00000000000..4c53bba6212 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java @@ -0,0 +1,263 @@ +package com.netgrif.application.engine.pfql.service.taskresource; + +import com.netgrif.application.engine.auth.service.interfaces.IUserService; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticTaskService; +import com.netgrif.application.engine.elastic.web.requestbodies.ElasticTaskSearchRequest; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.IResourceSearchService; +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; +import com.netgrif.application.engine.workflow.domain.Task; +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; + +/** + * Service implementation for searching Task resources using query language expressions. + *

+ * Provides functionality to search for tasks using both MongoDB and Elasticsearch backends. + * The service automatically determines which search backend to use based on the query + * evaluator configuration. It supports single result retrieval, paginated searches, + * counting matches, and existence checks. + *

+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskSearchService implements IResourceSearchService { + + private final ITaskService taskService; + private final IElasticTaskService elasticTaskService; + private final IUserService userService; + + /** + * Returns the query type handled by this service. + * + * @return {@link QueryType#TASK} indicating this service handles task queries + */ + @Override + public QueryType getQueryResourceType() { + return QueryType.TASK; + } + + /** + * Searches for a single task matching the provided query string. + * + * @param queryString the query string to be evaluated and executed + * @return the matching task, or null if no task is found + */ + @Override + public Task searchOne(String queryString) { + log.debug("Searching for single task with query: {}", queryString); + return searchOne(evaluateQuery(queryString)); + } + + /** + * Searches for a single task using a pre-evaluated query expression. + *

+ * The search is executed using either Elasticsearch or MongoDB based on the + * evaluator configuration. This method validates that the query expects a single + * result and throws an exception if multiple results are expected. + *

+ * + * @param evaluator the pre-evaluated query expression containing search criteria + * @return the matching task, or null if no task is found + * @throws IllegalArgumentException if evaluator is null or if the query expects multiple results + */ + @Override + public Task searchOne(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorIsSingle(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Searching for single task using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch query: {}", evaluator.getFullElasticQuery()); + Page taskInPage = findTasksElastic(evaluator.getFullElasticQuery(), PageRequest.of(0, 1)); + Task result = taskInPage.getContent().stream().findFirst().orElse(null); + log.trace("Elasticsearch search one result: {}", result != null ? result.getStringId() : "null"); + return result; + } else { + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Task result = taskService.searchOne(evaluator.getFullMongoQuery()); + log.trace("MongoDB search one result: {}", result != null ? result.getStringId() : "null"); + return result; + } + } + + /** + * Searches for all tasks matching the provided query string. + * + * @param queryString the query string to be evaluated and executed + * @return a page of matching tasks + */ + @Override + public Page searchAll(String queryString) { + log.debug("Searching for all tasks with query: {}", queryString); + return searchAll(evaluateQuery(queryString)); + } + + /** + * Searches for all tasks using a pre-evaluated query expression. + *

+ * The search is executed using either Elasticsearch or MongoDB based on the + * evaluator configuration. This method validates that the query expects multiple + * results and throws an exception if a single result is expected. + *

+ * + * @param evaluator the pre-evaluated query expression containing search criteria and pagination info + * @return a page of matching tasks + * @throws IllegalArgumentException if evaluator is null or if the query expects a single result + */ + @Override + public Page searchAll(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorIsMultiple(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Searching for all tasks using {} with pagination: page={}, size={}", + evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB", + evaluator.getPageable().getPageNumber(), evaluator.getPageable().getPageSize()); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch query: {}", evaluator.getFullElasticQuery()); + Page result = findTasksElastic(evaluator.getFullElasticQuery(), evaluator.getPageable()); + log.trace("Elasticsearch search all result: page size={}, total elements={}", result.getNumberOfElements(), result.getTotalElements()); + return result; + } else { + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Page result = taskService.search(evaluator.getFullMongoQuery(), evaluator.getPageable()); + log.trace("MongoDB search all result: page size={}, total elements={}", result.getNumberOfElements(), result.getTotalElements()); + return result; + } + } + + /** + * Counts the number of tasks matching the provided query string. + * + * @param queryString the query string to be evaluated and executed + * @return the count of matching tasks + */ + @Override + public long count(String queryString) { + log.debug("Counting tasks with query: {}", queryString); + return count(evaluateQuery(queryString)); + } + + /** + * Counts the number of tasks using a pre-evaluated query expression. + *

+ * The count is executed using either Elasticsearch or MongoDB based on the + * evaluator configuration. + *

+ * + * @param evaluator the pre-evaluated query expression containing search criteria + * @return the count of matching tasks + * @throws IllegalArgumentException if evaluator is null + */ + @Override + public long count(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Counting tasks using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch count query: {}", evaluator.getFullElasticQuery()); + long result = countTasksElastic(evaluator.getFullElasticQuery()); + log.trace("Elasticsearch count result: {}", result); + return result; + } else { + log.trace("Executing MongoDB count query: {}", evaluator.getFullMongoQuery()); + long result = taskService.count(evaluator.getFullMongoQuery()); + log.trace("MongoDB count result: {}", result); + return result; + } + } + + /** + * Checks whether any tasks exist that match the provided query string. + * + * @param queryString the query string to be evaluated and executed + * @return true if at least one matching task exists, false otherwise + */ + @Override + public boolean exists(String queryString) { + log.debug("Checking existence of task with query: {}", queryString); + return exists(evaluateQuery(queryString)); + } + + /** + * Checks whether any tasks exist using a pre-evaluated query expression. + *

+ * The existence check is executed using either Elasticsearch or MongoDB based on the + * evaluator configuration. + *

+ * + * @param evaluator the pre-evaluated query expression containing search criteria + * @return true if at least one matching task exists, false otherwise + * @throws IllegalArgumentException if evaluator is null + */ + @Override + public boolean exists(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Checking existence of tasks using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch exists query: {}", evaluator.getFullElasticQuery()); + boolean result = existsTasksElastic(evaluator.getFullElasticQuery()); + log.trace("Elasticsearch exists result: {}", result); + return result; + } else { + log.trace("Executing MongoDB exists query: {}", evaluator.getFullMongoQuery()); + boolean result = taskService.exists(evaluator.getFullMongoQuery()); + log.trace("MongoDB exists result: {}", result); + return result; + } + } + + /** + * Counts tasks using Elasticsearch. + * + * @param elasticQuery the Elasticsearch query string + * @return the count of matching tasks + */ + private long countTasksElastic(String elasticQuery) { + ElasticTaskSearchRequest taskSearchRequest = new ElasticTaskSearchRequest(); + taskSearchRequest.query = elasticQuery; + return elasticTaskService.count(List.of(taskSearchRequest), userService.getLoggedOrSystem().transformToLoggedUser(), + LocaleContextHolder.getLocale(), false); + } + + /** + * Finds tasks using Elasticsearch with pagination support. + * + * @param elasticQuery the Elasticsearch query string + * @param pageable the pagination information + * @return a page of matching tasks + */ + private Page findTasksElastic(String elasticQuery, Pageable pageable) { + ElasticTaskSearchRequest taskSearchRequest = new ElasticTaskSearchRequest(); + taskSearchRequest.query = elasticQuery; + return elasticTaskService.search(List.of(taskSearchRequest), userService.getLoggedOrSystem().transformToLoggedUser(), + pageable, LocaleContextHolder.getLocale(), false); + } + + /** + * Checks whether any tasks exist using Elasticsearch. + * + * @param elasticQuery the Elasticsearch query string + * @return true if at least one matching task exists, false otherwise + */ + private boolean existsTasksElastic(String elasticQuery) { + return countTasksElastic(elasticQuery) > 0; + } + +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java new file mode 100644 index 00000000000..ba659c3d508 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java @@ -0,0 +1,176 @@ +package com.netgrif.application.engine.pfql.service.userresource; + +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.auth.service.interfaces.IUserService; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.IResourceSearchService; +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; + +import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; + +/** + * Service for searching and querying user resources using PFQL (Process Flow Query Language). + *

+ * This service provides methods to search for users, count users, and check user existence + * based on PFQL query strings or evaluated query objects. It delegates the actual MongoDB + * queries to the {@link IUserService}. + *

+ * + * @see IResourceSearchService + * @see IUserService + * @see QueryLangEvaluator + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class UserSearchService implements IResourceSearchService { + + private final IUserService userService; + + /** + * Returns the resource type handled by this search service. + * + * @return {@link QueryType#USER} indicating this service handles user resources + */ + @Override + public QueryType getQueryResourceType() { + return QueryType.USER; + } + + /** + * Searches for a single user using a PFQL query string. + * + * @param queryString the PFQL query string to search with (e.g., "user: email == 'user@example.com'") + * @return the first user matching the query, or null if no user is found + * @throws IllegalArgumentException if the query string is invalid or evaluates to a non-USER resource type + */ + @Override + public IUser searchOne(String queryString) { + log.debug("Searching for single user with query: {}", queryString); + return searchOne(evaluateQuery(queryString)); + } + + /** + * Searches for a single user using a pre-evaluated query. + * + * @param evaluator the evaluated query object containing the MongoDB query and metadata + * @return the first user matching the query, or null if no user is found + * @throws IllegalArgumentException if the evaluator is null, not configured for single results, + * or has a resource type other than USER + */ + @Override + public IUser searchOne(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorIsSingle(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Searching for single user using MongoDB"); + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + IUser result = userService.searchOne(evaluator.getFullMongoQuery()); + log.trace("MongoDB search one result: {}", result != null ? result.getStringId() : "null"); + return result; + } + + /** + * Searches for all users matching a PFQL query string with pagination support. + * + * @param queryString the PFQL query string to search with (e.g., "users: email like '%@example.com'") + * @return a page of users matching the query + * @throws IllegalArgumentException if the query string is invalid or evaluates to a non-USER resource type + */ + @Override + public Page searchAll(String queryString) { + log.debug("Searching for all users with query: {}", queryString); + return searchAll(evaluateQuery(queryString)); + } + + /** + * Searches for all users matching a pre-evaluated query with pagination support. + * + * @param evaluator the evaluated query object containing the MongoDB query, pagination settings, and metadata + * @return a page of users matching the query with pagination information + * @throws IllegalArgumentException if the evaluator is null, not configured for multiple results, + * or has a resource type other than USER + */ + @Override + public Page searchAll(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorIsMultiple(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Searching for all users using MongoDB with pagination: page={}, size={}", + evaluator.getPageable().getPageNumber(), evaluator.getPageable().getPageSize()); + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Page result = userService.search(evaluator.getFullMongoQuery(), evaluator.getPageable()); + log.trace("MongoDB search all result: page size={}, total elements={}", result.getNumberOfElements(), result.getTotalElements()); + return result; + } + + /** + * Counts the number of users matching a PFQL query string. + * + * @param queryString the PFQL query string to count with (e.g., "users: email like '%@example.com'") + * @return the number of users matching the query + * @throws IllegalArgumentException if the query string is invalid or evaluates to a non-USER resource type + */ + @Override + public long count(String queryString) { + log.debug("Counting users with query: {}", queryString); + return count(evaluateQuery(queryString)); + } + + /** + * Counts the number of users matching a pre-evaluated query. + * + * @param evaluator the evaluated query object containing the MongoDB query and metadata + * @return the number of users matching the query + * @throws IllegalArgumentException if the evaluator is null or has a resource type other than USER + */ + @Override + public long count(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Counting users using MongoDB"); + log.trace("Executing MongoDB count query: {}", evaluator.getFullMongoQuery()); + long result = userService.count(evaluator.getFullMongoQuery()); + log.trace("MongoDB count result: {}", result); + return result; + } + + /** + * Checks if any user exists that matches a PFQL query string. + * + * @param queryString the PFQL query string to check with (e.g., "user: email == 'user@example.com'") + * @return true if at least one user matching the query exists, false otherwise + * @throws IllegalArgumentException if the query string is invalid or evaluates to a non-USER resource type + */ + @Override + public boolean exists(String queryString) { + log.debug("Checking existence of user with query: {}", queryString); + return exists(evaluateQuery(queryString)); + } + + /** + * Checks if any user exists that matches a pre-evaluated query. + * + * @param evaluator the evaluated query object containing the MongoDB query and metadata + * @return true if at least one user matching the query exists, false otherwise + * @throws IllegalArgumentException if the evaluator is null or has a resource type other than USER + */ + @Override + public boolean exists(QueryLangEvaluator evaluator) { + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Checking existence of users using MongoDB"); + log.trace("Executing MongoDB exists query: {}", evaluator.getFullMongoQuery()); + boolean result = userService.exists(evaluator.getFullMongoQuery()); + log.trace("MongoDB exists result: {}", result); + return result; + } +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/utils/QueryLangTreeNode.java b/src/main/java/com/netgrif/application/engine/pfql/service/utils/QueryLangTreeNode.java new file mode 100644 index 00000000000..465073d2186 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/utils/QueryLangTreeNode.java @@ -0,0 +1,43 @@ +package com.netgrif.application.engine.pfql.service.utils; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * https://stackoverflow.com/a/8948691 + */ +public class QueryLangTreeNode { + final String name; + final List children; + + public QueryLangTreeNode(String name, List children) { + this.name = name; + this.children = children; + } + + public QueryLangTreeNode(String name) { + this.name = name; + this.children = new ArrayList<>(); + } + + public String toString() { + StringBuilder buffer = new StringBuilder(50); + print(buffer, "", ""); + return buffer.toString(); + } + + private void print(StringBuilder buffer, String prefix, String childrenPrefix) { + buffer.append(prefix); + buffer.append(name); + buffer.append('\n'); + for (Iterator it = children.iterator(); it.hasNext();) { + QueryLangTreeNode next = it.next(); + if (it.hasNext()) { + next.print(buffer, childrenPrefix + "├── ", childrenPrefix + "│ "); + } else { + next.print(buffer, childrenPrefix + "└── ", childrenPrefix + " "); + } + } + } +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java new file mode 100644 index 00000000000..a9af2f6a57f --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java @@ -0,0 +1,449 @@ +package com.netgrif.application.engine.pfql.service.utils; + +import com.netgrif.application.engine.petrinet.domain.QPetriNet; +import com.netgrif.application.engine.petrinet.domain.version.QVersion; +import com.netgrif.application.engine.petrinet.domain.version.Version; +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangBaseListener; +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangLexer; +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangParser; +import com.netgrif.application.engine.pfql.domain.enums.ComparisonType; +import com.netgrif.application.engine.pfql.service.QueryLangErrorListener; +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; +import com.netgrif.application.engine.pfql.service.QueryLangExplainEvaluator; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.StringPath; +import lombok.extern.slf4j.Slf4j; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.bson.types.ObjectId; +import org.bson.types.QObjectId; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +public class SearchUtils { + + public static final Map> comparisonOperators = Map.of( + ComparisonType.ID, List.of(QueryLangParser.EQ, QueryLangParser.NEQ, QueryLangParser.IN), + ComparisonType.STRING, List.of(QueryLangParser.EQ, QueryLangParser.NEQ, QueryLangParser.CONTAINS, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.NUMBER, List.of(QueryLangParser.EQ, QueryLangParser.NEQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.DATE, List.of(QueryLangParser.EQ, QueryLangParser.NEQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.DATETIME, List.of(QueryLangParser.EQ, QueryLangParser.NEQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.BOOLEAN, List.of(QueryLangParser.EQ, QueryLangParser.NEQ) + ); + + public static final Map processAttrToSortPropMapping = Map.of( + "id", "id", + "identifier", "identifier", + "version", "version", + "title", "title.defaultValue", + "creationdate", "creationDate" + ); + + public static final Map caseAttrToSortPropMapping = Map.of( + "id", "id", + "processidentifier", "processIdentifier", + "processid", "petriNetObjectId", + "title", "title", + "creationdate", "creationDate", + "author", "author.id" + ); + + public static final Map caseAttrToSortPropElasticMapping = Map.of( + "id", "stringId.keyword", + "processidentifier", "processIdentifier.keyword", + "processid", "processId.keyword", + "title", "title.keyword", + "creationdate", "creationDateSortable", + "author", "author.keyword" + ); + + public static final Map taskAttrToSortPropMapping = Map.of( + "id", "id", + "transitionid", "transitionId", + "title", "title.defaultValue", + "state", "state", + "userid", "userId", + "caseid", "caseId", + "processid", "processId", + "lastassign", "lastAssigned", + "lastfinish", "lastFinished" + ); + + public static final Map userAttrToSortPropMapping = Map.of( + "id", "id", + "name", "name", + "surname", "surname", + "email", "email" + ); + + public static final String LEFT_OPEN_ENDPOINT = "("; + public static final String RIGHT_OPEN_ENDPOINT = ")"; + + public static String toDateString(LocalDate localDate) { + return localDate.format(DateTimeFormatter.ISO_LOCAL_DATE); + } + + public static String toDateString(LocalDateTime localDateTime) { + return localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE); + } + + public static String toDateTimeString(LocalDate localDate) { + return localDate.atTime(12, 0, 0).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } + + public static String toDateTimeString(LocalDateTime localDateTime) { + return localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } + + public static LocalDateTime toDateTime(String dateTimeString) { + try { + return LocalDateTime.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } catch (DateTimeParseException ignored) { + try { + return LocalDate.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE).atTime(12, 0, 0); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("Invalid date/datetime format", e); + } + } + } + + public static LocalDate toDate(String dateString) { + try { + return LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE); + } catch (DateTimeParseException ignored) { + try { + return LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME).toLocalDate(); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("Invalid date/datetime format", e); + } + } + } + + public static QueryLangExplainEvaluator explainQueryInternal(ParseTreeWalker walker, QueryLangParser.QueryContext query, QueryLangErrorListener errorListener) { + QueryLangExplainEvaluator evaluator = new QueryLangExplainEvaluator(); + walker.walk(evaluator, query); + + if (!errorListener.getErrorMessages().isEmpty()) { + throw new IllegalArgumentException("\n" + evaluator.explain() + "\n" + String.join("\n", errorListener.getErrorMessages())); + } + + return evaluator; + } + + private static QueryLangBaseListener evaluateQueryInternal(String input, boolean onlyExplain) { + if (input == null || input.isEmpty()) { + throw new IllegalArgumentException("Query cannot be empty."); + } + + QueryLangLexer lexer = new QueryLangLexer(CharStreams.fromString(input)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + QueryLangParser parser = new QueryLangParser(tokens); + QueryLangErrorListener errorListener = new QueryLangErrorListener(); + parser.removeErrorListeners(); + parser.addErrorListener(errorListener); + + QueryLangParser.QueryContext query = parser.query(); + ParseTreeWalker walker = new ParseTreeWalker(); + + if (onlyExplain || !errorListener.getErrorMessages().isEmpty()) { + return explainQueryInternal(walker, query, errorListener); + } + + QueryLangEvaluator evaluator = new QueryLangEvaluator(); + walker.walk(evaluator, query); + + return evaluator; + } + + public static QueryLangEvaluator evaluateQuery(String input) { + return (QueryLangEvaluator) evaluateQueryInternal(input, false); + } + + public static String explainQuery(String input) { + QueryLangExplainEvaluator evaluator = (QueryLangExplainEvaluator) evaluateQueryInternal(input, true); + return evaluator.explain(); + } + + public static String getStringValue(String queryLangString) { + return queryLangString.replace("'", ""); + } + + public static ObjectId getObjectIdValue(String queryLangString) { + String objectId = getStringValue(queryLangString); + if (ObjectId.isValid(objectId)) { + return new ObjectId(objectId); + } + + throw new IllegalArgumentException("Invalid objectId: " + objectId); + } + + public static void checkOp(ComparisonType type, Token op) { + if (!comparisonOperators.get(type).contains(op.getType())) { + throw new IllegalArgumentException("Operator " + op.getText() + " is not applicable for type " + type.toString()); + } + } + + public static Predicate buildObjectIdPredicate(QObjectId qObjectId, int op, ObjectId objectId, boolean not) { + if (op != QueryLangParser.EQ && op != QueryLangParser.NEQ) { + throw new UnsupportedOperationException("Operator is not available for id comparison"); + } + if (op == QueryLangParser.NEQ) { + not = !not; + } + + Predicate predicate = qObjectId.eq(objectId); + if (not) { + predicate = predicate.not(); + } + return predicate; + } + + public static Predicate buildObjectIdPredicateInList(QObjectId qObjectId, List values, boolean not) { + Predicate predicate = qObjectId.in(values); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildStringPredicate(StringPath stringPath, int op, String string, boolean not) { + Predicate predicate = null; + switch (op) { + case QueryLangParser.EQ: + predicate = stringPath.eq(string); + break; + case QueryLangParser.NEQ: + predicate = stringPath.ne(string); + break; + case QueryLangParser.CONTAINS: + predicate = stringPath.contains(string); + break; + case QueryLangParser.LT: + predicate = stringPath.lt(string); + break; + case QueryLangParser.LTE: + predicate = stringPath.loe(string); + break; + case QueryLangParser.GT: + predicate = stringPath.gt(string); + break; + case QueryLangParser.GTE: + predicate = stringPath.goe(string); + break; + } + + if (predicate == null) { + throw new UnsupportedOperationException("Operator is not available for string comparison"); + } + + if (not) { + predicate = predicate.not(); + } + return predicate; + } + + public static Predicate buildStringPredicateInList(StringPath stringPath, List values, boolean not) { + Predicate predicate = stringPath.in(values); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildStringPredicateInRange(StringPath stringPath, String leftValue, boolean leftEndpointOpen, String rightValue, boolean rightEndpointOpen, boolean not) { + BooleanExpression leftExpression = leftEndpointOpen ? stringPath.gt(leftValue) : stringPath.goe(leftValue); + BooleanExpression rightExpression = rightEndpointOpen ? stringPath.lt(rightValue) : stringPath.loe(rightValue); + Predicate predicate = leftExpression.and(rightExpression); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildVersionPredicate(int op, String versionString, boolean not) { + String[] versionNumber = versionString.split("\\."); + if (versionNumber.length != 3) { + throw new IllegalArgumentException("Version must be in format major.minor.patch: " + versionString); + } + long major, minor, patch; + try { + major = Long.parseLong(versionNumber[0]); + minor = Long.parseLong(versionNumber[1]); + patch = Long.parseLong(versionNumber[2]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid version number format: " + versionString, e); + } + + QVersion qVersion = QPetriNet.petriNet.version; + + Predicate predicate = null; + switch (op) { + case QueryLangParser.EQ: + predicate = qVersion.eq(new Version(major, minor, patch)); + break; + case QueryLangParser.NEQ: + predicate = qVersion.ne(new Version(major, minor, patch)); + break; + case QueryLangParser.GT: + predicate = qVersion.major.gt(major) + .or(qVersion.major.eq(major).and(qVersion.minor.gt(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.gt(patch)))); + break; + case QueryLangParser.GTE: + predicate = qVersion.major.goe(major) + .or(qVersion.major.eq(major).and(qVersion.minor.goe(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.goe(patch)))); + break; + case QueryLangParser.LT: + predicate = qVersion.major.lt(major) + .or(qVersion.major.eq(major).and(qVersion.minor.lt(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))); + break; + case QueryLangParser.LTE: + predicate = qVersion.major.loe(major) + .or(qVersion.major.eq(major).and(qVersion.minor.loe(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.loe(patch)))); + break; + } + + if (predicate == null) { + throw new UnsupportedOperationException("Operator is not available for version comparison"); + } + + if (not) { + predicate = predicate.not(); + } + return predicate; + } + + public static Predicate buildVersionPredicateInList(List values, boolean not) { + List versions = values.stream().map(stringVersion -> { + String[] versionNumber = stringVersion.split("\\."); + long major = Long.parseLong(versionNumber[0]); + long minor = Long.parseLong(versionNumber[1]); + long patch = Long.parseLong(versionNumber[2]); + + return new Version(major, minor, patch); + }).collect(Collectors.toList()); + + Predicate predicate = QPetriNet.petriNet.version.in(versions); + return not ? predicate.not() : predicate; + } + + public static Predicate buildVersionPredicateInRange(String leftValue, boolean leftEndpointOpen, String rightValue, boolean rightEndpointOpen, boolean not) { + Predicate leftExpression = buildVersionPredicate(leftEndpointOpen ? QueryLangParser.GT : QueryLangParser.GTE, leftValue, false); + Predicate rightExpression = buildVersionPredicate(rightEndpointOpen ? QueryLangParser.LT : QueryLangParser.LTE, rightValue, false); + + BooleanBuilder predicate = new BooleanBuilder(); + predicate.and(leftExpression); + predicate.and(rightExpression); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildDateTimePredicate(DateTimePath dateTimePath, int op, LocalDateTime localDateTime, boolean not) { + Predicate predicate = null; + switch (op) { + case QueryLangParser.EQ: + predicate = dateTimePath.eq(localDateTime); + break; + case QueryLangParser.NEQ: + predicate = dateTimePath.ne(localDateTime); + break; + case QueryLangParser.LT: + predicate = dateTimePath.lt(localDateTime); + break; + case QueryLangParser.LTE: + predicate = dateTimePath.loe(localDateTime); + break; + case QueryLangParser.GT: + predicate = dateTimePath.gt(localDateTime); + break; + case QueryLangParser.GTE: + predicate = dateTimePath.goe(localDateTime); + break; + } + + if (predicate == null) { + throw new UnsupportedOperationException("Operator is not available for date/datetime comparison"); + } + + if (not) { + predicate = predicate.not(); + } + return predicate; + } + + public static Predicate buildDateTimePredicateInList(DateTimePath dateTimePath, List values, boolean not) { + List dateTimes = values.stream().map(SearchUtils::toDateTime).collect(Collectors.toList()); + Predicate predicate = dateTimePath.in(dateTimes); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildDateTimePredicateInRange(DateTimePath dateTimePath, LocalDateTime leftValue, boolean leftEndpointOpen, LocalDateTime rightValue, boolean rightEndpointOpen, boolean not) { + BooleanExpression leftExpression = leftEndpointOpen ? dateTimePath.gt(leftValue) : dateTimePath.goe(leftValue); + BooleanExpression rightExpression = rightEndpointOpen ? dateTimePath.lt(rightValue) : dateTimePath.loe(rightValue); + Predicate predicate = leftExpression.and(rightExpression); + + return not ? predicate.not() : predicate; + } + + public static String buildElasticQuery(String attribute, int op, String value, boolean not) { + String query = null; + switch (op) { + case QueryLangParser.EQ: + case QueryLangParser.IN: + query = attribute + ":" + value; + break; + case QueryLangParser.NEQ: + query = attribute + ":" + value; + not = !not; + break; + case QueryLangParser.LT: + query = attribute + ":<" + value; + break; + case QueryLangParser.LTE: + query = attribute + ":<=" + value; + break; + case QueryLangParser.GT: + query = attribute + ":>" + value; + break; + case QueryLangParser.GTE: + query = attribute + ":>=" + value; + break; + case QueryLangParser.CONTAINS: + query = attribute + ":*" + value + "*"; + break; + } + + if (query == null) { + throw new UnsupportedOperationException("Operator is not available for elastic comparison"); + } + + if (not) { + query = "NOT " + query; + } + return query; + } + + public static String buildElasticQueryInList(String attribute, List values, boolean not) { + String valuesQuery = "(" + String.join(" OR ", values) + ")"; + return buildElasticQuery(attribute, QueryLangParser.IN, valuesQuery, not); + } + + public static String buildElasticQueryInRange(String attribute, String leftValue, boolean leftEndpointOpen, String rightValue, boolean rightEndpointOpen, boolean not) { + String query = "(" + + buildElasticQuery(attribute, leftEndpointOpen ? QueryLangParser.GT : QueryLangParser.GTE, leftValue, false) + + " AND " + + buildElasticQuery(attribute, rightEndpointOpen ? QueryLangParser.LT : QueryLangParser.LTE, rightValue, false) + + ")"; + return not ? "NOT " + query : query; + } +} diff --git a/src/main/java/com/netgrif/application/engine/files/throwable/BadRequestException.java b/src/main/java/com/netgrif/application/engine/utils/throwable/BadRequestException.java similarity index 54% rename from src/main/java/com/netgrif/application/engine/files/throwable/BadRequestException.java rename to src/main/java/com/netgrif/application/engine/utils/throwable/BadRequestException.java index 090e7456d2b..2c2181f8578 100644 --- a/src/main/java/com/netgrif/application/engine/files/throwable/BadRequestException.java +++ b/src/main/java/com/netgrif/application/engine/utils/throwable/BadRequestException.java @@ -1,5 +1,9 @@ -package com.netgrif.application.engine.files.throwable; +package com.netgrif.application.engine.utils.throwable; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.BAD_REQUEST) public class BadRequestException extends RuntimeException { public BadRequestException(String message) { super(message); diff --git a/src/main/java/com/netgrif/application/engine/utils/throwable/InternalServerErrorException.java b/src/main/java/com/netgrif/application/engine/utils/throwable/InternalServerErrorException.java new file mode 100644 index 00000000000..a7c012f6846 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/utils/throwable/InternalServerErrorException.java @@ -0,0 +1,11 @@ +package com.netgrif.application.engine.utils.throwable; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) +public class InternalServerErrorException extends RuntimeException { + public InternalServerErrorException(String message) { + super(message); + } +} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/Case.java b/src/main/java/com/netgrif/application/engine/workflow/domain/Case.java index 039f6db8b7d..7b01087cc04 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/Case.java +++ b/src/main/java/com/netgrif/application/engine/workflow/domain/Case.java @@ -104,7 +104,7 @@ public class Case implements Serializable { @Getter @Setter @Transient - private List immediateData; + private List> immediateData; @Getter @Setter @@ -251,9 +251,6 @@ public void populateDataSet(IInitValueExpressionEvaluator initValueExpressionEva if (field instanceof FieldWithAllowedNets) { this.dataSet.get(key).setAllowedNets(((FieldWithAllowedNets) field).getAllowedNets()); } - if (field instanceof FilterField) { - this.dataSet.get(key).setFilterMetadata(((FilterField) field).getFilterMetadata()); - } if (field instanceof MapOptionsField && ((MapOptionsField) field).isDynamic()) { dynamicOptionsFields.add((MapOptionsField) field); } @@ -354,9 +351,16 @@ public void resolveViewUserRefs() { public void resolveViewUsers() { getViewUsers(); this.viewUsers.clear(); + this.negativeViewUsers.clear(); this.users.forEach((user, perms) -> { - if (perms.containsKey(RolePermission.VIEW.getValue()) && perms.get(RolePermission.VIEW.getValue())) { + if (!perms.containsKey(RolePermission.VIEW.getValue())) { + return; + } + boolean viewPermission = perms.get(RolePermission.VIEW.getValue()); + if(viewPermission){ viewUsers.add(user); + } else { + negativeViewUsers.add(user); } }); } diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/DataField.java b/src/main/java/com/netgrif/application/engine/workflow/domain/DataField.java index 5734a9b3e93..5a122c2c2b3 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/DataField.java +++ b/src/main/java/com/netgrif/application/engine/workflow/domain/DataField.java @@ -43,9 +43,6 @@ public class DataField implements Referencable, Serializable { @Getter private List validations; - @Getter - private Map filterMetadata; - @Getter @Setter @JsonIgnore @@ -115,11 +112,6 @@ public void setAllowedNets(List allowedNets) { update(); } - public void setFilterMetadata(Map filterMetadata) { - this.filterMetadata = filterMetadata; - update(); - } - public void setOptions(Map options) { setOptions(options, true); } diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/Task.java b/src/main/java/com/netgrif/application/engine/workflow/domain/Task.java index 75d7e999c40..795059a595a 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/Task.java +++ b/src/main/java/com/netgrif/application/engine/workflow/domain/Task.java @@ -329,9 +329,16 @@ public void resolveViewUserRefs() { public void resolveViewUsers() { getViewUsers(); this.viewUsers.clear(); + this.negativeViewUsers.clear(); this.users.forEach((role, perms) -> { - if (perms.containsKey(RolePermission.VIEW.getValue()) && perms.get(RolePermission.VIEW.getValue())) { + if (!perms.containsKey(RolePermission.VIEW.getValue())) { + return; + } + boolean viewPermission = perms.get(RolePermission.VIEW.getValue()); + if (viewPermission) { viewUsers.add(role); + } else { + negativeViewUsers.add(role); } }); } diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/eventoutcomes/petrinetoutcomes/ImportPetriNetEventOutcome.java b/src/main/java/com/netgrif/application/engine/workflow/domain/eventoutcomes/petrinetoutcomes/ImportPetriNetEventOutcome.java index a5e94680e3b..309aa673de8 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/eventoutcomes/petrinetoutcomes/ImportPetriNetEventOutcome.java +++ b/src/main/java/com/netgrif/application/engine/workflow/domain/eventoutcomes/petrinetoutcomes/ImportPetriNetEventOutcome.java @@ -10,6 +10,8 @@ @Data public class ImportPetriNetEventOutcome extends PetriNetEventOutcome { + private String xmlContent; + public ImportPetriNetEventOutcome() { } diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuItemBody.java b/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuItemBody.java deleted file mode 100644 index 5f59e15ccd7..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuItemBody.java +++ /dev/null @@ -1,268 +0,0 @@ -package com.netgrif.application.engine.workflow.domain.menu; - -import com.netgrif.application.engine.petrinet.domain.I18nString; -import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; -import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.ActionDelegate; -import com.netgrif.application.engine.workflow.domain.Case; -import lombok.Data; -import lombok.NoArgsConstructor; - -import javax.annotation.Nullable; -import java.text.Normalizer; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * Class, that holds configurable attributes of menu item. In case of attribute addition, please update also - * {@link MenuItemBody#toDataSet(String, String, boolean)} method. - */ -@Data -@NoArgsConstructor -public class MenuItemBody { - - // generic attributes - private I18nString menuName; - private I18nString tabName; - private String menuIcon = "filter_none"; - private String tabIcon; - private String uri; - private String identifier; - private Case filter; - private Map allowedRoles; - private Map bannedRoles; - private boolean useTabIcon = true; - private boolean useCustomView = false; - private String customViewSelector; - - // case view attributes - private String caseViewSearchType = "fulltext_advanced"; - private String createCaseButtonTitle; - private String createCaseButtonIcon = "add"; - private boolean caseRequireTitleInCreation = true; - private boolean showCreateCaseButton = true; - private String bannedNetsInCreation; - private boolean caseShowMoreMenu = false; - private boolean caseAllowHeaderTableMode = true; - private List caseHeadersMode = new ArrayList<>(List.of("sort", "edit", "search")); - private String caseHeadersDefaultMode = "sort"; - private List caseDefaultHeaders; - private boolean caseIsHeaderModeChangeable = true; - private boolean caseUseDefaultHeaders = true; - private boolean caseAllowExport = false; - - // task view attributes - private Case additionalFilter; - private boolean mergeFilters = true; - private String taskViewSearchType = "fulltext_advanced"; - private List taskHeadersMode = new ArrayList<>(List.of("sort", "edit")); - private String taskHeadersDefaultMode = "sort"; - private boolean taskIsHeaderModeChangeable = true; - private boolean taskAllowHeaderTableMode = true; - private boolean taskUseDefaultHeaders = true; - private List taskDefaultHeaders; - private boolean taskShowMoreMenu = true; - - public MenuItemBody(I18nString name, String icon) { - this.menuName = name; - this.tabName = name; - this.menuIcon = icon; - this.tabIcon = icon; - } - - public MenuItemBody(I18nString menuName, I18nString tabName, String menuIcon, String tabIcon) { - this.menuName = menuName; - this.tabName = tabName; - this.menuIcon = menuIcon; - this.tabIcon = tabIcon; - } - - public MenuItemBody(String uri, String identifier, I18nString name, String icon) { - this.uri = uri; - this.identifier = identifier; - this.menuName = name; - this.tabName = name; - this.menuIcon = icon; - this.tabIcon = icon; - } - - public MenuItemBody(String uri, String identifier, I18nString menuName, I18nString tabName, String menuIcon, String tabIcon) { - this.uri = uri; - this.identifier = identifier; - this.menuName = menuName; - this.tabName = tabName; - this.menuIcon = menuIcon; - this.tabIcon = tabIcon; - } - - public MenuItemBody(String uri, String identifier, String name, String icon) { - this.uri = uri; - this.identifier = identifier; - this.menuName = new I18nString(name); - this.tabName = new I18nString(name); - this.menuIcon = icon; - this.tabIcon = icon; - } - - public MenuItemBody(String uri, String identifier, String menuName, String tabName, String menuIcon, String tabIcon) { - this.uri = uri; - this.identifier = identifier; - this.menuName = new I18nString(menuName); - this.tabName = new I18nString(tabName); - this.menuIcon = menuIcon; - this.tabIcon = tabIcon; - } - - private static void putDataSetEntry(Map> dataSet, MenuItemConstants fieldId, FieldType fieldType, - @Nullable Object fieldValue) { - Map fieldMap = new LinkedHashMap<>(); - fieldMap.put("type", fieldType.getName()); - fieldMap.put("value", fieldValue); - dataSet.put(fieldId.getAttributeId(), fieldMap); - } - - private static String sanitize(String input) { - if (input == null) { - return null; - } - return Normalizer.normalize(input.trim(), Normalizer.Form.NFD) - .replaceAll("[^\\p{ASCII}]", "") - .replaceAll("\\p{InCombiningDiacriticalMarks}+", "") - .replaceAll("[\\W-]+", "-") - .toLowerCase(); - } - - public String getIdentifier() { - return sanitize(this.identifier); - } - - public void setMenuName(I18nString name) { - this.menuName = name; - } - - public void setMenuName(String name) { - this.menuName = new I18nString(name); - } - - public void setTabName(I18nString name) { - this.tabName = name; - } - - public void setTabName(String name) { - this.tabName = new I18nString(name); - } - - /** - * Transforms attributes into dataSet for {@link ActionDelegate#setData} - * - * @return created dataSet from attributes - */ - public Map> toDataSet() { - return toDataSet(null, null, true); - } - - /** - * Transforms attributes into dataSet for {@link ActionDelegate#setData} - * - * @param parentId id of parent menu item instance - * @param nodePath uri, that represents the menu item (f.e.: "/myItem1/myItem2") - * @return created dataSet from attributes - */ - public Map> toDataSet(String parentId, String nodePath) { - return toDataSet(parentId, nodePath, false); - } - - private Map> toDataSet(String parentId, String nodePath, boolean ignoreParentId) { - Map> dataSet = new LinkedHashMap<>(); - - // GENERIC - ArrayList filterIdCaseRefValue = new ArrayList<>(); - if (this.filter != null) { - filterIdCaseRefValue.add(this.filter.getStringId()); - } - ArrayList parentIdCaseRef = new ArrayList<>(); - if (parentId != null) { - parentIdCaseRef.add(parentId); - } - - if (nodePath != null) { - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH, FieldType.TEXT, nodePath); - } - if (!ignoreParentId) { - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID, FieldType.CASE_REF, parentIdCaseRef); - } - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_NAME, FieldType.I18N, this.menuName); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_ICON, FieldType.TEXT, this.menuIcon); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TAB_NAME, FieldType.I18N, this.tabName); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TAB_ICON, FieldType.TEXT, this.tabIcon); - if (this.identifier != null) { - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_IDENTIFIER, FieldType.TEXT, this.getIdentifier()); - } - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_FILTER_CASE, FieldType.CASE_REF, filterIdCaseRefValue); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_USE_TAB_ICON, FieldType.BOOLEAN, this.useTabIcon); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_USE_CUSTOM_VIEW, FieldType.BOOLEAN, - this.useCustomView); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CUSTOM_VIEW_SELECTOR, FieldType.TEXT, - this.customViewSelector); - - // CASE - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP, - this.caseViewSearchType); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CREATE_CASE_BUTTON_TITLE, FieldType.TEXT, - this.createCaseButtonTitle); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CREATE_CASE_BUTTON_ICON, FieldType.TEXT, - this.createCaseButtonIcon); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_REQUIRE_TITLE_IN_CREATION, FieldType.BOOLEAN, - this.caseRequireTitleInCreation); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_SHOW_CREATE_CASE_BUTTON, FieldType.BOOLEAN, - this.showCreateCaseButton); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_BANNED_NETS_IN_CREATION, FieldType.TEXT, - this.bannedNetsInCreation); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_SHOW_MORE_MENU, FieldType.BOOLEAN, - this.caseShowMoreMenu); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_ALLOW_HEADER_TABLE_MODE, FieldType.BOOLEAN, - this.caseAllowHeaderTableMode); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_HEADERS_MODE, FieldType.MULTICHOICE_MAP, - this.caseHeadersMode == null ? new ArrayList<>() : this.caseHeadersMode); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP, - this.caseHeadersDefaultMode); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_DEFAULT_HEADERS, FieldType.TEXT, - this.caseDefaultHeaders != null ? String.join(",", this.caseDefaultHeaders) : null); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN, - this.caseIsHeaderModeChangeable); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_USE_CASE_DEFAULT_HEADERS, FieldType.BOOLEAN, - this.caseUseDefaultHeaders); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_ALLOW_EXPORT, FieldType.BOOLEAN, - this.caseAllowExport); - - // TASK - ArrayList additionalFilterIdCaseRefValue = new ArrayList<>(); - if (this.additionalFilter != null) { - additionalFilterIdCaseRefValue.add(this.additionalFilter.getStringId()); - } - - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_ADDITIONAL_FILTER_CASE, FieldType.CASE_REF, - additionalFilterIdCaseRefValue); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_MERGE_FILTERS, FieldType.BOOLEAN, - this.mergeFilters); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_VIEW_SEARCH_TYPE, FieldType.ENUMERATION_MAP, - this.taskViewSearchType); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_HEADERS_MODE, FieldType.MULTICHOICE_MAP, - this.taskHeadersMode == null ? new ArrayList<>() : this.taskHeadersMode); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_HEADERS_DEFAULT_MODE, FieldType.ENUMERATION_MAP, - this.taskHeadersDefaultMode); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_IS_HEADER_MODE_CHANGEABLE, FieldType.BOOLEAN, - this.taskIsHeaderModeChangeable); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_ALLOW_HEADER_TABLE_MODE, FieldType.BOOLEAN, - this.taskAllowHeaderTableMode); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_USE_TASK_DEFAULT_HEADERS, FieldType.BOOLEAN, - this.taskUseDefaultHeaders); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_DEFAULT_HEADERS, FieldType.TEXT, - this.taskDefaultHeaders != null ? String.join(",", this.taskDefaultHeaders) : null); - putDataSetEntry(dataSet, MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_SHOW_MORE_MENU, FieldType.BOOLEAN, - this.taskShowMoreMenu); - - return dataSet; - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuItemConstants.java b/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuItemConstants.java deleted file mode 100644 index b449bcc809a..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/menu/MenuItemConstants.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.netgrif.application.engine.workflow.domain.menu; - -import lombok.Getter; - -/** - * Enumeration for menu items. It contains any constants needed in application. - */ -public enum MenuItemConstants { - - // FIELDS - PREFERENCE_ITEM_FIELD_NEW_FILTER_ID("new_filter_id"), - PREFERENCE_ITEM_FIELD_FILTER_CASE("filter_case"), - PREFERENCE_ITEM_FIELD_PARENT_ID("parentId"), - PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS("childItemIds"), - PREFERENCE_ITEM_FIELD_HAS_CHILDREN("hasChildren"), - PREFERENCE_ITEM_FIELD_CASE_DEFAULT_HEADERS("case_default_headers"), - PREFERENCE_ITEM_FIELD_TASK_DEFAULT_HEADERS("task_default_headers"), - PREFERENCE_ITEM_FIELD_IDENTIFIER("menu_item_identifier"), - PREFERENCE_ITEM_FIELD_APPEND_MENU_ITEM("append_menu_item_stringId"), - PREFERENCE_ITEM_FIELD_ALLOWED_ROLES("allowed_roles"), - PREFERENCE_ITEM_FIELD_BANNED_ROLES("banned_roles"), - PREFERENCE_ITEM_FIELD_MENU_NAME("menu_name"), - PREFERENCE_ITEM_FIELD_MENU_ICON("menu_icon"), - PREFERENCE_ITEM_FIELD_TAB_NAME("tab_name"), - PREFERENCE_ITEM_FIELD_USE_TAB_ICON("use_tab_icon"), - PREFERENCE_ITEM_FIELD_TAB_ICON("tab_icon"), - PREFERENCE_ITEM_FIELD_NODE_PATH("nodePath"), - PREFERENCE_ITEM_FIELD_NODE_NAME("nodeName"), - PREFERENCE_ITEM_FIELD_DUPLICATE_TITLE("duplicate_new_title"), - PREFERENCE_ITEM_FIELD_DUPLICATE_IDENTIFIER("duplicate_view_identifier"), - PREFERENCE_ITEM_FIELD_DUPLICATE_RESET_CHILD_ITEM_IDS("duplicate_reset_childItemIds"), - PREFERENCE_ITEM_FIELD_REQUIRE_TITLE_IN_CREATION("case_require_title_in_creation"), - PREFERENCE_ITEM_FIELD_USE_CUSTOM_VIEW("use_custom_view"), - PREFERENCE_ITEM_FIELD_CUSTOM_VIEW_SELECTOR("custom_view_selector"), - PREFERENCE_ITEM_FIELD_CASE_VIEW_SEARCH_TYPE("case_view_search_type"), - PREFERENCE_ITEM_FIELD_CREATE_CASE_BUTTON_TITLE("create_case_button_title"), - PREFERENCE_ITEM_FIELD_CREATE_CASE_BUTTON_ICON("create_case_button_icon"), - PREFERENCE_ITEM_FIELD_BANNED_NETS_IN_CREATION("case_banned_nets_in_creation"), - PREFERENCE_ITEM_FIELD_SHOW_CREATE_CASE_BUTTON("show_create_case_button"), - PREFERENCE_ITEM_FIELD_CASE_SHOW_MORE_MENU("case_show_more_menu"), - PREFERENCE_ITEM_FIELD_CASE_ALLOW_HEADER_TABLE_MODE("case_allow_header_table_mode"), - PREFERENCE_ITEM_FIELD_CASE_HEADERS_MODE("case_headers_mode"), - PREFERENCE_ITEM_FIELD_CASE_HEADERS_DEFAULT_MODE("case_headers_default_mode"), - PREFERENCE_ITEM_FIELD_CASE_IS_HEADER_MODE_CHANGEABLE("case_is_header_mode_changeable"), - PREFERENCE_ITEM_FIELD_USE_CASE_DEFAULT_HEADERS("case_is_header_mode_changeable"), - PREFERENCE_ITEM_FIELD_CASE_ALLOW_EXPORT("case_allow_export"), - PREFERENCE_ITEM_FIELD_ADDITIONAL_FILTER_CASE("additional_filter_case"), - PREFERENCE_ITEM_FIELD_MERGE_FILTERS("merge_filters"), - PREFERENCE_ITEM_FIELD_TASK_VIEW_SEARCH_TYPE("task_view_search_type"), - PREFERENCE_ITEM_FIELD_TASK_HEADERS_MODE("task_headers_mode"), - PREFERENCE_ITEM_FIELD_TASK_HEADERS_DEFAULT_MODE("task_headers_default_mode"), - PREFERENCE_ITEM_FIELD_TASK_IS_HEADER_MODE_CHANGEABLE("task_is_header_mode_changeable"), - PREFERENCE_ITEM_FIELD_TASK_ALLOW_HEADER_TABLE_MODE("task_allow_header_table_mode"), - PREFERENCE_ITEM_FIELD_USE_TASK_DEFAULT_HEADERS("use_task_default_headers"), - PREFERENCE_ITEM_FIELD_TASK_SHOW_MORE_MENU("task_show_more_menu"), - - // TRANSITIONS - PREFERENCE_ITEM_SETTINGS_TRANS_ID("item_settings"), - PREFERENCE_ITEM_FIELD_INIT_TRANS_ID("initialize"); - @Getter - private final String attributeId; - - MenuItemConstants(String attributeId) { - this.attributeId = attributeId; - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/DataService.java b/src/main/java/com/netgrif/application/engine/workflow/service/DataService.java index 548ee926a2b..77a6cffdf34 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/DataService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/DataService.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.netgrif.application.engine.auth.domain.IUser; import com.netgrif.application.engine.auth.service.interfaces.IUserService; @@ -291,12 +292,6 @@ public SetDataEventOutcome setData(Task task, ObjectNode values, Map filterMetadata = parseFilterMetadataValue((ObjectNode) entry.getValue(), fieldType); - if (filterMetadata != null) { - dataField.setFilterMetadata(filterMetadata); - changedField.addAttribute("filterMetadata", filterMetadata); - modified = true; - } Map options = parseOptionsNode(entry.getValue(), fieldType); if (options != null) { setDataFieldOptions(options, dataField, changedField, fieldType); @@ -1010,7 +1005,7 @@ private List parseListLongValues(ObjectNode node) { private List parseAllowedNetsValue(JsonNode jsonNode) { ObjectNode node = (ObjectNode) jsonNode; String fieldType = getFieldTypeFromNode(node); - if (Objects.equals(fieldType, FieldType.CASE_REF.getName()) || Objects.equals(fieldType, FieldType.FILTER.getName())) { + if (Objects.equals(fieldType, FieldType.CASE_REF.getName())) { return parseListStringAllowedNets(node); } return null; @@ -1021,10 +1016,11 @@ private List parseListStringAllowedNets(ObjectNode node) { } private List parseListString(ObjectNode node, String attributeKey) { - ArrayNode arrayNode = (ArrayNode) node.get(attributeKey); - if (arrayNode == null) { + Object objectNode = node.get(attributeKey); + if (objectNode == null || objectNode instanceof NullNode) { return null; } + ArrayNode arrayNode = (ArrayNode) objectNode; ArrayList list = new ArrayList<>(); arrayNode.forEach(string -> list.add(string.asText())); return list; @@ -1053,19 +1049,6 @@ private String getFieldTypeFromNode(ObjectNode node) { return node.get("type").asText(); } - private Map parseFilterMetadataValue(ObjectNode node, String fieldType) { - if (Objects.equals(fieldType, FieldType.FILTER.getName())) { - JsonNode filterMetadata = node.get("filterMetadata"); - if (filterMetadata == null) { - return null; - } - ObjectMapper mapper = new ObjectMapper(); - return mapper.convertValue(filterMetadata, new TypeReference>() { - }); - } - return null; - } - private I18nString parseI18nStringValues(ObjectNode node) { String defaultValue = node.get("defaultValue") != null ? node.get("defaultValue").asText() : ""; Map translations = new HashMap<>(); diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java b/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java deleted file mode 100644 index 75446ea8ed2..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java +++ /dev/null @@ -1,380 +0,0 @@ -package com.netgrif.application.engine.workflow.service; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; -import com.google.common.collect.Lists; -import com.netgrif.application.engine.auth.domain.IUser; -import com.netgrif.application.engine.auth.service.interfaces.IUserService; -import com.netgrif.application.engine.configuration.properties.FilterProperties; -import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; -import com.netgrif.application.engine.workflow.domain.filter.FilterImportExport; -import com.netgrif.application.engine.workflow.domain.filter.FilterImportExportList; -import com.netgrif.application.engine.petrinet.domain.I18nString; -import com.netgrif.application.engine.petrinet.domain.PetriNet; -import com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField; -import com.netgrif.application.engine.petrinet.domain.dataset.FileFieldValue; -import com.netgrif.application.engine.petrinet.domain.dataset.logic.FieldBehavior; -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; -import com.netgrif.application.engine.startup.DefaultFiltersRunner; -import com.netgrif.application.engine.startup.ImportHelper; -import com.netgrif.application.engine.utils.InputStreamToString; -import com.netgrif.application.engine.workflow.domain.*; -import com.netgrif.application.engine.workflow.service.interfaces.IDataService; -import com.netgrif.application.engine.workflow.service.interfaces.IFilterImportExportService; -import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; -import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import javax.xml.XMLConstants; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; -import java.io.*; -import java.util.*; - -/** - * Service that provides methods for creation of filter export/import cases for users. - * Also provides methods to export and import filters. - */ - -@Service -public class FilterImportExportService implements IFilterImportExportService { - - private static final Logger log = LoggerFactory.getLogger(FilterImportExportService.class); - - private static final String EXPORT_NET_IDENTIFIER = "export_filters"; - private static final String IMPORT_NET_IDENTIFIER = "import_filters"; - private static final String FILTER_NET_IDENTIFIER = "filter"; - - private static final String UPLOAD_FILE_FIELD = "upload_file"; - - private static final String IMPORT_FILTER_TRANSITION = "import_filter"; - - private static final String FIELD_VISIBILITY = "visibility"; - private static final String FIELD_FILTER_TYPE = "filter_type"; - private static final String FIELD_FILTER = "filter"; - private static final String FIELD_NAME = "i18n_filter_name"; - private static final String FIELD_PARENT_CASE_ID = "parent_filter_id"; - private static final String FIELD_PARENT_VIEW_ID = "origin_view_id"; - private static final String FIELD_MISSING_ALLOWED_NETS = "missing_allowed_nets"; - private static final String FIELD_MISSING_NETS_TRANSLATION = "missing_nets_translation"; - - @Autowired - IUserService userService; - - @Autowired - IWorkflowService workflowService; - - @Autowired - IPetriNetService petriNetService; - - @Autowired - DefaultFiltersRunner defaultFiltersRunner; - - @Autowired - private ITaskService taskService; - - @Autowired - private IDataService dataService; - - @Autowired - private FileStorageConfiguration fileStorageConfiguration; - - @Autowired - private FilterProperties filterProperties; - - @Override - public void createFilterImport(IUser author) { - workflowService.createCaseByIdentifier(IMPORT_NET_IDENTIFIER, "Import filters " + author.getFullName(), "", author.transformToLoggedUser()); - } - - @Override - public void createFilterExport(IUser author) { - workflowService.createCaseByIdentifier(EXPORT_NET_IDENTIFIER, "Export filters " + author.getFullName(), "", author.transformToLoggedUser()); - } - - /** - * Method which performs export of selected filters into xml file. - * Method finds all cases by provided ids, transform them into FilterImportExportList object - * and serialize them into xml file on path: storage/filterExport//filters.xml - * - * @param filtersToExport - set of ids of filter cases, which should be exported - * @return FileFieldValue - file field value with path to xml file of exported filters - * @throws IOException - if file which contains exported filters cannot be created - */ - @Override - public FileFieldValue exportFiltersToFile(Collection filtersToExport) throws IOException { - log.info("Exporting selected filters"); - return createXML(exportFilters(filtersToExport)); - } - - /** - * Method which performs export of selected filters into xml file. - * Method finds all cases by provided ids, transform them into FilterImportExportList object - * - * @param filtersToExport - set of ids of filter cases, which should be exported - * @return a serializable wrapper of a list of filter objects in serializable form - */ - @Override - public FilterImportExportList exportFilters(Collection filtersToExport) { - List selectedFilterCases = this.workflowService.findAllById(Lists.newArrayList(filtersToExport)); - FilterImportExportList filterList = new FilterImportExportList(); - - Set exportedFilterIds = new HashSet<>(); - - for (Case exportedFilter : selectedFilterCases) { - LinkedList chain = new LinkedList<>(); - Case currentCase = exportedFilter; - while (true) { - if (exportedFilterIds.contains(currentCase.getStringId())) { - break; - } - exportedFilterIds.add(currentCase.getStringId()); - FilterImportExport currentFilter = createExportClass(currentCase); - chain.push(currentFilter); - if (currentFilter.getParentCaseId() != null) { - currentCase = this.workflowService.findOne(currentFilter.getParentCaseId()); - } else { - break; - } - } - filterList.getFilters().addAll(chain); - } - - return filterList; - } - - /** - * Method which performs import of filters from uploaded xml file. - * Method firstly loads xml file from file field and validates it against xml schema for filters - * export located on path: filter_export_schema.xml - * If the file is correct, method calls performImport method which - * creates filter cases - * - * @return List - list of task ids of imported filter cases in - import_filter transition - * @throws IOException - if imported file is not found - * @throws IllegalFilterFileException - if uploaded xml is not in correct xml format and invalidate against schema - */ - @Override - public List importFilters() throws IOException, IllegalFilterFileException, TransitionNotExecutableException { - log.info("Importing filters"); - FilterImportExportList filterList = loadFromXML(); - return new ArrayList<>(performImport(filterList).values()); - } - - /** - * Method which performs import of filters from already created filter import class instances - * passed in as parameter. - * - * @param filterList - instance of class FilterImportExportList - * @return a mapping of original filter case ids to task ids of imported filter cases in - import_filter transition - * @throws IOException - if imported file is not found - */ - @Override - public Map importFilters(FilterImportExportList filterList) throws IOException, TransitionNotExecutableException { - log.info("Importing filters from imported menu"); - return performImport(filterList); - } - - protected Map performImport(FilterImportExportList filterList) throws IOException, TransitionNotExecutableException { - Map oldToNewFilterId = new HashMap<>(); - Map importedFilterTaskIds = new HashMap<>(); - - if (filterList == null) { - throw new FileNotFoundException(); - } - - filterList.getFilters().forEach(filter -> { - if (filter.getAllowedNets() == null) { - filter.setAllowedNets(new ArrayList<>()); - } - - String parentId = null; - boolean viewOrigin = false; - - if (filter.getParentCaseId() != null && !filter.getParentCaseId().equals("")) { - parentId = oldToNewFilterId.get(filter.getParentCaseId()); - if (parentId == null) { - log.error("Imported filter with ID '" + filter.getCaseId() + "' could not find an imported mapping of its parent case with original ID '" + filter.getParentCaseId() + "'"); - } - } else if (filter.getParentViewId() != null && !filter.getParentViewId().equals("")) { - parentId = filter.getParentViewId(); - viewOrigin = true; - } - - Optional filterCase = defaultFiltersRunner.createFilter( - filter.getFilterName().getDefaultValue(), - filter.getIcon(), - filter.getType(), - filter.getVisibility(), - filter.getFilterValue(), - filter.getAllowedNets(), - filter.getFilterMetadataExport().getMapObject(), - filter.getFilterName().getTranslations(), - filter.getFilterMetadataExport().isDefaultSearchCategories(), - filter.getFilterMetadataExport().isInheritAllowedNets(), - parentId, - viewOrigin, - true - ); - - if (filterCase.isEmpty()) { - return; - } - - oldToNewFilterId.put(filter.getCaseId(), filterCase.get().getStringId()); - - Task importedFilterTask = taskService.searchOne( - QTask.task.transitionId.eq(IMPORT_FILTER_TRANSITION) - .and(QTask.task.caseId.eq(filterCase.get().getStringId())) - ); - importedFilterTaskIds.put(filter.getCaseId(), importedFilterTask.getStringId()); - - // TODO: delete after fixed issue: https://netgrif.atlassian.net/jira/servicedesk/projects/NGSD/issues/ - filterCase.get().getDataSet().get(FIELD_MISSING_ALLOWED_NETS).addBehavior(IMPORT_FILTER_TRANSITION, Collections.singleton(FieldBehavior.HIDDEN)); - filterCase.get().getDataSet().get(FIELD_FILTER).addBehavior(IMPORT_FILTER_TRANSITION, Collections.singleton(FieldBehavior.VISIBLE)); - workflowService.save(filterCase.get()); - }); - taskService.assignTasks(taskService.findAllById(new ArrayList<>(importedFilterTaskIds.values())), userService.getLoggedUser()); - changeFilterField(importedFilterTaskIds.values()); - return importedFilterTaskIds; - } - - /** - * Method which provides reloading of imported filters fields, so if allowed nets are missing, - * htmlTextArea is shown with list of missing allowed nets, otherwise filter preview is shown. - * - * @param filterFields - list of task ids of filters which value should be reloaded - */ - @Override - public void changeFilterField(Collection filterFields) { - filterFields.forEach(f -> { - Task importedFilterTask = taskService.findOne(f); - Case filterCase = workflowService.findOne(importedFilterTask.getCaseId()); - PetriNet filterNet = petriNetService.getNewestVersionByIdentifier(FILTER_NET_IDENTIFIER); - List requiredNets = filterCase.getDataSet().get(FIELD_FILTER).getAllowedNets(); - List currentNets = petriNetService.getExistingPetriNetIdentifiersFromIdentifiersList(requiredNets); - if (currentNets.size() < requiredNets.size()) { - requiredNets.removeAll(currentNets); - StringBuilder htmlTextAreaValue = new StringBuilder( - ((EnumerationMapField) filterNet.getDataSet().get(FIELD_MISSING_NETS_TRANSLATION)).getOptions().get( - LocaleContextHolder.getLocale().getLanguage() - ).getDefaultValue() - ); - htmlTextAreaValue.append("
    "); - requiredNets.forEach(net -> htmlTextAreaValue.append("
  • ").append(net).append("
  • ")); - htmlTextAreaValue.append("
"); - Map> taskData = new HashMap<>(); - Map missingNets = new HashMap<>(); - missingNets.put("type", "text"); - missingNets.put("value", htmlTextAreaValue.toString()); - taskData.put(FIELD_MISSING_ALLOWED_NETS, missingNets); - this.dataService.setData(importedFilterTask, ImportHelper.populateDataset(taskData)); - filterCase = workflowService.findOne(filterCase.getStringId()); - changeVisibilityByAllowedNets(true, filterCase); - } else { - changeVisibilityByAllowedNets(false, filterCase); - } - workflowService.save(filterCase); - }); - } - - private void changeVisibilityByAllowedNets(boolean allowedNetsMissing, Case filterCase) { - filterCase.getDataSet().get(allowedNetsMissing ? FIELD_MISSING_ALLOWED_NETS : FIELD_FILTER).makeVisible(IMPORT_FILTER_TRANSITION); - filterCase.getDataSet().get(allowedNetsMissing ? FIELD_FILTER : FIELD_MISSING_ALLOWED_NETS).makeHidden(IMPORT_FILTER_TRANSITION); - } - - @Transactional - protected FilterImportExportList loadFromXML() throws IOException, IllegalFilterFileException { - Case exportCase = workflowService.searchOne( - QCase.case$.processIdentifier.eq(IMPORT_NET_IDENTIFIER) - .and(QCase.case$.author.id.eq(userService.getLoggedUser().getStringId())) - ); - - FileFieldValue ffv = (FileFieldValue) exportCase.getDataSet().get(UPLOAD_FILE_FIELD).getValue(); - if (ffv == null) { - throw new FileNotFoundException(); - } - - File f = new File(ffv.getPath()); - validateFilterXML(new FileInputStream(f)); - String importedFilter = InputStreamToString.inputStreamToString(new FileInputStream(f)); - SimpleModule module = new SimpleModule().addDeserializer(Object.class, FilterDeserializer.getInstance()); - XmlMapper xmlMapper = (XmlMapper) new XmlMapper().registerModule(module); - return xmlMapper.readValue(importedFilter, FilterImportExportList.class); - } - - @Transactional - protected FileFieldValue createXML(FilterImportExportList filters) throws IOException { - String filePath = fileStorageConfiguration.getStoragePath() + "/filterExport/" + userService.getLoggedUser().getStringId() + "/" + filterProperties.getFileName(); - File f = new File(filePath); - f.getParentFile().mkdirs(); - - XmlMapper xmlMapper = new XmlMapper(); - xmlMapper.enable(SerializationFeature.INDENT_OUTPUT); - xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true); - xmlMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - xmlMapper.writeValue(baos, filters); - - FileOutputStream fos = new FileOutputStream(f); - baos.writeTo(fos); - - return new FileFieldValue(filterProperties.getFileName(), filePath); - } - - protected FilterImportExport createExportClass(Case filter) { - FilterImportExport exportFilter = new FilterImportExport(); - exportFilter.setCaseId(filter.getStringId()); - exportFilter.setIcon(filter.getIcon()); - - DataField parentCaseId = filter.getDataField(FIELD_PARENT_CASE_ID); - if (parentCaseId.getValue() != null && !parentCaseId.getValue().equals("")) { - exportFilter.setParentCaseId((String) parentCaseId.getValue()); - } - - DataField parentViewId = filter.getDataField(FIELD_PARENT_VIEW_ID); - if (parentViewId.getValue() != null && !parentViewId.getValue().equals("")) { - exportFilter.setParentViewId((String) parentViewId.getValue()); - } - - DataField filterField = filter.getDataField(FIELD_FILTER); - exportFilter.setFilterValue((String) filterField.getValue()); - exportFilter.setAllowedNets(filterField.getAllowedNets()); - exportFilter.setFilterMetadataExport(filterField.getFilterMetadata()); - - DataField visibility = filter.getDataField(FIELD_VISIBILITY); - exportFilter.setVisibility(visibility.getValue().toString()); - - DataField type = filter.getDataField(FIELD_FILTER_TYPE); - exportFilter.setType(type.getValue().toString()); - - DataField name = filter.getDataField(FIELD_NAME); - exportFilter.setFilterName((I18nString) name.getValue()); - - return exportFilter; - } - - private static void validateFilterXML(InputStream xml) throws IllegalFilterFileException { - try { - SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - Schema schema = factory.newSchema(FilterImportExportService.class.getResource("/petriNets/filter_export_schema.xsd")); - Validator validator = schema.newValidator(); - validator.validate(new StreamSource(xml)); - } catch (Exception ex) { - throw new IllegalFilterFileException(ex); - } - } -} - - - diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java similarity index 87% rename from src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java rename to src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java index 1a2fcc0316f..444c480fcb0 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java @@ -31,9 +31,9 @@ import java.util.stream.Collectors; @Service -public class CaseSearchService extends MongoSearchService { +public class LegacyCaseSearchService extends MongoSearchService { - private static final Logger log = LoggerFactory.getLogger(CaseSearchService.class.getName()); + private static final Logger log = LoggerFactory.getLogger(LegacyCaseSearchService.class.getName()); public static final String ROLE = "role"; public static final String DATA = "data"; @@ -54,47 +54,53 @@ public class CaseSearchService extends MongoSearchService { private IPetriNetService petriNetService; public Predicate buildQuery(Map requestQuery, LoggedUser user, Locale locale) { - BooleanBuilder builder = new BooleanBuilder(); LoggedUser loggedOrImpersonated = user.getSelfOrImpersonated(); - + List singleQueries = new ArrayList<>(); if (requestQuery.containsKey(PETRINET)) { - builder.and(petriNet(requestQuery.get(PETRINET), loggedOrImpersonated, locale)); + singleQueries.add(petriNet(requestQuery.get(PETRINET), loggedOrImpersonated, locale)); } if (requestQuery.containsKey(AUTHOR)) { - builder.and(author(requestQuery.get(AUTHOR))); + singleQueries.add(author(requestQuery.get(AUTHOR))); } if (requestQuery.containsKey(TRANSITION)) { - builder.and(transition(requestQuery.get(TRANSITION))); + singleQueries.add(transition(requestQuery.get(TRANSITION))); } if (requestQuery.containsKey(FULLTEXT)) { - builder.and(fullText(requestQuery.getOrDefault(PETRINET, null), (String) requestQuery.get(FULLTEXT))); + singleQueries.add(fullText(requestQuery.getOrDefault(PETRINET, null), (String) requestQuery.get(FULLTEXT))); } if (requestQuery.containsKey(ROLE)) { - builder.and(role(requestQuery.get(ROLE))); + singleQueries.add(role(requestQuery.get(ROLE))); } if (requestQuery.containsKey(DATA)) { - builder.and(data(requestQuery.get(DATA))); + singleQueries.add(data(requestQuery.get(DATA))); } if (requestQuery.containsKey(TAGS)) { - builder.and(tags(requestQuery.get(TAGS))); + singleQueries.add(tags(requestQuery.get(TAGS))); } if (requestQuery.containsKey(CASE_ID)) { - builder.and(caseId(requestQuery.get(CASE_ID))); + singleQueries.add(caseId(requestQuery.get(CASE_ID))); } if (requestQuery.containsKey(GROUP)) { Predicate groupPredicate = group(requestQuery.get(GROUP), loggedOrImpersonated, locale); if (groupPredicate != null) { - builder.and(groupPredicate); + singleQueries.add(groupPredicate); } else { return null; } } - BooleanBuilder permissionConstraints = new BooleanBuilder(buildViewRoleQueryConstraint(loggedOrImpersonated)); - permissionConstraints.andNot(buildNegativeViewRoleQueryConstraint(loggedOrImpersonated)); - permissionConstraints.or(buildViewUserQueryConstraint(loggedOrImpersonated)); - permissionConstraints.andNot(buildNegativeViewUsersQueryConstraint(loggedOrImpersonated)); - builder.and(permissionConstraints); - return builder; + BooleanBuilder builder = constructPredicateTree(singleQueries, BooleanBuilder::or); + return builder.and(buildPermissionConstraints(loggedOrImpersonated)); + } + + public BooleanBuilder buildPermissionConstraints(LoggedUser loggedOrImpersonated) { + // (Rp!=0 & Rn = 0) + BooleanBuilder constraints = new BooleanBuilder(buildViewRoleQueryConstraint(loggedOrImpersonated)) + .andNot(buildNegativeViewRoleQueryConstraint(loggedOrImpersonated)); + // ((Rp!=0 & Rn = 0) or Up!=0) + constraints.or(buildViewUserQueryConstraint(loggedOrImpersonated)); + // (((Rp!=0 & Rn = 0) or Up!=0) & Un=0) == 1 + constraints.andNot(buildNegativeViewUsersQueryConstraint(loggedOrImpersonated)); + return constraints; } protected Predicate buildViewRoleQueryConstraint(LoggedUser user) { @@ -103,7 +109,7 @@ protected Predicate buildViewRoleQueryConstraint(LoggedUser user) { } public Predicate viewRoleQuery(String role) { - return QCase.case$.viewUserRefs.isEmpty().and(QCase.case$.viewRoles.isEmpty()).or(QCase.case$.viewRoles.contains(role)); + return QCase.case$.viewRoles.contains(role); } protected Predicate buildViewUserQueryConstraint(LoggedUser user) { @@ -112,7 +118,7 @@ protected Predicate buildViewUserQueryConstraint(LoggedUser user) { } public Predicate viewUserQuery(String userId) { - return QCase.case$.viewUserRefs.isEmpty().and(QCase.case$.viewRoles.isEmpty()).or(QCase.case$.viewUsers.contains(userId)); + return QCase.case$.viewUsers.contains(userId); } protected Predicate buildNegativeViewRoleQueryConstraint(LoggedUser user) { @@ -134,22 +140,19 @@ public Predicate negativeViewUserQuery(String userId) { } public Predicate petriNet(Object query, LoggedUser user, Locale locale) { - List allowedNets = petriNetService.getReferencesByUsersProcessRoles(user, locale); if (query instanceof ArrayList) { BooleanBuilder builder = new BooleanBuilder(); - List expressions = (List) ((ArrayList) query).stream().filter(q -> q instanceof HashMap).map(q -> petriNetObject((HashMap) q, allowedNets)).collect(Collectors.toList()); + List expressions = (List) ((ArrayList) query).stream().filter(q -> q instanceof HashMap).map(q -> petriNetObject((HashMap) q)).collect(Collectors.toList()); expressions.forEach(builder::or); return builder; } else if (query instanceof HashMap) { - return petriNetObject((HashMap) query, allowedNets); + return petriNetObject((HashMap) query); } return null; } - private static BooleanExpression petriNetObject(HashMap query, List allowedNets) { - if (query.containsKey(PETRINET_IDENTIFIER) && allowedNets.stream().anyMatch(net -> net.getIdentifier().equalsIgnoreCase(query.get(PETRINET_IDENTIFIER)))) - return QCase.case$.processIdentifier.equalsIgnoreCase(query.get(PETRINET_IDENTIFIER)); - return null; + private static BooleanExpression petriNetObject(HashMap query) { + return QCase.case$.processIdentifier.equalsIgnoreCase(query.get(PETRINET_IDENTIFIER)); } public Predicate author(Object query) { diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/TaskSearchService.java b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java similarity index 86% rename from src/main/java/com/netgrif/application/engine/workflow/service/TaskSearchService.java rename to src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java index aace6fb0348..b7ba32b7b6c 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/TaskSearchService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java @@ -19,7 +19,7 @@ import java.util.stream.Collectors; @Service -public class TaskSearchService extends MongoSearchService { +public class LegacyTaskSearchService extends MongoSearchService { @Autowired private IPetriNetService petriNetService; @@ -33,7 +33,7 @@ public Predicate buildQuery(List requests, LoggedUser user, L return null; } else if (!isIntersection) { singleQueries = singleQueries.stream().filter(Objects::nonNull).collect(Collectors.toList()); - if (singleQueries.size() == 0) { + if (singleQueries.isEmpty()) { // all queries result in an empty set => the entire result is an empty set return null; } @@ -41,26 +41,21 @@ public Predicate buildQuery(List requests, LoggedUser user, L BooleanBuilder builder = constructPredicateTree(singleQueries, isIntersection ? BooleanBuilder::and : BooleanBuilder::or); - BooleanBuilder constraints = new BooleanBuilder(buildRolesQueryConstraint(loggedOrImpersonated)); - constraints.or(buildUserRefQueryConstraint(loggedOrImpersonated)); - builder.and(constraints); - - BooleanBuilder permissionConstraints = new BooleanBuilder(buildViewRoleQueryConstraint(loggedOrImpersonated)); - permissionConstraints.andNot(buildNegativeViewRoleQueryConstraint(loggedOrImpersonated)); - permissionConstraints.or(buildViewUserQueryConstraint(loggedOrImpersonated)); - permissionConstraints.andNot(buildNegativeViewUsersQueryConstraint(loggedOrImpersonated)); - builder.and(permissionConstraints); - return builder; + return builder.and(buildPermissionConstraints(loggedOrImpersonated)); } - protected Predicate buildRolesQueryConstraint(LoggedUser user) { - List roleConstraints = user.getProcessRoles().stream().map(this::roleQuery).collect(Collectors.toList()); - return constructPredicateTree(roleConstraints, BooleanBuilder::or); - } + public BooleanBuilder buildPermissionConstraints(LoggedUser loggedOrImpersonated) { + // (Rp!=0 & Rn = 0) + BooleanBuilder constraints = new BooleanBuilder(buildViewRoleQueryConstraint(loggedOrImpersonated)) + .andNot(buildNegativeViewRoleQueryConstraint(loggedOrImpersonated)); + + // ((Rp!=0 & Rn = 0) or Up!=0) + constraints.or(buildViewUserQueryConstraint(loggedOrImpersonated)); + + // (((Rp!=0 & Rn = 0) or Up!=0) & Un=0) == 1 + constraints.andNot(buildNegativeViewUsersQueryConstraint(loggedOrImpersonated)); - protected Predicate buildUserRefQueryConstraint(LoggedUser user) { - Predicate userRefConstraints = userRefQuery(user.getId()); - return constructPredicateTree(Collections.singletonList(userRefConstraints), BooleanBuilder::or); + return constraints; } protected Predicate buildViewRoleQueryConstraint(LoggedUser user) { @@ -69,7 +64,7 @@ protected Predicate buildViewRoleQueryConstraint(LoggedUser user) { } public Predicate viewRoleQuery(String role) { - return QTask.task.viewUserRefs.isEmpty().and(QTask.task.viewRoles.isEmpty()).or(QTask.task.viewRoles.contains(role)); + return QTask.task.viewRoles.contains(role); } protected Predicate buildViewUserQueryConstraint(LoggedUser user) { @@ -78,7 +73,7 @@ protected Predicate buildViewUserQueryConstraint(LoggedUser user) { } public Predicate viewUsersQuery(String userId) { - return QTask.task.negativeViewRoles.isEmpty().and(QTask.task.viewUserRefs.isEmpty()).and(QTask.task.viewRoles.isEmpty()).or(QTask.task.viewUsers.contains(userId)); + return QTask.task.viewUsers.contains(userId); } protected Predicate buildNegativeViewRoleQueryConstraint(LoggedUser user) { @@ -100,7 +95,7 @@ public Predicate negativeViewUsersQuery(String userId) { } - private Predicate buildSingleQuery(TaskSearchRequest request, LoggedUser user, Locale locale) { + public Predicate buildSingleQuery(TaskSearchRequest request, LoggedUser user, Locale locale) { BooleanBuilder builder = new BooleanBuilder(); buildStringIdQuery(request, builder); diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java b/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java deleted file mode 100644 index edb99e46623..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java +++ /dev/null @@ -1,383 +0,0 @@ -package com.netgrif.application.engine.workflow.service; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; -import com.netgrif.application.engine.auth.service.interfaces.IUserService; -import com.netgrif.application.engine.files.StorageResolverService; -import com.netgrif.application.engine.petrinet.domain.I18nString; -import com.netgrif.application.engine.petrinet.domain.PetriNet; -import com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField; -import com.netgrif.application.engine.petrinet.domain.dataset.FileField; -import com.netgrif.application.engine.petrinet.domain.dataset.FileFieldValue; -import com.netgrif.application.engine.petrinet.domain.dataset.MultichoiceMapField; -import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; -import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; -import com.netgrif.application.engine.startup.DefaultFiltersRunner; -import com.netgrif.application.engine.startup.ImportHelper; -import com.netgrif.application.engine.utils.InputStreamToString; -import com.netgrif.application.engine.workflow.domain.*; -import com.netgrif.application.engine.workflow.domain.menu.Menu; -import com.netgrif.application.engine.workflow.domain.menu.MenuAndFilters; -import com.netgrif.application.engine.workflow.domain.menu.MenuEntry; -import com.netgrif.application.engine.workflow.domain.menu.MenuEntryRole; -import com.netgrif.application.engine.workflow.service.interfaces.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import javax.xml.XMLConstants; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; -import java.io.*; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; - -@Service -public class MenuImportExportService implements IMenuImportExportService { - - private static final Logger log = LoggerFactory.getLogger(MenuImportExportService.class); - - private static final String MENU_ITEM_NAME = "entry_name"; - private static final String USE_ICON = "use_icon"; - private static final String ALLOWED_ROLES = "allowed_roles"; - private static final String BANNED_ROLES = "banned_roles"; - private static final String MENU_IDENTIFIER = "menu_identifier"; - private static final String PARENT_ID = "parentId"; - private static final String ENTRY_DEFAULT_NAME = "entry_default_name"; - private static final String GROUP_NAV_TASK = "navigationMenuConfig"; - - @Autowired - IUserService userService; - - @Autowired - IWorkflowService workflowService; - - @Autowired - IPetriNetService petriNetService; - - @Autowired - DefaultFiltersRunner defaultFiltersRunner; - - @Autowired - private ITaskService taskService; - - @Autowired - private IDataService dataService; - - @Autowired - private IFilterImportExportService filterImportExportService; - - @Autowired - private StorageResolverService storageResolverService; - - /** - * Method which performs export of selected menu entries with their filters into xml file. - * Method finds all cases by provided ids, transform them into FilterImportExportList object - * and serialize them into xml file on path: storage/filterExport//filters.xml - * - * @param menusForExport - EnumerationMapField with Ids (delimited by ",") of menu entries as keys - * and identifier of menu they belong to as value - * @param groupId case Id of active group - * @return FileFieldValue - file field value of active group file field used to store exported file - * @throws IOException - if file which contains exported menus cannot be created - */ - @Override - public FileFieldValue exportMenu(EnumerationMapField menusForExport, String groupId, FileField fileField) throws IOException { - log.info("Exporting menu"); - MenuAndFilters menuAndFilters = new MenuAndFilters(); - - for (Map.Entry option : menusForExport.getOptions().entrySet()) { - Menu menu = new Menu(); - menu.setMenuIdentifier(option.getValue().toString()); - - List menuItemCaseIds = Arrays.asList(option.getKey().split(",")); - List filterCaseIds = new ArrayList<>(); - menu.setMenuEntries(new ArrayList<>()); - - menuItemCaseIds.forEach(menuItemCaseId -> { - Case menuItem = workflowService.findOne(menuItemCaseId); - String filterId = ((List) menuItem.getDataSet().get("filter_case").getValue()).get(0); - menu.getMenuEntries().add(createMenuEntryExportClass(menuItem, filterId)); - filterCaseIds.add(filterId); - }); - menuAndFilters.getFilterList().getFilters().addAll(filterImportExportService.exportFilters(filterCaseIds).getFilters()); - - menuAndFilters.getMenuList().getMenus().add(menu); - } - return createXML(menuAndFilters, groupId, fileField); - } - - /** - * Method which performs import of menus from uploaded xml file. - * Method firstly loads xml file from file field and validates it against xml schema for menusWithFilters - * located on path: menu_export_schema.xml. - * Then it calls import of filters provided in uploaded xml file. - * Method then deletes any Preference filter item cases of active group case with same menu identifier - * as any of the menus imported. - * If the file is correct, method creates new instance of Preference filter item cases - * for each menu entry by calling method "createMenuItemCase". - * - * @param menuItemCases - list of Preference filter item cases in active group - * @param ffv - file field from active group case containing uploaded xml file. - * @param parentId - id of active group case - * @return List - list of values delimited by "," containing - * preferenceItem case Id, filter case Id, boolean value determining if Icon should be displayed - * @throws IOException - if imported file is not found - * @throws IllegalMenuFileException - if uploaded xml is not in correct xml format and invalidate against schema - */ - @Override - public List importMenu(List menuItemCases, FileFieldValue ffv, String parentId) throws IOException, IllegalMenuFileException, TransitionNotExecutableException { - StringBuilder resultMessage = new StringBuilder(""); - - List importedEntryAndFilterCaseIds = new ArrayList<>(); - MenuAndFilters menuAndFilters = loadFromXML(ffv); - - //Firstly, remove existing preference_filter_item cases having the same menu identifier as any menu - // which is being currently imported. - List menuItemIdsToReplace = menuItemCases.stream().filter(caze -> menuAndFilters.getMenuList().getMenus().stream() - .anyMatch(menu -> Objects.equals(menu.getMenuIdentifier(), caze.getDataSet().get(MENU_IDENTIFIER).getValue()))) - .map(Case::getStringId).collect(Collectors.toList()); - - //Change remove_option button value to trigger its SET action - if (!menuItemIdsToReplace.isEmpty()) menuItemIdsToReplace.forEach(id -> { - Map> caseToRemoveData = new HashMap<>(); - Map removeBtnData = new HashMap<>(); - removeBtnData.put("type", "button"); - removeBtnData.put("value", "removed"); - caseToRemoveData.put("remove_option", removeBtnData); - - Case caseToRemove = workflowService.findOne(id); - QTask qTask = new QTask("task"); - Task task = taskService.searchOne(qTask.transitionId.eq("view").and(qTask.caseId.eq(caseToRemove.getStringId()))); - dataService.setData(task, ImportHelper.populateDataset(caseToRemoveData)); - }); - - //Import filters - Map importedFilterTaskIds = filterImportExportService.importFilters(menuAndFilters.getFilterList()); - - //Import each menu individually - menuAndFilters.getMenuList().getMenus().forEach(menu -> { - resultMessage.append("\nIMPORTING MENU \"").append(menu.getMenuIdentifier()).append("\":\n"); - menu.getMenuEntries().forEach(menuItem -> { - String entryAndFilterCaseId = createMenuItemCase(resultMessage, menuItem, menu.getMenuIdentifier(), parentId, importedFilterTaskIds.get(menuItem.getFilterCaseId())); - if (!entryAndFilterCaseId.equals("")) importedEntryAndFilterCaseIds.add(entryAndFilterCaseId); - }); - - QTask qTask = new QTask("task"); - Task task = taskService.searchOne(qTask.transitionId.eq(GROUP_NAV_TASK).and(qTask.caseId.eq(parentId))); - - Map> groupData = new HashMap<>(); - Map groupImportResultMessage = new HashMap<>(); - groupImportResultMessage.put("type", "text"); - groupImportResultMessage.put("value", resultMessage.toString()); - groupData.put("import_results", groupImportResultMessage); - dataService.setData(task, ImportHelper.populateDataset(groupData)); - }); - - importedFilterTaskIds.values().forEach(taskId -> { - Task importedFilterTask = taskService.findOne(taskId); - Case filterCase = workflowService.findOne(importedFilterTask.getCaseId()); - try { - taskService.assignTask(importedFilterTask.getStringId()); - taskService.finishTask(importedFilterTask.getStringId()); - workflowService.save(filterCase); - } catch (TransitionNotExecutableException e) { - log.error("Failed to execute \"import_filter\" task with id: " + taskId, e); - } - }); - - return importedEntryAndFilterCaseIds; - } - - /** - * Method which creates new Preference filter item case from imported menuEntry class. - * Before case creation method prepares allowed and banned roles data for the entry while performing checks - * for imported filter and role nets in engine. - * - * @param item - imported MenuEntry item - * @param menuIdentifier - Identifier of new imported menu. - * @param parentId - id of active group case - * @param filterTaskId - task id of imported filter belonging to men item case being created - * @return String - values delimited by "," containing case Id of newly created Preference filter item case, - * Id of its filter case, boolean value determining if Preference filter item case should display icon. - */ - @Override - public String createMenuItemCase(StringBuilder resultMessage, MenuEntry item, String menuIdentifier, String parentId, String filterTaskId) { - AtomicBoolean netCheck = new AtomicBoolean(true); - resultMessage.append("\nMenu entry \"").append(item.getEntryName()).append("\": "); - - Task importedFilterTask = taskService.findOne(filterTaskId); - Case filterCase = workflowService.findOne(importedFilterTask.getCaseId()); - if (filterCase == null) { - resultMessage.append("Filter not found! Menu entry was skipped.\n"); - return ""; - } - - //Creating role entries for allowed_roles/banned_roles datafields - Map allowedRoles = new LinkedHashMap<>(); - Map bannedRoles = new LinkedHashMap<>(); - - //Checks if role nets and imported filters are present in engine and prepares role constrictions data. - if (item.getMenuEntryRoleList() != null && !item.getMenuEntryRoleList().isEmpty()) { - item.getMenuEntryRoleList().forEach(menuEntryRole -> { - String roleImportId = menuEntryRole.getRoleImportId(); - String netImportId = menuEntryRole.getNetImportId(); - if (netImportId != null) { - PetriNet net = petriNetService.getNewestVersionByIdentifier(netImportId); - if (net == null) { - resultMessage.append("\n- Missing net with import ID: \"").append(netImportId).append("\"").append("for role ").append(roleImportId).append("\n"); - netCheck.set(false); - } else { - Optional role = net.getRoles().values().stream().filter(r -> r.getImportId().equals(roleImportId)) - .findFirst(); - if (role.isPresent()) { - //TODO: extend funcionality - add translations to nets and roles - if (menuEntryRole.getAuthorizationType().equals(AuthorizationType.allowed)) { - allowedRoles.put(roleImportId + ":" + netImportId, new I18nString(role.get().getName() + "(" + net.getTitle() + ")")); - } else { - bannedRoles.put(roleImportId + ":" + netImportId, new I18nString(role.get().getName() + "(" + net.getTitle() + ")")); - } - } else { - resultMessage.append("\n- Role with import ID \"").append(roleImportId).append("\" ").append("does not exist in the latest present version of net \"").append(netImportId).append("\"\n"); - netCheck.set(false); - } - } - } - }); - } - //Creating new Case of preference_filter_item net and setting its data... - Case menuItemCase = workflowService.createCase( - petriNetService.getNewestVersionByIdentifier("preference_filter_item").getStringId(), - item.getEntryName() + "_" + menuIdentifier, - "", - userService.getSystem().transformToLoggedUser() - ).getCase(); - - QTask qTask = new QTask("task"); - Task task = taskService.searchOne(qTask.transitionId.eq("init").and(qTask.caseId.eq(menuItemCase.getStringId()))); - try { - taskService.assignTask(task, userService.getLoggedUser()); - menuItemCase.getDataSet().get(MENU_IDENTIFIER).setValue(menuIdentifier); - menuItemCase.getDataSet().get(PARENT_ID).setValue(parentId); - menuItemCase.getDataSet().get(ALLOWED_ROLES).setOptions(allowedRoles); - menuItemCase.getDataSet().get(BANNED_ROLES).setOptions(bannedRoles); - workflowService.save(menuItemCase); - } catch (TransitionNotExecutableException e) { - log.error("Failed to execute \"init\" task on preference filter item case with id: " + menuItemCase.getStringId(), e); - netCheck.set(false); - resultMessage.append("- Failed to execute \"init\" task"); - } - if (netCheck.get()) resultMessage.append("OK\n"); - - return task.getCaseId() + "," + filterCase.getStringId() + "," + item.getUseIcon().toString(); - } - - @Transactional - protected MenuAndFilters loadFromXML(FileFieldValue ffv) throws IOException, IllegalMenuFileException { - File f = new File(ffv.getPath()); - validateFilterXML(new FileInputStream(f)); - SimpleModule module = new SimpleModule().addDeserializer(Object.class, FilterDeserializer.getInstance()); - XmlMapper xmlMapper = (XmlMapper) new XmlMapper().registerModule(module); - String xml = InputStreamToString.inputStreamToString(new FileInputStream(f)); - return xmlMapper.readValue(xml, MenuAndFilters.class); - } - - @Transactional - protected FileFieldValue createXML(MenuAndFilters menuAndFilters, String parentId, FileField fileField) throws IOException { - FileFieldValue ffv = new FileFieldValue(); - try { - ffv.setName("menu_" + userService.getLoggedUser().getFullName().replaceAll("\\s+", "") + ".xml"); - ffv.setPath(storageResolverService.resolve(fileField.getStorageType()).getPath(parentId, fileField.getImportId(), ffv.getName())); - File f = new File(ffv.getPath()); - XmlMapper xmlMapper = new XmlMapper(); - xmlMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); - xmlMapper.enable(SerializationFeature.INDENT_OUTPUT); - xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - xmlMapper.writeValue(baos, menuAndFilters); - - FileOutputStream fos = new FileOutputStream(f); - baos.writeTo(fos); - } catch (Exception e) { - log.error("Failed to export menu!", e); - } - return ffv; - } - - private MenuEntry createMenuEntryExportClass(Case menuItemCase, String filterCaseId) { - Map allowedRoles = menuItemCase.getDataSet().get(ALLOWED_ROLES).getOptions(); - Map bannedRoles = menuItemCase.getDataSet().get(BANNED_ROLES).getOptions(); - - List menuEntryRoleList = new ArrayList<>(); - - if (allowedRoles != null && !allowedRoles.isEmpty()) { - menuEntryRoleList.addAll(allowedRoles.keySet().stream().map(roleNet -> { - MenuEntryRole newMenuEntryRole = new MenuEntryRole(); - newMenuEntryRole.setRoleImportId(roleNet.split(":")[0]); - newMenuEntryRole.setNetImportId(roleNet.split(":")[1]); - newMenuEntryRole.setAuthorizationType(AuthorizationType.allowed); - return newMenuEntryRole; - }).collect(Collectors.toList())); - } - - if (bannedRoles != null && !bannedRoles.isEmpty()) { - menuEntryRoleList.addAll(bannedRoles.keySet().stream().map(roleNet -> { - MenuEntryRole newMenuEntryRole = new MenuEntryRole(); - newMenuEntryRole.setRoleImportId(roleNet.split(":")[0]); - newMenuEntryRole.setNetImportId(roleNet.split(":")[1]); - newMenuEntryRole.setAuthorizationType(AuthorizationType.banned); - return newMenuEntryRole; - }).collect(Collectors.toList())); - } - - MenuEntry exportMenuItem = new MenuEntry(); - exportMenuItem.setEntryName(menuItemCase.getDataSet().get(MENU_ITEM_NAME).toString()); - exportMenuItem.setFilterCaseId(filterCaseId); - exportMenuItem.setUseIcon((Boolean) menuItemCase.getDataSet().get(USE_ICON).getValue()); - if (!menuEntryRoleList.isEmpty()) exportMenuItem.setMenuEntryRoleList(menuEntryRoleList); - - return exportMenuItem; - } - - @Override - public Map createAvailableEntriesChoices(List menuItemCases) { - Map availableItems; - availableItems = menuItemCases.stream() - .collect(Collectors.toMap(Case::getStringId, v -> new I18nString((String) v.getDataSet().get(ENTRY_DEFAULT_NAME).getValue()))); - - return availableItems; - } - - @Override - public Map addSelectedEntriesToExport(MultichoiceMapField availableEntries, EnumerationMapField menusForExport, String menuIdentifier) { - Map updatedOptions = new LinkedHashMap<>(menusForExport.getOptions()); - String menuCaseIds = ""; - if (availableEntries.getOptions().size() != 0) { - for (String id : availableEntries.getValue()) { - menuCaseIds = menuCaseIds.concat(id + ","); - } - updatedOptions.put(menuCaseIds, new I18nString(menuIdentifier)); - } - return updatedOptions; - } - - private static void validateFilterXML(InputStream xml) throws IllegalMenuFileException { - try { - SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - Schema schema = factory.newSchema(FilterImportExportService.class.getResource("/petriNets/menu_export_schema.xsd")); - Validator validator = schema.newValidator(); - validator.validate(new StreamSource(xml)); - } catch (Exception ex) { - throw new IllegalMenuFileException(ex); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java b/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java index 910e7590002..2e455c3e0c6 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java @@ -44,6 +44,7 @@ import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.netgrif.application.engine.workflow.web.requestbodies.TaskSearchRequest; import com.netgrif.application.engine.workflow.web.responsebodies.TaskReference; +import com.querydsl.core.types.ExpressionUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -82,7 +83,7 @@ public class TaskService implements ITaskService { protected MongoTemplate mongoTemplate; @Autowired - protected TaskSearchService searchService; + protected LegacyTaskSearchService searchService; @Autowired @Qualifier("taskScheduler") @@ -690,6 +691,29 @@ public long count(List requests, LoggedUser user, Locale loca } } + @Override + public long count(com.querydsl.core.types.Predicate predicate) { + if (predicate == null) { + return 0; + } + + com.querydsl.core.types.Predicate permissionConstraints = searchService.buildPermissionConstraints( + userService.getLoggedOrSystem().transformToLoggedUser()); + com.querydsl.core.types.Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints); + return taskRepository.count(finalPredicate); + } + + @Override + public boolean exists(com.querydsl.core.types.Predicate predicate) { + if (predicate == null) { + return false; + } + com.querydsl.core.types.Predicate permissionConstraints = searchService.buildPermissionConstraints( + userService.getLoggedOrSystem().transformToLoggedUser()); + com.querydsl.core.types.Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints); + return taskRepository.exists(finalPredicate); + } + @Override public Page findByCases(Pageable pageable, List cases) { return loadUsers(taskRepository.findByCaseIdIn(pageable, cases)); @@ -733,15 +757,22 @@ public Page searchAll(com.querydsl.core.types.Predicate predicate) { @Override public Page search(com.querydsl.core.types.Predicate predicate, Pageable pageable) { - Page tasks = taskRepository.findAll(predicate, pageable); + if (predicate == null) { + return Page.empty(); + } + com.querydsl.core.types.Predicate permissionConstraints = searchService.buildPermissionConstraints( + userService.getLoggedOrSystem().transformToLoggedUser()); + com.querydsl.core.types.Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints); + Page tasks = taskRepository.findAll(finalPredicate, pageable); return loadUsers(tasks); } @Override public Task searchOne(com.querydsl.core.types.Predicate predicate) { - Page tasks = taskRepository.findAll(predicate, PageRequest.of(0, 1)); - if (tasks.getTotalElements() > 0) + Page tasks = search(predicate, PageRequest.of(0, 1)); + if (tasks.getTotalElements() > 0) { return tasks.getContent().get(0); + } return null; } @@ -780,12 +811,9 @@ public List resolveUserRef(Case useCase) { @Override public Task resolveUserRef(Task task, Case useCase) { task.getUsers().clear(); - task.getNegativeViewUsers().clear(); task.getUserRefs().forEach((id, permission) -> { List userIds = getExistingUsers((UserListFieldValue) useCase.getDataSet().get(id).getValue()); - if (userIds != null && !userIds.isEmpty() && permission.containsKey("view") && !permission.get("view")) { - task.getNegativeViewUsers().addAll(userIds); - } else if (userIds != null && !userIds.isEmpty()) { + if (userIds != null && !userIds.isEmpty()) { task.addUsers(new HashSet<>(userIds), permission); } }); diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/UserFilterSearchService.java b/src/main/java/com/netgrif/application/engine/workflow/service/UserFilterSearchService.java deleted file mode 100644 index ad133d28856..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/service/UserFilterSearchService.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.netgrif.application.engine.workflow.service; - -import com.netgrif.application.engine.auth.service.interfaces.IUserService; -import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; -import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; -import com.netgrif.application.engine.startup.FilterRunner; -import com.netgrif.application.engine.workflow.domain.Case; -import com.netgrif.application.engine.workflow.service.interfaces.IUserFilterSearchService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; - -import java.util.Collections; -import java.util.List; - -@Service -public class UserFilterSearchService implements IUserFilterSearchService { - - @Autowired - private IElasticCaseService caseSearchService; - - @Autowired - private IUserService userService; - - @Override - public List autocompleteFindFilters(String userInput) { - Page page = this.caseSearchService.search(Collections.singletonList( - CaseSearchRequest.builder() - .process(Collections.singletonList(new CaseSearchRequest.PetriNet(FilterRunner.FILTER_PETRI_NET_IDENTIFIER))) - .query( - String.format("(title:%s*) AND ((dataSet.visibility.keyValue:private AND authorEmail:%s) OR (dataSet.visibility.keyValue:public))", - userInput, - userService.getLoggedUser().getEmail()) - ) - .transition(Collections.singletonList("view_filter")) - .build() - ), - this.userService.getLoggedOrSystem().transformToLoggedUser(), - PageRequest.of(0, 100), - LocaleContextHolder.getLocale(), - true); - if (page.hasContent()) { - return page.getContent(); - } - return Collections.emptyList(); - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java b/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java index 87bcb62a025..888a074efec 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java @@ -5,7 +5,6 @@ import com.netgrif.application.engine.auth.service.interfaces.IUserService; import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseMappingService; import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; -import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; import com.netgrif.application.engine.history.domain.caseevents.CreateCaseEventLog; import com.netgrif.application.engine.history.domain.caseevents.DeleteCaseEventLog; import com.netgrif.application.engine.history.service.IHistoryService; @@ -31,6 +30,7 @@ import com.netgrif.application.engine.workflow.service.interfaces.IInitValueExpressionEvaluator; import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import com.querydsl.core.types.ExpressionUtils; import com.querydsl.core.types.Predicate; import org.bson.types.ObjectId; import org.slf4j.Logger; @@ -75,7 +75,7 @@ public class WorkflowService implements IWorkflowService { protected ITaskService taskService; @Autowired - protected CaseSearchService searchService; + protected LegacyCaseSearchService searchService; @Autowired protected ApplicationEventPublisher publisher; @@ -186,8 +186,14 @@ public Page findAllByUri(String uri, Pageable pageable) { @Override public Page search(Predicate predicate, Pageable pageable) { - Page page = repository.findAll(predicate, pageable); + if (predicate == null) { + return Page.empty(); + } + Predicate permissionConstraints = searchService.buildPermissionConstraints(userService.getLoggedOrSystem().transformToLoggedUser()); + Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints); + Page page = repository.findAll(finalPredicate, pageable); page.getContent().forEach(this::setPetriNet); + decryptDataSets(page.getContent()); return setImmediateDataFields(page); } @@ -215,10 +221,31 @@ public long count(Map request, LoggedUser user, Locale locale) { } } + @Override + public long count(Predicate predicate) { + if (predicate == null) { + return 0; + } + + Predicate permissionConstraints = searchService.buildPermissionConstraints(userService.getLoggedOrSystem().transformToLoggedUser()); + Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints); + return repository.count(finalPredicate); + } + + @Override + public boolean exists(Predicate predicate) { + if (predicate == null) { + return false; + } + + Predicate permissionConstraints = searchService.buildPermissionConstraints(userService.getLoggedOrSystem().transformToLoggedUser()); + Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints); + return repository.exists(finalPredicate); + } + @Override public Case resolveUserRef(Case useCase) { useCase.getUsers().clear(); - useCase.getNegativeViewUsers().clear(); useCase.getUserRefs().forEach((id, permission) -> { resolveUserRefPermissions(useCase, id, permission); }); @@ -230,11 +257,7 @@ public Case resolveUserRef(Case useCase) { private void resolveUserRefPermissions(Case useCase, String userListId, Map permission) { List userIds = getExistingUsers((UserListFieldValue) useCase.getDataSet().get(userListId).getValue()); if (userIds != null && !userIds.isEmpty()) { - if (permission.containsKey("view") && !permission.get("view")) { - useCase.getNegativeViewUsers().addAll(userIds); - } else { - useCase.addUsers(new HashSet<>(userIds), permission); - } + useCase.addUsers(new HashSet<>(userIds), permission); } } @@ -309,8 +332,12 @@ public CreateCaseEventOutcome createCase(String netId, Function ma } public CreateCaseEventOutcome createCase(String netId, Function makeTitle, String color, LoggedUser user, Map params) { + if (!petriNetService.isDeployed(netId)) { + throw new IllegalArgumentException("Petri net with id [" + netId + "] is not deployed."); + } LoggedUser loggedOrImpersonated = user.getSelfOrImpersonated(); PetriNet petriNet = petriNetService.clone(new ObjectId(netId)); + int rulesExecuted; Case useCase = new Case(petriNet); useCase.populateDataSet(initValueExpressionEvaluator, params); @@ -508,11 +535,11 @@ public List getData(String caseId) { } private void setImmediateDataFieldsReadOnly(Case useCase) { - List immediateData = new ArrayList<>(); + List> immediateData = new ArrayList<>(); useCase.getImmediateDataFields().forEach(fieldId -> { try { - Field clone = fieldFactory.buildImmediateField(useCase, fieldId); + Field clone = fieldFactory.buildImmediateField(useCase, fieldId); immediateData.add(clone); } catch (Exception e) { log.error("Could not built immediate field [" + fieldId + "]"); @@ -529,7 +556,7 @@ protected Page setImmediateDataFields(Page cases) { } protected Case setImmediateDataFields(Case useCase) { - List immediateData = new ArrayList<>(); + List> immediateData = new ArrayList<>(); useCase.getImmediateDataFields().forEach(fieldId -> immediateData.add(fieldFactory.buildImmediateField(useCase, fieldId)) diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFilterImportExportService.java b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFilterImportExportService.java deleted file mode 100644 index 6a4cd89ef46..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFilterImportExportService.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.netgrif.application.engine.workflow.service.interfaces; - -import com.netgrif.application.engine.auth.domain.IUser; -import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; -import com.netgrif.application.engine.workflow.domain.filter.FilterImportExportList; -import com.netgrif.application.engine.petrinet.domain.dataset.FileFieldValue; -import com.netgrif.application.engine.workflow.domain.IllegalFilterFileException; - -import java.io.IOException; -import java.util.Collection; -import java.util.List; -import java.util.Map; - - -/** - * Interface which provides methods for filter import and export. - */ - -public interface IFilterImportExportService { - - FileFieldValue exportFiltersToFile(Collection filtersToExport) throws IOException; - - FilterImportExportList exportFilters(Collection filtersToExport); - - List importFilters() throws IOException, IllegalFilterFileException, TransitionNotExecutableException; - - Map importFilters(FilterImportExportList filters) throws IOException, TransitionNotExecutableException; - - void createFilterImport(IUser author); - - void createFilterExport(IUser author); - - void changeFilterField(Collection filterFields); -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IMenuImportExportService.java b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IMenuImportExportService.java deleted file mode 100644 index 60a3cf857f7..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IMenuImportExportService.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.netgrif.application.engine.workflow.service.interfaces; - -import com.netgrif.application.engine.petrinet.domain.I18nString; -import com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField; -import com.netgrif.application.engine.petrinet.domain.dataset.FileField; -import com.netgrif.application.engine.petrinet.domain.dataset.FileFieldValue; -import com.netgrif.application.engine.petrinet.domain.dataset.MultichoiceMapField; -import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; -import com.netgrif.application.engine.workflow.domain.Case; -import com.netgrif.application.engine.workflow.domain.IllegalMenuFileException; -import com.netgrif.application.engine.workflow.domain.menu.MenuEntry; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - - -/** - * Interface which provides methods for menu import and export. - */ - -public interface IMenuImportExportService { - - Map createAvailableEntriesChoices(List menuItemCases); - - Map addSelectedEntriesToExport(MultichoiceMapField availableEntries, EnumerationMapField menusForExport, String menuidentifier); - - FileFieldValue exportMenu(EnumerationMapField menusForExport, String groupId, FileField fileField) throws IOException; - - List importMenu(List menuItemCases, FileFieldValue ffv, String groupCaseId) throws IOException, IllegalMenuFileException, TransitionNotExecutableException; - - String createMenuItemCase(StringBuilder resultMessage, MenuEntry item, String menuIdentifier, String groupCaseId, String filterCaseId); -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskService.java b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskService.java index 691b2de4a9b..0a5e825b973 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskService.java @@ -33,6 +33,10 @@ public interface ITaskService { long count(List requests, LoggedUser user, Locale locale, Boolean isIntersection); + long count(com.querydsl.core.types.Predicate predicate); + + boolean exists(com.querydsl.core.types.Predicate predicate); + Page findByCases(Pageable pageable, List cases); List findAllById(List ids); diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IUserFilterSearchService.java b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IUserFilterSearchService.java deleted file mode 100644 index 5689e4c41b0..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IUserFilterSearchService.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.netgrif.application.engine.workflow.service.interfaces; - -import com.netgrif.application.engine.workflow.domain.Case; - -import java.util.List; - -public interface IUserFilterSearchService { - List autocompleteFindFilters(String userInput); -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowService.java b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowService.java index 7eb8318173a..f9e347b56fd 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowService.java @@ -75,6 +75,10 @@ public interface IWorkflowService { long count(Map request, LoggedUser user, Locale locale); + long count(Predicate predicate); + + boolean exists(Predicate predicate); + // List getCaseFieldChoices(Pageable pageable, String caseId, String fieldId); boolean removeTasksFromCase(List tasks, String caseId); diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java b/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java index 0e295542652..b03a22608b4 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java +++ b/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java @@ -7,6 +7,11 @@ import com.netgrif.application.engine.eventoutcomes.LocalisedEventOutcomeFactory; import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; +import com.netgrif.application.engine.pfql.service.taskresource.TaskSearchService; +import com.netgrif.application.engine.pfql.service.utils.SearchUtils; +import com.netgrif.application.engine.utils.throwable.BadRequestException; +import com.netgrif.application.engine.utils.throwable.InternalServerErrorException; import com.netgrif.application.engine.workflow.domain.IllegalArgumentWithChangedFieldsException; import com.netgrif.application.engine.workflow.domain.MergeFilterOperation; import com.netgrif.application.engine.workflow.domain.Task; @@ -15,14 +20,17 @@ import com.netgrif.application.engine.workflow.domain.eventoutcomes.response.EventOutcomeWithMessage; import com.netgrif.application.engine.workflow.domain.eventoutcomes.response.EventOutcomeWithMessageResource; import com.netgrif.application.engine.workflow.service.FileFieldInputStream; +import com.netgrif.application.engine.workflow.service.LegacyTaskSearchService; import com.netgrif.application.engine.workflow.service.interfaces.IDataService; import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.netgrif.application.engine.workflow.web.requestbodies.file.FileFieldRequest; import com.netgrif.application.engine.workflow.web.requestbodies.singleaslist.SingleTaskSearchRequestAsList; import com.netgrif.application.engine.workflow.web.responsebodies.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; @@ -43,26 +51,15 @@ import java.util.*; import java.util.stream.Collectors; +@Slf4j +@RequiredArgsConstructor public abstract class AbstractTaskController { - public static final Logger log = LoggerFactory.getLogger(TaskController.class); - private final ITaskService taskService; - private final IDataService dataService; - private final IWorkflowService workflowService; - - private final IElasticTaskService searchService; - - public AbstractTaskController(ITaskService taskService, IDataService dataService, IWorkflowService workflowService, - IElasticTaskService searchService) { - this.taskService = taskService; - this.dataService = dataService; - this.workflowService = workflowService; - this.searchService = searchService; - } - + private final IElasticTaskService elasticTaskService; + private final LegacyTaskSearchService searchService; public PagedModel getAll(Authentication auth, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); @@ -184,7 +181,7 @@ public PagedModel searchPublic(LoggedUser loggedUser, Pag } public PagedModel searchElastic(Authentication auth, Pageable pageable, SingleElasticTaskSearchRequestAsList searchBody, MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { - Page tasks = searchService.search(searchBody.getList(), (LoggedUser) auth.getPrincipal(), pageable, locale, operation == MergeFilterOperation.AND); + Page tasks = elasticTaskService.search(searchBody.getList(), (LoggedUser) auth.getPrincipal(), pageable, locale, operation == MergeFilterOperation.AND); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) .searchElastic(auth, pageable, searchBody, operation, assembler, locale)).withRel("search_es"); PagedModel resources = assembler.toModel(tasks, new TaskResourceAssembler(locale), selfLink); @@ -192,8 +189,34 @@ public PagedModel searchElastic(Authentication auth, Page return resources; } + public PagedModel searchPfql(Authentication auth, Pageable pageable, SingleTaskSearchRequestAsList searchBody, MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { + try { + Predicate completePredicate = searchBody.getList().stream() + .map((request) -> { + if (request.query == null || request.query.isEmpty()) { + return searchService.buildSingleQuery(request, (LoggedUser) auth.getPrincipal(), locale); + } else { + QueryLangEvaluator evaluator = SearchUtils.evaluateQuery(request.query); + return evaluator.getFullMongoQuery(); + } + }) + .reduce(ExpressionUtils::and).orElse(null); + Page tasks = taskService.search(completePredicate, pageable); + Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) + .searchPfql(auth, pageable, searchBody, operation, assembler, locale)).withRel("search_pfql"); + PagedModel resources = assembler.toModel(tasks, new TaskResourceAssembler(locale), selfLink); + ResourceLinkAssembler.addLinks(resources, Task.class, selfLink.getRel().toString()); + return resources; + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad request: " + e.getMessage()); + } catch (Exception e) { + log.error("Something went wrong while searching for tasks", e); + throw new InternalServerErrorException("Something went wrong"); + } + } + public CountResponse count(SingleElasticTaskSearchRequestAsList query, MergeFilterOperation operation, Authentication auth, Locale locale) { - long count = searchService.count(query.getList(), (LoggedUser) auth.getPrincipal(), locale, operation == MergeFilterOperation.AND); + long count = elasticTaskService.count(query.getList(), (LoggedUser) auth.getPrincipal(), locale, operation == MergeFilterOperation.AND); return CountResponse.taskCount(count); } diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java b/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java index 02818262191..eed5c242756 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java +++ b/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java @@ -5,6 +5,7 @@ import com.netgrif.application.engine.auth.service.interfaces.IUserService; import com.netgrif.application.engine.workflow.domain.MergeFilterOperation; import com.netgrif.application.engine.workflow.domain.eventoutcomes.response.EventOutcomeWithMessage; +import com.netgrif.application.engine.workflow.service.LegacyTaskSearchService; import com.netgrif.application.engine.workflow.service.interfaces.IDataService; import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; @@ -46,15 +47,13 @@ @RequestMapping({"/api/public/task"}) public class PublicTaskController extends AbstractTaskController { - final IUserService userService; + private final IUserService userService; private final ITaskService taskService; - private final IDataService dataService; public PublicTaskController(ITaskService taskService, IDataService dataService, IUserService userService, - IWorkflowService workflowService) { - super(taskService, dataService, workflowService, null); + IWorkflowService workflowService, LegacyTaskSearchService searchService) { + super(taskService, dataService, workflowService, null, searchService); this.taskService = taskService; - this.dataService = dataService; this.userService = userService; } diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java b/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java index 593f37ce4ee..6bd73eaaed2 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java +++ b/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java @@ -7,6 +7,7 @@ import com.netgrif.application.engine.workflow.domain.MergeFilterOperation; import com.netgrif.application.engine.workflow.domain.Task; import com.netgrif.application.engine.workflow.domain.eventoutcomes.response.EventOutcomeWithMessage; +import com.netgrif.application.engine.workflow.service.LegacyTaskSearchService; import com.netgrif.application.engine.workflow.service.interfaces.IDataService; import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; @@ -20,8 +21,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.core.io.Resource; import org.springframework.data.domain.Pageable; @@ -40,21 +40,20 @@ import java.util.List; import java.util.Locale; +@Slf4j @RestController +@Tag(name = "Task") @RequestMapping("/api/task") @ConditionalOnProperty( value = "nae.task.web.enabled", havingValue = "true", matchIfMissing = true ) -@Tag(name = "Task") public class TaskController extends AbstractTaskController { - public static final Logger log = LoggerFactory.getLogger(TaskController.class); - public TaskController(ITaskService taskService, IDataService dataService, IWorkflowService workflowService, - IElasticTaskService searchService) { - super(taskService, dataService, workflowService, searchService); + IElasticTaskService elasticTaskService, LegacyTaskSearchService searchService) { + super(taskService, dataService, workflowService, elasticTaskService, searchService); } @Override @@ -169,6 +168,13 @@ public PagedModel searchElastic(Authentication auth, Page return super.searchElastic(auth, pageable, searchBody, operation, assembler, locale); } + @Override + @Operation(summary = "PFQL task search", security = {@SecurityRequirement(name = "BasicAuth")}) + @PostMapping(value = "/search_pfql", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + public PagedModel searchPfql(Authentication auth, Pageable pageable, @RequestBody SingleTaskSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { + return super.searchPfql(auth, pageable, searchBody, operation, assembler, locale); + } + @Override @Operation(summary = "Count tasks by provided criteria", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/count", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java b/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java index d063e4ca274..095a6de4f80 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java +++ b/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java @@ -5,6 +5,10 @@ import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; import com.netgrif.application.engine.elastic.web.requestbodies.singleaslist.SingleCaseSearchRequestAsList; import com.netgrif.application.engine.eventoutcomes.LocalisedEventOutcomeFactory; +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; +import com.netgrif.application.engine.pfql.service.utils.SearchUtils; +import com.netgrif.application.engine.utils.throwable.BadRequestException; +import com.netgrif.application.engine.utils.throwable.InternalServerErrorException; import com.netgrif.application.engine.workflow.domain.Case; import com.netgrif.application.engine.workflow.domain.MergeFilterOperation; import com.netgrif.application.engine.workflow.domain.eventoutcomes.caseoutcomes.CreateCaseEventOutcome; @@ -17,15 +21,13 @@ import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.netgrif.application.engine.workflow.web.requestbodies.CreateCaseBody; import com.netgrif.application.engine.workflow.web.responsebodies.*; -import com.querydsl.core.types.Predicate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.io.InputStreamResource; @@ -33,7 +35,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.data.querydsl.binding.QuerydslPredicate; import org.springframework.data.web.PagedResourcesAssembler; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; @@ -55,29 +56,22 @@ import java.util.Locale; import java.util.Map; -@RestController() +@Slf4j +@RestController +@Tag(name = "Workflow") +@RequiredArgsConstructor @RequestMapping("/api/workflow") @ConditionalOnProperty( value = "nae.case.web.enabled", havingValue = "true", matchIfMissing = true ) -@Tag(name = "Workflow") public class WorkflowController { - private static final Logger log = LoggerFactory.getLogger(WorkflowController.class.getName()); - - @Autowired - private IWorkflowService workflowService; - - @Autowired - private ITaskService taskService; - - @Autowired - private IElasticCaseService elasticCaseService; - - @Autowired - private IDataService dataService; + private final IWorkflowService workflowService; + private final ITaskService taskService; + private final IElasticCaseService elasticCaseService; + private final IDataService dataService; @PreAuthorize("@workflowAuthorizationService.canCallCreate(#auth.getPrincipal(), #body.netId)") @@ -106,17 +100,6 @@ public PagedModel getAll(Pageable pageable, PagedResourcesAssemble return resources; } - @Operation(summary = "Generic case search with QueryDSL predicate", security = {@SecurityRequirement(name = "BasicAuth")}) - @PostMapping(value = "/case/search2", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public PagedModel search2(@QuerydslPredicate(root = Case.class) Predicate predicate, Pageable pageable, PagedResourcesAssembler assembler) { - Page cases = workflowService.search(predicate, pageable); - Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(WorkflowController.class) - .search2(predicate, pageable, assembler)).withRel("search2"); - PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); - ResourceLinkAssembler.addLinks(resources, Case.class, selfLink.getRel().toString()); - return resources; - } - @Operation(summary = "Generic case search on Elasticsearch database", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/case/search", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) public PagedModel search(@RequestBody SingleCaseSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, Pageable pageable, PagedResourcesAssembler assembler, Authentication auth, Locale locale) { @@ -131,18 +114,34 @@ public PagedModel search(@RequestBody SingleCaseSearchRequestAsLis return resources; } - @Operation(summary = "Generic case search on Mongo database", security = {@SecurityRequirement(name = "BasicAuth")}) - @PostMapping(value = "/case/search_mongo", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public PagedModel searchMongo(@RequestBody Map searchBody, Pageable pageable, PagedResourcesAssembler assembler, Authentication auth, Locale locale) { - Page cases = workflowService.search(searchBody, pageable, (LoggedUser) auth.getPrincipal(), locale); - Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(WorkflowController.class) - .searchMongo(searchBody, pageable, assembler, auth, locale)).withRel("search"); - PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); - ResourceLinkAssembler.addLinks(resources, Case.class, selfLink.getRel().toString()); - return resources; + @Operation(summary = "PFQL case search", security = {@SecurityRequirement(name = "BasicAuth")}) + @PostMapping(value = "/case/search_pfql", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + public PagedModel searchPfql(@RequestBody SingleCaseSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, Pageable pageable, PagedResourcesAssembler assembler, Authentication auth, Locale locale) { + try { + LoggedUser user = (LoggedUser) auth.getPrincipal(); + searchBody.getList().forEach((request) -> { + if (request.query == null || request.query.isEmpty()) { + return; + } + QueryLangEvaluator evaluator = SearchUtils.evaluateQuery(request.query); + request.query = evaluator.getFullElasticQuery(); + }); + Page cases = elasticCaseService.search(searchBody.getList(), user, pageable, locale, operation == MergeFilterOperation.AND); + + Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(WorkflowController.class) + .searchPfql(searchBody, operation, pageable, assembler, auth, locale)).withRel("search_pfql"); + + PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); + ResourceLinkAssembler.addLinks(resources, ElasticCase.class, selfLink.getRel().toString()); + return resources; + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad request: " + e.getMessage()); + } catch (Exception e) { + log.error("Something went wrong while searching cases by PFQL", e); + throw new InternalServerErrorException("Something went wrong while searching cases by PFQL"); + } } - @Operation(summary = "Get count of the cases", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/case/count", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public CountResponse count(@RequestBody SingleCaseSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, Authentication auth, Locale locale) { diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/TaskSearchRequest.java b/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/TaskSearchRequest.java index 65f48794c15..68b2a1e8f75 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/TaskSearchRequest.java +++ b/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/TaskSearchRequest.java @@ -48,4 +48,6 @@ public class TaskSearchRequest implements Serializable { public List stringId; public Map tags; + + public String query; } \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedCaseFilterField.java b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedCaseFilterField.java new file mode 100644 index 00000000000..d63e8d1d946 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedCaseFilterField.java @@ -0,0 +1,18 @@ +package com.netgrif.application.engine.workflow.web.responsebodies; + +import com.netgrif.application.engine.petrinet.domain.Component; +import com.netgrif.application.engine.petrinet.domain.dataset.CaseFilterField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Locale; + +@Data +@EqualsAndHashCode(callSuper = true) +public class LocalisedCaseFilterField extends LocalisedField { + + public LocalisedCaseFilterField(CaseFilterField field, Locale locale) { + super(field, locale); + setComponent(new Component("string_query")); + } +} diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedFieldFactory.java b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedFieldFactory.java index 34cdb1bfb3a..2244feac92a 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedFieldFactory.java +++ b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedFieldFactory.java @@ -35,8 +35,12 @@ public static LocalisedField from(Field field, Locale locale) { return fromCase((CaseField) field, locale); } else if (field instanceof FileListField) { return fromFileList((FileListField) field, locale); - } else if (field instanceof FilterField) { - return fromFilter((FilterField) field, locale); + } else if (field instanceof CaseFilterField) { + return fromCaseFilter((CaseFilterField) field, locale); + } else if (field instanceof TaskFilterField) { + return fromTaskFilter((TaskFilterField) field, locale); + } else if (field instanceof ProcessFilterField) { + return fromProcessFilter((ProcessFilterField) field, locale); } else if (field instanceof I18nField) { return fromI18n((I18nField) field, locale); } else { @@ -100,8 +104,16 @@ public static LocalisedField fromFileList(FileListField field, Locale locale) { return new LocalisedFileListField(field, locale); } - public static LocalisedField fromFilter(FilterField field, Locale locale) { - return new LocalisedFilterField(field, locale); + public static LocalisedField fromCaseFilter(CaseFilterField field, Locale locale) { + return new LocalisedCaseFilterField(field, locale); + } + + public static LocalisedField fromTaskFilter(TaskFilterField field, Locale locale) { + return new LocalisedTaskFilterField(field, locale); + } + + public static LocalisedField fromProcessFilter(ProcessFilterField field, Locale locale) { + return new LocalisedProcessFilterField(field, locale); } public static LocalisedField fromI18n(I18nField field, Locale locale) { diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedFilterField.java b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedFilterField.java deleted file mode 100644 index 6ce7ede4136..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedFilterField.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.netgrif.application.engine.workflow.web.responsebodies; - -import com.netgrif.application.engine.petrinet.domain.dataset.FilterField; -import lombok.Data; - -import java.util.List; -import java.util.Locale; - -@Data -public class LocalisedFilterField extends LocalisedField { - - private List allowedNets; - private Object filterMetadata; - - public LocalisedFilterField(FilterField field, Locale locale) { - super(field, locale); - this.allowedNets = field.getAllowedNets(); - this.filterMetadata = field.getFilterMetadata(); - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedProcessFilterField.java b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedProcessFilterField.java new file mode 100644 index 00000000000..70886f8caaf --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedProcessFilterField.java @@ -0,0 +1,18 @@ +package com.netgrif.application.engine.workflow.web.responsebodies; + +import com.netgrif.application.engine.petrinet.domain.Component; +import com.netgrif.application.engine.petrinet.domain.dataset.ProcessFilterField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Locale; + +@Data +@EqualsAndHashCode(callSuper = true) +public class LocalisedProcessFilterField extends LocalisedField { + + public LocalisedProcessFilterField(ProcessFilterField field, Locale locale) { + super(field, locale); + setComponent(new Component("string_query")); + } +} diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskFilterField.java b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskFilterField.java new file mode 100644 index 00000000000..1a04a13a75a --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskFilterField.java @@ -0,0 +1,18 @@ +package com.netgrif.application.engine.workflow.web.responsebodies; + +import com.netgrif.application.engine.petrinet.domain.Component; +import com.netgrif.application.engine.petrinet.domain.dataset.TaskFilterField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Locale; + +@Data +@EqualsAndHashCode(callSuper = true) +public class LocalisedTaskFilterField extends LocalisedField { + + public LocalisedTaskFilterField(TaskFilterField field, Locale locale) { + super(field, locale); + setComponent(new Component("string_query")); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 24e03947dae..da0e9456ff9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -187,3 +187,5 @@ nae.filter.export.file-name=filters.xml # Minio nae.storage.minio.enabled=false + +nae.actions.star-imports=java.time,com.netgrif.application.engine.petrinet.domain.version,com.netgrif.application.engine.petrinet.domain \ No newline at end of file diff --git a/src/main/resources/petriNets/FM_v0_2.xml b/src/main/resources/petriNets/FM_v0_2.xml index e6620e07125..7da0e68bde7 100644 --- a/src/main/resources/petriNets/FM_v0_2.xml +++ b/src/main/resources/petriNets/FM_v0_2.xml @@ -1,7 +1,8 @@ examples/FM/fm_archivacia - FM Archivácia + 1.0.0 + FM Archivácia FM Archivácia diff --git a/src/main/resources/petriNets/all_data.xml b/src/main/resources/petriNets/all_data.xml index fa77752807f..9753cf7f6f3 100644 --- a/src/main/resources/petriNets/all_data.xml +++ b/src/main/resources/petriNets/all_data.xml @@ -1,7 +1,8 @@ data/all_data - All Data + 1.0.0 + All Data ALL All data test default case name diff --git a/src/main/resources/petriNets/engine-processes/dashboard.xml b/src/main/resources/petriNets/engine-processes/dashboard.xml index 832052df500..d88e2bba709 100644 --- a/src/main/resources/petriNets/engine-processes/dashboard.xml +++ b/src/main/resources/petriNets/engine-processes/dashboard.xml @@ -1,5 +1,6 @@ dashboard + 1.0.0 DSH Dashboard dashboard diff --git a/src/main/resources/petriNets/engine-processes/dashboard_item.xml b/src/main/resources/petriNets/engine-processes/dashboard_item.xml new file mode 100644 index 00000000000..517ecd41614 --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/dashboard_item.xml @@ -0,0 +1,769 @@ + + dashboard_item + 1.0.0 + DBI + Dashboard item + crop_square + true + false + false + + + system + + true + true + true + + + + admin + + true + true + true + + + + default + + true + + + + + + menu_item_delete + + + removeRefFromParent() + + + + + + + system + System + + + admin + Admin + + + + { -> + def cases = findCasesElastic("processIdentifier:\"dashboard_management\" AND dataSet.items_order.textValue:\"$useCase.stringId\"") + cases.each { refCase -> + def refTask = refCase.tasks.find { it.transition == "configuration" }.task + def itemsOrder = refCase.dataSet["items_order"].value.split(",") as ArrayList + itemsOrder.remove(useCase.stringId) + + def dashboardItemsListOptions = refCase.dataSet["dashboard_item_list"].options + dashboardItemsListOptions.remove(useCase.stringId) + + setData(refTask,[ + "dashboard_item_list": ["type": "enumeration_map", "value": "", "options": dashboardItemsListOptions], + "items_order": ["type": "text", "value": itemsOrder.join(",")] + ]) + } + + } + + + + item_id + Dashboard item ID + Unique identifier of dashboard item + + item_id_set + + + item_id: f.item_id; + + if (item_id.value == "") { + return + } + def itemWithIdExists = findCaseElastic("processIdentifier:\"dashboard_item\" AND dataSet.item_id.textValue.keyword:\"${item_id.value}\" AND dataSet.is_active.booleanValue:true" as String) + if (itemWithIdExists != null) { + throw new IllegalArgumentException("Dashboard item with given ID already exists.") + } + + + + + + menu_items_list + Existing menu items + List of all existing menu items + + autocomplete_dynamic + + + menu_items_list_get + + + menu_items_list: f.menu_items_list; + + def menuItemCases = findCasesElastic("processIdentifier:\"menu_item\"" as String) + .collectEntries { [(it.stringId): it.dataSet["menu_name"].value] } + change menu_items_list options { menuItemCases } + + + + + menu_items_list_set + + + icon_color: f.icon_color, + font_color: f.font_color, + font_weight: f.font_weight, + inherit_name: f.inherit_name, + inherit_icon: f.inherit_icon, + menu_item_taskRef: f.menu_item_taskRef, + filter_divider: f.filter_divider, + menu_items_list: f.menu_items_list, + trans: t.configuration; + + change inherit_icon value { true } + change inherit_name value { true } + if (menu_items_list.value != null && org.bson.types.ObjectId.isValid(menu_items_list.value)) { + make [inherit_name, inherit_icon, icon_color, font_color, font_weight], editable on trans when { true } + make [menu_item_taskRef, filter_divider], visible on trans when { true } + def menuItemCase = workflowService.findOne(menu_items_list.value) + change menu_item_taskRef value { [menuItemCase.tasks.find { it.transition == "item_settings" }?.task] } + } else { + make [inherit_name, inherit_icon, icon_color, font_color, font_weight], visible on trans when { true } + make [menu_item_taskRef, filter_divider], hidden on trans when { true } + change menu_item_taskRef value { [] } + } + + + + + + is_internal + Internal item + Internal menu item or link to external website URL + true + + is_internal_set + + + item_name: f.item_name, + item_icon: f.item_icon, + external_icon: f.external_icon, + menu_items_list: f.menu_items_list, + external_url: f.external_url, + is_internal: f.is_internal; + + change external_icon value { false } + if (is_internal.value) { + change menu_items_list value { "" } + } else { + change item_icon value { "" } + change item_name value { new com.netgrif.application.engine.petrinet.domain.I18nString("") } + change external_url value { "" } + } + + + font_color: f.font_color, + font_weight: f.font_weight, + is_internal: f.is_internal, + item_name: f.item_name, + item_icon: f.item_icon, + external_icon: f.external_icon, + menu_items_list: f.menu_items_list, + item_icon_preview: f.item_icon_preview, + external_url: f.external_url, + inherit_name: f.inherit_name, + inherit_icon: f.inherit_icon, + menu_item_taskRef: f.menu_item_taskRef, + filter_divider: f.filter_divider, + trans: t.configuration; + + if (is_internal.value) { + make [menu_items_list], editable on trans when { true } + make [external_url], hidden on trans when { true } + } else { + make [item_icon, item_name, external_url, external_icon, font_color, font_weight], editable on trans when { true } + make item_icon_preview, visible on trans when { true } + make [inherit_name, inherit_icon, menu_items_list, menu_item_taskRef, filter_divider], hidden on trans when { true } + } + + + + + + external_url + External URL + Link for external website URL + + + external_icon + Icon from URL + Icon will be loaded from external website URL + + external_icon_set + + + menu_items_list: f.menu_items_list, + inherit_icon: f.inherit_icon, + external_icon: f.external_icon, + icon_color: f.icon_color, + item_icon: f.item_icon, + is_internal: f.is_internal, + trans: t.configuration; + + if (!inherit_icon.value || !is_internal.value) { + change item_icon value { "" } + } + if (external_icon.value) { + make icon_color, visible on trans when { true } + } else if (!(is_internal.value && (menu_items_list.value == null || !org.bson.types.ObjectId.isValid(menu_items_list.value)))) { + make icon_color, editable on trans when { true } + } + + + + + + icon_color + Icon color + Icon color of item. Valid CSS property value font-color. + #0790ff + + icon_color_set + + + icon_color: f.icon_color, + icon: f.item_icon, + iconPreview: f.item_icon_preview; + + change iconPreview value { + + icon_color.value + """]]> + icon.value + """]]> + } + + + + + + item_icon + Item icon identifier + Material icon identifier. List of icons with identifiers is available online. Icon will be displayed in dashboard. + + icon_color: f.icon_color, + external_icon: f.external_icon, + icon: f.this, + iconPreview: f.item_icon_preview; + + if (icon.value == "") { + change iconPreview value {"""]]>} + return; + } + if (external_icon.value) { + change iconPreview value { + + icon.value + """]]> + } + } else { + change iconPreview value { + + icon_color.value + """]]> + icon.value + """]]> + } + } + + + + + item_icon_preview + + <component> + <name>htmltextarea</name> + </component> + </data> + <data type="text" immediate="true"> + <id>font_color</id> + <title name="font_color">Font color + Font color of item name. Valid CSS property value font-color. + #0790ff + + + font_weight + Font weight + Font weight of item name. Valid CSS property value font-weight. + 400 + + + item_name + Item name + Item name which will be displayed in dashboard + + + inherit_icon + Inherit icon + Check if icon should be inherited from menu item + true + + inherit_icon_set + + + inherit_icon: f.inherit_icon, + item_icon_preview: f.item_icon_preview, + external_icon: f.external_icon, + item_icon: f.item_icon, + menu_items_list: f.menu_items_list, + trans: t.configuration; + + change external_icon value { false } + if (inherit_icon.value) { + make [item_icon, item_icon_preview, external_icon], visible on trans when { true } + if (menu_items_list.value != null && org.bson.types.ObjectId.isValid(menu_items_list.value)) { + def menuItemCase = workflowService.findOne(menu_items_list.value) + change item_icon value { menuItemCase.dataSet["menu_icon"].value } + } else { + change item_icon value { "" } + } + } else { + make [item_icon, external_icon], editable on trans when { true } + make item_icon_preview, visible on trans when { true } + change item_icon value { "" } + } + + + + + + inherit_name + Inherit name from menu item + Name should be inherited from menu item + true + + inherit_name_set + + + inherit_name: f.inherit_name, + item_name: f.item_name, + menu_items_list: f.menu_items_list; + + if (inherit_name.value) { + if (menu_items_list.value != null && org.bson.types.ObjectId.isValid(menu_items_list.value)) { + def menuItemCase = workflowService.findOne(menu_items_list.value) + change item_name value { menuItemCase.dataSet["menu_name"].value } + } else { + change item_name value { new com.netgrif.application.engine.petrinet.domain.I18nString("") } + } + } else { + change item_name value { new com.netgrif.application.engine.petrinet.domain.I18nString("") } + } + + + inherit_name: f.inherit_name, + item_name: f.item_name, + trans: t.configuration; + + if (inherit_name.value) { + make item_name, visible on trans when { true } + } else { + make item_name, editable on trans when { true } + } + + + + + + menu_item_taskRef + + </data> + <data type="i18n"> + <id>filter_divider</id> + <title/> + <init dynamic="true"> + new com.netgrif.application.engine.petrinet.domain.I18nString("Menu item data", [sk: "Dáta menu položky", de: "Daten zum Menüpunkt"]) + </init> + <component> + <name>divider</name> + <property key="fontSize">16</property> + </component> + </data> + <data type="boolean" immediate="true"> + <id>is_active</id> + <title/> + <init>false</init> + </data> + + + <!-- I18NS --> + <i18n locale="sk"> + <i18nString name="item_id">ID položky panelu</i18nString> + <i18nString name="item_id_desc">Unikátny identifikátor položky panelu</i18nString> + <i18nString name="menu_items_list">Existujúce menu položky</i18nString> + <i18nString name="menu_items_list_desc">Zoznam všetkých existujúcich menu položiek</i18nString> + <i18nString name="is_internal">Interná položka</i18nString> + <i18nString name="is_internal_desc">Položka interného menu alebo odkaz na externú webovú adresu URL</i18nString> + <i18nString name="external_url">Externá adresa URL</i18nString> + <i18nString name="external_url_desc">Odkaz na externú webovú adresu URL</i18nString> + <i18nString name="item_icon">Identifikátor ikony položky</i18nString> + <i18nString name="item_icon_desc">Identifikátor Material ikony. Zoznam ikon s identifikátormi je dostupný online. Ikona sa zobrazí v panely.</i18nString> + <i18nString name="external_icon">Ikona z adresy URL</i18nString> + <i18nString name="external_icon_desc">Ikona bude načítaná z externej URL adresy</i18nString> + <i18nString name="icon_color">Farba ikony</i18nString> + <i18nString name="icon_color_desc">Farba ikony položky. Valídna CSS hodnota font-color.</i18nString> + <i18nString name="font_color">Farba písma</i18nString> + <i18nString name="font_color_desc">Farba písma názvu položky. Valídna CSS hodnota font-color.</i18nString> + <i18nString name="font_weight">Hrúbka písma</i18nString> + <i18nString name="font_weight_desc">Hrúbka písma menu položky. Valídna CSS hodnota font-weight.</i18nString> + <i18nString name="item_name">Názov položky</i18nString> + <i18nString name="item_name_desc">Názov položky, ktorá sa zobrazí v panely</i18nString> + <i18nString name="inherit_icon">Prebrať ikonu</i18nString> + <i18nString name="inherit_icon_desc">Ikona by mala byť prebratá z menu položky</i18nString> + <i18nString name="inherit_name">Prebrať názov</i18nString> + <i18nString name="inherit_name_desc">Názov by mal byť prebratý z menu položky</i18nString> + <i18nString name="configuration_label">Konfigurácia položky panelu</i18nString> + </i18n> + <i18n locale="de"> + <i18nString name="item_id">Dashboard-Element-ID</i18nString> + <i18nString name="item_id_desc">Eindeutiger Bezeichner des Dashboard-Elements</i18nString> + <i18nString name="menu_items_list">Vorhandene Menüpunkte</i18nString> + <i18nString name="menu_items_list_desc">Liste aller vorhandenen Menüpunkte</i18nString> + <i18nString name="is_internal">Interner Posten</i18nString> + <i18nString name="is_internal_desc">Interner Menüpunkt oder Link zu externer Website-URL</i18nString> + <i18nString name="external_url">Externe URL</i18nString> + <i18nString name="external_url_desc">Link für externe Website-URL</i18nString> + <i18nString name="item_icon">Bezeichner des Artikelsymbols</i18nString> + <i18nString name="item_icon_desc">Kennung der Materialikone. Die Liste der Symbole mit den Bezeichnungen ist online verfügbar. Das Symbol wird im Dashboard angezeigt.</i18nString> + <i18nString name="external_icon">Symbol aus URL</i18nString> + <i18nString name="external_icon_desc">Das Symbol wird von einer externen Website-URL geladen</i18nString> + <i18nString name="icon_color">Farbe des Symbols</i18nString> + <i18nString name="icon_color_desc">Symbolfarbe des Elements. Gültiger CSS-Eigenschaftswert font-color.</i18nString> + <i18nString name="font_color">Schriftfarbe</i18nString> + <i18nString name="font_color_desc">Schriftfarbe des Artikelnamens. Gültiger CSS-Eigenschaftswert font-color.</i18nString> + <i18nString name="font_weight">Gewicht der Schriftart</i18nString> + <i18nString name="font_weight_desc">Schriftstärke des Artikelnamens. Gültiger CSS-Eigenschaftswert font-weight.</i18nString> + <i18nString name="item_name">Name des Gegenstands</i18nString> + <i18nString name="item_name_desc">Artikelbezeichnung, die im Dashboard angezeigt wird</i18nString> + <i18nString name="inherit_icon">Symbol vererben</i18nString> + <i18nString name="inherit_icon_desc">Prüfen, ob das Symbol vom Menüpunkt geerbt werden soll</i18nString> + <i18nString name="inherit_name">Name von Menüpunkt vererben</i18nString> + <i18nString name="inherit_name_desc">Name sollte vom Menüpunkt geerbt werden</i18nString> + <i18nString name="configuration_label">Konfiguration von Dashboard-Elementen</i18nString> + </i18n> + + <!-- TRANSITIONS --> + <transition> + <id>configuration</id> + <x>400</x> + <y>112</y> + <label name="configuration_label">Dashboard item configuration</label> + <icon>tune</icon> + <assignPolicy>auto</assignPolicy> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + <view>true</view> + <cancel>true</cancel> + <assign>true</assign> + </logic> + </roleRef> + <dataGroup> + <id>configuration_d1</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>item_id</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>is_internal</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>2</x> + <y>0</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <dataGroup> + <id>configuration_internal</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>menu_items_list</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>inherit_name</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>2</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>inherit_icon</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>3</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <dataGroup> + <id>configuration_general</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>item_name</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>font_color</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>2</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>font_weight</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>3</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>item_icon</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>external_icon</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>1</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>standard</appearance> + </layout> + </dataRef> + <dataRef> + <id>icon_color</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>2</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>item_icon_preview</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>3</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>standard</appearance> + </layout> + </dataRef> + <dataRef> + <id>filter_divider</id> + <logic> + <behavior>hidden</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>menu_item_taskRef</id> + <logic> + <behavior>hidden</behavior> + </logic> + <layout> + <x>0</x> + <y>3</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <dataGroup> + <id>configuration_external</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>external_url</id> + <logic> + <behavior>hidden</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>configuration_assign</id> + <actions phase="post"> + <action> + is_active: f.is_active; + + change is_active value { false } + </action> + </actions> + </event> + <event type="finish"> + <id>configuration_finish</id> + <actions phase="pre"> + <action> + item_id: f.item_id, + item_name: f.item_name, + is_internal: f.is_internal, + menu_items_list: f.menu_items_list, + external_url: f.external_url, + is_active: f.is_active; + + if (item_id.value == null || item_id.value == "") { + throw new IllegalArgumentException("Dashboard item ID cannot be empty.") + } + def itemWithIdExists = findCaseElastic("processIdentifier:\"dashboard_item\" AND dataSet.item_id.textValue.keyword:\"${item_id.value}\" AND dataSet.is_active.booleanValue:true" as String) + if (itemWithIdExists != null) { + throw new IllegalArgumentException("Dashboard item with given ID already exists.") + } + if (is_internal.value) { + if (menu_items_list.value == null || !org.bson.types.ObjectId.isValid(menu_items_list.value)){ + throw new IllegalArgumentException("Internal menu item must be selected.") + } + } else { + if (external_url.value == null || external_url.value == "") { + throw new IllegalArgumentException("External URL cannot be empty.") + } + } + change is_active value { true } + </action> + </actions> + </event> + </transition> + <place> + <id>p1</id> + <x>208</x> + <y>112</y> + <label/> + <tokens>1</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>regular</type> + <sourceId>p1</sourceId> + <destinationId>configuration</destinationId> + <multiplicity>1</multiplicity> + <breakpoint> + <x>304</x> + <y>144</y> + </breakpoint> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>configuration</sourceId> + <destinationId>p1</destinationId> + <multiplicity>1</multiplicity> + <breakpoint> + <x>304</x> + <y>80</y> + </breakpoint> + </arc> +</document> diff --git a/src/main/resources/petriNets/engine-processes/dashboard_management.xml b/src/main/resources/petriNets/engine-processes/dashboard_management.xml new file mode 100644 index 00000000000..fa3e8406e8f --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/dashboard_management.xml @@ -0,0 +1,739 @@ +<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://petriflow.com/petriflow.schema.xsd"> + <id>dashboard_management</id> + <version>1.0.0</version> + <initials>DBM</initials> + <title>Dashboard management + dashboard + true + false + false + + + system + + true + true + true + + + + admin + + true + true + true + + + + default + + true + + + + + system + System + + + admin + Admin + + + + dashboard_id + Dashboard ID + Unique identifier of dashboard + + dashboard_id_set + + + dashboard_id: f.dashboard_id; + + if (dashboard_id.value == "") { + return + } + def dashboardWithIdExists = findCaseElastic("processIdentifier:\"dashboard_management\" AND dataSet.dashboard_id.textValue.keyword:\"${dashboard_id.value}\" AND dataSet.is_active.booleanValue:true" as String) + if (dashboardWithIdExists != null) { + throw new IllegalArgumentException("Dashboard with given ID already exists.") + } + + + + + + dashboard_name + Dashboard name + Dashboard name to be displayed in application dashboard + + + dashboard_logo + Dashboard logo + Dashboard logo to be displayed in application dashboard + assets/img/netgrif_full_white.svg + + + dashboard_item_list + Dashboard items + List of dashboard items to be displayed in application dashboard + + list + + + + dashboard_item_to_menu_item + + </data> + <data type="button"> + <id>order_down</id> + <title/> + <placeholder>south</placeholder> + <component> + <name>icon</name> + <property key="stretch">true</property> + </component> + <event type="set"> + <id>order_down_set</id> + <actions phase="post"> + <action> + dashboard_item_to_menu_item: f.dashboard_item_to_menu_item, + items_order: f.items_order, + dashboard_item_list: f.dashboard_item_list; + + if (dashboard_item_list.value == null || dashboard_item_list.value == "") { + return + } + def keysDashboard = dashboard_item_list.options.keySet() as List + def keysMenu = dashboard_item_to_menu_item.options.keySet() as List + def currentIndex = keysDashboard.indexOf(dashboard_item_list.value) + if (currentIndex == keysDashboard.size() - 1) { + return + } + java.util.Collections.swap(keysDashboard, currentIndex, currentIndex + 1) + java.util.Collections.swap(keysMenu, currentIndex, currentIndex + 1) + def newOrderDashboard = [:] + def newOrderMenu = [:] + keysDashboard.forEach { newOrderDashboard[it] = dashboard_item_list.options[it]} + keysMenu.forEach { newOrderMenu[it] = dashboard_item_to_menu_item.options[it]} + change dashboard_item_list options { newOrderDashboard } + change dashboard_item_to_menu_item options { newOrderMenu } + change items_order value { String.join(",", newOrderDashboard.keySet()) } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>order_up</id> + <title/> + <placeholder>north</placeholder> + <component> + <name>icon</name> + <property key="stretch">true</property> + </component> + <event type="set"> + <id>order_up_set</id> + <actions phase="post"> + <action> + dashboard_item_to_menu_item: f.dashboard_item_to_menu_item, + items_order: f.items_order, + dashboard_item_list: f.dashboard_item_list; + + if (dashboard_item_list.value == null || dashboard_item_list.value == "") { + return + } + def keysDashboard = dashboard_item_list.options.keySet() as List + def keysMenu = dashboard_item_to_menu_item.options.keySet() as List + def currentIndex = keysDashboard.indexOf(dashboard_item_list.value) + if (currentIndex == 0) { + return + } + java.util.Collections.swap(keysDashboard, currentIndex, currentIndex - 1) + java.util.Collections.swap(keysMenu, currentIndex, currentIndex - 1) + def newOrderDashboard = [:] + def newOrderMenu = [:] + keysDashboard.forEach { newOrderDashboard[it] = dashboard_item_list.options[it]} + keysMenu.forEach { newOrderMenu[it] = dashboard_item_to_menu_item.options[it]} + change dashboard_item_list options { newOrderDashboard } + change dashboard_item_to_menu_item options { newOrderMenu } + change items_order value { String.join(",", newOrderDashboard.keySet()) } + </action> + </actions> + </event> + </data> + <data type="enumeration_map"> + <id>existing_menu_items</id> + <title name="existing_menu_items">Existing dashboard items + List of all existing dashboard items which can be added to dashboard + + autocomplete_dynamic + + + existing_menu_items_get + + + dashboard_item_list: f.dashboard_item_list, + existing_menu_items: f.existing_menu_items; + + def dashboardItemCases = findCasesElastic("processIdentifier:\"dashboard_item\" AND dataSet.is_active.booleanValue:true" as String) + .collectEntries { [(it.stringId): it.dataSet["item_name"].value] } + dashboardItemCases.keySet().removeAll(dashboard_item_list.options.keySet()) + change existing_menu_items options { dashboardItemCases } + + + + + existing_menu_items_set + + + item_divider: f.item_divider, + dashboard_item_taskRef: f.dashboard_item_taskRef, + existing_menu_items: f.existing_menu_items, + trans: t.configuration; + + if (existing_menu_items.value == null || !org.bson.types.ObjectId.isValid(existing_menu_items.value)) { + change dashboard_item_taskRef value { [] } + make item_divider, hidden on trans when { true } + return + } + def dashboardItemCase = workflowService.findOne(existing_menu_items.value) + make item_divider, visible on trans when { true } + change dashboard_item_taskRef value { [dashboardItemCase.tasks.find { it.transition == "configuration" }?.task] } + + + + + + add_new_item + + <placeholder>Add new item</placeholder> + <event type="set"> + <id>add_new_item_set</id> + <actions phase="post"> + <action> + dashboard_item_to_menu_item: f.dashboard_item_to_menu_item, + items_order: f.items_order, + dashboard_item_list: f.dashboard_item_list, + existing_menu_items: f.existing_menu_items; + + if (existing_menu_items.value == null || !org.bson.types.ObjectId.isValid(existing_menu_items.value)) { + throw new IllegalArgumentException("No menu item was selected.") + } + def dashboardItemOptions = dashboard_item_list.options + dashboardItemOptions.put(existing_menu_items.value, existing_menu_items.options.get(existing_menu_items.value)) + change dashboard_item_list options { dashboardItemOptions } + change items_order value { String.join(",", dashboardItemOptions.keySet()) } + + def dashboardItemCase = workflowService.findOne(existing_menu_items.value) + def menuItemOptions = dashboard_item_to_menu_item.options + if (dashboardItemCase.dataSet["is_internal"].value) { + menuItemOptions.put(existing_menu_items.value, dashboardItemCase.dataSet["menu_items_list"].value) + } else { + menuItemOptions.put(existing_menu_items.value, null) + } + change dashboard_item_to_menu_item options { menuItemOptions } + + def dashboardItemCases = findCasesElastic("processIdentifier:\"dashboard_item\" AND dataSet.is_active.booleanValue:true" as String) + .collectEntries { [(it.stringId): it.dataSet["item_name"].value] } + dashboardItemCases.keySet().removeAll(dashboard_item_list.options.keySet()) + change existing_menu_items options { dashboardItemCases } + change existing_menu_items value { "" } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>remove_new_item</id> + <title/> + <placeholder>Remove item</placeholder> + <event type="set"> + <id>remove_new_item_set</id> + <actions phase="post"> + <action> + dashboard_item_to_menu_item: f.dashboard_item_to_menu_item, + dashboard_item_list: f.dashboard_item_list, + items_order: f.items_order, + existing_menu_items: f.existing_menu_items; + + if (dashboard_item_list.value == null || !org.bson.types.ObjectId.isValid(dashboard_item_list.value)) { + throw new IllegalArgumentException("No menu item was selected.") + } + def dashboardItemOptions = dashboard_item_list.options + dashboardItemOptions.keySet().removeAll([dashboard_item_list.value]) + change dashboard_item_list options { dashboardItemOptions } + change items_order value { String.join(",", dashboardItemOptions.keySet()) } + + def menuItemOptions = dashboard_item_to_menu_item.options + menuItemOptions.keySet().removeAll([dashboard_item_list.value]) + change dashboard_item_to_menu_item options { menuItemOptions } + + def dashboardItemCases = findCasesElastic("processIdentifier:\"dashboard_item\" AND dataSet.is_active.booleanValue:true" as String) + .collectEntries { [(it.stringId): it.dataSet["item_name"].value] } + dashboardItemCases.keySet().removeAll(dashboard_item_list.options.keySet()) + change existing_menu_items options { dashboardItemCases } + change existing_menu_items value { "" } + </action> + </actions> + </event> + </data> + <data type="taskRef"> + <id>dashboard_item_taskRef</id> + <title/> + </data> + <data type="boolean"> + <id>is_active</id> + <title/> + </data> + <data type="i18n"> + <id>item_divider</id> + <title/> + <init dynamic="true"> + new com.netgrif.application.engine.petrinet.domain.I18nString("Dashboard item data", [sk: "Dáta položky panelu", de: "Daten zum Dashboard-Element"]) + </init> + <component> + <name>divider</name> + <property key="fontSize">18</property> + </component> + </data> + <data type="boolean" immediate="true"> + <id>simple_dashboard_toolbar</id> + <title name="simple_dashboard_toolbar">Simple dashboard toolbar + Should dashboard toolbar contains menu with options + true + + + profile_dashboard_toolbar + Profile in dashboard toolbar + Should dashboard toolbar contains profile button + false + + profile_dashboard_toolbar_set + + + profile_dashboard_toolbar: f.profile_dashboard_toolbar, + profile_url: f.profile_url, + trans: t.configuration; + + make profile_url, visible on trans when { !profile_dashboard_toolbar.value } + make profile_url, editable on trans when { profile_dashboard_toolbar.value } + + + + + + language_dashboard_toolbar + Language select in dashboard toolbar + Should dashboard toolbar contains menu with language selection + false + + + logout_dashboard_toolbar + Logout in dashboard toolbar + Should dashboard toolbar contains logout button + false + + logout_dashboard_toolbar_set + + + logout_dashboard_toolbar: f.logout_dashboard_toolbar, + login_url: f.login_url, + trans: t.configuration; + + make login_url, visible on trans when { !logout_dashboard_toolbar.value } + make login_url, editable on trans when { logout_dashboard_toolbar.value } + + + + + + items_order + + </data> + <data type="text" immediate="true"> + <id>profile_url</id> + <title name="profile_url">Profile URL + URL address of profile page + profile + + + login_url + Login URL + URL address of login page. Let empty to decide by application. + + + + + ID panelu + Unikátny identifikátor panelu + Názov panelu + Názov panelu, ktorý bude zobrazený v panely aplikácie + Logo panelu + Logo panelu, ktoré bude zobrazené v panely aplikácie + Položky panelu + Zoznam položiek panalu, ktoré budú zobrazené v panely aplikácie + Existujúce položky panelu + Zoznam všetkých existujúcich položiek panelu, ktoré budú zobrazené v panely aplikácie + Konfigurácia panela + + Jednoduchý panel nástrojov + Panel nástrojov by mal obsahovať ponuku s možnosťami + Profil na paneli nástrojov + Mal by panel nástrojov obsahovať tlačidlo profilu + Výber jazyka na paneli nástrojov + Mal by panel nástrojov obsahovať menu s výberom jazyka + Odhlásenie na paneli nástrojov + Mal by panel nástrojov obsahovať tlačidlo na odhlásenie + + URL profilu + URL adresa na stránku profilu + URL prihlásenia + URL adresa na stránku prihlásenia. Nechajte prázdne pre rozhodnutie aplikáciou. + + + Dashboard-ID + Eindeutiger Bezeichner des Dashboards + Name des Dashboards + Name des Dashboards, der im Dashboard der Anwendung angezeigt werden soll + Logo für das Armaturenbrett + Dashboard-Logo, das im Dashboard der Anwendung angezeigt werden soll + Dashboard-Elemente + Liste der Dashboard-Elemente, die im Dashboard der Anwendung angezeigt werden sollen + Vorhandene Dashboard-Elemente + Liste aller vorhandenen Dashboard-Elemente, die dem Dashboard hinzugefügt werden können + Konfiguration des Dashboards + + Einfache Dashboard-Symbolleiste + Die Symbolleiste des Dashboards sollte ein Menü mit Optionen enthalten + Profil in der Dashboard-Symbolleiste + Die Symbolleiste des Dashboards sollte eine Profilschaltfläche enthalten + Sprachauswahl in der Dashboard-Symbolleiste + Sollte die Symbolleiste des Dashboards ein Menü mit Sprachauswahl enthalten + Abmelden in der Dashboard-Symbolleiste + Die Dashboard-Symbolleiste sollte eine Abmeldeschaltfläche enthalten + + Profil-URL + URL-Adresse der Profilseite + Anmelde-URL + URL-Adresse der Anmeldeseite. Lassen Sie leer, um durch Anwendung zu entscheiden. + + + + + configuration + 400 + 112 + + settings + auto + + admin + + true + true + true + true + + + + configuration_d1 + 4 + grid + + dashboard_id + + editable + + + 0 + 0 + 1 + 2 + + outline + + + + dashboard_name + + editable + + + 0 + 1 + 1 + 2 + + outline + + + + dashboard_logo + + editable + + + 2 + 1 + 1 + 2 + + outline + + + + simple_dashboard_toolbar + + editable + + + 0 + 2 + 1 + 1 + + outline + + + + profile_dashboard_toolbar + + editable + + + 1 + 2 + 1 + 1 + + outline + + + + language_dashboard_toolbar + + editable + + + 2 + 2 + 1 + 1 + + outline + + + + logout_dashboard_toolbar + + editable + + + 3 + 2 + 1 + 1 + + outline + + + + profile_url + + visible + + + 0 + 3 + 1 + 2 + + outline + + + + login_url + + visible + + + 2 + 3 + 1 + 2 + + outline + + + + dashboard_item_list + + editable + + + 0 + 4 + 2 + 2 + + outline + + + + order_up + + editable + + + 2 + 4 + 1 + 1 + + outline + + + + order_down + + editable + + + 2 + 5 + 1 + 1 + + outline + + + + remove_new_item + + editable + + + 3 + 4 + 1 + 1 + + outline + + + + existing_menu_items + + editable + + + 0 + 6 + 1 + 2 + + outline + + + + add_new_item + + editable + + + 2 + 6 + 1 + 2 + + outline + + + + item_divider + + hidden + + + 0 + 7 + 1 + 4 + + outline + + + + dashboard_item_taskRef + + visible + + + 0 + 8 + 1 + 4 + + outline + + + + + configuration_assign + + + is_active: f.is_active; + + change is_active value { false } + + + + + configuration_finish + + + dashboard_id: f.dashboard_id, + dashboard_name: f.dashboard_name, + is_active: f.is_active; + + if (dashboard_id.value == null || dashboard_id.value == "") { + throw new IllegalArgumentException("Dashboard ID cannot be empty.") + } + def dashboardWithIdExists = findCaseElastic("processIdentifier:\"dashboard_management\" AND dataSet.dashboard_id.textValue.keyword:\"${dashboard_id.value}\" AND dataSet.is_active.booleanValue:true" as String) + if (dashboardWithIdExists != null) { + throw new IllegalArgumentException("Dashboard with given ID already exists.") + } + change is_active value { true } + + + + + + p1 + 208 + 112 + + + a1 + regular + p1 + configuration + 1 + + 304 + 144 + + + + a2 + regular + configuration + p1 + 1 + + 304 + 80 + + + diff --git a/src/main/resources/petriNets/engine-processes/dashboard_tile.xml b/src/main/resources/petriNets/engine-processes/dashboard_tile.xml index ad0fe2b6829..f4bcfd4c6e7 100644 --- a/src/main/resources/petriNets/engine-processes/dashboard_tile.xml +++ b/src/main/resources/petriNets/engine-processes/dashboard_tile.xml @@ -1,5 +1,6 @@ dashboard_tile + 1.0.0 DST Dashboard Tile dashboard_customize diff --git a/src/main/resources/petriNets/engine-processes/export_filters.xml b/src/main/resources/petriNets/engine-processes/export_filters.xml deleted file mode 100644 index 43e332135c5..00000000000 --- a/src/main/resources/petriNets/engine-processes/export_filters.xml +++ /dev/null @@ -1,171 +0,0 @@ - - - export_filters - FTE - Export of filters - file_download - true - false - Export of filters - - - - - - - - exportable_filters - Exportable filters - List of all exportable filters - Select one or more filters to export - - exportable_filters: f.exportable_filters; - - change exportable_filters options { - return findAllFilters() - .collectEntries({filterCase -> [filterCase.stringId, filterCase.title]}) - } - - - - export_btn - Export filters - Export selected filters into xml file - - exportable_filters: f.exportable_filters, - export_file: f.export_file, - trans: t.exportFilter; - - change export_file value { exportFilters(exportable_filters.value) } - change exportable_filters value { [] } - make export_file, visible on trans when {exportable_filters.value.length != 0} - make export_file, hidden on trans when {exportable_filters.value.length == 0} - - - raised - - - - export_file - Exported filters - File with exported filters - Download file with exported filters in xml format - - - - Export filtrov - Exportovateľné filtre - Zoznam všetkých exportovateľných filtrov - Vyberte jeden alebo viac filtrov na export - Exportovať filtre - Export vybraných filtrov do súboru xml - Exportované filtre - Súbor s exportovanými filtrami - Stiahnutie súboru s exportovanými filtrami vo formáte xml - - - Exportieren von Filtern - Exportierbare Filter - Liste aller exportierbaren Filter - Wählen Sie einen oder mehrere Filter für den Export - Filter exportieren - Ausgewählte Filter in xml-Datei exportieren - Exportierte Filter - Datei mit exportierten Filtern - Datei mit exportierten Filtern im xml-Format herunterladen - - - - exportFilter - 200 - 100 - - 0 - - auto - - - DataGroup_0 - grid - - exportable_filters - - editable - - - 0 - 0 - 1 - 2 - 0 - - outline - - - - export_btn - - editable - - - 2 - 0 - 1 - 2 - 0 - - outline - - - - export_file - - hidden - - - 0 - 1 - 1 - 2 - 0 - - outline - - - - - - - export_file: f.export_file, - trans: t.exportFilter; - - make export_file, hidden on trans when {true} - - - - - - - p1 - 100 - 100 - - 1 - false - - - - a1 - regular - p1 - exportFilter - 1 - - - a2 - regular - exportFilter - p1 - 1 - - diff --git a/src/main/resources/petriNets/engine-processes/filter.xml b/src/main/resources/petriNets/engine-processes/filter.xml deleted file mode 100644 index 4563e2d4c5e..00000000000 --- a/src/main/resources/petriNets/engine-processes/filter.xml +++ /dev/null @@ -1,1094 +0,0 @@ - - - filter - FTR - Filter - filter_alt - true - false - New filter - - - - create - - - caseId: f.filter_case_id; - - change caseId value {return useCase.stringId}; - - - - - - - - system - System - - - - - - { - 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]} - } - - - - filter - Filter - - - filter_type - Filter type - - - - - - - visibility - Filter visibility - - - - - public - - - origin_view_id - Origin ViewId - - set - - - origin_view_id: f.origin_view_id, - and_view: f.and_view, - trimmed_origin_view_id: f.trimmed_origin_view_id, - t1: t.view_filter, - t2: t.view_as_ancestor; - - if (origin_view_id.value == "") { - return; - } - - // the same regex is used in the frontend FilterExtractionService. Please keep them in sync - def match = origin_view_id.value =~ "^.*?(-\\d+)?\$" - match.matches() - - def trimmed_id; - - if (match.start(1) == -1) { - trimmed_id = origin_view_id.value - } else { - trimmed_id = origin_view_id.value.substring(0, match.start(1)) - } - - change trimmed_origin_view_id value {trimmed_id} - make and_view, visible on t1 when {true} - make and_view, visible on t2 when {true} - make trimmed_origin_view_id, visible on t1 when {true} - make trimmed_origin_view_id, visible on t2 when {true} - make origin_view_id, hidden on t1 when {true} - make origin_view_id, hidden on t2 when {true} - - - - - - parent_filter_id - parent filter ID - - set - - - parent_filter_id: f.parent_filter_id, - ancestor_task_ref: f.taskref_and_parent; - - def ancestor_view_filter = findTask({it.caseId.eq(parent_filter_id.value).and(it.task.transitionId.eq("view_as_ancestor"))}) - - change ancestor_task_ref value {[ancestor_view_filter.stringId]} - - - - - - new_title - Filter name - - - i18n_filter_name - - - - filter_case_id - - - - menu_identifier - - - - and_me - AND - - - and_view - AND - - - taskref_and_parent - - - - trimmed_origin_view_id - Filter from view with ID - - - is_imported - imported - 0 - - isNotImported: f.is_not_imported; - - change isNotImported value { return 0 } - - - - is_not_imported - not imported - 1 - - - missing_allowed_nets - Missing processes - List of missing processes for current filter - - htmltextarea - - - - missing_nets_translation - - - - - - - - - my_full_filter - - - - allowed_nets - Allowed nets - - - - Filter - Nový Filter - Filter - Druh filtra - Filter prípadov - Filter úloh - Viditeľnosť filtra - Verejný - Súkromný - ViewId pôvodu - Názov filtra - Vytvoriť nový filter - Podrobnosti - Uložiť - A SÚČASNE - Filter z obrazovky s ID - Názov prípadu - Importovať filter - Chýbajúce procesy - Zoznam chýbajúcich procesov pre aktuálny filter - ID rodičovského filtra - Povolené siete - - - Filter - Neuer Filter - Filter - Filtertyp - Fallfilter - Aufgabenfilter - Filter Sichtbarkeit - Öffentlich - Persönlich - Herkunft ViewId - Filtername - Neuer Filter erstellen - Details - Speichern - UND - Filter von der Ansicht mit der ID - Fallname - Importfilter - Fehlende Prozesse - Liste der fehlenden Prozesse für den aktuellen Filter - Elternfilter-ID - Erlaubte Netze - - - - newFilter - 620 - 260 - - - 0 - - auto - - myFilter - grid - - my_full_filter - - visible - - - 0 - 0 - 1 - 4 - 0 - - - - - DataGroup_0 - grid - - visibility - - editable - required - - - 0 - 1 - 1 - 4 - 0 - - outline - - - - new_title - - editable - required - - userTitle: f.new_title, - i18nName: f.i18n_filter_name; - - change i18nName value { new com.netgrif.application.engine.petrinet.domain.I18nString(userTitle.value) } - - - - 0 - 2 - 1 - 4 - 0 - - outline - - - - allowed_nets - - editable - - - 0 - 3 - 1 - 4 - 0 - - outline - - - - - - Save - - - name: f.new_title; - useCase.setTitle(name.value) - workflowService.save(useCase) - - - - - - t2 - 740 - 380 - - 0 - - - - DataGroup_0 - grid - - filter_type - - visible - - - 0 - 0 - 1 - 1 - 0 - - outline - - - - visibility - - visible - - - 1 - 0 - 1 - 1 - 0 - - outline - - - - - myFilter - grid - - my_full_filter - - visible - - - 0 - 0 - 1 - 4 - 0 - - - - - - frontend_create - 380 - 260 - - - 0 - - - DataGroup_0 - grid - - filter - - editable - - - 0 - 0 - 1 - 4 - 0 - - outline - - - - filter_type - - editable - - - 0 - 1 - 1 - 4 - 0 - - outline - - - - origin_view_id - - editable - - - 0 - 2 - 1 - 4 - 0 - - outline - - - - parent_filter_id - - editable - - - 0 - 3 - 1 - 4 - 0 - - outline - - - - allowed_nets - - editable - - - 0 - 4 - 1 - 4 - 0 - - outline - - - - - finish - - - taskRef: f.my_full_filter; - - initializeMyFullFilterTaskRef(taskRef) - - - - - - auto_create - 500 - 180 - - - 0 - - - DataGroup_0 - grid - - filter - - editable - - - 0 - 0 - 1 - 1 - 0 - - outline - - - - filter_type - - editable - - - 1 - 0 - 1 - 1 - 0 - - outline - - - - visibility - - editable - - - 2 - 0 - 1 - 1 - 0 - - outline - - - - origin_view_id - - editable - - - 3 - 0 - 1 - 1 - 0 - - outline - - - - parent_filter_id - - editable - - - 0 - 1 - 1 - 1 - 0 - - outline - - - - - finish - - - taskRef: f.my_full_filter; - - initializeMyFullFilterTaskRef(taskRef) - - - - - - view_filter - 550 - 420 - - - 0 - - - system - - true - - - - DataGroup_0 - grid - - filter - - visible - - - 0 - 0 - 1 - 4 - 0 - - outline - - - - filter_case_id - - hidden - - - 0 - 1 - 1 - 4 - 0 - - outline - - - - origin_view_id - - forbidden - - - 0 - 2 - 1 - 4 - 0 - - outline - - - - taskref_and_parent - - visible - - - 0 - 3 - 1 - 4 - 0 - - outline - - - - new_title - - hidden - - name: f.new_title, - i18nName: f.i18n_filter_name; - - change i18nName value { new com.netgrif.application.engine.petrinet.domain.I18nString(name.value) } - - - - 0 - 4 - 1 - 4 - 0 - - outline - - - - - DataGroup_1 - grid - - and_view - - forbidden - - - 0 - 0 - 1 - 1 - 0 - - outline - - - - trimmed_origin_view_id - - forbidden - - - 1 - 0 - 1 - 3 - 0 - - outline - - - - - - view_as_ancestor - 451 - 418 - - - 0 - - - system - - true - - - - DataGroup_0 - grid - - and_me - - visible - - - 0 - 0 - 1 - 1 - 0 - - outline - - - - filter - - visible - - - 1 - 0 - 1 - 3 - 0 - - outline - - - - taskref_and_parent - - visible - - - 0 - 3 - 1 - 4 - 0 - - outline - - - - - DataGroup_1 - grid - - and_view - - forbidden - - - 0 - 0 - 1 - 1 - 0 - - outline - - - - trimmed_origin_view_id - - forbidden - - - 1 - 0 - 1 - 3 - 0 - - outline - - - - origin_view_id - - forbidden - - - 0 - 3 - 1 - 3 - 0 - - outline - - - - - - import_filter - 740 - 180 - - - 0 - - - DataGroup_0 - grid - - - new_title - - editable - - name: f.new_title; - - change name value { useCase.getTitle() } - - - name: f.new_title, - i18nName: f.i18n_filter_name; - - change i18nName value { new com.netgrif.application.engine.petrinet.domain.I18nString(name.value) } - - - - 0 - 0 - 1 - 1 - 0 - - outline - - - - filter_type - - visible - - - 1 - 0 - 1 - 1 - 0 - - outline - - - - visibility - - editable - - - 2 - 0 - 1 - 1 - 0 - - outline - - - - missing_allowed_nets - - hidden - - - 0 - 2 - 1 - 2 - 0 - - outline - - - - - DataGroup_1 - grid - - my_full_filter - - visible - - - 0 - 0 - 1 - 4 - 0 - - outline - - - - - - - - name: f.new_title; - changeCaseProperty "title" about { name.value } - - - - - - - - p1 - 260 - 260 - - 1 - false - - - p2 - 740 - 260 - - 0 - false - - - p3 - 540 - 260 - - 0 - false - - - p4 - 660 - 140 - - 0 - false - - - p5 - 500 - 340 - - 0 - false - - - - a3 - regular - p1 - frontend_create - 1 - - - a4 - regular - frontend_create - p3 - 1 - - - a5 - regular - p3 - newFilter - 1 - - - a6 - regular - newFilter - p2 - 1 - - - a7 - read - p2 - t2 - 1 - - - a8 - regular - p1 - auto_create - 1 - - - a9 - regular - auto_create - p2 - 1 - is_not_imported - - - a12 - regular - import_filter - p2 - 1 - - - a13 - regular - auto_create - p4 - 0 - is_imported - - - a14 - regular - p4 - import_filter - 1 - - - a15 - regular - frontend_create - p5 - 1 - - - a18 - read - p5 - view_as_ancestor - 1 - - - a19 - read - p5 - view_filter - 1 - - - a20 - regular - auto_create - p5 - 1 - - diff --git a/src/main/resources/petriNets/engine-processes/import_filters.xml b/src/main/resources/petriNets/engine-processes/import_filters.xml deleted file mode 100644 index af74e2c35f9..00000000000 --- a/src/main/resources/petriNets/engine-processes/import_filters.xml +++ /dev/null @@ -1,182 +0,0 @@ - - - import_filters - FTI - Import of filters - file_upload - true - false - Import of filters - - - - - - - - upload_file - Upload filter file - Insert file containing filters - Insert xml file that contains filters - - importedFilters: f.imported_filters; - - taskService.findAllById(importedFilters.value).forEach({filterTask -> - workflowService.deleteCase(filterTask.caseId) - }) - change importedFilters value { return [] } - - - - import_file - - Import filters - Import filters from uploaded xml file - - raised - - - - imported_filters - - - importedFilters: f.imported_filters; - - filterImportExportService.changeFilterField(importedFilters.value) - - - - - Import filtrov - Nahratie súboru s filtrami - Vložete súbor obsahujúci filtre - Vložte xml súbor, ktorý obsahuje filtre - Importovať filtre - Importovať filtre z nahraného xml súboru - - - Import von Filtern - Filterdatei hochladen - Datei mit Filtern einfügen - Xml-Datei einfügen, die Filter enthält - Filter importieren - Filter aus hochgeladener xml-Datei importieren - - - - importFilter - 200 - 100 - - - 0 - - auto - - DataGroup_0 - grid - - upload_file - - editable - - - 0 - 0 - 1 - 2 - 0 - - outline - - - - import_file - - editable - - importedFiltersRef: f.imported_filters; - - change importedFiltersRef value { importFilters() } - - - - 2 - 0 - 1 - 2 - 0 - - outline - - - - - DataGroup_1 - grid - - imported_filters - - editable - - - 0 - 1 - 1 - 4 - 0 - - outline - - - - - - - - importedFilters: f.imported_filters; - - taskService.finishTasks(taskService.findAllById(importedFilters.value), userService.getLoggedUser()) - change importedFilters value { [] } - - - - - - - - importedFilters: f.imported_filters; - - taskService.findAllById(importedFilters.value).forEach({filterTask -> - workflowService.deleteCase(filterTask.caseId) - }) - change importedFilters value { [] } - - - - - - - p1 - 100 - 100 - - 1 - false - - - - a1 - regular - p1 - importFilter - 1 - - - a2 - regular - importFilter - p1 - 1 - - diff --git a/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml new file mode 100644 index 00000000000..57cb6d1f10a --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/menu/case_view_configuration.xml @@ -0,0 +1,1512 @@ + + case_view_configuration + 1.0.0 + CVC + Case view configuration + check_box_outline_blank + true + false + false + + system + + true + true + true + + + + admin + + true + true + true + + + + default + + true + + + + + view_create + + + is_tabbed_var_arc: f.is_tabbed_var_arc; + change is_tabbed_var_arc value { Boolean.valueOf(params["is_tabbed"]) ? 1 : 0 } + + + + + view_delete + + + removeViewCase() + + + + + + system + System + + + admin + Admin + + + + { -> + def advancedContentFormField = useCase.getField("advanced_content_form") + def taskIds = [useCase.tasks.find { taskPair -> taskPair.transition == "header_settings"}.task, + useCase.tasks.find { taskPair -> taskPair.transition == "create_case_btn_settings"}.task, + useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task] + String nextViewTaskId = useCase.tasks.find { taskPair -> taskPair.transition == "open_case_settings"}?.task + if (nextViewTaskId != null) { + taskIds.add(nextViewTaskId) + } + change advancedContentFormField value { taskIds } + } + + + { -> + + def viewIdAsList = useCase.dataSet['view_configuration_id'].value + if (viewIdAsList == null || viewIdAsList.isEmpty()) { + return + } + + async.run { + workflowService.deleteCase(viewIdAsList[0]) + } + } + + + { + com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField toBeUpdated, + com.netgrif.application.engine.petrinet.domain.dataset.MultichoiceMapField valueSelector, + com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField optionsHolder + -> + def existingOptions = optionsHolder.options + def selectedValues = valueSelector.value + def newOptions = [:] + + if (selectedValues != null) { + existingOptions.each { key, value -> + if (selectedValues.contains(key)) { + newOptions.put(key, value) + } + } + } + + if (!newOptions.containsKey(toBeUpdated.value)) { + change toBeUpdated value { null } + } + + change toBeUpdated options { newOptions } + } + + + + + case_view_search_type + Search type for case view + + + + + + fulltext_advanced + + + create_case_button_title + "New case" button title + + + create_case_button_icon_preview + Icon preview + add]]> + + htmltextarea + + + + create_case_button_icon + "New case" button icon identifier + add + + create_case_button_icon_preview: f.create_case_button_icon_preview, + create_case_button_icon: f.create_case_button_icon; + + + if (create_case_button_icon.value == "") { + change create_case_button_icon_preview value {"""]]>} + return; + } + + change create_case_button_icon_preview value { + """]]> + create_case_button_icon.value + """]]> + } + + + + show_create_case_button + Show create case button? + true + + + require_title_in_creation + Require title input in case creation? + true + + + banned_nets_in_creation + Banned processes for creation + Write down process identifiers separated by comma. Example: mynet1,mynet2 + + bannedNets: f.this; + + String trimmed = bannedNets.value?.replaceAll("\\s","") + if (bannedNets.value != trimmed) { + change bannedNets value { trimmed } + } + + + + view_header + + <init name="view_header">Case view</init> + <component> + <name>divider</name> + </component> + </data> + <data type="boolean" immediate="true"> + <id>case_show_more_menu</id> + <title name="case_show_more_menu">Show more menu for case item? + false + + + case_allow_header_table_mode + Allow table mode for headers? + true + + + case_headers_mode + Header mode + + + + + + sort,edit,search + + headersMode: f.case_headers_mode, + defaultMode: f.case_headers_default_mode, + holder: f.headers_options_holder; + + updateOptionsBasedOnValue(defaultMode, headersMode, holder) + + + headersMode: f.case_headers_mode, + defaultMode: f.case_headers_default_mode, + holder: f.headers_options_holder; + + updateOptionsBasedOnValue(defaultMode, headersMode, holder) + + + + case_headers_default_mode + Default header mode + + + + + + sort + + + headers_options_holder + + <options> + <option key="sort" name="sort">Sort</option> + <option key="search" name="search">Search</option> + <option key="edit" name="edit">Edit</option> + </options> + </data> + <data type="boolean" immediate="true"> + <id>case_is_header_mode_changeable</id> + <title name="case_is_header_mode_changeable">Can header mode be changed? + true + + + use_case_default_headers + Use custom default headers? + true + + + case_default_headers + Set default headers + Example: "meta-title", "meta-visualId" + + + view_configuration_type + View type + + menuItemService.getAvailableViewsAsOptions(true, useCase.processIdentifier) + + + view_configuration_type: f.view_configuration_type; + + if (view_configuration_type.options == null || view_configuration_type.options.isEmpty()) { + change view_configuration_type options { menuItemService.getAvailableViewsAsOptions(true, useCase.processIdentifier) } + } + + + + view_configuration_id + + <allowedNets> + <allowedNet>task_view_configuration</allowedNet> + </allowedNets> + </data> + <data type="taskRef"> + <id>view_configuration_form</id> + <title/> + </data> + <data type="taskRef"> + <id>view_configuration_all_data_form</id> + <title/> + </data> + <data type="taskRef"> + <id>advanced_content_form</id> + <title/> + <component> + <name>task-list</name> + <property key="headers">meta-caseId,meta-title</property> + </component> + </data> + <data type="number"> + <id>is_tabbed_var_arc</id> + <title>Is tabbed + + + case_empty_content_text + Empty content text + If empty, default text will be used + + + case_empty_content_icon + Empty content icon + If empty, default icon will be used + + 0 + + + icon: f.this, + iconPreview: f.case_empty_content_icon_preview; + + if (icon.value == "") { + change iconPreview value {"""]]>} + return + } + + change iconPreview value { + """]]> + icon.value + """]]> + } + + + + + + case_empty_content_icon_preview + Empty content icon preview + + htmltextarea + + + + case_filter + Filter + Use PFQL query + cases + + + case_allow_export + Allow export? + + + case_allowed_nets + Allowed nets + + + case_all_allowed_nets + All allowed nets? + true + + + case_inherit_allowed_nets + Inherit allowed nets? + true + + + + + Názov tlačidla "Nová inštancia" + Identifikátor ikony tlačidla "Nová inštancia" + Náhľad ikony + Predvolené hlavičky + Napríklad: "meta-title", "meta-visualId" + Typ vyhľadávania prípadov + Skryté + Fulltext + Fulltext a rozšírené + Vyžadovať názov inštancii pri vytváraní? + Zakázané siete pri vytváraní + Uveďte identifikátory procesov oddelené čiarkou. Napríklad: mynet1,mynet2 + Zobraziť tlačidlo na vytvorenie prípadu? + Zobrazovať menu pre prípadovú položku? + Zoraďovanie + Vyhľadávanie + Upravovanie + Mód hlavičiek + Predvolený mód hlavičiek + Môže byť mód hlavičiek zmenený? + Povoliť tabuľkový mód pre hlavičky? + Použiť vlastné predvolené hlavičky? + Nastavenie položky + Zobrazenie prípadov + Všeobecné + Typ zobrazenia + Nastavenie + Povoliť export? + Text prázdneho obsahu + Ak je toto pole prázdne, bude použitý predvolený text + Ikona prázdneho obsahu + Ak je toto pole prázdne, bude použitá predvolená ikona + Náhľad ikony prázdneho obsahu + Filter + Použi PFQL query + Povolené procesy + Povoliť všetky procesy? + Zdediť povolené procesy? + Filter + Zobrazenie po kliknutí na inštanciu + Tlačidlo vytvorenia inštancie + Hlavičky + + + Schaltflächentitel "Neuer Fall" + Ikone ID + Ikonevorschau + Anzuzeigende Attributmenge auswählen + Beispiel: "meta-title", "meta-visualId" + Versteckt + Einfacher Suchmodus + Sortieren + Suchen + Bearbeiten + Kopfzeilenmodus + Standardkopfzeilenmodus + Erlaube Änderung des Kopfzeilenmodus? + Erlaube Tabellenmodus? + Eigene Kopfzeilen verwenden? + Allgemein + Suchmodus im Fallansicht + Einfacher und erweiterter Suchmodus + Erforde den Titel beim erzeugen von Fällen? + Ausgeschlossene Prozesse + Trenne die Prozessidentifikatoren mit einer Komma. z.B.: netz1,netz2 + Schaltfläche „Fall erstellen“ anzeigen? + "Erweiterte Optionen" Taste bei einzelnen Fällen anzeigen + Menüeintrageinstellungen + Fallansicht + Der Ansichtstyp + Einstellungen + Erlaube Export? + Leerer Inhaltstext + Wenn dieses Feld leer ist, wird der Standardtext verwendet + + Leeres Inhaltssymbol + Wenn dieses Feld leer ist, wird das Standardsymbol verwendet + + Vorschau des leeren Inhaltssymbols + Filter + Verwenden Sie PFQL Query + Erlaubte Prozesse + Alle Prozesse erlauben? + Erlaubte Prozesse erben? + Filter + Ansicht nach dem Klicken auf die Instanz + Schaltfläche zum Erstellen einer Instanz + Kopfzeilen + + + + initialize + 368 + 208 + + hourglass_empty + + admin + + true + + + + finish + + + initializeAdvancedForm() + + + + + + data_sync + 368 + 328 + + + system + + true + + + + + settings + 496 + 112 + + settings + + admin + + true + + + + default + + true + + + + view_dataGroup + 3 + grid + + case_view_search_type + + editable + required + + + 0 + 0 + 1 + 1 + + outline + + + + case_show_more_menu + + editable + + + 1 + 0 + 1 + 1 + + outline + + + + case_allow_export + + editable + + + 2 + 0 + 1 + 1 + + standard + + + + case_empty_content_text + + editable + + + 0 + 1 + 1 + 1 + + outline + + + + case_empty_content_icon + + editable + + + 1 + 1 + 1 + 1 + + outline + + + + case_empty_content_icon_preview + + visible + + + 2 + 1 + 1 + 1 + + standard + + + + case_all_allowed_nets + + editable + + + 0 + 2 + 1 + 1 + + outline + + + + case_allowed_nets + + editable + + + 1 + 2 + 1 + 1 + + outline + + + + case_inherit_allowed_nets + + editable + + + 2 + 2 + 1 + 1 + + outline + + + + advanced_content_form + + editable + + + 0 + 3 + 1 + 3 + + outline + + + + + assign_0 + + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + </transition> + <transition> + <id>filter_settings</id> + <x>432</x> + <y>48</y> + <label name="filter_settings">Filter</label> + <icon>filter_alt</icon> + <assignPolicy>auto</assignPolicy> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>filter_0</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>case_filter</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>assign</id> + <title/> + </event> + <event type="cancel"> + <id>cancel</id> + <title/> + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + <event type="delegate"> + <id>delegate</id> + <title/> + </event> + </transition> + <transition> + <id>open_case_settings</id> + <x>336</x> + <y>48</y> + <label name="open_case_settings">Open case view</label> + <icon>queue_play_next</icon> + <assignPolicy>auto</assignPolicy> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>associated_view_0</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>view_configuration_type</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>view_configuration_form</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>4</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>assign</id> + <title/> + </event> + <event type="cancel"> + <id>cancel</id> + <title/> + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + <event type="delegate"> + <id>delegate</id> + <title/> + </event> + </transition> + <transition> + <id>create_case_btn_settings</id> + <x>656</x> + <y>48</y> + <label name="create_case_btn_settings">Create case button</label> + <icon>add_box</icon> + <assignPolicy>auto</assignPolicy> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>create_case_btn_0</id> + <cols>3</cols> + <layout>grid</layout> + <dataRef> + <id>show_create_case_button</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>require_title_in_creation</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>1</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>standard</appearance> + </layout> + </dataRef> + <dataRef> + <id>create_case_button_title</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>create_case_button_icon</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>1</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>create_case_button_icon_preview</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>2</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>standard</appearance> + </layout> + </dataRef> + <dataRef> + <id>banned_nets_in_creation</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>3</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>assign</id> + <title/> + </event> + <event type="cancel"> + <id>cancel</id> + <title/> + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + <event type="delegate"> + <id>delegate</id> + <title/> + </event> + </transition> + <transition> + <id>header_settings</id> + <x>560</x> + <y>48</y> + <label name="header_settings">Header</label> + <icon>view_column</icon> + <assignPolicy>auto</assignPolicy> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>header_0</id> + <cols>3</cols> + <layout>grid</layout> + <dataRef> + <id>case_is_header_mode_changeable</id> + <logic> + <behavior>editable</behavior> + <behavior>required</behavior> + <action trigger="set"> + trans: t.this, + isChangeable: f.case_is_header_mode_changeable, + mode: f.case_headers_mode, + defaultMode: f.case_headers_default_mode; + + make [mode, defaultMode], editable on trans when { isChangeable.value } + make [mode, defaultMode], required on trans when { isChangeable.value } + + make [mode, defaultMode], hidden on trans when { !isChangeable.value } + make [mode, defaultMode], optional on trans when { !isChangeable.value } + </action> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>use_case_default_headers</id> + <logic> + <behavior>editable</behavior> + <action trigger="set"> + trans: t.this, + use: f.use_case_default_headers, + headers: f.case_default_headers; + + make headers,editable on trans when { use.value } + make headers,visible on trans when { !use.value } + </action> + </logic> + <layout> + <x>1</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_default_headers</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>2</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_allow_header_table_mode</id> + <logic> + <behavior>editable</behavior> + <behavior>required</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_headers_mode</id> + <logic> + <behavior>editable</behavior> + <behavior>required</behavior> + </logic> + <layout> + <x>1</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_headers_default_mode</id> + <logic> + <behavior>editable</behavior> + <behavior>required</behavior> + </logic> + <layout> + <x>2</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>assign</id> + <title/> + </event> + <event type="cancel"> + <id>cancel</id> + <title/> + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + <event type="delegate"> + <id>delegate</id> + <title/> + </event> + </transition> + <transition> + <id>all_menu_data</id> + <x>10</x> + <y>10</y> + <label>All menu data</label> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>all_menu_data_0</id> + <cols>1</cols> + <layout>grid</layout> + <dataRef> + <id>case_filter</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_view_search_type</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>show_create_case_button</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_show_more_menu</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>3</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_empty_content_text</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>4</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_empty_content_icon</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>5</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_empty_content_icon_preview</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>6</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_all_allowed_nets</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>7</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_allowed_nets</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>8</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_inherit_allowed_nets</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>9</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>create_case_button_title</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>10</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>create_case_button_icon</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>11</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>create_case_button_icon_preview</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>12</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>require_title_in_creation</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>13</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>banned_nets_in_creation</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>14</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_is_header_mode_changeable</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>15</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_allow_header_table_mode</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>16</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_headers_mode</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>17</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_headers_default_mode</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>18</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>use_case_default_headers</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>19</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_default_headers</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>20</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>view_configuration_type</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>21</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>view_configuration_all_data_form</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>22</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>case_allow_export</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>23</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + </transition> + <place> + <id>initialized</id> + <x>496</x> + <y>208</y> + <label>initialized</label> + <tokens>0</tokens> + <static>false</static> + </place> + <place> + <id>uninitialized</id> + <x>240</x> + <y>208</y> + <label>uninitialized</label> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>is_tabbed</id> + <x>240</x> + <y>48</y> + <label>is_tabbed</label> + <tokens>0</tokens> + <static>false</static> + </place> + <place> + <id>is_untabbed</id> + <x>112</x> + <y>144</y> + <label>is_untabbed</label> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>read</type> + <sourceId>initialized</sourceId> + <destinationId>settings</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>uninitialized</sourceId> + <destinationId>initialize</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a3</id> + <type>regular</type> + <sourceId>initialize</sourceId> + <destinationId>initialized</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a4</id> + <type>read</type> + <sourceId>initialized</sourceId> + <destinationId>filter_settings</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a5</id> + <type>read</type> + <sourceId>initialized</sourceId> + <destinationId>open_case_settings</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a6</id> + <type>read</type> + <sourceId>initialized</sourceId> + <destinationId>header_settings</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a7</id> + <type>read</type> + <sourceId>initialized</sourceId> + <destinationId>create_case_btn_settings</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a8</id> + <type>regular</type> + <sourceId>initialize</sourceId> + <destinationId>is_tabbed</destinationId> + <multiplicity>1</multiplicity> + <reference>is_tabbed_var_arc</reference> + </arc> + <arc> + <id>a9</id> + <type>read</type> + <sourceId>is_tabbed</sourceId> + <destinationId>open_case_settings</destinationId> + <multiplicity>1</multiplicity> + </arc> +</document> \ No newline at end of file diff --git a/src/main/resources/petriNets/engine-processes/preference_item.xml b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml similarity index 52% rename from src/main/resources/petriNets/engine-processes/preference_item.xml rename to src/main/resources/petriNets/engine-processes/menu/menu_item.xml index b3a4b556764..aa52e26d39c 100644 --- a/src/main/resources/petriNets/engine-processes/preference_item.xml +++ b/src/main/resources/petriNets/engine-processes/menu/menu_item.xml @@ -1,7 +1,8 @@ -<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://petriflow.org/petriflow.schema.xsd"> - <id>preference_item</id> - <initials>PRI</initials> - <title>Preference Item + + menu_item + 1.0.0 + MNI + Menu Item check_box_outline_blank true false @@ -25,17 +26,17 @@ default - false - false true - preference_item_delete + menu_item_delete - removeItemChildCases(useCase) + removeRefFromDashboardItem() + removeItemChildCases() + removeViewCase() @@ -48,8 +49,27 @@ admin Admin + + { -> + def advancedContentFormField = useCase.getField("advanced_content_form") + def taskIds = [useCase.tasks.find { taskPair -> taskPair.transition == "role_settings"}.task, + useCase.tasks.find { taskPair -> taskPair.transition == "view_settings"}.task] + + def viewConfigurationIdDataField = useCase.getDataField("view_configuration_id") + if (viewConfigurationIdDataField.value != null && !viewConfigurationIdDataField.value.isEmpty()) { + def viewCase = workflowService.findOne(viewConfigurationIdDataField.value[0]) + def taskIdsFromView = viewCase.getFieldValue("advanced_content_form") + if (taskIdsFromView != null) { + taskIds.addAll(taskIdsFromView) + make ["advanced_content_form"], hidden on (viewCase.tasks.findAll { it.transition == "settings" }?.task as List) when { true } + } + } + + change advancedContentFormField value { taskIds } + } + - { com.netgrif.application.engine.workflow.domain.Case useCase -> + { -> def childCaseIds = useCase.dataSet['childItemIds'].value if (childCaseIds == null || childCaseIds.isEmpty()) { @@ -66,63 +86,64 @@ } } - - { - com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField filterAutocomplete, - com.netgrif.application.engine.petrinet.domain.dataset.TaskField previewTaskRef, - com.netgrif.application.engine.petrinet.domain.dataset.CaseField selectedFilterRef, - com.netgrif.application.engine.petrinet.domain.dataset.ButtonField updateBtn, - com.netgrif.application.engine.petrinet.domain.Transition trans, - boolean taskTypeOnly - -> - if (filterAutocomplete.getOptions().containsKey(filterAutocomplete.value)) { - change previewTaskRef value { - return [findTask({it.caseId.eq(filterAutocomplete.value).and(it.transitionId.eq("view_filter"))}).stringId] - } - make updateBtn,editable on trans when { true } - } else { - change filterAutocomplete options { - def findAllPredicate - if (taskTypeOnly) { - findAllPredicate = { filterCase -> - !selectedFilterRef.value.contains(filterCase.stringId) && - filterCase.dataSet["filter_type"].value == "Task" - } - } else { - findAllPredicate = { filterCase -> !selectedFilterRef.value.contains(filterCase.stringId) } - } - return findFilters(filterAutocomplete.value != null ? filterAutocomplete.value : "") - .findAll(findAllPredicate) - .collectEntries({filterCase -> [filterCase.stringId, filterCase.title]}) - } - change previewTaskRef value { [] } - make updateBtn,visible on trans when { true } + + { -> + + def viewIdAsList = useCase.dataSet['view_configuration_id'].value + if (viewIdAsList == null || viewIdAsList.isEmpty()) { + return + } + + async.run { + workflowService.deleteCase(viewIdAsList[0]) } } - - { - com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField toBeUpdated, - com.netgrif.application.engine.petrinet.domain.dataset.MultichoiceMapField valueSelector, - com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField optionsHolder - -> - def existingOptions = optionsHolder.options - def selectedValues = valueSelector.value - def newOptions = [:] - - if (selectedValues != null) { - existingOptions.each { key, value -> - if (selectedValues.contains(key)) { - newOptions.put(key, value) - } - } + + { -> + def cases = findCasesElastic("processIdentifier:\"dashboard_item\" AND dataSet.menu_items_list.keyValue:\"$useCase.stringId\"") + cases.each { refCase -> + def refTask = refCase.tasks.find { it.transition == "configuration" }.task + setData(refTask,[ + "menu_items_list": ["type": "enumeration_map", "value": ""], + "menu_item_taskRef": ["type": "taskRef", "value": []] + ]) } - if (!newOptions.containsKey(toBeUpdated.value)) { - change toBeUpdated value { null } + } + + + { boolean useTabIcon, boolean useTabbedView, String transId = "view_settings" -> + def settingsTrans = useCase.petriNet.transitions[transId] + + make [useCase.getField("use_tab_icon"), useCase.getField("tab_icon"), useCase.getField("tab_name")], + editable on settingsTrans when { useTabbedView && useTabIcon } + make useCase.getField("tab_icon_preview"), visible on settingsTrans when { useTabbedView && useTabIcon } + + make useCase.getField("use_tab_icon"), editable on settingsTrans when { useTabbedView && !useTabIcon } + make [useCase.getField("tab_icon_preview"), useCase.getField("tab_icon"), useCase.getField("tab_name")], + hidden on settingsTrans when { useTabbedView && !useTabIcon } + + make [useCase.getField("use_tab_icon"), useCase.getField("tab_icon_preview"), useCase.getField("tab_icon"), + useCase.getField("tab_name")], hidden on settingsTrans when { !useTabbedView } + } + + + { + -> + String query = String.format("processIdentifier:menu_item AND uriNodeId:%s AND dataSet.is_auto_select.booleanValue:true AND NOT stringId:%s", + useCase.uriNodeId, useCase.stringId) + def itemCase = findCaseElastic(query) + if (itemCase == null) { + return } - change toBeUpdated options { newOptions } + setData("item_settings", itemCase, [ + "is_auto_select": [ + "value": false, + "type": "boolean" + ] + ]) } @@ -130,7 +151,7 @@ parentId <allowedNets> - <allowedNet>preference_item</allowedNet> + <allowedNet>menu_item</allowedNet> </allowedNets> </data> <data type="text"> @@ -144,13 +165,6 @@ <component> <name>autocomplete</name> </component> - <action trigger="get"> - moveDestUri: f.move_dest_uri; - - String uriNodeId = elasticCaseService.findUriNodeId(useCase) - def node = uriService.findById(uriNodeId) - updateMultichoiceWithCurrentNode(moveDestUri, node) - </action> <action trigger="set"> prevDestUri: f.move_previous_dest_uri, moveDestUri: f.move_dest_uri; @@ -192,6 +206,7 @@ prefixUri = prefixUri.replace("//","/") String newUri = prefixUri + uriService.getUriSeparator() + newNodeName.value + newUri = newUri.replace("//","/") def newNode = uriService.getOrCreate(newUri, com.netgrif.application.engine.petrinet.domain.UriContentType.CASE) change selectedUri value { splitUriPath(newNode.uriPath) } @@ -212,7 +227,7 @@ <id>childItemIds</id> <title/> <allowedNets> - <allowedNet>preference_item</allowedNet> + <allowedNet>menu_item</allowedNet> </allowedNets> </data> <data type="taskRef"> @@ -238,6 +253,10 @@ <id>menu_item_identifier</id> <title name="menu_item_identifier">Menu item identifier + + menu_item_identifier_on_create + Menu item identifier + nodePath Item URI @@ -288,7 +307,7 @@ menu_name_as_visible Name of the item - Is shown in the menu + Will be shown in the menu autocomplete @@ -306,41 +325,6 @@ change menu_name_as_visible value { name.value }
- - tab_icon - Tab icon identifier - Material icon identifier. List of icons with identifiers is available online. - - icon: f.this, - iconPreview: f.tab_icon_preview; - - if (icon.value == "") { - change iconPreview value {"""]]>} - return; - } - - change iconPreview value { - """]]> + icon.value + """]]> - } - - - - tab_icon_preview - Tab icon preview - - htmltextarea - - - - use_tab_icon - Display tab icon? - true - - - tab_name - Name of the item - Will be shown in tab - add_allowed_roles @@ -414,7 +398,11 @@ <action trigger="get"> processes: f.this; - change processes options { return configurableMenuService.getNetsByAuthorAsMapOptions(loggedUser(), org.springframework.context.i18n.LocaleContextHolder.locale) } + try { + change processes options { return configurableMenuService.getNetsByAuthorAsMapOptions(loggedUser(), org.springframework.context.i18n.LocaleContextHolder.locale) } + } catch (Exception e) { + log.error("Cannot resolve processes: ", e) + } </action> <action trigger="set"> processes: f.this, @@ -450,132 +438,6 @@ <init dynamic="true">[:]</init> </options> </data> - <data type="taskRef"> - <id>selected_filter_preview</id> - <title/> - </data> - <data type="taskRef"> - <id>current_filter_preview</id> - <title/> - </data> - <data type="i18n"> - <id>filter_header</id> - <title/> - <init name="filter_header">Current filter</init> - <component> - <name>divider</name> - </component> - </data> - <data type="text"> - <id>new_filter_id</id> - <title/> - </data> - <data type="enumeration_map"> - <id>filter_autocomplete_selection</id> - <title name="filter_autocomplete_selection">Select new filter - - autocomplete_dynamic - - - trans: t.item_settings, - useCustomView: f.use_custom_view, - filterAutocomplete: f.this, - filter_case: f.filter_case, - update_filter: f.update_filter, - previewTaskRef: f.selected_filter_preview; - - updateFilterAutocompleteOptions(filterAutocomplete, previewTaskRef, filter_case, update_filter, trans, false) - - make update_filter,hidden on trans when { useCustomView.value } - - - trans: t.item_settings, - useCustomView: f.use_custom_view, - filterAutocomplete: f.this, - filter_case: f.filter_case, - update_filter: f.update_filter, - previewTaskRef: f.selected_filter_preview; - - updateFilterAutocompleteOptions(filterAutocomplete, previewTaskRef, filter_case, update_filter, trans, false) - - make update_filter,hidden on trans when { useCustomView.value } - - - - update_filter - - <placeholder name="update_filter">Update view with selected filter</placeholder> - <component> - <name>raised</name> - </component> - <action trigger="set"> - trans: t.item_settings, - update_filter: f.update_filter, - filter_case: f.filter_case, - filterAutocomplete: f.filter_autocomplete_selection; - - change filter_case value { [filterAutocomplete.value] } - change filterAutocomplete value { "" } - make update_filter,visible on trans when { true } - </action> - </data> - <data type="caseRef"> - <id>filter_case</id> - <title/> - <action trigger="set"> - additionalFilterCase: f.additional_filter_case, - additionalAutocomplete: f.additional_filter_autocomplete_selection, - additionalUpdate: f.update_additional_filter, - additionalFilterPreview: f.selected_additional_filter_preview, - mergeFilters: f.merge_filters, - filterHeader: f.filter_header, - currentAdditionalFilterPreview: f.current_additional_filter_preview, - taskSettingsTrans: t.task_view_settings, - settingsTrans: t.item_settings, - caseViewHeader: f.case_view_header, - caseViewSettingsTaskRef: f.case_view_settings_taskRef, - taskViewHeader: f.task_view_header, - taskViewSettingsTaskRef: f.task_view_settings_taskRef, - filterTaskRef: f.current_filter_preview, - filterCaseRef: f.filter_case; - - if (filterCaseRef.value == null || filterCaseRef.value == []) { - return - } - - def filterCase = findCase({it._id.eq(filterCaseRef.value[0])}) - change filterTaskRef value {return [findTask({it.caseId.eq(filterCase.stringId).and(it.transitionId.eq("view_filter"))}).stringId]} - - if (filterCase.dataSet["filter_type"].value == "Case") { - make caseViewHeader,editable on settingsTrans when { true } - make caseViewSettingsTaskRef,editable on settingsTrans when { true } - - make additionalAutocomplete,editable on taskSettingsTrans when { true } - make additionalUpdate,visible on taskSettingsTrans when { true } - make additionalFilterPreview,visible on taskSettingsTrans when { true } - make mergeFilters,visible on taskSettingsTrans when { true } - make filterHeader,visible on taskSettingsTrans when { true } - make currentAdditionalFilterPreview,visible on taskSettingsTrans when { true } - } else { - make caseViewHeader,hidden on settingsTrans when { true } - make caseViewSettingsTaskRef,hidden on settingsTrans when { true } - - make additionalAutocomplete,hidden on taskSettingsTrans when { true } - make additionalUpdate,hidden on taskSettingsTrans when { true } - make additionalFilterPreview,hidden on taskSettingsTrans when { true } - make mergeFilters,hidden on taskSettingsTrans when { true } - make filterHeader,hidden on taskSettingsTrans when { true } - make currentAdditionalFilterPreview,hidden on taskSettingsTrans when { true } - } - make taskViewHeader,editable on settingsTrans when { true } - make taskViewSettingsTaskRef,editable on settingsTrans when { true } - - change additionalFilterCase value { [] } - </action> - <allowedNets> - <allowedNet>filter</allowedNet> - </allowedNets> - </data> <data type="boolean" immediate="true"> <id>use_custom_view</id> <title name="use_custom_view">Use custom view? @@ -586,404 +448,156 @@ Custom view configuration selector Example: "demo-tabbed-views" - - - - case_view_search_type - Search type for case view - - - - - - fulltext_advanced - - - create_case_button_title - "New case" button title - - - create_case_button_icon_preview - Icon preview - add]]> - - htmltextarea - - - - create_case_button_icon - "New case" button icon identifier - add + + is_auto_select + View auto selection + If selected, the view will be automatically opened + false - create_case_button_icon_preview: f.create_case_button_icon_preview, - create_case_button_icon: f.create_case_button_icon; + is_auto_select: f.is_auto_select; + if (!is_auto_select.value) + return - if (create_case_button_icon.value == "") { - change create_case_button_icon_preview value {"""]]>} - return; - } - - change create_case_button_icon_preview value { - """]]> + create_case_button_icon.value + """]]> - } + removeAnyAutoSelectFlagInFolder() - show_create_case_button - Show create case button? - true - - - case_require_title_in_creation - Require title input in case creation? + use_tabbed_view + Do you want to use view with tabs? true + + 0 + + + use_tabbed_view: f.use_tabbed_view, + use_tab_icon: f.use_tab_icon; + + manageBehaviorOfTabFields(use_tab_icon.value, use_tabbed_view.value) + + + + + - case_banned_nets_in_creation - Banned processes for creation - Write down process identifiers separated by comma. Example: mynet1,mynet2 + tab_icon + Tab icon identifier + Material icon identifier. List of icons with identifiers is available online. - bannedNets: f.this; + icon: f.this, + iconPreview: f.tab_icon_preview; + + if (icon.value == "") { + change iconPreview value {"""]]>} + return; + } - String trimmed = bannedNets.value?.replaceAll("\\s","") - if (bannedNets.value != trimmed) { - change bannedNets value { trimmed } + change iconPreview value { + """]]> + icon.value + """]]> } - - case_view_header - - <init name="case_view_header">Case view</init> + <data type="text"> + <id>tab_icon_preview</id> + <title name="tab_icon_preview">Tab icon preview - divider + htmltextarea - - case_view_settings_taskRef - - <init>case_view_settings</init> - </data> - <data type="boolean" immediate="true"> - <id>case_show_more_menu</id> - <title name="case_show_more_menu">Show more menu for case item? - false - - case_allow_header_table_mode - Allow table mode for headers? + use_tab_icon + Display tab icon? true - - case_headers_mode - Header mode - - - - - - sort,edit,search - - headersMode: f.case_headers_mode, - defaultMode: f.case_headers_default_mode, - holder: f.case_headers_options_holder; - - updateOptionsBasedOnValue(defaultMode, headersMode, holder) - - - headersMode: f.case_headers_mode, - defaultMode: f.case_headers_default_mode, - holder: f.case_headers_options_holder; - - updateOptionsBasedOnValue(defaultMode, headersMode, holder) - + + tab_name + Name of the item + Will be shown in tab + + new com.netgrif.application.engine.petrinet.domain.I18nString("View", [sk: "Zobrazenie", de: "Die Ansicht"]) + - case_headers_default_mode - Default header mode - - - - - - sort - - - case_headers_options_holder - + <id>view_configuration_type</id> + <title name="view_configuration_type">View type - - - + menuItemService.getAvailableViewsAsOptions(true, true) - - - case_is_header_mode_changeable - Can header mode be changed? - true - - - use_case_default_headers - Use custom default headers? - true - - - case_default_headers - Set default headers - Example: "meta-title,meta-visualId" - defaultHeaders: f.this; + use_tabbed_view: f.use_tabbed_view, + view_configuration_type: f.view_configuration_type; - String trimmed = defaultHeaders.value?.replaceAll("\\s","") - if (defaultHeaders.value != trimmed) { - change defaultHeaders value { trimmed } + if (view_configuration_type.options == null || view_configuration_type.options.isEmpty()) { + change view_configuration_type options { menuItemService.getAvailableViewsAsOptions(use_tabbed_view.value, true) } } - - + + view_configuration_id + + <allowedNets> + <allowedNet>case_view_configuration</allowedNet> + <allowedNet>task_view_configuration</allowedNet> + <allowedNet>tabbed_ticket_view_configuration</allowedNet> + <allowedNet>single_task_view_configuration</allowedNet> + </allowedNets> + </data> <data type="taskRef"> - <id>selected_additional_filter_preview</id> + <id>view_configuration_form</id> <title/> </data> <data type="taskRef"> - <id>current_additional_filter_preview</id> + <id>view_configuration_all_data_form</id> <title/> </data> - <data type="enumeration_map"> - <id>additional_filter_autocomplete_selection</id> - <title name="filter_autocomplete_selection">Select new filter + + order_down + + <placeholder>south</placeholder> <component> - <name>autocomplete_dynamic</name> + <name>icon</name> + <property key="stretch">true</property> </component> <action trigger="set"> - trans: t.task_view_settings, - filterAutocomplete: f.this, - filterCase: f.additional_filter_case, - updateFilter: f.update_additional_filter, - previewTaskRef: f.selected_additional_filter_preview; + parentId: f.parentId; - updateFilterAutocompleteOptions(filterAutocomplete, previewTaskRef, filterCase, updateFilter, trans, true) - </action> - <action trigger="get"> - trans: t.task_view_settings, - filterAutocomplete: f.this, - filterCase: f.additional_filter_case, - updateFilter: f.update_additional_filter, - previewTaskRef: f.selected_additional_filter_preview; + def parentCase = workflowService.findOne(parentId.value[0]) + def taskId = useCase.tasks.find { it.transition == "row_for_ordering" }.task + def taskRefValue = parentCase.dataSet['childItemForms'].value + int taskRefValueSize = taskRefValue.size() + def caseRefValue = parentCase.dataSet['childItemIds'].value + int caseRefValueSize = caseRefValue.size() + + int idxInTaskRef = taskRefValue.indexOf(taskId) + if (idxInTaskRef < taskRefValueSize - 1) { + Collections.swap(taskRefValue, idxInTaskRef, idxInTaskRef + 1) + } + + int idxInCaseRef = caseRefValue.indexOf(useCase.stringId) + if (idxInCaseRef < caseRefValueSize - 1) { + Collections.swap(caseRefValue, idxInCaseRef, idxInCaseRef + 1) + } - updateFilterAutocompleteOptions(filterAutocomplete, previewTaskRef, filterCase, updateFilter, trans, true) + setData("children_order", parentCase, [ + "childItemForms" : [ + "value" : taskRefValue, + "type" : "taskRef" + ], + "childItemIds" : [ + "value" : caseRefValue, + "type" : "caseRef" + ] + ]) </action> </data> <data type="button"> - <id>update_additional_filter</id> + <id>order_up</id> <title/> - <placeholder name="update_filter">Update view with selected filter</placeholder> + <placeholder>north</placeholder> <component> - <name>raised</name> - </component> - <action trigger="set"> - trans: t.task_view_settings, - updateFilter: f.update_additional_filter, - filterCase: f.additional_filter_case, - filterAutocomplete: f.additional_filter_autocomplete_selection; - - change filterCase value { [filterAutocomplete.value] } - change filterAutocomplete value { "" } - make updateFilter,visible on trans when { true } - </action> - </data> - <data type="button"> - <id>remove_additional_filter</id> - <title/> - <placeholder name="remove_additional_filter">Remove additional filter</placeholder> - <component> - <name>raised</name> - </component> - <action trigger="set"> - filterCase: f.additional_filter_case; - - change filterCase value { [] } - </action> - </data> - <data type="caseRef"> - <id>additional_filter_case</id> - <title/> - <action trigger="set"> - taskViewTrans: t.task_view_settings, - mergeFilters: f.merge_filters, - filterHeader: f.filter_header, - filterTaskRef: f.current_additional_filter_preview, - removeButton: f.remove_additional_filter, - filterCaseRef: f.additional_filter_case; - - if (filterCaseRef.value[0] == null) { - make mergeFilters,hidden on taskViewTrans when { true } - make filterHeader,hidden on taskViewTrans when { true } - make removeButton,hidden on taskViewTrans when { true } - change filterTaskRef value { [] } - return - } - - def filterCase = findCase({ it._id.eq(filterCaseRef.value[0]) }) - change filterTaskRef value { return [findTask({it.caseId.eq(filterCase.stringId).and(it.transitionId.eq("view_filter"))}).stringId] } - make mergeFilters,editable on taskViewTrans when { true } - make filterHeader,visible on taskViewTrans when { true } - make removeButton,editable on taskViewTrans when { true } - </action> - <allowedNets> - <allowedNet>filter</allowedNet> - </allowedNets> - </data> - <data type="boolean" immediate="true"> - <id>merge_filters</id> - <title name="merge_filters">Merge with base filter? - true - - - task_view_settings_taskRef - - <init>task_view_settings</init> - </data> - <data type="i18n"> - <id>task_view_header</id> - <title/> - <init name="task_view_header">Task view</init> - <component> - <name>divider</name> - </component> - </data> - <data type="enumeration_map" immediate="true"> - <id>task_view_search_type</id> - <title name="task_view_search_type">Search type for task view - - - - - - fulltext_advanced - - - task_headers_mode - Header mode - - - - - sort,edit - - headersMode: f.task_headers_mode, - defaultMode: f.task_headers_default_mode, - holder: f.task_headers_options_holder; - - updateOptionsBasedOnValue(defaultMode, headersMode, holder) - - - headersMode: f.case_headers_mode, - defaultMode: f.case_headers_default_mode, - holder: f.case_headers_options_holder; - - updateOptionsBasedOnValue(defaultMode, headersMode, holder) - - - - task_headers_default_mode - Default header mode - - - - - sort - - - task_headers_options_holder - - <options> - <option key="sort" name="sort">Sort</option> - <option key="edit" name="edit">Edit</option> - </options> - </data> - <data type="boolean" immediate="true"> - <id>task_is_header_mode_changeable</id> - <title name="is_header_mode_changeable">Can header mode be changed? - true - - - task_allow_header_table_mode - Allow table mode for headers? - true - - - use_task_default_headers - Use custom default headers? - true - - - task_default_headers - Set default headers - Example: "meta-title,meta-user" - - defaultHeaders: f.this; - - String trimmed = defaultHeaders.value?.replaceAll("\\s","") - if (defaultHeaders.value != trimmed) { - change defaultHeaders value { trimmed } - } - - - - task_show_more_menu - Show more menu for task item? - true - - - order_down - - <placeholder>south</placeholder> - <component> - <name>icon</name> - <property key="stretch">true</property> - </component> - <action trigger="set"> - parentId: f.parentId; - - def parentCase = workflowService.findOne(parentId.value[0]) - def taskId = useCase.tasks.find { it.transition == "row_for_ordering" }.task - def taskRefValue = parentCase.dataSet['childItemForms'].value - int taskRefValueSize = taskRefValue.size() - def caseRefValue = parentCase.dataSet['childItemIds'].value - int caseRefValueSize = caseRefValue.size() - - int idxInTaskRef = taskRefValue.indexOf(taskId) - if (idxInTaskRef < taskRefValueSize - 1) { - Collections.swap(taskRefValue, idxInTaskRef, idxInTaskRef + 1) - } - - int idxInCaseRef = caseRefValue.indexOf(useCase.stringId) - if (idxInCaseRef < caseRefValueSize - 1) { - Collections.swap(caseRefValue, idxInCaseRef, idxInCaseRef + 1) - } - - setData("children_order", parentCase, [ - "childItemForms" : [ - "value" : taskRefValue, - "type" : "taskRef" - ], - "childItemIds" : [ - "value" : caseRefValue, - "type" : "caseRef" - ] - ]) - </action> - </data> - <data type="button"> - <id>order_up</id> - <title/> - <placeholder>north</placeholder> - <component> - <name>icon</name> - <property key="stretch">true</property> + <name>icon</name> + <property key="stretch">true</property> </component> <action trigger="set"> parentId: f.parentId; @@ -1019,9 +633,27 @@ ]) </action> </data> - <data type="boolean" immediate="true"> - <id>case_allow_export</id> - <title name="case_allow_export">Allow export? + + role_divider + + <component> + <name>divider</name> + </component> + </data> + <data type="taskRef"> + <id>advanced_content_form</id> + <title/> + <component> + <name>task-list</name> + <property key="headers">meta-title</property> + </component> + </data> + <data type="enumeration_map"> + <id>configuration_templates</id> + <title name="configuration_templates">Configuration template + + com.netgrif.application.engine.menu.service.MenuItemTemplateHolder.transformToOptions() + @@ -1036,12 +668,6 @@ Vaše procesy Vyberte proces obsahujúci roly ktoré chcete pridať do zoznamu povolených alebo zakázaných rolí. Dostupné roly - Názov tlačidla "Nová inštancia" - Identifikátor ikony tlačidla "Nová inštancia" - Náhľad ikony - Predvolené hlavičky - Napríklad: "meta-title,meta-visualId" - Zvoľte nový filter Cieľové URI Názov duplikovanej položky Identifikátor duplikovanej položky @@ -1050,59 +676,40 @@ Bude zobrazený v menu Identifikátor ikony v karte Identifikátor Material ikony. Zoznam ikon s identifikátormi je dostupný online. + Náhľad ikony v karte Zobraziť ikonu v karte? Názov položky Bude zobrazený v karte - Aktualizovať zobrazenie s vybraným filtrom Použiť vlastné zobrazenie? Konfiguračný identifikátor vlastného zobrazenia Napríklad: "demo-tabbed-views" - Typ vyhľadávania prípadov - Skryté - Fulltext - Fulltext a rozšírené - Vyžadovať názov inštancii pri vytváraní? - Zakázané siete pri vytváraní - Uveďte identifikátory procesov oddelené čiarkou. Napríklad: mynet1,mynet2 - Zobraziť tlačidlo na vytvorenie prípadu? - Zobrazovať menu pre prípadovú položku? - Zoraďovanie - Vyhľadávanie - Upravovanie - Zjednotiť filter so základným filtrom? - Typ vyhľadávania úloh - Mód hlavičiek - Predvolený mód hlavičiek - Môže byť mód hlavičiek zmenený? - Povoliť tabuľkový mód pre hlavičky? - Použiť vlastné predvolené hlavičky? - Predvolené hlavičky - Napríklad: "meta-title,meta-user" - Zobrazovať menu pre úlohovú položku? Nastavenie položky Roly - Filter Presunúť položku Presunúť Duplikovať položku Duplikovať - Dodatočný filter - Súčasný filter - Zobrazenie prípadov - Zobrazenie úloh Povolené roly Zoznam povolených rolí, ktoré môžu zobraziť túto položku Zakázané roly Zoznam zakázaných rolí, ktoré nemôžu zobraziť túto položku - Všeobecné Identifikátor položky - Odstrániť dodatočný filter URI položky Nový uzol Uveďte názov uzlu, ktorý chcete pridať Zoznam uzlov reprezentujúce cieľovú URI Pridať - Povoliť export? + Automatické zvolenie zobrazenia + Po automatickom zvolení sa dané zobrazenie používateľovi otvorí + Použiť zobrazenie v taboch? + Typ zobrazenia + Nastavenie zobrazenia + Vytvoriť menu položku + Zoradiť pod-položky + Vytvoriť menu položku + Konfiguračná šablóna + Rola + Zobrazenie Ikonevorschau @@ -1115,34 +722,11 @@ Ihre Prozesse Wählen Sie einen Prozess mit Rollen aus, die Sie zu Listen mit zulässigen oder verbotenen Rollen hinzufügen möchten. Verfügbare Rollen - Schaltflächentitel "Neuer Fall" - Ikone ID - Ikonevorschau - Anzuzeigende Attributmenge auswählen - Neue Filter auswählen - Beispiel: "meta-title,meta-visualId" Material Ikone ID. Liste den Ikonen mit IDs ist online verfügbar. Beispiel: "demo-tabbed-views" - Versteckt - Einfacher Suchmodus - Sortieren - Suchen - Bearbeiten - Kopfzeilenmodus - Standardkopfzeilenmodus - Erlaube Änderung des Kopfzeilenmodus? - Erlaube Tabellenmodus? - Eigene Kopfzeilen verwenden? - Anzuzeigende Attributmenge auswählen - Beispiel: "meta-title,meta-user" Rollen - Filter - Zusätzlicher Filter - Aktueller Filter Zulässige Rollen - Allgemein Identifikationsnummer des Menüeintrages - Zusatzfilter entfernen Menüeintrag-URI Neuer Knoten Hinzufügen @@ -1153,35 +737,33 @@ Titel des Eintrages Wird im Menü angezeigt Ikonen Identifikator der Registerkarte + Vorschau der Registerkarte Ikone Zeige die Registerkarte Ikone an? Titel der Registerkarte Wird in der Registerkarte angezeigt - Aktualisiere die Ansicht mit dem ausgewählten Filter Eigener Ansicht anwenden? Konfigurationsidentifikator der eigenen Ansicht - Suchmodus im Fallansicht - Einfacher und erweiterter Suchmodus - Erforde den Titel beim erzeugen von Fällen? - Ausgeschlossene Prozesse - Trenne die Prozessidentifikatoren mit einer Komma. z.B.: netz1,netz2 - Schaltfläche „Fall erstellen“ anzeigen? - "Erweiterte Optionen" Taste bei einzelnen Fällen anzeigen - Mit dem Basisfilter kombinieren? - Suchmodus im Aufgabenansicht - "Erweiterte Optionen" Taste bei einzelnen Aufgaben anzeigen Menüeintrageinstellungen Menüeintrag verschieben verschieben Menüeintrag duplizieren duplizieren - Fallansicht - Aufgabenansicht Rollen mit Zugriff auf diesen Menüeintrag Verbotene Rollen Rollen, für die wird den Menüeintrag ausgeblendet Nächste URI-Teil angeben Teile der Ziel URI - Erlaube Export? + Automatische Anzeigeauswahl + Wenn ausgewählt, wird die Ansicht automatisch geöffnet + Möchten Sie die Ansicht mit Registerkarten verwenden? + Der Ansichtstyp + Die Ansichtskonfiguration + Menüeintrag erstellen + Untereintrage sortieren + Erstellen + Konfigurationsvorlage + Rolle + Ansicht @@ -1189,65 +771,27 @@ initialize 340 220 - + + + true + hourglass_empty - - admin - - true - true - true - true - - - - view - - filter_case - - forbidden - - filterCaseRef: f.filter_case, - menu_name: f.menu_name; - - if (filterCaseRef.value == null || filterCaseRef.value == []) { - return - } - - def filterCase = findCase({it._id.eq(filterCaseRef.value[0])}) - if (!menu_name.value) { - change menu_name value {return filterCase.dataSet["i18n_filter_name"].value} - } - - - - - - - - item_settings - 460 - 100 - - settings auto admin true - true - true - true - pre_general + initialize_0 4 grid - menu_item_identifier + menu_name - visible + editable + required 0 @@ -1257,11 +801,34 @@ outline + + 0 + + + menu_item_identifier: f.menu_item_identifier_on_create, + menu_name: f.menu_name; + + if (menu_item_identifier.value != null && !menu_item_identifier.value.isEmpty() + || menu_name.value == null) { + return + } + + String defaultName = menu_name.value.defaultValue + if (defaultName == null || defaultName.isEmpty()) { + defaultName = menu_name.value.translations?.values()?.find() + } + + String identifier = com.netgrif.application.engine.menu.utils.MenuItemUtils.sanitize(defaultName) + change menu_item_identifier value { identifier } + + + - nodePath + menu_item_identifier_on_create - visible + editable + required 2 @@ -1271,21 +838,37 @@ outline + + 0 + + + menu_item_identifier: f.menu_item_identifier_on_create; + + if (menuItemService.existsMenuItem(menu_item_identifier.value)) { + change menu_item_identifier validations { validation('''regex $.^''', + new com.netgrif.application.engine.petrinet.domain.I18nString("Such menu item already exists", + [sk: "Taká položka menu už existuje", de: "Ein solcher Menüeintrag existiert bereits"])) + } + } else { + change menu_item_identifier validations { null } + String sanitizedIdentifier = com.netgrif.application.engine.menu.utils.MenuItemUtils.sanitize(menu_item_identifier.value) + if (menu_item_identifier.value != sanitizedIdentifier) { + change menu_item_identifier value { sanitizedIdentifier } + } + } + + + - - - general_0 - 4 - grid - General - menu_name + move_dest_uri editable + required 0 - 0 + 1 1 2 @@ -1293,13 +876,13 @@ - menu_icon + move_dest_uri_new_node editable 2 - 0 + 1 1 1 @@ -1307,440 +890,240 @@ - menu_icon_preview + move_add_node - visible + editable 3 - 0 + 1 1 1 - standard + outline - tab_name + configuration_templates editable + required 0 - 1 + 2 1 - 1 + 4 outline - - use_tab_icon - - editable - - - 1 - 1 - 1 - 1 - 0 - - - - 0 - - - trans: t.this, - iconPreview: f.tab_icon_preview, - icon: f.tab_icon, - useIcon: f.use_tab_icon; + + + assign + + + moveDestUri: f.move_dest_uri; - make iconPreview,visible on trans when { useIcon.value } - make icon,editable on trans when { useIcon.value } + String uriNodeId = elasticCaseService.findUriNodeId(useCase) + def node = uriService.findById(uriNodeId) + updateMultichoiceWithCurrentNode(moveDestUri, node) + + + + + finish + + + def outcome = menuItemService.handleConfigurationTemplate(useCase) + outcome.getMapping().each { fieldId, value -> + change useCase.getField(fieldId) value { value } + } + + + trans: t.view_settings, + useTabIcon: f.use_tab_icon, + view_configuration_form: f.view_configuration_form, + view_configuration_type: f.view_configuration_type, + tabIconPreview: f.tab_icon_preview, + tabName: f.tab_name, + tabIcon: f.tab_icon, + use_custom_view: f.use_custom_view, + selector: f.custom_view_selector; + + if (use_custom_view.value) { + make selector, editable on trans when { true } + make [useTabIcon, tabIconPreview, tabName, tabIcon, view_configuration_form, + view_configuration_type], hidden on trans when { true } + } + + + + + initializeAdvancedForm() + + + name: f.menu_name, + menu_item_identifier: f.menu_item_identifier, + menu_item_identifier_on_create: f.menu_item_identifier_on_create, + dest: f.move_dest_uri; - make iconPreview,hidden on trans when { !useIcon.value } - make icon,hidden on trans when { !useIcon.value } - - - -
- - tab_icon - - editable - - - 2 - 1 - 1 - 1 - - outline - - - - tab_icon_preview - - visible - - - 3 - 1 - 1 - 1 - - standard - - - - use_custom_view - - editable - - - 0 - 2 - 1 - 1 - - outline - - - 0 - - - trans: t.this, - caseHeader: f.case_view_header, - caseTaskRef: f.case_view_settings_taskRef, - taskHeader: f.task_view_header, - taskTaskRef: f.task_view_settings_taskRef, - - useTabIcon: f.use_tab_icon, - tabIconPreview: f.tab_icon_preview, - tabName: f.tab_name, - tabIcon: f.tab_icon, - filterSelection: f.filter_autocomplete_selection, - updateFilter: f.update_filter, - selectedFilterPreview: f.selected_filter_preview, - currentFilterHeader: f.filter_header, - currentFilterPreview: f.current_filter_preview, - - use: f.use_custom_view, - selector: f.custom_view_selector; - - make selector,editable on trans when { use.value } - make selector,visible on trans when { !use.value } - - make caseHeader,visible on trans when { !use.value } - make caseHeader,hidden on trans when { use.value } - make caseTaskRef,editable on trans when { !use.value } - make caseTaskRef,hidden on trans when { use.value } - - make taskHeader,visible on trans when { !use.value } - make taskHeader,hidden on trans when { use.value } - make taskTaskRef,editable on trans when { !use.value } - make taskTaskRef,hidden on trans when { use.value } - - make useTabIcon,editable on trans when { !use.value } - make useTabIcon,hidden on trans when { use.value } - make tabIconPreview,visible on trans when { !use.value } - make tabIconPreview,hidden on trans when { use.value } - make tabName,editable on trans when { !use.value } - make tabName,hidden on trans when { use.value } - make tabIcon,editable on trans when { !use.value } - make tabIcon,hidden on trans when { use.value } - make filterSelection,editable on trans when { !use.value } - make filterSelection,hidden on trans when { use.value } - make updateFilter,visible on trans when { !use.value } - make updateFilter,hidden on trans when { use.value } - make selectedFilterPreview,visible on trans when { !use.value } - make selectedFilterPreview,hidden on trans when { use.value } - make currentFilterHeader,visible on trans when { !use.value } - make currentFilterHeader,hidden on trans when { use.value } - make currentFilterPreview,visible on trans when { !use.value } - make currentFilterPreview,hidden on trans when { use.value } - - - - - - custom_view_selector - - visible - - - 1 - 2 - 1 - 3 - - outline - - - + change menu_item_identifier value { menu_item_identifier_on_create.value } + + String newUri = dest.value.join("/") + newUri = newUri.replace("//","/") + + changeMenuItem useCase uri { newUri } +
+ + Create + + + + data_sync + 340 + 112 + + + system + + true + + + + + + item_settings + 460 + 100 + + + true + + settings + auto + + admin + + true + + + + default + + true + + - roles_management - 5 + general_0 + 4 grid - Roles - processes_available + menu_item_identifier - editable + visible 0 0 - 2 - 1 - 0 - - outline - - - - roles_available - - editable - - - 1 - 0 - 2 - 1 - 0 + 1 + 2 outline - add_allowed_roles + nodePath - editable + visible 2 0 1 - 1 - 0 - - - - - allowed_roles - - editable - - - 3 - 0 - 1 - 1 - 0 + 2 outline - remove_allowed_roles + configuration_templates - editable - - - 4 - 0 - 1 - 1 - 0 - - - - - add_banned_roles - - editable - - - 2 - 1 - 1 - 1 - 0 - - - - - banned_roles - - editable + visible - 3 + 0 1 1 - 1 - 0 + 2 outline - remove_banned_roles + menu_name editable - 4 + 2 1 1 - 1 - 0 + 2 + outline - - - filter_update - 4 - grid - Filter - filter_autocomplete_selection + menu_icon editable 0 - 0 + 2 1 - 3 + 2 outline - update_filter - - visible - - - 3 - 0 - 1 - 1 - - standard - - - - selected_filter_preview + menu_icon_preview visible - 0 - 1 + 2 + 2 1 - 4 + 2 standard - - - current_filter - 4 - grid - filter_header + is_auto_select - visible - - - 0 - 0 - 1 - 4 - - outline - - - - current_filter_preview - - visible + editable 0 - 1 + 3 1 - 4 + 2 standard - - - case_view_settings_dataGroup - 4 - grid - - case_view_header - - hidden - - - 0 - 0 - 1 - 4 - - outline - - - case_view_settings_taskRef + advanced_content_form - hidden - - - 0 - 1 - 1 - 4 - - outline - - - - - task_view_settings_dataGroup - 4 - grid - - task_view_header - - hidden - - - 0 - 0 - 1 - 4 - - outline - - - - task_view_settings_taskRef - - hidden + editable 0 - 1 + 4 1 4 @@ -1748,6 +1131,14 @@ + + assign_0 + + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> </transition> <transition> @@ -1755,15 +1146,15 @@ <x>580</x> <y>100</y> <label name="move_item">Move item</label> + <tags> + <tag key="visible">true</tag> + </tags> <icon>move_down</icon> <assignPolicy>auto</assignPolicy> <roleRef> <id>admin</id> <logic> <perform>true</perform> - <view>true</view> - <cancel>true</cancel> - <assign>true</assign> </logic> </roleRef> <dataGroup> @@ -1814,6 +1205,18 @@ </layout> </dataRef> </dataGroup> + <event type="assign"> + <id>assign</id> + <actions phase="pre"> + <action> + moveDestUri: f.move_dest_uri; + + String uriNodeId = elasticCaseService.findUriNodeId(useCase) + def node = uriService.findById(uriNodeId) + updateMultichoiceWithCurrentNode(moveDestUri, node) + </action> + </actions> + </event> <event type="finish"> <id>finish</id> <actions phase="pre"> @@ -1839,15 +1242,15 @@ <x>580</x> <y>340</y> <label name="duplicate_item">Duplicate item</label> + <tags> + <tag key="visible">true</tag> + </tags> <icon>content_copy</icon> <assignPolicy>auto</assignPolicy> <roleRef> <id>admin</id> <logic> <perform>true</perform> - <view>true</view> - <cancel>true</cancel> - <assign>true</assign> </logic> </roleRef> <dataGroup> @@ -1855,31 +1258,162 @@ <cols>4</cols> <layout>grid</layout> <dataRef> - <id>duplicate_new_title</id> + <id>duplicate_new_title</id> + <logic> + <behavior>editable</behavior> + <behavior>required</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>duplicate_view_identifier</id> + <logic> + <behavior>editable</behavior> + <behavior>required</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="finish"> + <id>finish</id> + <actions phase="pre"> + <action> + identifier: f.duplicate_view_identifier, + title: f.duplicate_new_title; + + duplicateMenuItem(useCase, title.value, identifier.value) + </action> + </actions> + <title name="duplicate_item_finish">Duplicate + + + + + children_order + 580 + 220 + + + true + + low_priority + auto + + admin + + true + + + + children_order_0 + 4 + grid + + childItemForms + + editable + + forms: f.childItemForms, + ids: f.childItemIds; + + def orderedTaskIds = ids.value?.collect { id -> workflowService.findOne(id).tasks.find { it.transition == "row_for_ordering" }.task } + change forms value { orderedTaskIds } + + + + 0 + 0 + 1 + 4 + + outline + + + + + + row_for_ordering + 741 + 219 + + + system + + true + + + + row_for_ordering_0 + 6 + grid + + menu_item_identifier + + visible + + + 0 + 0 + 1 + 2 + + outline + + + + menu_name_as_visible + + visible + + + 2 + 0 + 1 + 2 + + outline + + + + order_down editable - required - 0 + 4 0 1 - 4 + 1 + 1 outline - duplicate_view_identifier + order_up editable - required - 0 - 1 + 5 + 0 1 - 4 + 1 + 1 outline @@ -1887,419 +1421,495 @@ finish - - - identifier: f.duplicate_view_identifier, - title: f.duplicate_new_title; - - duplicateMenuItem(useCase, title.value, identifier.value) - - - Duplicate + + </event> + <event type="delegate"> + <id>delegate</id> + <title/> </event> </transition> - <transition> - <id>change_filter</id> - <x>460</x> - <y>340</y> - <label>[Change filter]</label> + <id>system_initialize</id> + <x>336</x> + <y>304</y> + <label>System create</label> + <icon>computer</icon> <roleRef> <id>system</id> <logic> <perform>true</perform> </logic> </roleRef> - <dataRef> - <id>new_filter_id</id> - <logic> - <behavior>editable</behavior> - <behavior>required</behavior> - </logic> - <event type="set"> - <id>set_event_0</id> - <actions phase="post"> - <action> - new_filter_id: f.new_filter_id, - filterTaskRef: f.current_filter_preview, - filterCaseRef: f.filter_case; - - change filterCaseRef value { [new_filter_id.value] } - def filterCase = findCase({it._id.eq(filterCaseRef.value[0])}) - change filterTaskRef value {return [findTask({it.caseId.eq(filterCase.stringId).and(it.transitionId.eq("view_filter"))}).stringId]} - </action> - </actions> - </event> - </dataRef> + <event type="finish"> + <id>finish</id> + <actions phase="pre"> + <action> + trans: t.view_settings, + useTabIcon: f.use_tab_icon, + view_configuration_form: f.view_configuration_form, + view_configuration_type: f.view_configuration_type, + tabIconPreview: f.tab_icon_preview, + tabName: f.tab_name, + tabIcon: f.tab_icon, + use_custom_view: f.use_custom_view, + selector: f.custom_view_selector; + + if (use_custom_view.value) { + make selector, editable on trans when { true } + make [useTabIcon, tabIconPreview, tabName, tabIcon, view_configuration_form, + view_configuration_type], hidden on trans when { true } + } + </action> + </actions> + <actions phase="post"> + <action> + initializeAdvancedForm() + </action> + </actions> + </event> </transition> - <transition> - <id>case_view_settings</id> - <x>340</x> - <y>100</y> - <label>Case view settings [referenced]</label> + <id>role_settings</id> + <x>400</x> + <y>48</y> + <label name="role_settings">Role</label> + <icon>person</icon> + <assignPolicy>auto</assignPolicy> <roleRef> - <id>system</id> + <id>admin</id> <logic> <perform>true</perform> </logic> </roleRef> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> <dataGroup> - <id>case_view_dataGroup</id> - <cols>4</cols> + <id>roles_management</id> + <cols>3</cols> <layout>grid</layout> <dataRef> - <id>case_view_search_type</id> + <id>processes_available</id> <logic> <behavior>editable</behavior> - <behavior>required</behavior> </logic> <layout> <x>0</x> <y>0</y> - <rows>1</rows> + <rows>2</rows> <cols>1</cols> + <offset>0</offset> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>case_allow_export</id> + <id>roles_available</id> <logic> <behavior>editable</behavior> - <behavior>required</behavior> </logic> <layout> <x>1</x> <y>0</y> - <rows>1</rows> + <rows>2</rows> <cols>1</cols> + <offset>0</offset> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>show_create_case_button</id> + <id>add_allowed_roles</id> <logic> <behavior>editable</behavior> - <behavior>required</behavior> </logic> <layout> <x>2</x> <y>0</y> <rows>1</rows> <cols>1</cols> + <offset>0</offset> <template>material</template> - <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>case_show_more_menu</id> + <id>add_banned_roles</id> <logic> <behavior>editable</behavior> - <behavior>required</behavior> </logic> <layout> - <x>3</x> - <y>0</y> + <x>2</x> + <y>1</y> <rows>1</rows> <cols>1</cols> + <offset>0</offset> <template>material</template> - <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>create_case_button_title</id> + <id>role_divider</id> <logic> - <behavior>editable</behavior> + <behavior>visible</behavior> </logic> <layout> <x>0</x> - <y>1</y> + <y>2</y> <rows>1</rows> - <cols>1</cols> + <cols>3</cols> <offset>0</offset> <template>material</template> - <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>create_case_button_icon</id> + <id>allowed_roles</id> <logic> <behavior>editable</behavior> </logic> <layout> - <x>1</x> - <y>1</y> + <x>0</x> + <y>3</y> <rows>1</rows> - <cols>1</cols> + <cols>2</cols> <offset>0</offset> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>create_case_button_icon_preview</id> + <id>remove_allowed_roles</id> <logic> - <behavior>visible</behavior> + <behavior>editable</behavior> </logic> <layout> <x>2</x> - <y>1</y> + <y>3</y> <rows>1</rows> <cols>1</cols> <offset>0</offset> <template>material</template> - <appearance>standard</appearance> </layout> </dataRef> <dataRef> - <id>case_require_title_in_creation</id> + <id>banned_roles</id> <logic> <behavior>editable</behavior> </logic> <layout> - <x>3</x> - <y>1</y> + <x>0</x> + <y>4</y> <rows>1</rows> - <cols>1</cols> + <cols>2</cols> <offset>0</offset> <template>material</template> - <appearance>standard</appearance> + <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>case_banned_nets_in_creation</id> + <id>remove_banned_roles</id> <logic> <behavior>editable</behavior> </logic> <layout> - <x>0</x> - <y>2</y> + <x>2</x> + <y>4</y> <rows>1</rows> - <cols>4</cols> + <cols>1</cols> <offset>0</offset> <template>material</template> - <appearance>outline</appearance> </layout> </dataRef> </dataGroup> + <event type="assign"> + <id>assign</id> + <title/> + </event> + <event type="cancel"> + <id>cancel</id> + <title/> + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + <event type="delegate"> + <id>delegate</id> + <title/> + </event> + </transition> + <transition> + <id>view_settings</id> + <x>528</x> + <y>48</y> + <label name="view_settings">View</label> + <icon>image</icon> + <assignPolicy>auto</assignPolicy> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> <dataGroup> - <id>case_view_headers</id> - <cols>5</cols> + <id>configuration_view</id> + <cols>3</cols> <layout>grid</layout> <dataRef> - <id>case_is_header_mode_changeable</id> + <id>view_configuration_type</id> <logic> - <behavior>editable</behavior> - <behavior>required</behavior> - <action trigger="set"> - trans: t.this, - isChangeable: f.case_is_header_mode_changeable, - mode: f.case_headers_mode, - defaultMode: f.case_headers_default_mode; - - make mode,editable on trans when { isChangeable.value } - make mode,required on trans when { isChangeable.value } - make defaultMode,editable on trans when { isChangeable.value } - make defaultMode,required on trans when { isChangeable.value } - - make mode,hidden on trans when { !isChangeable.value } - make mode,optional on trans when { !isChangeable.value } - make defaultMode,hidden on trans when { !isChangeable.value } - make defaultMode,optional on trans when { !isChangeable.value } - </action> + <behavior>visible</behavior> </logic> <layout> <x>0</x> <y>0</y> <rows>1</rows> <cols>1</cols> - <offset>0</offset> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>case_allow_header_table_mode</id> + <id>tab_name</id> <logic> <behavior>editable</behavior> - <behavior>required</behavior> </logic> <layout> <x>1</x> <y>0</y> <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>custom_view_selector</id> + <logic> + <behavior>hidden</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>3</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>use_tab_icon</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> <cols>1</cols> <offset>0</offset> <template>material</template> - <appearance>outline</appearance> </layout> + <event type="set"> + <id>0</id> + <actions phase="post"> + <action> + use_tabbed_view: f.use_tabbed_view, + useIcon: f.use_tab_icon; + + manageBehaviorOfTabFields(useIcon.value, use_tabbed_view.value) + </action> + </actions> + </event> </dataRef> <dataRef> - <id>case_headers_mode</id> + <id>tab_icon</id> <logic> <behavior>editable</behavior> - <behavior>required</behavior> + </logic> + <layout> + <x>1</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>tab_icon_preview</id> + <logic> + <behavior>visible</behavior> </logic> <layout> <x>2</x> - <y>0</y> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>standard</appearance> + </layout> + </dataRef> + <dataRef> + <id>view_configuration_form</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>3</y> <rows>1</rows> <cols>2</cols> - <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>assign</id> + <title/> + </event> + <event type="cancel"> + <id>cancel</id> + <title/> + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + <event type="delegate"> + <id>delegate</id> + <title/> + </event> + </transition> + <transition> + <id>all_menu_data</id> + <x>10</x> + <y>10</y> + <label>All menu data</label> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>all_menu_data_0</id> + <cols>1</cols> + <layout>grid</layout> + <dataRef> + <id>menu_item_identifier</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>case_headers_default_mode</id> + <id>nodePath</id> <logic> - <behavior>editable</behavior> - <behavior>required</behavior> + <behavior>visible</behavior> </logic> <layout> - <x>4</x> - <y>0</y> + <x>0</x> + <y>1</y> <rows>1</rows> <cols>1</cols> - <offset>0</offset> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>use_case_default_headers</id> + <id>menu_name</id> <logic> - <behavior>editable</behavior> - <action trigger="set"> - trans: t.this, - use: f.use_case_default_headers, - headers: f.case_default_headers; - - make headers,editable on trans when { use.value } - make headers,visible on trans when { !use.value } - </action> + <behavior>visible</behavior> </logic> <layout> <x>0</x> - <y>1</y> + <y>2</y> <rows>1</rows> <cols>1</cols> - <offset>0</offset> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>case_default_headers</id> + <id>menu_icon</id> <logic> - <behavior>editable</behavior> + <behavior>visible</behavior> </logic> <layout> - <x>1</x> - <y>1</y> + <x>0</x> + <y>3</y> <rows>1</rows> - <cols>4</cols> - <offset>0</offset> + <cols>1</cols> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> - </dataGroup> - </transition> - - <transition> - <id>task_view_settings</id> - <x>340</x> - <y>340</y> - <label>Task view settings [referenced]</label> - <roleRef> - <id>system</id> - <logic> - <perform>true</perform> - </logic> - </roleRef> - <dataGroup> - <id>task_view_dataGroup</id> - <cols>4</cols> - <layout>grid</layout> <dataRef> - <id>task_view_search_type</id> + <id>menu_icon_preview</id> <logic> - <behavior>editable</behavior> - <behavior>required</behavior> + <behavior>visible</behavior> </logic> <layout> <x>0</x> - <y>0</y> + <y>4</y> <rows>1</rows> - <cols>3</cols> + <cols>1</cols> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>task_show_more_menu</id> + <id>hasChildren</id> <logic> - <behavior>editable</behavior> - <behavior>required</behavior> + <behavior>visible</behavior> </logic> <layout> - <x>3</x> - <y>0</y> + <x>0</x> + <y>5</y> <rows>1</rows> <cols>1</cols> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> - </dataGroup> - <dataGroup> - <id>task_view_headers</id> - <cols>5</cols> - <layout>grid</layout> <dataRef> - <id>task_is_header_mode_changeable</id> + <id>is_auto_select</id> <logic> - <behavior>editable</behavior> - <behavior>required</behavior> - <action trigger="set"> - trans: t.this, - isChangeable: f.task_is_header_mode_changeable, - mode: f.task_headers_mode, - defaultMode: f.task_headers_default_mode; - - make mode,editable on trans when { isChangeable.value } - make mode,required on trans when { isChangeable.value } - make defaultMode,editable on trans when { isChangeable.value } - make defaultMode,required on trans when { isChangeable.value } - - make mode,hidden on trans when { !isChangeable.value } - make mode,optional on trans when { !isChangeable.value } - make defaultMode,hidden on trans when { !isChangeable.value } - make defaultMode,optional on trans when { !isChangeable.value } - </action> + <behavior>visible</behavior> </logic> <layout> <x>0</x> - <y>0</y> + <y>6</y> <rows>1</rows> <cols>1</cols> - <offset>0</offset> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>task_allow_header_table_mode</id> + <id>processes_available</id> <logic> - <behavior>editable</behavior> - <behavior>required</behavior> + <behavior>visible</behavior> </logic> <layout> - <x>1</x> - <y>0</y> + <x>0</x> + <y>7</y> <rows>1</rows> <cols>1</cols> <template>material</template> @@ -2307,29 +1917,27 @@ </layout> </dataRef> <dataRef> - <id>task_headers_mode</id> + <id>roles_available</id> <logic> - <behavior>editable</behavior> - <behavior>required</behavior> + <behavior>visible</behavior> </logic> <layout> - <x>2</x> - <y>0</y> + <x>0</x> + <y>8</y> <rows>1</rows> - <cols>2</cols> + <cols>1</cols> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>task_headers_default_mode</id> + <id>add_allowed_roles</id> <logic> - <behavior>editable</behavior> - <behavior>required</behavior> + <behavior>visible</behavior> </logic> <layout> - <x>4</x> - <y>0</y> + <x>0</x> + <y>9</y> <rows>1</rows> <cols>1</cols> <template>material</template> @@ -2337,282 +1945,202 @@ </layout> </dataRef> <dataRef> - <id>use_task_default_headers</id> + <id>allowed_roles</id> <logic> - <behavior>editable</behavior> - <action trigger="set"> - trans: t.this, - use: f.use_task_default_headers, - headers: f.task_default_headers; - - make headers,editable on trans when { use.value } - make headers,visible on trans when { !use.value } - </action> + <behavior>visible</behavior> </logic> <layout> <x>0</x> - <y>1</y> + <y>10</y> <rows>1</rows> <cols>1</cols> - <offset>0</offset> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> <dataRef> - <id>task_default_headers</id> + <id>remove_allowed_roles</id> <logic> - <behavior>editable</behavior> + <behavior>visible</behavior> </logic> <layout> - <x>1</x> - <y>1</y> + <x>0</x> + <y>11</y> <rows>1</rows> - <cols>4</cols> - <offset>0</offset> + <cols>1</cols> <template>material</template> <appearance>outline</appearance> </layout> </dataRef> - </dataGroup> - <dataGroup> - <id>additional_filter_update</id> - <cols>4</cols> - <layout>grid</layout> - <title name="additional_filter_update">Additional filter - additional_filter_autocomplete_selection + add_banned_roles - editable + visible 0 - 0 + 12 1 - 3 + 1 outline - update_additional_filter + banned_roles - editable + visible - 3 - 0 + 0 + 13 1 1 - standard + outline - selected_additional_filter_preview + remove_banned_roles visible 0 - 1 + 14 1 - 4 + 1 - standard + outline - - - current_additional_filter - 4 - grid - filter_header + use_custom_view - hidden + visible 0 - 0 + 15 1 - 4 + 1 outline - current_additional_filter_preview + custom_view_selector visible 0 - 1 + 16 1 - 4 + 1 - standard + outline - merge_filters + use_tabbed_view - hidden + visible 0 - 2 + 17 1 1 - standard + outline - remove_additional_filter + tab_name - hidden + visible - 1 - 2 + 0 + 18 1 1 - standard + outline - - - - children_order - 580 - 220 - - low_priority - auto - - admin - - true - true - true - true - - - - children_order_0 - 4 - grid - childItemForms + use_tab_icon - editable - - forms: f.childItemForms, - ids: f.childItemIds; - - def orderedTaskIds = ids.value?.collect { id -> workflowService.findOne(id).tasks.find { it.transition == "row_for_ordering" }.task } - change forms value { orderedTaskIds } - + visible 0 - 0 + 19 1 - 4 + 1 outline - - - - row_for_ordering - 741 - 219 - - - system - - true - true - true - true - - - - row_for_ordering_0 - 6 - grid - menu_item_identifier + tab_icon visible 0 - 0 + 20 1 - 2 + 1 outline - menu_name_as_visible + tab_icon_preview visible - 2 - 0 + 0 + 21 1 - 2 + 1 outline - order_down + view_configuration_type - editable + visible - 4 - 0 + 0 + 22 1 1 - 1 outline - order_up + view_configuration_all_data_form - editable + visible - 5 - 0 + 0 + 23 1 1 - 1 outline - - finish - - - - delegate - - @@ -2670,17 +2198,38 @@ 1 - a12 + a13 read initialized - change_filter + children_order 1 - a13 + a14 + regular + uninitialized + system_initialize + 1 + + + a15 + regular + system_initialize + initialized + 1 + + + a16 read initialized - children_order + view_settings + 1 + + + a17 + read + initialized + role_settings 1 - + \ No newline at end of file diff --git a/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml new file mode 100644 index 00000000000..4d405e12d39 --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/menu/single_task_view_configuration.xml @@ -0,0 +1,358 @@ + + single_task_view_configuration + 1.0.0 + TST + Single task view configuration + check_box_outline_blank + true + false + false + + system + + true + true + true + + + + admin + + true + true + true + + + + default + + true + + + + system + System + + + admin + Admin + + + + { -> + def advancedContentFormField = useCase.getField("advanced_content_form") + change advancedContentFormField value { [useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task] } + } + + + + advanced_content_form + + <component> + <name>task-list</name> + <property key="headers">meta-title</property> + </component> + </data> + <data type="boolean"> + <id>show_page_header</id> + <title name="show_page_header">Show page header + true + + + show_page_footer + Show page footer + false + + + task_filter + Filter + Use PFQL query + task + + + + + Vybrať zobrazenie + Nastavenie + Zobraziť hlavičku stránky + Zobraziť pätu stránky + Filter + Použi PFQL query + Filter + + + Wählen Sie einen Ansichtstyp + Einstellungen + Seitenkopf anzeigen + Seitenfuß anzeigen + Filter + Verwenden Sie PFQL Query + Filter + + + + initialize + 368 + 208 + + hourglass_empty + + admin + + true + + + + finish + + + initializeAdvancedForm() + + + + + + data_sync + 368 + 328 + + + system + + true + + + + + settings + 496 + 112 + + settings + + admin + + true + + + + default + + true + + + + view_dataGroup + 4 + grid + + show_page_header + + editable + + + 0 + 0 + 1 + 2 + + outline + + + + show_page_footer + + editable + + + 2 + 0 + 1 + 2 + + outline + + + + advanced_content_form + + editable + + + 0 + 1 + 1 + 4 + + outline + + + + + assign_0 + + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + </transition> + <transition> + <id>filter_settings</id> + <x>432</x> + <y>48</y> + <label name="filter_settings">Filter</label> + <icon>filter_alt</icon> + <assignPolicy>auto</assignPolicy> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>filter_0</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>task_filter</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>assign</id> + <title/> + </event> + <event type="cancel"> + <id>cancel</id> + <title/> + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + <event type="delegate"> + <id>delegate</id> + <title/> + </event> + </transition> + <transition> + <id>all_menu_data</id> + <x>10</x> + <y>10</y> + <label>All menu data</label> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>all_menu_data_0</id> + <cols>1</cols> + <layout>grid</layout> + <dataRef> + <id>show_page_header</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>show_page_footer</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_filter</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + </transition> + <place> + <id>initialized</id> + <x>496</x> + <y>208</y> + <label>initialized</label> + <tokens>0</tokens> + <static>false</static> + </place> + <place> + <id>uninitialized</id> + <x>240</x> + <y>208</y> + <label>uninitialized</label> + <tokens>1</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>read</type> + <sourceId>initialized</sourceId> + <destinationId>settings</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>uninitialized</sourceId> + <destinationId>initialize</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a3</id> + <type>regular</type> + <sourceId>initialize</sourceId> + <destinationId>initialized</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a4</id> + <type>read</type> + <sourceId>initialized</sourceId> + <destinationId>filter_settings</destinationId> + <multiplicity>1</multiplicity> + </arc> +</document> \ No newline at end of file diff --git a/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml new file mode 100644 index 00000000000..29fcb550684 --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/menu/tabbed_ticket_view_configuration.xml @@ -0,0 +1,366 @@ +<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://petriflow.com/petriflow.schema.xsd"> + <id>tabbed_ticket_view_configuration</id> + <version>1.0.0</version> + <initials>TVC</initials> + <title>Tabbed ticket view configuration + check_box_outline_blank + true + false + false + + system + + true + true + true + + + + admin + + true + true + true + + + + default + + true + + + + + view_delete + + + removeViewCase() + + + + + + system + System + + + admin + Admin + + + + { -> + def advancedContentFormField = useCase.getField("advanced_content_form") + change advancedContentFormField value { [useCase.tasks.find { taskPair -> taskPair.transition == "next_view_settings"}.task] } + } + + + { -> + + def viewIdAsList = useCase.dataSet['view_configuration_id'].value + if (viewIdAsList == null || viewIdAsList.isEmpty()) { + return + } + + async.run { + workflowService.deleteCase(viewIdAsList[0]) + } + } + + + + + view_configuration_type + View type + + menuItemService.getAvailableViewsAsOptions(true, useCase.processIdentifier) + + + view_configuration_type: f.view_configuration_type; + + if (view_configuration_type.options == null || view_configuration_type.options.isEmpty()) { + change view_configuration_type options { menuItemService.getAvailableViewsAsOptions(true, useCase.processIdentifier) } + } + + + + view_configuration_id + + <allowedNets> + <allowedNet>single_task_view_configuration</allowedNet> + </allowedNets> + </data> + <data type="taskRef"> + <id>view_configuration_form</id> + <title/> + </data> + <data type="taskRef"> + <id>view_configuration_all_data_form</id> + <title/> + </data> + <!-- END OF ASSOCIATED VIEW --> + + <!-- VIEW CONFIGURATION DATA --> + <data type="taskRef"> + <id>advanced_content_form</id> + <title/> + <component> + <name>task-list</name> + <property key="headers">meta-title</property> + </component> + </data> + <!-- END OF VIEW CONFIGURATION DATA --> + + <!-- I18NS --> + <i18n locale="sk"> + <i18nString name="view_configuration_type">Zobrazenie</i18nString> + <i18nString name="settings">Nastavenie</i18nString> + <i18nString name="next_view_settings">Ďalšie zobrazenie</i18nString> + </i18n> + <i18n locale="de"> + <i18nString name="view_configuration_type">Der Ansichtstyp</i18nString> + <i18nString name="settings">Einstellungen</i18nString> + <i18nString name="next_view_settings">Nächste Ansicht</i18nString> + </i18n> + + <transition> + <id>initialize</id> + <x>368</x> + <y>208</y> + <label>initialize [await sync]</label> + <icon>hourglass_empty</icon> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + <event type="finish"> + <id>finish</id> + <actions phase="post"> + <action> + initializeAdvancedForm() + </action> + </actions> + </event> + </transition> + <transition> + <id>data_sync</id> + <x>368</x> + <y>328</y> + <label>Data sync</label> + <roleRef> + <id>system</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + </transition> + <transition> + <id>settings</id> + <x>496</x> + <y>112</y> + <label name="settings">Settings</label> + <icon>settings</icon> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>settings_0</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>advanced_content_form</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>assign_0</id> + <title/> + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + </transition> + <transition> + <id>next_view_settings</id> + <x>400</x> + <y>48</y> + <label name="next_view_settings">Next view</label> + <icon>queue_play_next</icon> + <assignPolicy>auto</assignPolicy> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>associated_view</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>view_configuration_type</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>view_configuration_form</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>4</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>assign</id> + <title/> + </event> + <event type="cancel"> + <id>cancel</id> + <title/> + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + <event type="delegate"> + <id>delegate</id> + <title/> + </event> + </transition> + <transition> + <id>all_menu_data</id> + <x>10</x> + <y>10</y> + <label>All menu data</label> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>all_menu_data_0</id> + <cols>1</cols> + <layout>grid</layout> + <dataRef> + <id>view_configuration_type</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>view_configuration_all_data_form</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + </transition> + <place> + <id>initialized</id> + <x>496</x> + <y>208</y> + <label>initialized</label> + <tokens>0</tokens> + <static>false</static> + </place> + <place> + <id>uninitialized</id> + <x>240</x> + <y>208</y> + <label>uninitialized</label> + <tokens>1</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>read</type> + <sourceId>initialized</sourceId> + <destinationId>settings</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>uninitialized</sourceId> + <destinationId>initialize</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a3</id> + <type>regular</type> + <sourceId>initialize</sourceId> + <destinationId>initialized</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a4</id> + <type>read</type> + <sourceId>initialized</sourceId> + <destinationId>next_view_settings</destinationId> + <multiplicity>1</multiplicity> + </arc> +</document> \ No newline at end of file diff --git a/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml new file mode 100644 index 00000000000..98133e4e758 --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/menu/task_view_configuration.xml @@ -0,0 +1,989 @@ +<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://petriflow.com/petriflow.schema.xsd"> + <id>task_view_configuration</id> + <version>1.0.0</version> + <initials>TTV</initials> + <title>Task view configuration + check_box_outline_blank + true + false + false + + system + + true + true + true + + + + admin + + true + true + true + + + + default + + true + + + + system + System + + + admin + Admin + + + + { -> + def advancedContentFormField = useCase.getField("advanced_content_form") + change advancedContentFormField value { [useCase.tasks.find { taskPair -> taskPair.transition == "filter_settings"}.task, + useCase.tasks.find { taskPair -> taskPair.transition == "header_settings"}.task] } + } + + + { + com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField toBeUpdated, + com.netgrif.application.engine.petrinet.domain.dataset.MultichoiceMapField valueSelector, + com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField optionsHolder + -> + def existingOptions = optionsHolder.options + def selectedValues = valueSelector.value + def newOptions = [:] + + if (selectedValues != null) { + existingOptions.each { key, value -> + if (selectedValues.contains(key)) { + newOptions.put(key, value) + } + } + } + + if (!newOptions.containsKey(toBeUpdated.value)) { + change toBeUpdated value { null } + } + + change toBeUpdated options { newOptions } + } + + + + merge_filters + Merge with base filter if present? + true + + + task_filter + Filter + Use PFQL query + tasks + + + + + task_view_search_type + Search type for task view + + + + + + fulltext_advanced + + + task_headers_mode + Header mode + + + + + sort,edit + + headersMode: f.task_headers_mode, + defaultMode: f.task_headers_default_mode, + holder: f.headers_options_holder; + + updateOptionsBasedOnValue(defaultMode, headersMode, holder) + + + headersMode: f.task_headers_mode, + defaultMode: f.task_headers_default_mode, + holder: f.headers_options_holder; + + updateOptionsBasedOnValue(defaultMode, headersMode, holder) + + + + task_headers_default_mode + Default header mode + + + + + sort + + + headers_options_holder + + <options> + <option key="sort" name="sort">Sort</option> + <option key="edit" name="edit">Edit</option> + </options> + </data> + <data type="boolean" immediate="true"> + <id>task_is_header_mode_changeable</id> + <title name="task_is_header_mode_changeable">Can header mode be changed? + true + + + task_allow_header_table_mode + Allow table mode for headers? + true + + + use_task_default_headers + Use custom default headers? + true + + + task_default_headers + Set default headers + Example: "meta-title", "meta-user" + + + task_show_more_menu + Show more menu for task item? + true + + + advanced_content_form + + <component> + <name>task-list</name> + <property key="headers">meta-caseId,meta-title</property> + </component> + </data> + <data type="i18n"> + <id>task_empty_content_text</id> + <title name="task_empty_content_text">Empty content text + If empty, default text will be used + + + task_empty_content_icon + Empty content icon + If empty, default icon will be used + + 0 + + + icon: f.this, + iconPreview: f.task_empty_content_icon_preview; + + if (icon.value == "") { + change iconPreview value {"""]]>} + return + } + + change iconPreview value { + """]]> + icon.value + """]]> + } + + + + + + task_empty_content_icon_preview + Empty content icon preview + + htmltextarea + + + + task_allowed_nets + Allowed nets + + + task_all_allowed_nets + All allowed nets? + true + + + task_inherit_allowed_nets + Inherit allowed nets? + true + + + + + Skryté + Fulltext + Fulltext a rozšírené + Zoraďovanie + Vyhľadávanie + Upravovanie + Zjednotiť filter so základným filtrom ak existuje? + Typ vyhľadávania úloh + Mód hlavičiek + Predvolený mód hlavičiek + Môže byť mód hlavičiek zmenený? + Povoliť tabuľkový mód pre hlavičky? + Použiť vlastné predvolené hlavičky? + Predvolené hlavičky + Napríklad: "meta-title", "meta-user" + Zobrazovať menu pre úlohovú položku? + Nastavenie položky + Všeobecné + Text prázdneho obsahu + Ak je toto pole prázdne, bude použitý predvolený text + Ikona prázdneho obsahu + Ak je toto pole prázdne, bude použitá predvolená ikona + Náhľad ikony prázdneho obsahu + Povolené procesy + Povoliť všetky procesy? + Zdediť povolené procesy? + Filter + Použi PFQL query + Filter + Hlavičky + + + Versteckt + Einfacher Suchmodus + Sortieren + Suchen + Bearbeiten + Kopfzeilenmodus + Standardkopfzeilenmodus + Erlaube Änderung des Kopfzeilenmodus? + Erlaube Tabellenmodus? + Eigene Kopfzeilen verwenden? + Anzuzeigende Attributmenge auswählen + Beispiel: "meta-title", "meta-user" + Allgemein + Einfacher und erweiterter Suchmodus + Mit dem Basisfilter kombinieren, falls vorhanden? + Suchmodus im Aufgabenansicht + "Erweiterte Optionen" Taste bei einzelnen Aufgaben anzeigen + Menüeintrageinstellungen + Text für leeren Inhalt + Wenn dieses Feld leer ist, wird der Standardtext verwendet + Icon für leeren Inhalt + Wenn dieses Feld leer ist, wird das Standard-Icon verwendet + Vorschau des Icons für leeren Inhalt + Erlaubte Prozesse + Alle Prozesse erlauben? + Erlaubte Prozesse erben? + Filter + Verwenden Sie PFQL Query + Filter + Kopfzeilen + + + + initialize + 368 + 208 + + hourglass_empty + + admin + + true + + + + finish + + + initializeAdvancedForm() + + + + + + data_sync + 368 + 328 + + + system + + true + + + + + settings + 496 + 112 + + settings + + admin + + true + + + + default + + true + + + + view_dataGroup + 3 + grid + + task_view_search_type + + editable + required + + + 0 + 0 + 1 + 1 + + outline + + + + task_show_more_menu + + editable + + + 1 + 0 + 1 + 1 + + outline + + + + task_empty_content_text + + editable + + + 0 + 1 + 1 + 1 + + outline + + + + task_empty_content_icon + + editable + + + 1 + 1 + 1 + 1 + + outline + + + + task_empty_content_icon_preview + + visible + + + 2 + 1 + 1 + 1 + + standard + + + + task_all_allowed_nets + + editable + + + 0 + 2 + 1 + 1 + + outline + + + + task_allowed_nets + + editable + + + 1 + 2 + 1 + 1 + + outline + + + + task_inherit_allowed_nets + + editable + + + 2 + 2 + 1 + 1 + + outline + + + + advanced_content_form + + editable + + + 0 + 3 + 1 + 3 + + outline + + + + + assign_0 + + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + </transition> + <transition> + <id>filter_settings</id> + <x>400</x> + <y>48</y> + <label name="filter_settings">Filter</label> + <icon>filter_alt</icon> + <assignPolicy>auto</assignPolicy> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>filter_update</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>task_filter</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>merge_filters</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>standard</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>assign</id> + <title/> + </event> + <event type="cancel"> + <id>cancel</id> + <title/> + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + <event type="delegate"> + <id>delegate</id> + <title/> + </event> + </transition> + <transition> + <id>header_settings</id> + <x>592</x> + <y>48</y> + <label name="header_settings">Header</label> + <icon>view_column</icon> + <assignPolicy>auto</assignPolicy> + <roleRef> + <id>admin</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>view_headers</id> + <cols>3</cols> + <layout>grid</layout> + <dataRef> + <id>task_is_header_mode_changeable</id> + <logic> + <behavior>editable</behavior> + <action trigger="set"> + trans: t.this, + isChangeable: f.task_is_header_mode_changeable, + mode: f.task_headers_mode, + defaultMode: f.task_headers_default_mode; + + make [mode, defaultMode], editable on trans when { isChangeable.value } + make [mode, defaultMode], required on trans when { isChangeable.value } + + make [mode, defaultMode], hidden on trans when { !isChangeable.value } + make [mode, defaultMode], optional on trans when { !isChangeable.value } + </action> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>use_task_default_headers</id> + <logic> + <behavior>editable</behavior> + <action trigger="set"> + trans: t.this, + use: f.use_task_default_headers, + headers: f.task_default_headers; + + make headers,editable on trans when { use.value } + make headers,visible on trans when { !use.value } + </action> + </logic> + <layout> + <x>1</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_default_headers</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>2</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <offset>0</offset> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_allow_header_table_mode</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_headers_mode</id> + <logic> + <behavior>editable</behavior> + <behavior>required</behavior> + </logic> + <layout> + <x>1</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_headers_default_mode</id> + <logic> + <behavior>editable</behavior> + <behavior>required</behavior> + </logic> + <layout> + <x>2</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>assign</id> + <title/> + </event> + <event type="cancel"> + <id>cancel</id> + <title/> + </event> + <event type="finish"> + <id>finish_0</id> + <title/> + </event> + <event type="delegate"> + <id>delegate</id> + <title/> + </event> + </transition> + <transition> + <id>all_menu_data</id> + <x>10</x> + <y>10</y> + <label>All menu data</label> + <roleRef> + <id>default</id> + <logic> + <view>true</view> + </logic> + </roleRef> + <dataGroup> + <id>all_menu_data_0</id> + <cols>1</cols> + <layout>grid</layout> + <dataRef> + <id>task_filter</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>merge_filters</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_view_search_type</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_show_more_menu</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>3</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_empty_content_text</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>4</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_empty_content_icon</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>5</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_empty_content_icon_preview</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>6</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_all_allowed_nets</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>7</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_allowed_nets</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>8</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_inherit_allowed_nets</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>9</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_is_header_mode_changeable</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>10</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_allow_header_table_mode</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>11</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_headers_mode</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>12</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_headers_default_mode</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>13</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>use_task_default_headers</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>14</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>task_default_headers</id> + <logic> + <behavior>visible</behavior> + </logic> + <layout> + <x>0</x> + <y>15</y> + <rows>1</rows> + <cols>1</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + </transition> + <place> + <id>initialized</id> + <x>496</x> + <y>208</y> + <label>initialized</label> + <tokens>0</tokens> + <static>false</static> + </place> + <place> + <id>uninitialized</id> + <x>240</x> + <y>208</y> + <label>uninitialized</label> + <tokens>1</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>read</type> + <sourceId>initialized</sourceId> + <destinationId>settings</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>uninitialized</sourceId> + <destinationId>initialize</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a3</id> + <type>regular</type> + <sourceId>initialize</sourceId> + <destinationId>initialized</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a4</id> + <type>read</type> + <sourceId>initialized</sourceId> + <destinationId>filter_settings</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a5</id> + <type>read</type> + <sourceId>initialized</sourceId> + <destinationId>header_settings</destinationId> + <multiplicity>1</multiplicity> + </arc> +</document> \ No newline at end of file diff --git a/src/main/resources/petriNets/engine-processes/org_group.xml b/src/main/resources/petriNets/engine-processes/org_group.xml index b23102b4812..426eae220d3 100644 --- a/src/main/resources/petriNets/engine-processes/org_group.xml +++ b/src/main/resources/petriNets/engine-processes/org_group.xml @@ -12,16 +12,6 @@ <!-- PROCESS ROLE REFS --> <!-- PROCESS USER REFS --> <!-- FUNCTIONS --> - <function scope="process" name="updateAvailableEntries"> - { - com.netgrif.application.engine.workflow.domain.Case useCase -> - - def cases = findCasesElastic( - "processIdentifier:\"preference_filter_item\" AND dataSet.parentId.textValue.keyword:\"$useCase.stringId\"" as String, - org.springframework.data.domain.PageRequest.of(0, 1000)) - change availableEntries options { return menuImportExportService.createAvailableEntriesChoices(cases) } - } - </function> <role> <id>system</id> <title>System @@ -122,11 +112,6 @@ update_available_entries_btn Update entries - - availableEntries: f.available_export_entries; - - updateAvailableEntries(useCase) - clear_menu_selection_btn @@ -265,8 +250,6 @@ finishTask(initTask) workflowService.save(useCase) - - updateAvailableEntries(useCase) @@ -384,30 +367,6 @@ - - 1 - - - availableEntries: f.available_export_entries; - - defaultFilters = findDefaultFilters() - - defaultFilters.forEach { filterCase -> - def optionCase = createCase("preference_filter_item", filterCase.title + " filter preference") - - def initTask = findTask({it.transitionId.eq("init").and(it.caseId.eq(optionCase.stringId))}) - assignTask(initTask) - setData(initTask, [ - "filter_case": ["type":"caseRef", "value": [filterCase.stringId], "allowedNets":["filter"]], - "parentId": ["type":"text", "value":useCase.stringId], "menu_identifier": ["type":"text", value:"defaultMenu"] - ]) - finishTask(initTask) - } - - updateAvailableEntries(useCase) - - - 6 diff --git a/src/main/resources/petriNets/engine-processes/preference_filter_item.xml b/src/main/resources/petriNets/engine-processes/preference_filter_item.xml deleted file mode 100644 index 2e12b82a51d..00000000000 --- a/src/main/resources/petriNets/engine-processes/preference_filter_item.xml +++ /dev/null @@ -1,983 +0,0 @@ - - - preference_filter_item - PFI - Preference filter item - list - true - false - - - - - - - menu_identifier - Identifier of menu which is this menu entry part of. - default - - - entry_name - - - - entry_default_name - Menu item title - - - entry_marker - - - - move_option_up - - Up - - parentId: f.parentId; - - if (!task.isPresent()) { - return; - } - - def parentCase = findCase({it._id.eq(new org.bson.types.ObjectId(parentId.value))}); - - def previousTaskRefValue = parentCase.dataSet.get("filter_tasks").value - - def currentIndex = previousTaskRefValue.findIndexOf({ id -> id == task.get().stringId}) - - if (currentIndex != 0) { - previousTaskRefValue.remove(currentIndex) - previousTaskRefValue.add(currentIndex - 1, task.get().stringId) - - def parentTask = findTask({it.caseId.eq(parentId.value).and(it.transitionId.eq("navigationMenuConfig"))}) - - setDataWithPropagation(parentTask, [ - "filter_tasks": ["type": "taskRef", "value": previousTaskRefValue] - ]) - } - - - - move_option_down - - Down - - parentId: f.parentId; - - if (!task.isPresent()) { - return; - } - - def parentCase = findCase({it._id.eq(new org.bson.types.ObjectId(parentId.value))}); - - def previousTaskRefValue = parentCase.dataSet.get("filter_tasks").value - - def currentIndex = previousTaskRefValue.findIndexOf({ id -> id == task.get().stringId}) - - if (currentIndex != previousTaskRefValue.size() - 1) { - previousTaskRefValue.remove(currentIndex) - previousTaskRefValue.add(currentIndex + 1, task.get().stringId) - - def parentTask = findTask({it.caseId.eq(parentId.value).and(it.transitionId.eq("navigationMenuConfig"))}) - - setDataWithPropagation(parentTask, [ - "filter_tasks": ["type": "taskRef", "value": previousTaskRefValue] - ]) - } - - - - remove_option - - Remove - - parentId: f.parentId, - filterCase: f.filter_case; - - if (!task.isPresent()) { - return; - } - - def parentCase = findCase({it._id.eq(new org.bson.types.ObjectId(parentId.value))}); - - - def previousTaskRefValue = parentCase.dataSet.get("filter_tasks").value - - def currentIndex = previousTaskRefValue.findIndexOf({ id -> id == task.get().stringId}) - - previousTaskRefValue.remove(currentIndex) - - - def previousCaseRefValue = parentCase.dataSet.get("referenced_filters").value - - currentIndex = previousCaseRefValue.findIndexOf({id -> id == filterCase.value[0]}) - - previousCaseRefValue.remove(currentIndex) - - - def parentTask = findTask({it.caseId.eq(parentId.value).and(it.transitionId.eq("navigationMenuConfig"))}) - - setDataWithPropagation(parentTask, [ - "filter_tasks": ["type": "taskRef", "value": previousTaskRefValue], - "referenced_filters": ["type": "caseRef", "value": previousCaseRefValue], - ]) - async.run { - workflowService.deleteCase(useCase.stringId) - } - - - - filter_preview - - - - filter_case - - - filter - - - - parentId - - - - use_icon - Display menu icon? - true - - - icon_preview - Menu icon preview - - htmltextarea - - - - - icon_name - Menu icon identifier - Material icon identifier. List of icons with identifiers is available online. - - iconIdentifier: f.this, - iconPreview: f.icon_preview; - - if (iconIdentifier.value == "") { - change iconPreview value {"""]]>} - return; - } - - change iconPreview value { - """]]> + iconIdentifier.value + """]]> - } - - - - add_allowed_roles - - Allow view for roles - - allowedRoles: f.allowed_roles, - processesAvailable: f.processes_available, - rolesAvailable: f.roles_available; - - change allowedRoles options {return configurableMenuService.addSelectedRoles(allowedRoles, processesAvailable, rolesAvailable)} - - change rolesAvailable value {[]} - change rolesAvailable options {[:]} - change processesAvailable value {null} - - - - remove_allowed_roles - - Remove from allowed roles - - allowedRoles: f.allowed_roles, - processesAvailable: f.processes_available, - rolesAvailable: f.roles_available; - - change allowedRoles options {return configurableMenuService.removeSelectedRoles(allowedRoles)} - - change allowedRoles value {[]} - change rolesAvailable value {[]} - change rolesAvailable options {[:]} - change processesAvailable value {null} - - - - add_banned_roles - - Ban view for roles - - bannedRoles: f.banned_roles, - processesAvailable: f.processes_available, - rolesAvailable: f.roles_available; - - change bannedRoles options {return configurableMenuService.addSelectedRoles(bannedRoles, processesAvailable, rolesAvailable)} - - change rolesAvailable value {[]} - change rolesAvailable options {[:]} - change processesAvailable value {null} - - - - remove_banned_roles - - Remove from banned roles - - bannedRoles: f.banned_roles, - processesAvailable: f.processes_available, - rolesAvailable: f.roles_available; - - change bannedRoles options {return configurableMenuService.removeSelectedRoles(bannedRoles)} - - change bannedRoles value {[]} - change rolesAvailable value {[]} - change rolesAvailable options {[:]} - change processesAvailable value {null} - - - - processes_available - Your processes - Select a process containing roles you wish to add to allowed or banned roles lists. - - processes: f.this; - - change processes options {return configurableMenuService.getNetsByAuthorAsMapOptions(loggedUser(), org.springframework.context.i18n.LocaleContextHolder.locale)} - - - processes: f.this, - allowedRoles: f.allowed_roles, - bannedRoles: f.banned_roles, - rolesAvailable: f.roles_available; - - change rolesAvailable options {return configurableMenuService.getAvailableRolesFromNet(processes, allowedRoles, bannedRoles)} - change rolesAvailable value {[]} - - - - roles_available - Available roles from selected process - - - allowed_roles - Allowed roles - List of roles allowed to view this menu entry. - - - banned_roles - Banned roles - List of roles not allowed to view this menu entry. - - - new_filter_id - - </data> - <data type="text" immediate="true"> - <id>menu_item_identifier</id> - <title>Identifikátor (Filter API) - - - create_case_button_title - "New case" button title - - - create_case_button_icon_preview - Menu icon preview - - htmltextarea - - - - create_case_button_icon - "New case" button icon identifier - - create_case_button_icon_preview: f.create_case_button_icon_preview, - create_case_button_icon: f.create_case_button_icon; - - - if (create_case_button_icon.value == "") { - change create_case_button_icon_preview value {"""]]>} - return; - } - - change create_case_button_icon_preview value { - """]]> + create_case_button_icon.value + """]]> - } - - - - - default_headers - Set default headers - - - - allowed_nets - Allowed nets - - - - - - - - - - - - - - - - - - - - - - Názov položky menu - Posunúť nahor - Posunúť nadol - Odstrániť - Zobrazovať ikonu? - Náhľad ikony - Identifikátor ikony - Identifikátor Material ikony. Zoznam ikon s identifikátormi je dostupný online. - Pridaj k povoleným roliam - Odstráň z povolených rolí - Pridaj k zakázaným roliam - Odstráň zo zakázaných rolí - Vaše procesy - Vyberte proces obsahujúci roly ktoré chcete pridať do zoznamu povolených alebo zakázaných rolí. - Dostupné roly - Povolené roly - Zoznam rolí ktoré budú upravovaný menu záznam vidieť. - Zakázané roly - Zoznam rolí pre ktoré bude upravovaný menu záznam skrytý. - Názov tlačidla "Nová inštancia" - Identifikátor ikony tlačidla "Nová inštancia" - Náhľad ikony - Predvolené hlavičky - - - Menüpunkttitel - nach oben - nach unten - entfernen - Menüikone anzeigen - Ikonevorschau - Ikone ID - Material Ikone ID. Liste den Ikonen mit IDs ist online verfügbar. - Zu zulässigen Rollen hinzufügen - Aus zulässigen Rollen entfernen - Zu verbotenen Rollen hinzufügen - Aus verbotenen Rollen entfernen - Ihre Prozesse - Wählen Sie einen Prozess mit Rollen aus, die Sie zu Listen mit zulässigen oder verbotenen Rollen hinzufügen möchten. - Verfügbare Rollen - Zulässige Rollen - Liste der Rollen, die diesen Menüeintrag anzeigen dürfen. - Verbotene Rollen - Liste der Rollen, die diesen Menüeintrag nicht anzeigen dürfen. - Schaltflächentitel "Neuer Fall" - Ikone ID - Ikonevorschau - Anzuzeigende Attributmenge auswählen - - - - init - 420 - 260 - - 0 - - - - filter_case - - forbidden - - - 1 - 0 - 1 - 2 - - - - - filterTaskRef: f.filter_preview, - filterCaseRef: f.filter_case, - entryDefaultName: f.entry_default_name, - entryName: f.entry_name, - icon: f.icon_name; - - def filterCase = findCase({it._id.eq(filterCaseRef.value[0])}) - - change entryDefaultName value {return filterCase.title} - - change entryName value {return filterCase.dataSet["i18n_filter_name"].value} - - change filterTaskRef value {return [findTask({it.caseId.eq(filterCase.stringId).and(it.transitionId.eq("view_filter"))}).stringId]} - - change icon value {filterCase.icon} - - - - - parentId - - editable - - - - icon_name - - editable - - - - - view - 660 - 260 - - 5 - 0 - - - - DataGroup_0 - - grid - - entry_default_name - - editable - - entryName: f.entry_name, - filterTask: f.filter_preview, - filterCaseRef: f.filter_case, - entryDefaultName: f.entry_default_name; - - def filterCase = findCase({it._id.eq(new org.bson.types.ObjectId(filterCaseRef.value[0]))}) - filterCase.setTitle(entryDefaultName.value) - workflowService.save(filterCase) - def filterTask = findTask({ it._id.eq(filterTask.value[0])}) - setData(filterTask, [ - "new_title": ["type":"text", "value": entryDefaultName.value] - ]) - - change entryName value {return new com.netgrif.application.engine.petrinet.domain.I18nString(entryDefaultName.value)} - - - - 0 - 0 - 1 - 2 - 0 - - outline - - - - move_option_up - - editable - - - 2 - 0 - 1 - 1 - 0 - - fill - - - - move_option_down - - editable - - - 3 - 0 - 1 - 1 - 0 - - - - - remove_option - - editable - - - 4 - 0 - 1 - 1 - 0 - - - - - processes_available - - editable - - - 0 - 1 - 2 - 1 - 0 - - outline - - - - roles_available - - editable - - - 1 - 1 - 2 - 1 - 0 - - outline - - - - add_allowed_roles - - editable - - - 2 - 1 - 1 - 1 - 0 - - - - - allowed_roles - - editable - - - 3 - 1 - 1 - 1 - 0 - - outline - - - - remove_allowed_roles - - editable - - - 4 - 1 - 1 - 1 - 0 - - - - - add_banned_roles - - editable - - - 2 - 2 - 1 - 1 - 0 - - - - - banned_roles - - editable - - - 3 - 2 - 1 - 1 - 0 - - outline - - - - remove_banned_roles - - editable - - - 4 - 2 - 1 - 1 - 0 - - - - - use_icon - - editable - - - 0 - 3 - 1 - 1 - 0 - - - - 0 - - - trans: t.this, - iconPreview: f.icon_preview, - iconName: f.icon_name, - useIcon: f.use_icon; - - make iconPreview,visible on trans when {useIcon.value} - make iconName,editable on trans when {useIcon.value} - - make iconPreview,hidden on trans when {!useIcon.value} - make iconName,hidden on trans when {!useIcon.value} - - - - - - icon_preview - - visible - - - 1 - 3 - 1 - 1 - 0 - - - - icon_name - - editable - - iconIdentifier: f.icon_name, - filterCaseId: f.filter_case; - - def filterCase = findCase({it._id.eq(new org.bson.types.ObjectId(filterCaseId.value[0]))}) - filterCase.setIcon(iconIdentifier.value) - workflowService.save(filterCase) - - - - 2 - 3 - 1 - 2 - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - filter_preview - - editable - - - 0 - 3 - 1 - 5 - 0 - - outline - - - - entry_marker - - hidden - - - 0 - 4 - 1 - 1 - 0 - - - - - - entry_name - - hidden - - - 1 - 4 - 1 - 1 - 0 - - - - - - create_case_button_title - - editable - - - 0 - 5 - 1 - 2 - 0 - - outline - - - - create_case_button_icon_preview - - visible - - - 2 - 5 - 1 - 1 - 0 - - outline - - - - create_case_button_icon - - editable - - - 3 - 5 - 1 - 2 - 0 - - outline - - - - default_headers - - editable - - - 0 - 6 - 1 - 4 - 0 - - outline - - - - - - - change_filter - 420 - 350 - - - - new_filter_id - - editable - required - - - set_event_0 - - - new_filter_id: f.new_filter_id, - filterTaskRef: f.filter_preview, - filterCaseRef: f.filter_case, - entryDefaultName: f.entry_default_name, - entryName: f.entry_name, - icon: f.icon_name; - - change filterCaseRef value { [new_filter_id.value] } - def filterCase = findCase({it._id.eq(filterCaseRef.value[0])}) - - change entryDefaultName value {return filterCase.title} - - change entryName value {return filterCase.dataSet["i18n_filter_name"].value} - - change filterTaskRef value {return [findTask({it.caseId.eq(filterCase.stringId).and(it.transitionId.eq("view_filter"))}).stringId]} - - change icon value {filterCase.icon} - - - - - - - - p1 - 300 - 260 - - 1 - false - - - p2 - 540 - 260 - - 0 - false - - - - a4 - regular - p1 - init - 1 - - - a5 - regular - init - p2 - 1 - - - a6 - read - p2 - view - 1 - - - a7 - read - p2 - change_filter - 1 - - diff --git a/src/main/resources/petriNets/engine-processes/process.xml b/src/main/resources/petriNets/engine-processes/process.xml new file mode 100644 index 00000000000..2f9602ddb04 --- /dev/null +++ b/src/main/resources/petriNets/engine-processes/process.xml @@ -0,0 +1,1095 @@ + + process + 1.0.0 + PRO + Process + device_hub + true + true + false + New Process + + system + System + + + + try { + petriNetService.deletePetriNet(mongo_id.value, loggedUser().transformToLoggedUser()) + change log_delete value { "Petri net deleted successfully" } + } catch (Exception e) { + String message = "Error deleting Petri net with ObjectId(${mongo_id.value}): ${e.message}" as String + log.error(message, e) + change log_delete value { message } + } + } + ]]> + + + copy_version + Version of the copy + + + form_ref + + <inits> + <init>form</init> + </inits> + </data> + <data type="text"> + <id>icon</id> + <title>Icon + device_hub + + + id + Identifier + new_process + + + log_archive + Log + + + log_delete + Log + + textarea + + + + log_deploy + Deploy Log + + + message_archive + + <init>This process version will be archived, existing instances will continue their execution, new instances could not be created.</init> + <component> + <name>richtextarea</name> + </component> + </data> + <data type="text"> + <id>message_copy</id> + <title/> + <init>Finishing this task will create a copy of this process with increased version.</init> + </data> + <data type="text"> + <id>message_delete</id> + <title/> + <init>This will permanently delete this process version and all its instances and tasks.</init> + <component> + <name>richtextarea</name> + </component> + </data> + <data type="text"> + <id>message_deploy</id> + <title/> + <init>Finishing this task will deploy the process and enable creating new instances of this process.</init> + </data> + <data type="text"> + <id>mongo_id</id> + <title>Mongo ID + + + state + State + + + + + + + draft + + + title + Title + New Process + + title_set + + + + + + + + + version + Version + + + regex ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$ + + + 1.0.0 + + version_set + + + + + + + + + version_increment + Version increment + + + + + + + version_increment_set + + + + + + + + + xml_file + XML file + + + xml_text + XML Text + + textarea + + + + deploy + 176 + 176 + + upload_file + auto + + system + + true + + + + deploy_0 + 6 + grid + + message_deploy + + visible + + + 0 + 0 + 1 + 6 + + outline + + + richtextarea + + + + title + + visible + + + 0 + 1 + 1 + 2 + + outline + + + + id + + visible + + + 2 + 1 + 1 + 2 + + outline + + + + version + + visible + + + 4 + 1 + 1 + 2 + + outline + + + + log_deploy + + visible + + + 0 + 2 + 2 + 6 + + outline + + + textarea + + + + + deploy_finish + Deploy + + + + + + + + + edit + 80 + 80 + + edit + + system + + true + + + + edit_0 + 4 + grid + + icon + + editable + + + 0 + 0 + 1 + 2 + + outline + + + + id + + editable + + + 2 + 0 + 1 + 2 + + outline + + + + title + + editable + + + 0 + 1 + 1 + 2 + + outline + + + + version + + editable + + + 2 + 1 + 1 + 2 + + outline + + + + xml_text + + editable + + + 0 + 2 + 2 + 4 + + outline + + + + + + form + 176 + 80 + + format_align_justify + + anonymous + + false + + + + default + + false + true + true + + + + system + + true + + + + form_0 + 4 + grid + + id + + editable + required + + + 0 + 0 + 1 + 2 + + outline + + + + title + + editable + required + + + 2 + 0 + 1 + 2 + + outline + + + + version + + editable + required + + + 0 + 1 + 1 + 2 + + outline + + + + icon + + editable + required + + + 2 + 1 + 1 + 2 + + outline + + + + mongo_id + + visible + + + 0 + 2 + 1 + 2 + + outline + + + + state + + visible + + + 2 + 2 + 1 + 2 + + outline + + + + xml_file + + hidden + + + 0 + 3 + 1 + 4 + + outline + + + + + + t10 + 368 + 272 + + delete_forever + + system + + true + + + + t10_0 + 4 + grid + + message_delete + + visible + + + 0 + 0 + 1 + 4 + + outline + + + + log_delete + + visible + + + 0 + 1 + 2 + 4 + + outline + + + + + delete_with_cases_finish + Delete + + + + + + + + + t11 + 464 + 272 + + delete_forever + + system + + true + + + + t11_0 + 4 + grid + + message_delete + + visible + + + 0 + 0 + 1 + 4 + + outline + + + richtextarea + + + + log_delete + + visible + + + 0 + 1 + 2 + 4 + + outline + + + textarea + + + + + t11_finish + Delete + + + + + + + + + t4 + 368 + 80 + + plus_one + auto + + system + + true + + + + t4_0 + 4 + grid + + message_copy + + visible + + + 0 + 0 + 1 + 4 + + outline + + + richtextarea + + + + version_increment + + editable + required + + + 0 + 1 + 1 + 2 + + outline + + + + copy_version + + visible + + + 2 + 1 + 1 + 2 + + outline + + + + + t4_finish + + + ${version.value}", "${copy_version.value}") as String + def newProcess = createCase("process", title.value) + def editTask = assignTask("edit", newProcess) + // TODO: extract metadata from xml_text and only set xml_text + setData(editTask, [ + "id": [ + "type": "text", + "value": id.value + ], + "version": [ + "type": "text", + "value": copy_version.value + ], + "title": [ + "type": "text", + "value": title.value + ], + "icon": [ + "type": "text", + "value": icon.value + ], + "xml_text": [ + "type":"text", + "value": updatedXml + ] + ]) + finishTask(editTask) + ]]> + + + + + + t6 + 80 + 272 + + delete_forever + + system + + true + + + + t6_0 + 4 + grid + + message_delete + + visible + + + 0 + 0 + 1 + 4 + + outline + + + richtextarea + + + + log_delete + + visible + + + 0 + 1 + 2 + 4 + + outline + + + textarea + + + + + t6_finish + Delete + + + + + + + + + t9 + 368 + 208 + + folder + + system + + true + + + + t9_0 + 4 + grid + + message_archive + + visible + + + 0 + 0 + 2 + 4 + + outline + + + + log_archive + + visible + + + 0 + 2 + 2 + 4 + + outline + + + textarea + + + + + t9_finish + + + + + + + + + view + 368 + 144 + + remove_red_eye + + view_0 + 4 + grid + + form_ref + + visible + + + 0 + 0 + 1 + 4 + + outline + + + + xml_text + + visible + + + 0 + 1 + 2 + 4 + + outline + + + + + view_finish + Create + + + + p1 + 80 + 176 + + 1 + false + + + p2 + 272 + 272 + + 0 + false + + + p5 + 464 + 208 + + 0 + false + + + p7 + 272 + 112 + 0 + false + + + p8 + 464 + 368 + + 0 + false + + + a1 + read + p1 + edit + 1 + + + a13 + regular + t9 + p5 + 1 + + + a14 + regular + p2 + t9 + 1 + + + a17 + regular + deploy + p7 + 1 + + + a18 + read + p7 + t4 + 1 + + + a19 + read + p7 + view + 1 + + + a2 + regular + p1 + deploy + 1 + + + a21 + regular + p2 + t10 + 1 + + + a22 + regular + p7 + t10 + 1 + + + a23 + regular + t10 + p8 + 1 + + + a24 + regular + p5 + t11 + 1 + + + a25 + regular + t11 + p8 + 1 + + + a26 + regular + t6 + p8 + 1 + + + a27 + regular + p7 + t11 + 1 + + + a3 + regular + deploy + p2 + 1 + + + a9 + regular + p1 + t6 + 1 + + \ No newline at end of file diff --git a/src/main/resources/petriNets/insurance_portal_demo.xml b/src/main/resources/petriNets/insurance_portal_demo.xml index ee5d43f9590..91b87cb32dd 100644 --- a/src/main/resources/petriNets/insurance_portal_demo.xml +++ b/src/main/resources/petriNets/insurance_portal_demo.xml @@ -3,6 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://petriflow.org/petriflow.schema.xsd"> examples/insurance/insurance + 1.0.0 IPD Insurance Portal Demo false diff --git a/src/main/resources/petriNets/mortgage/address.xml b/src/main/resources/petriNets/mortgage/address.xml index 2b100032882..71a3979c421 100644 --- a/src/main/resources/petriNets/mortgage/address.xml +++ b/src/main/resources/petriNets/mortgage/address.xml @@ -1,6 +1,7 @@ address + 1.0.0 ADD Address diff --git a/src/main/resources/petriNets/mortgage/financial_data.xml b/src/main/resources/petriNets/mortgage/financial_data.xml index 035fd5ab531..1f8d8436aa3 100644 --- a/src/main/resources/petriNets/mortgage/financial_data.xml +++ b/src/main/resources/petriNets/mortgage/financial_data.xml @@ -1,6 +1,7 @@ financial_data + 1.0.0 FIN Financial Data diff --git a/src/main/resources/petriNets/mortgage/financial_data_doc.xml b/src/main/resources/petriNets/mortgage/financial_data_doc.xml index f61bd98057b..285d3f0b9ba 100644 --- a/src/main/resources/petriNets/mortgage/financial_data_doc.xml +++ b/src/main/resources/petriNets/mortgage/financial_data_doc.xml @@ -1,6 +1,7 @@ financial_data_doc + 1.0.0 FDC Financial Data Doc diff --git a/src/main/resources/petriNets/mortgage/financial_data_func.xml b/src/main/resources/petriNets/mortgage/financial_data_func.xml index 400b8604216..f96d9a7e33e 100644 --- a/src/main/resources/petriNets/mortgage/financial_data_func.xml +++ b/src/main/resources/petriNets/mortgage/financial_data_func.xml @@ -1,6 +1,7 @@ financial_data_func + 1.0.0 FIN Financial Data diff --git a/src/main/resources/petriNets/mortgage/mortgage.xml b/src/main/resources/petriNets/mortgage/mortgage.xml index ac51fd99e94..5f6b7090a35 100644 --- a/src/main/resources/petriNets/mortgage/mortgage.xml +++ b/src/main/resources/petriNets/mortgage/mortgage.xml @@ -1,6 +1,7 @@ mortgage + 1.0.0 MOR Mortgage false diff --git a/src/main/resources/petriNets/mortgage/personal_information.xml b/src/main/resources/petriNets/mortgage/personal_information.xml index 693af2b11f8..72d8676af86 100644 --- a/src/main/resources/petriNets/mortgage/personal_information.xml +++ b/src/main/resources/petriNets/mortgage/personal_information.xml @@ -1,6 +1,7 @@ personal_information + 1.0.0 PER Personal Information false diff --git a/src/main/resources/petriNets/posudky.xml b/src/main/resources/petriNets/posudky.xml index 3256813e8cd..0fefb9c9c5c 100644 --- a/src/main/resources/petriNets/posudky.xml +++ b/src/main/resources/petriNets/posudky.xml @@ -2,6 +2,7 @@ management/posudky + 1.0.0 Posudky PSD Posudok diff --git a/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy b/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy index db7992527bc..39db9e45ccb 100644 --- a/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy +++ b/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy @@ -3,6 +3,7 @@ package com.netgrif.application.engine import com.netgrif.application.engine.auth.domain.repositories.UserRepository import com.netgrif.application.engine.elastic.domain.ElasticCaseRepository import com.netgrif.application.engine.elastic.domain.ElasticTaskRepository +import com.netgrif.application.engine.menu.service.interfaces.IMenuItemService import com.netgrif.application.engine.petrinet.domain.repository.UriNodeRepository import com.netgrif.application.engine.petrinet.domain.roles.ProcessRoleRepository import com.netgrif.application.engine.petrinet.service.ProcessRoleService @@ -43,7 +44,7 @@ class TestHelper { @Autowired private IFieldActionsCacheService actionsCacheService @Autowired - private FilterRunner filterRunner + private MenuRunner filterRunner @Autowired private FinisherRunner finisherRunner @Autowired @@ -52,6 +53,12 @@ class TestHelper { private UriRunner uriRunner @Autowired private IPetriNetService petriNetService + @Autowired + private IMenuItemService menuItemService + @Autowired + private MongoDbRunner mongoDbRunner + @Autowired + private ProcessRunner processRunner void truncateDbs() { template.db.drop() @@ -66,14 +73,17 @@ class TestHelper { actionsCacheService.clearNamespaceFunctionCache() petriNetService.evictAllCaches() + mongoDbRunner.run() defaultRoleRunner.run() anonymousRoleRunner.run() systemUserRunner.run() uriRunner.run() + processRunner.run() groupRunner.run() filterRunner.run() impersonationRunner.run() superCreator.run() finisherRunner.run() + menuItemService.ensureDatabaseIndexes() } } \ No newline at end of file diff --git a/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy b/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy index 4900ac8595b..b3c47c77e37 100644 --- a/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy @@ -7,11 +7,20 @@ import com.netgrif.application.engine.auth.domain.IUser import com.netgrif.application.engine.auth.service.interfaces.IUserService import com.netgrif.application.engine.auth.web.requestbodies.NewUserRequest import com.netgrif.application.engine.configuration.PublicViewProperties +import com.netgrif.application.engine.petrinet.domain.I18nString +import com.netgrif.application.engine.petrinet.domain.PetriNet +import com.netgrif.application.engine.petrinet.domain.VersionType +import com.netgrif.application.engine.petrinet.domain.dataset.EnumerationMapField +import com.netgrif.application.engine.petrinet.domain.dataset.Field import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.ActionDelegate -import com.netgrif.application.engine.workflow.service.interfaces.IFilterImportExportService +import com.netgrif.application.engine.petrinet.domain.version.Version +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService +import com.netgrif.application.engine.workflow.domain.Case +import com.netgrif.application.engine.workflow.domain.DataField +import com.netgrif.application.engine.workflow.domain.Task import com.netgrif.application.engine.workflow.web.responsebodies.MessageResource +import org.bson.types.ObjectId import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired @@ -21,7 +30,8 @@ import org.springframework.test.context.junit.jupiter.SpringExtension import javax.mail.internet.MimeMessage -import static java.util.Base64.* +import static java.util.Base64.getEncoder +import static org.junit.jupiter.api.Assertions.* @SpringBootTest @ActiveProfiles(["test"]) @@ -34,30 +44,28 @@ class ActionDelegateTest { @Autowired private ActionDelegate actionDelegate - @Autowired - private IFilterImportExportService importExportService - @Autowired private IUserService userService @Autowired private PublicViewProperties publicViewProperties + @Autowired + private IPetriNetService petriNetService + + private static final ACTION_API_NET_IDENTIFIER = "action_api_improvements" + private static final ERROR_MESSAGE_TEMPLATE = "field [|fieldId|] in [|testedMethod|] method returned null" + private static final FIELD_ID_TEMPLATE = "|fieldId|" + private static final TESTED_METHOD_TEMPLATE = "|testedMethod|" + @BeforeEach void before() { testHelper.truncateDbs() + actionDelegate.outcomes = [] } @Test - @Disabled("Context user") - void importFiltersTest(){ - List actionDelegateList = actionDelegate.importFilters() - List importedTasksIds = importExportService.importFilters() - assert actionDelegateList.size() == importedTasksIds.size() - } - - @Test - void inviteUser(){ + void inviteUser() { GreenMail smtpServer = new GreenMail(new ServerSetup(2525, null, "smtp")) smtpServer.start() @@ -70,7 +78,7 @@ class ActionDelegateTest { } @Test - void deleteUser(){ + void deleteUser() { GreenMail smtpServer = new GreenMail(new ServerSetup(2525, null, "smtp")) smtpServer.start() String mail = "test@netgrif.com"; @@ -88,7 +96,7 @@ class ActionDelegateTest { @Test - void inviteUserNewUserRequest(){ + void inviteUserNewUserRequest() { GreenMail smtpServer = new GreenMail(new ServerSetup(2525, null, "smtp")) smtpServer.start() @@ -113,4 +121,336 @@ class ActionDelegateTest { assert actionDelegate.makeUrl(publicViewProperties.url, identifier) == url assert actionDelegate.makeUrl("test.netgrif.com/public", "identifier") == "test.netgrif.com/public/${getEncoder().encodeToString(identifier.bytes)}" } + + @Test + void testTaskActions() { + importTestPetriNet() + Case testCase = actionDelegate.createCase(ACTION_API_NET_IDENTIFIER) + List taskIds = testCase.tasks.collect { it.task } + List tasks = actionDelegate.findTasks(taskIds) + + assertNotNull(tasks) + assertFalse(tasks.empty) + assertEquals(tasks.size(), taskIds.size()) + + testCase = actionDelegate.setData("t1", testCase, [ + "enumeration_map" : [ + "type" : "enumeration_map", + "value": taskIds[0] + ], + "multichoice_map" : [ + "type" : "multichoice_map", + "value": taskIds + ], + "stringCollection": [ + "type" : "stringCollection", + "value": taskIds + ], + "taskRef" : [ + "type" : "taskRef", + "value": taskIds + ], + "text" : [ + "type" : "text", + "value": taskIds[0] + ] + ]).case + actionDelegate.useCase = testCase + +// fields with single value + ["enumeration_map", "text"].forEach { + assertTaskSearchResults(it, testCase, 1, true) + } + +// fields with collection value + ["multichoice_map", "stringCollection", "taskRef"].forEach { + assertTaskSearchResults(it, testCase, taskIds.size(), false) + } + } + + @Test + void testCaseActions() { + importTestPetriNet() + Case testCase1 = actionDelegate.createCase(ACTION_API_NET_IDENTIFIER) + Case testCase2 = actionDelegate.createCase(ACTION_API_NET_IDENTIFIER) + Case testCase3 = actionDelegate.createCase(ACTION_API_NET_IDENTIFIER) + Case testCase4 = actionDelegate.createCase(ACTION_API_NET_IDENTIFIER) + + List caseIds = [testCase1.stringId, testCase2.stringId, testCase3.stringId, testCase4.stringId] + + Case searchedCase = actionDelegate.findCase(caseIds[0]) + assertNotNull(searchedCase) + assertEquals(searchedCase.stringId, testCase1.stringId) + + List searchedCases = actionDelegate.findCases(caseIds) + assertNotNull(searchedCases) + assertFalse(searchedCases.empty) + assertEquals(searchedCases.size(), caseIds.size()) + + testCase1 = actionDelegate.setData("t1", testCase1, [ + "enumeration_map" : [ + "type" : "enumeration_map", + "value": caseIds[0] + ], + "multichoice_map" : [ + "type" : "multichoice_map", + "value": caseIds + ], + "stringCollection": [ + "type" : "stringCollection", + "value": caseIds + ], + "caseRef" : [ + "type" : "caseRef", + "value": caseIds + ], + "text" : [ + "type" : "text", + "value": caseIds[0] + ] + ]).case + + actionDelegate.useCase = testCase1 +// fields with single value + ["enumeration_map", "text"].forEach { + assertCaseSearchResults(it, testCase1, 1, true) + } + +// fields with collection value + ["multichoice_map", "stringCollection", "caseRef"].forEach { + assertCaseSearchResults(it, testCase1, caseIds.size(), false) + } + } + + @Test + void testCaseDeletionActions() { + importTestPetriNet() + Case testCase1 = actionDelegate.createCase(ACTION_API_NET_IDENTIFIER) + Case testCase2 = actionDelegate.createCase(ACTION_API_NET_IDENTIFIER) + + testCase1 = actionDelegate.deleteCase(testCase1) + assertCaseDeletion(testCase1.stringId) + + testCase2 = actionDelegate.deleteCase(testCase2.stringId) + assertCaseDeletion(testCase2.stringId) + } + + @Test + void testPetriNetActions() { + PetriNet importedNet = importTestPetriNet() + String mongoId = importedNet.stringId + String netIdentifier = importedNet.getIdentifier() + ObjectId objectId = importedNet.objectId + + PetriNet foundNet = actionDelegate.findPetriNet(mongoId) + assertNotNull(foundNet) + assertEquals(foundNet.objectId, objectId) + assertEquals(foundNet.identifier, netIdentifier) + foundNet = null + + foundNet = actionDelegate.findPetriNet(objectId) + assertNotNull(foundNet) + assertEquals(foundNet.stringId, mongoId) + assertEquals(foundNet.identifier, netIdentifier) + foundNet = null + + PetriNet menuItemNet = petriNetService.getByIdentifier("menu_item")[0] + String mongoId2 = menuItemNet.stringId + ObjectId objectId3 = menuItemNet.objectId + + def searchTargets = [mongoId, mongoId2] + List searchResults = actionDelegate.findPetriNets(searchTargets as List) + assertNotNull(searchResults) + assertFalse(searchResults.empty) + assertTrue(searchResults.collect { it.stringId }.containsAll(searchTargets)) + searchResults = null + + searchTargets = [objectId, objectId3] + searchResults = actionDelegate.findPetriNetsByObjectIds(searchTargets as List) + assertNotNull(searchResults) + assertFalse(searchResults.empty) + assertTrue(searchResults.collect { it.objectId }.containsAll(searchTargets)) + + PetriNet importedNet2 = importTestPetriNet() + assertNotNull(importedNet2) + assertEquals(importedNet2.identifier, netIdentifier) + assertNotEquals(importedNet2.stringId, mongoId) + assertNotEquals(importedNet2.version, importedNet.version) + + foundNet = actionDelegate.findPetriNetByIdentifier(importedNet2.identifier, new Version(1, 0, 0)) + assertNotNull(foundNet, null) + assertEquals(foundNet.identifier, netIdentifier) + assertEquals(foundNet.stringId, mongoId) + assertEquals(foundNet.version, importedNet.version) + assertNotEquals(foundNet.version, importedNet2.version) + foundNet = null + +// find newest + foundNet = actionDelegate.findPetriNetByIdentifier(importedNet2.identifier) + assertNotNull(foundNet) + assertEquals(foundNet.identifier, netIdentifier) + assertNotEquals(foundNet.stringId, mongoId) + assertNotEquals(foundNet.version, importedNet.version) + assertEquals(foundNet.version, importedNet2.version) + } + + @Test + void testOptionsActions() { + PetriNet net = importTestPetriNet() + Case case1 = actionDelegate.createCase(net, "Test title 1") + Case case2 = actionDelegate.createCase(net, "Test title 2") + + List cases = [case1, case2] + Map options = actionDelegate.casesToOptions(cases) + assertNotNull(options) + assertEquals(options.size(), cases.size()) + assertTrue(options.keySet().containsAll(cases.collect { it.stringId })) + assertEquals(options.get(case1.stringId).defaultValue, case1.title) + assertEquals(options.get(case2.stringId).defaultValue, case2.title) + options = null + + String keyTransformationTestString = "Key transformation test " + String valueTransformationTestString = "Value transformation test " + options = actionDelegate.casesToOptions(cases, { return "Value transformation test ".concat(it.title) }, { return "Key transformation test ".concat(it.stringId) }) + assertNotNull(options) + assertEquals(options.size(), cases.size()) + assertTrue(options.keySet().containsAll(cases.collect { keyTransformationTestString.concat(it.stringId) })) + assertEquals(options.get(keyTransformationTestString.concat(case1.stringId)).defaultValue, valueTransformationTestString.concat(case1.title)) + assertEquals(options.get(keyTransformationTestString.concat(case2.stringId)).defaultValue, valueTransformationTestString.concat(case2.title)) + + case1.dataSet["enumeration_map"].options = options + actionDelegate.useCase = case1 + actionDelegate.initFieldsMap(["enumeration_map": "enumeration_map"]) + EnumerationMapField field = (EnumerationMapField) actionDelegate.map.get("enumeration_map") + assertNotNull(field) + assertNotNull(field.options) + I18nString option = field.getOption(keyTransformationTestString.concat(case1.stringId)) + assertNotNull(option) + assertEquals(option.defaultValue, valueTransformationTestString.concat(case1.title)) + } + + @Test + void testTaskEventActions() { + importTestPetriNet() + Case testCase = actionDelegate.createCase(ACTION_API_NET_IDENTIFIER) + List transitionIds = ["t1", "t2"] + IUser user = userService.getLoggedOrSystem() + +// testing "byTransition" variants should be enough, as they call methods, that take List instead of List + List tasks = actionDelegate.assignTasksByTransitions(transitionIds, testCase) + assertNotNull(tasks) + assertEquals(tasks.size(), transitionIds.size()) + assertTrue(tasks.stream().allMatch { it.user.email == user.email }) + assertTrue(tasks.stream().allMatch { it.userId == user.stringId }) + assertTrue(tasks.stream().allMatch { it.startDate != null }) + tasks = null + + tasks = actionDelegate.cancelTasksByTransitions(transitionIds, testCase) + assertNotNull(tasks) + assertEquals(tasks.size(), transitionIds.size()) + assertTrue(tasks.stream().allMatch { it.userId == null }) + assertTrue(tasks.stream().allMatch { it.startDate == null }) + tasks = null + + actionDelegate.assignTasksByTransitions(transitionIds, testCase) + tasks = actionDelegate.finishTasksByTransitions(transitionIds, testCase) + assertNotNull(tasks) + assertEquals(tasks.size(), transitionIds.size()) + assertTrue(tasks.stream().allMatch { it.finishedBy == user.stringId }) + assertTrue(tasks.stream().allMatch { it.finishDate != null }) + assertTrue(tasks.stream().allMatch { it.userId == null }) + tasks = actionDelegate.findTasks(tasks.collect { it.stringId }) + assertEquals(tasks.size(), 1) + assertEquals(tasks[0].transitionId, "t2") + } + + private void assertCaseDeletion(String deletedCaseId) { + Exception e = assertThrows(IllegalArgumentException.class, () -> { + actionDelegate.findCase(deletedCaseId) + }) + + String expectedMessage = "Could not find Case with id [${deletedCaseId}]" + assertEquals(expectedMessage, e.getMessage()) + } + + private void assertTaskSearchResults(String fieldId, Case testCase, int sizeToCheck, boolean singleValueField) { + actionDelegate.initFieldsMap([(fieldId): fieldId]) + String errorMessageWithFieldId = ERROR_MESSAGE_TEMPLATE.replace(FIELD_ID_TEMPLATE, fieldId) + Field field = actionDelegate.map.get(fieldId) + String firstTaskId = ([field.value].flatten() as List)[0] + + Task task = actionDelegate.findTask(field) + String errorMessage = errorMessageWithFieldId.replace(TESTED_METHOD_TEMPLATE, "findTask(Field)") + assertNotNull(task, errorMessage) + assertEquals(task.stringId, firstTaskId, errorMessage) + task = null + + List tasks = actionDelegate.findTasks(field) + errorMessage = errorMessageWithFieldId.replace(TESTED_METHOD_TEMPLATE, "findTasks(Field)") + assertNotNull(tasks, errorMessage) + assertFalse(tasks.empty, errorMessage) + assertEquals(tasks.size(), sizeToCheck, errorMessage) + if (singleValueField) { + assertEquals(tasks[0].stringId, firstTaskId, errorMessage) + } + tasks = null + + DataField dataField = testCase.getDataField(fieldId) + task = actionDelegate.findTask(dataField) + errorMessage = errorMessageWithFieldId.replace(TESTED_METHOD_TEMPLATE, "findTask(DataField)") + assertNotNull(task, errorMessage) + assertEquals(task.stringId, firstTaskId, errorMessage) + + tasks = actionDelegate.findTasks(dataField) + errorMessage = errorMessageWithFieldId.replace(TESTED_METHOD_TEMPLATE, "findTasks(DataField)") + assertNotNull(tasks, errorMessage) + assertFalse(tasks.empty, errorMessage) + assertEquals(tasks.size(), sizeToCheck, errorMessage) + if (singleValueField) { + assertEquals(tasks[0].stringId, firstTaskId, errorMessage) + } + } + + private void assertCaseSearchResults(String fieldId, Case testCase, int sizeToCheck, boolean singleValueField) { + actionDelegate.initFieldsMap([(fieldId): fieldId]) + String errorMessageWithFieldId = ERROR_MESSAGE_TEMPLATE.replace(FIELD_ID_TEMPLATE, fieldId) + Field field = actionDelegate.map.get(fieldId) + String firstCaseId = ([field.value].flatten() as List)[0] + + Case searchedCase = actionDelegate.findCase(field) + String errorMessage = errorMessageWithFieldId.replace(TESTED_METHOD_TEMPLATE, "findCase(Field)") + assertNotNull(searchedCase, errorMessage) + assertEquals(searchedCase.stringId, firstCaseId, errorMessage) + searchedCase = null + + List searchedCases = actionDelegate.findCases(field) + errorMessage = errorMessageWithFieldId.replace(TESTED_METHOD_TEMPLATE, "findCases(Field)") + assertNotNull(searchedCases, errorMessage) + assertFalse(searchedCases.empty, errorMessage) + assertEquals(searchedCases.size(), sizeToCheck, errorMessage) + if (singleValueField) { + assertEquals(searchedCases[0].stringId, firstCaseId, errorMessage) + } + searchedCases = null + + + DataField dataField = testCase.getDataField(fieldId) + searchedCase = actionDelegate.findCase(dataField) + errorMessage = errorMessageWithFieldId.replace(TESTED_METHOD_TEMPLATE, "findCase(DataField)") + assertNotNull(searchedCase, errorMessage) + assertEquals(searchedCase.stringId, firstCaseId, errorMessage) + + searchedCases = actionDelegate.findCases(dataField) + errorMessage = errorMessageWithFieldId.replace(TESTED_METHOD_TEMPLATE, "findCases(DataField)") + assertNotNull(searchedCases, errorMessage) + assertFalse(searchedCases.empty, errorMessage) + assertEquals(searchedCases.size(), sizeToCheck, errorMessage) + if (singleValueField) { + assertEquals(searchedCases[0].stringId, firstCaseId, errorMessage) + } + } + + private PetriNet importTestPetriNet() { + return petriNetService.importPetriNet(new FileInputStream("src/test/resources/petriNets/NAE-2390_action_api_improvements.xml"), VersionType.MAJOR, userService.getLoggedOrSystem().transformToLoggedUser()).getNet() + } } diff --git a/src/test/groovy/com/netgrif/application/engine/action/FilterApiTest.groovy b/src/test/groovy/com/netgrif/application/engine/action/FilterApiTest.groovy deleted file mode 100644 index fca6c5d1fbe..00000000000 --- a/src/test/groovy/com/netgrif/application/engine/action/FilterApiTest.groovy +++ /dev/null @@ -1,184 +0,0 @@ -package com.netgrif.application.engine.action - - -import com.netgrif.application.engine.TestHelper -import com.netgrif.application.engine.auth.service.interfaces.IUserService -import com.netgrif.application.engine.orgstructure.groups.interfaces.INextGroupService -import com.netgrif.application.engine.petrinet.domain.UriContentType -import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.ActionDelegate -import com.netgrif.application.engine.petrinet.service.interfaces.IUriService -import com.netgrif.application.engine.startup.FilterRunner -import com.netgrif.application.engine.startup.ImportHelper -import com.netgrif.application.engine.workflow.domain.Case -import com.netgrif.application.engine.workflow.domain.QCase -import com.netgrif.application.engine.workflow.service.interfaces.IDataService -import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService -import org.bson.types.ObjectId -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles -import org.springframework.test.context.junit.jupiter.SpringExtension - -@Disabled -@SpringBootTest -@ActiveProfiles(["test"]) -@ExtendWith(SpringExtension.class) -class FilterApiTest { - - @Autowired - private TestHelper testHelper - - @Autowired - private ImportHelper helper - - @Autowired - private FilterRunner filterRunner - - @Autowired - private IUserService userService - - @Autowired - private IWorkflowService workflowService - - @Autowired - private IDataService dataService - - @Autowired - private IUriService uriService - - @Autowired - private INextGroupService nextGroupService - - @BeforeEach - void before() { - testHelper.truncateDbs() - helper.createNet("filter_api_test.xml") - } - - @Test - @Disabled("Fix") - void testCreateFilterAndMenu() { - Case caze = createMenuItem() - Case item = getMenuItem(caze) - Case filter = getFilter(caze) - - Case defGroup = nextGroupService.findDefaultGroup() - - assert item.uriNodeId == uriService.findByUri("netgrif/test").id - assert item.dataSet["icon_name"].value == "device_hub" - assert item.dataSet["entry_name"].value.toString() == "FILTER" - assert item.dataSet["menu_item_identifier"].value.toString() == "new_menu_item" - assert item.dataSet["parentId"].value.toString() == defGroup.stringId - - assert filter.dataSet["filter"].filterMetadata["filterType"] == "Case" - assert filter.dataSet["filter"].allowedNets == ["filter", "preference_filter_item"] - assert filter.dataSet["filter"].value == "processIdentifier:filter OR processIdentifier:preference_filter_item" - assert filter.dataSet["filter_type"].value == "Case" - - List taskIds = (defGroup.dataSet[ActionDelegate.ORG_GROUP_FIELD_FILTER_TASKS].value ?: []) as List - assert taskIds.contains(item.tasks.find { it.transition == "view" }.task) - } - - - @Test - @Disabled("Fix NullPointer") - void testChangeFilterAndMenu() { - Case caze = createMenuItem() - def newUri = uriService.getOrCreate("netgrif/test_new", UriContentType.DEFAULT) - caze = setData(caze, [ - "uri": newUri.uriPath, - "title": "CHANGED FILTER", - "allowed_nets": "filter", - "query": "processIdentifier:filter", - "type": "Case", - "icon": "", - "change_filter_and_menu": "0" - ]) - Case item = getMenuItem(caze) - Case filter = getFilter(caze) - - assert item.dataSet["icon_name"].value == "" - assert item.dataSet["entry_name"].value.toString() == "CHANGED FILTER" - assert item.dataSet["allowed_roles"].options.entrySet()[0].key.contains("role_2") - assert item.uriNodeId == newUri.id - - assert filter.dataSet["filter"].allowedNets == ["filter"] - assert filter.dataSet["filter"].filterMetadata["defaultSearchCategories"] == false - assert filter.dataSet["filter"].value == "processIdentifier:filter" - } - - @Test - @Disabled("Fix") - void testDeleteItemAndFilter() { - Case caze = createMenuItem() - - Case item = getMenuItem(caze) - Case filter = getFilter(caze) - caze = setData(caze, [ - "delete_filter_and_menu": "0" - ]) - - Case defGroup = nextGroupService.findDefaultGroup() - List taskIds = (defGroup.dataSet[ActionDelegate.ORG_GROUP_FIELD_FILTER_TASKS].value ?: []) as List - assert !taskIds - - Thread.sleep(10000); - - assert workflowService.searchOne(QCase.case$._id.eq(new ObjectId(item.stringId))) == null - assert workflowService.searchOne(QCase.case$._id.eq(new ObjectId(filter.stringId))) == null - } - - - @Test - @Disabled("Fix") - void testFindFilter() { - Case caze = createMenuItem() - Case filter = getFilter(caze) - - caze = setData(caze, [ - "find_filter": "0" - ]) - - assert caze.dataSet["found_filter"].value == filter.stringId - } - - Case createMenuItem() { - Case caze = getCase() - caze = setData(caze, [ - "uri": "netgrif/test", - "title": "FILTER", - "allowed_nets": "filter,preference_filter_item", - "query": "processIdentifier:filter OR processIdentifier:preference_filter_item", - "type": "Case", - "group": null, - "identifier": "new_menu_item", - "icon": "device_hub", - "create_filter_and_menu": "0" - ]) - return caze - } - - Case getCase() { - return workflowService.searchOne(QCase.case$.processIdentifier.eq("netgrif/test/filter_api_test")) - } - - Case getMenuItem(Case caze) { - return workflowService.findOne(caze.dataSet["menu_stringId"].value as String) - } - - Case getFilter(Case caze) { - return workflowService.findOne(caze.dataSet["filter_stringId"].value as String) - } - - def setData(Case caze, Map dataSet) { - dataService.setData(caze.tasks[0].task, ImportHelper.populateDataset(dataSet.collectEntries { - [(it.key): (["value": it.value, "type": "text"])] - })) - return workflowService.findOne(caze.stringId) - } - -} diff --git a/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy b/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy deleted file mode 100644 index 1422efaebf9..00000000000 --- a/src/test/groovy/com/netgrif/application/engine/action/MenuItemApiTest.groovy +++ /dev/null @@ -1,349 +0,0 @@ -package com.netgrif.application.engine.action - - -import com.netgrif.application.engine.TestHelper -import com.netgrif.application.engine.auth.service.interfaces.IUserService -import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService -import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest -import com.netgrif.application.engine.orgstructure.groups.interfaces.INextGroupService -import com.netgrif.application.engine.petrinet.domain.I18nString -import com.netgrif.application.engine.petrinet.domain.UriContentType -import com.netgrif.application.engine.petrinet.domain.UriNode -import com.netgrif.application.engine.petrinet.service.interfaces.IUriService -import com.netgrif.application.engine.startup.FilterRunner -import com.netgrif.application.engine.startup.ImportHelper -import com.netgrif.application.engine.workflow.domain.Case -import com.netgrif.application.engine.workflow.domain.QCase -import com.netgrif.application.engine.workflow.service.interfaces.IDataService -import com.netgrif.application.engine.workflow.service.interfaces.ITaskService -import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService -import com.netgrif.application.engine.workflow.domain.menu.MenuItemConstants -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.context.i18n.LocaleContextHolder -import org.springframework.data.domain.PageRequest -import org.springframework.data.domain.Pageable -import org.springframework.test.context.ActiveProfiles -import org.springframework.test.context.junit.jupiter.SpringExtension - -import static org.junit.jupiter.api.Assertions.assertThrows - -@Disabled -@SpringBootTest -@ActiveProfiles(["test"]) -@ExtendWith(SpringExtension.class) -class MenuItemApiTest { - - @Autowired - private TestHelper testHelper - - @Autowired - private ImportHelper helper - - @Autowired - private FilterRunner filterRunner - - @Autowired - private IUserService userService - - @Autowired - private IWorkflowService workflowService - - @Autowired - private IDataService dataService - - @Autowired - private IUriService uriService - - @Autowired - private INextGroupService nextGroupService - - @Autowired - private IElasticCaseService elasticCaseService - - @Autowired - private ITaskService taskService - - @BeforeEach - void before() { - testHelper.truncateDbs() - helper.createNet("filter_api_test.xml") - } - - @Test - void testCreateFilterAndMenuItems() { - Case caze = createMenuItem() - Case item = getMenuItem(caze) - Case filter = getFilter(caze) - - Thread.sleep(4000) - UriNode leafNode = uriService.findByUri("/netgrif/test/new_menu_item") - assert item.uriNodeId == uriService.findByUri("/netgrif/test").stringId - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_ICON.attributeId].value == "device_hub" - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_NAME.attributeId].value == new I18nString("FILTER") - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_IDENTIFIER.attributeId].value.toString() == "new_menu_item" - assert (item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_FILTER_CASE.attributeId].value as List)[0] == filter.stringId - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_ALLOWED_ROLES.attributeId].options.containsKey("role_1:filter_api_test") - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_BANNED_ROLES.attributeId].options.containsKey("role_2:filter_api_test") - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_DEFAULT_HEADERS.attributeId].value == "meta-title,meta-title" - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_DEFAULT_HEADERS.attributeId].value == "meta-title,meta-title" - - assert filter.dataSet["filter"].filterMetadata["filterType"] == "Case" - assert filter.dataSet["filter"].allowedNets == ["filter", "preference_item"] - assert filter.dataSet["filter"].value == "processIdentifier:filter OR processIdentifier:preference_item" - assert filter.dataSet["filter_type"].value == "Case" - assert leafNode != null - - Case testFolder = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue.keyword:\"/netgrif/test\"", PageRequest.of(0, 1))[0] - Case netgrifFolder = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue.keyword:\"/netgrif\"", PageRequest.of(0, 1))[0] - UriNode testNode = uriService.findByUri("/netgrif") - UriNode netgrifNode = uriService.getRoot() - Case rootFolder = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue.keyword:\"/\"", PageRequest.of(0, 1))[0] - - assert testFolder != null && testNode != null - assert testFolder.uriNodeId == testNode.stringId - assert testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value == [netgrifFolder.stringId] - assert (testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList).contains(item.stringId) - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value == [testFolder.stringId] - assert netgrifFolder.uriNodeId == netgrifNode.stringId - assert netgrifFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value == [rootFolder.stringId] - assert (netgrifFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList).contains(testFolder.stringId) - assert rootFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value == [] - assert (rootFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList).contains(netgrifFolder.stringId) - } - - @Test - void testChangeFilterAndMenuItems() { - Case caze = createMenuItem() - Thread.sleep(3000) - def newUri = uriService.getOrCreate("/netgrif/test_new", UriContentType.DEFAULT) - caze = setData(caze, [ - "uri": newUri.uriPath, - "title": "CHANGED FILTER", - "allowed_nets": "filter", - "query": "processIdentifier:filter", - "type": "Case", - "icon": "", - "change_filter_and_menu": "0" - ]) - Case item = getMenuItem(caze) - Case filter = getFilter(caze) - - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_NAME.attributeId].value.toString() == "CHANGED FILTER" - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_ALLOWED_ROLES.attributeId].options.entrySet()[0].key.contains("role_2") - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CASE_DEFAULT_HEADERS.attributeId].value == "meta-title,meta-title,meta-title" - assert item.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_TASK_DEFAULT_HEADERS.attributeId].value == "meta-title,meta-title,meta-title" - assert item.uriNodeId == newUri.stringId - - assert filter.dataSet["filter"].allowedNets == ["filter"] - assert filter.dataSet["filter"].filterMetadata["defaultSearchCategories"] == false - assert filter.dataSet["filter"].value == "processIdentifier:filter" - } - - @Test - void testFindFilter() { - Case caze = createMenuItem() - Case filter = getFilter(caze) - - caze = setData(caze, [ - "find_filter": "0" - ]) - - assert caze.dataSet["found_filter"].value == filter.stringId - } - - @Test - void testMoveMenuItem() { - Case apiCase = createMenuItem("/netgrif/test") - String viewId = apiCase.dataSet["menu_stringId"].value - apiCase = createMenuItem("/netgrif2/test2", "new_menu_item2") - String viewId2 = apiCase.dataSet["menu_stringId"].value - - - // move view - Thread.sleep(2000) - apiCase = setData(apiCase, [ - "move_dest_uri": "/netgrif2", - "move_item_id": viewId, - "move_folder_path": null, - "move_item": "0" - ]) - - Case viewCase = workflowService.findOne(viewId) - Thread.sleep(2000) - - UriNode node = uriService.findByUri("/netgrif2") - Case folderCase = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue:\"/netgrif2\"", PageRequest.of(0, 1))[0] - - assert viewCase.uriNodeId == node.stringId - ArrayList childIds = folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList - assert childIds.contains(viewId) && childIds.size() == 2 - - - // cyclic move - assertThrows(IllegalArgumentException.class, () -> { - setData(apiCase, [ - "move_dest_uri": "/netgrif2/cyclic", - "move_item_id": null, - "move_folder_path": "/netgrif2", - "move_item": "0" - ]) - }) - - - // move folder - setData(apiCase, [ - "move_dest_uri": "/netgrif/test3", - "move_item_id": null, - "move_folder_path": "/netgrif2", - "move_item": "0" - ]) - Thread.sleep(2000) - - folderCase = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue:\"/netgrif/test3\"", PageRequest.of(0, 1))[0] - Case folderCase2 = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue:\"/netgrif\"", PageRequest.of(0, 1))[0] - assert folderCase != null && folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value == [folderCase2.stringId] - - folderCase = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue:\"/netgrif/test3/netgrif2\"", PageRequest.of(0, 1))[0] - assert folderCase != null - node = uriService.findByUri("/netgrif/test3") - assert node != null - assert folderCase.uriNodeId == node.stringId - assert folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId].value == "/netgrif/test3/netgrif2" - - childIds = folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList - assert childIds.size() == 2 - - folderCase = workflowService.findOne(childIds[0]) - node = uriService.findByUri("/netgrif/test3/netgrif2") - assert folderCase.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId].value == "/netgrif/test3/netgrif2/test2" - assert folderCase.uriNodeId == node.stringId - - viewCase = workflowService.findOne(viewId2) - node = uriService.findByUri("/netgrif/test3/netgrif2/test2") - assert viewCase.uriNodeId == node.stringId - } - - @Test - void testDuplicateMenuItem() { - String starterUri = "/netgrif/test" - Case apiCase = createMenuItem(starterUri, "new_menu_item") - String itemId = apiCase.dataSet["menu_stringId"].value - Case origin = workflowService.findOne(itemId) - Case testFolder = workflowService.findOne((origin.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value as ArrayList)[0]) - - String newTitle = "New title" - String newIdentifier = "new_identifier" - - String duplicateTaskId = testFolder.tasks.find { it.transition == "duplicate_item" }.task - taskService.assignTask(duplicateTaskId) - - assertThrows(IllegalArgumentException.class, () -> { - testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_TITLE.attributeId].value = new I18nString("") - testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_IDENTIFIER.attributeId].value = newIdentifier - testFolder = workflowService.save(testFolder) - taskService.finishTask(duplicateTaskId) - }) - - assertThrows(IllegalArgumentException.class, () -> { - testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_TITLE.attributeId].value = new I18nString(newTitle) - testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_IDENTIFIER.attributeId].value = "new_menu_item" - testFolder = workflowService.save(testFolder) - taskService.finishTask(duplicateTaskId) - }) - - testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_TITLE.attributeId].value = new I18nString(newTitle) - testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_IDENTIFIER.attributeId].value = newIdentifier - testFolder = workflowService.save(testFolder) - taskService.finishTask(duplicateTaskId) - - Case duplicated = workflowService.searchOne(QCase.case$.processIdentifier.eq("preference_item").and(QCase.case$.dataSet.get(MenuItemConstants.PREFERENCE_ITEM_FIELD_IDENTIFIER.attributeId).value.eq(newIdentifier))) - assert duplicated != null - - UriNode leafNode = uriService.findByUri("/netgrif/" + newIdentifier) - - assert duplicated.uriNodeId == testFolder.uriNodeId - assert leafNode != null - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_TITLE.attributeId].value == null - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_DUPLICATE_IDENTIFIER.attributeId].value == null - assert duplicated.title == newTitle - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_MENU_NAME.attributeId].value == new I18nString(newTitle) - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_IDENTIFIER.attributeId].value == newIdentifier - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId].value == "/netgrif/" + newIdentifier - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value == [] - assert duplicated.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_HAS_CHILDREN.attributeId].value == false - assert duplicated.activePlaces["initialized"] == 1 - } - - List findCasesElastic(String query, Pageable pageable) { - CaseSearchRequest request = new CaseSearchRequest() - request.query = query - List result = elasticCaseService.search([request], userService.system.transformToLoggedUser(), pageable, LocaleContextHolder.locale, false).content - return result - } - - Case createMenuItem(String uri = "/netgrif/test", String identifier = "new_menu_item") { - Case caze = getCase() - caze = setData(caze, [ - "uri": uri, - "title": "FILTER", - "allowed_nets": "filter,preference_item", - "query": "processIdentifier:filter OR processIdentifier:preference_item", - "type": "Case", - "identifier": identifier, - "icon": "device_hub", - "create_filter_and_menu": "0" - ]) - return caze - } - - @Test - void testRemoveMenuItem() { - String starterUri = "/netgrif/test" - Case apiCase = createMenuItem(starterUri, "new_menu_item") - String leafItemId = apiCase.dataSet["menu_stringId"].value - - Case testFolder = findCasesElastic("processIdentifier:$FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER AND dataSet.${MenuItemConstants.PREFERENCE_ITEM_FIELD_NODE_PATH.attributeId}.textValue:\"/netgrif/test\"", PageRequest.of(0, 1))[0] - String netgrifFolderId = (testFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_PARENT_ID.attributeId].value as ArrayList)[0] - - Case netgrifFolder = workflowService.findOne(netgrifFolderId) - assert (netgrifFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList).contains(testFolder.stringId) - assert workflowService.findOne(testFolder.stringId) != null - assert workflowService.findOne(leafItemId) != null - - workflowService.deleteCase(testFolder) - sleep(2000) - netgrifFolder = workflowService.findOne(netgrifFolderId) - assert !(netgrifFolder.dataSet[MenuItemConstants.PREFERENCE_ITEM_FIELD_CHILD_ITEM_IDS.attributeId].value as ArrayList).contains(testFolder.stringId) - assertThrows(IllegalArgumentException.class, () -> { - workflowService.findOne(testFolder.stringId) - }) - assertThrows(IllegalArgumentException.class, () -> { - workflowService.findOne(leafItemId) - }) - } - - Case getCase() { - return workflowService.searchOne(QCase.case$.processIdentifier.eq("filter_api_test")) - } - - Case getMenuItem(Case caze) { - return workflowService.findOne(caze.dataSet["menu_stringId"].value as String) - } - - Case getFilter(Case caze) { - return workflowService.findOne(caze.dataSet["filter_stringId"].value as String) - } - - def setData(Case caze, Map dataSet) { - dataService.setData(caze.tasks[0].task, ImportHelper.populateDataset(dataSet.collectEntries { - [(it.key): (["value": it.value, "type": "text"])] - })) - return workflowService.findOne(caze.stringId) - } - -} diff --git a/src/test/groovy/com/netgrif/application/engine/elastic/DataSearchRequestTest.groovy b/src/test/groovy/com/netgrif/application/engine/elastic/DataSearchRequestTest.groovy index 56cd0c04d9b..7092ba1ded8 100644 --- a/src/test/groovy/com/netgrif/application/engine/elastic/DataSearchRequestTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/elastic/DataSearchRequestTest.groovy @@ -234,7 +234,7 @@ class DataSearchRequestTest { log.info(String.format("Testing %s == %s", testCase.getKey(), testCase.getValue())) - Page result = searchService.search([request] as List, mockService.mockLoggedUser(), PageRequest.of(0, 100), null, false) + Page result = searchService.search([request] as List, superCreator.getLoggedSuper(), PageRequest.of(0, 100), null, false) assert result assert result.size() == 1 } diff --git a/src/test/groovy/com/netgrif/application/engine/elastic/ReindexTest.groovy b/src/test/groovy/com/netgrif/application/engine/elastic/ReindexTest.groovy index db93093eb26..851f27d19e6 100644 --- a/src/test/groovy/com/netgrif/application/engine/elastic/ReindexTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/elastic/ReindexTest.groovy @@ -35,13 +35,13 @@ class ReindexTest { private SuperCreator superCreator @Autowired - protected IElasticCaseService elasticCaseService + private IElasticCaseService elasticCaseService @Autowired private ReindexingTask reindexingTask @Autowired - TestHelper testHelper + private TestHelper testHelper @BeforeEach void before() { diff --git a/src/test/groovy/com/netgrif/application/engine/export/service/XlsExportServiceTest.java b/src/test/groovy/com/netgrif/application/engine/export/service/XlsExportServiceTest.java index 392d9b1f063..27955dc119d 100644 --- a/src/test/groovy/com/netgrif/application/engine/export/service/XlsExportServiceTest.java +++ b/src/test/groovy/com/netgrif/application/engine/export/service/XlsExportServiceTest.java @@ -1,11 +1,9 @@ package com.netgrif.application.engine.export.service; -import com.netgrif.application.engine.auth.domain.IUser; import com.netgrif.application.engine.auth.domain.LoggedUser; -import com.netgrif.application.engine.auth.service.interfaces.IUserService; import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; import com.netgrif.application.engine.export.service.interfaces.IXlsExportService; -import com.netgrif.application.engine.startup.FilterRunner; +import com.netgrif.application.engine.menu.domain.MenuItemConstants; import com.netgrif.application.engine.startup.SuperCreator; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.netgrif.application.engine.export.web.requestbodies.FilteredCasesRequest; @@ -42,7 +40,7 @@ public class XlsExportServiceTest { void shouldCreateXlsxFile() throws Exception { LoggedUser superUser = superCreator.getSuperUser().transformToLoggedUser(); - IntStream.range(0,5).forEach(idx -> workflowService.createCaseByIdentifier(FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER, "Test case", "", superUser)); + IntStream.range(0,5).forEach(idx -> workflowService.createCaseByIdentifier(MenuItemConstants.PROCESS_IDENTIFIER, "Test case", "", superUser)); FilteredCasesRequest request = getTestRequest(); File excel = xlsExportService.getExportFilteredCasesFile(request, superUser, Locale.ENGLISH); @@ -59,7 +57,7 @@ FilteredCasesRequest getTestRequest() { FilteredCasesRequest request = new FilteredCasesRequest(); request.setQuery(List.of( CaseSearchRequest.builder() - .query("processIdentifier:" + FilterRunner.PREFERRED_ITEM_NET_IDENTIFIER) + .query("processIdentifier:" + MenuItemConstants.PROCESS_IDENTIFIER) .build())); request.setSelectedDataFieldNames(List.of("Menu Item Identifier", "Item URI", "Menu icon identifier", "Name of the item", "Tab icon identifier", "Name of the item")); request.setSelectedDataFieldIds(List.of("menu_item_identifier", "nodePath", "menu_icon", "menu_name", "tab_icon", "tab_name")); diff --git a/src/test/groovy/com/netgrif/application/engine/filters/FilterImportExportTest.groovy b/src/test/groovy/com/netgrif/application/engine/filters/FilterImportExportTest.groovy deleted file mode 100644 index d726724f21b..00000000000 --- a/src/test/groovy/com/netgrif/application/engine/filters/FilterImportExportTest.groovy +++ /dev/null @@ -1,546 +0,0 @@ -package com.netgrif.application.engine.filters - -import com.netgrif.application.engine.TestHelper -import com.netgrif.application.engine.auth.domain.Authority -import com.netgrif.application.engine.auth.domain.User -import com.netgrif.application.engine.auth.domain.UserState -import com.netgrif.application.engine.petrinet.domain.PetriNet -import com.netgrif.application.engine.petrinet.domain.dataset.FileFieldValue -import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole -import com.netgrif.application.engine.startup.DefaultFiltersRunner -import com.netgrif.application.engine.startup.FilterRunner -import com.netgrif.application.engine.startup.ImportHelper -import com.netgrif.application.engine.workflow.domain.* -import com.netgrif.application.engine.workflow.domain.filter.FilterImportExportList -import com.netgrif.application.engine.workflow.service.UserFilterSearchService -import com.netgrif.application.engine.workflow.service.interfaces.IDataService -import com.netgrif.application.engine.workflow.service.interfaces.IFilterImportExportService -import com.netgrif.application.engine.workflow.service.interfaces.ITaskService -import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.core.io.ClassPathResource -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken -import org.springframework.security.core.Authentication -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.test.context.ActiveProfiles -import org.springframework.test.context.junit.jupiter.SpringExtension - -import javax.xml.XMLConstants -import javax.xml.transform.stream.StreamSource -import javax.xml.validation.Schema -import javax.xml.validation.SchemaFactory -import javax.xml.validation.Validator -import java.util.stream.Collectors - -@ExtendWith(SpringExtension.class) -@ActiveProfiles(["test"]) -@SpringBootTest -class FilterImportExportTest { - - public static final String DUMMY_USER_MAIL = "dummy@netgrif.com" - public static final String DUMMY_USER_PASSWORD = "password" - - private static final String EXPORT_NET_IDENTIFIER = "export_filters" - private static final String IMPORT_NET_IDENTIFIER = "import_filters" - - private static final int DEFAULT_FILTERS_SIZE = 5 - private static final String[] FILTERS_TO_EXPORT = ["My cases", "My tasks", "Test filter"] - private static final String[] FILTERS_TO_EXPORT_NEW = ["My cases new", "My tasks new", "Test filter new"] - - private static final String FILTER_VISIBILITY_PUBLIC = "public" - private static final String FILTER_VISIBILITY_PRIVATE = "private" - - private static final String GERMAN_ISO_3166_CODE = "de" - private static final String SLOVAK_ISO_3166_CODE = "sk" - - private static final String UPLOAD_FILE_FIELD = "upload_file" - private static final String NEW_TITLE_FIELD = "new_title" - private static final String VISIBILITY_FIELD = "visibility" - private static final String IMPORTED_FILTERS_FIELD = "imported_filters" - private static final String FILTER_FIELD = "filter" - - @Autowired - IFilterImportExportService importExportService - - @Autowired - FilterRunner filterRunner - - @Autowired - TestHelper testHelper - - @Autowired - IWorkflowService workflowService - - @Autowired - ImportHelper importHelper - - @Autowired - DefaultFiltersRunner defaultFiltersRunner - - @Autowired - UserFilterSearchService userFilterSearchService - - @Autowired - ITaskService taskService - - @Autowired - private IDataService dataService - - private Authentication userAuth - private User dummyUser - private Case importCase - private Case exportCase - - @BeforeEach - void setup() { - this.testHelper.truncateDbs() - this.defaultFiltersRunner.run() - createTestFilter() - dummyUser = createDummyUser() - userAuth = new UsernamePasswordAuthenticationToken(dummyUser.transformToLoggedUser(), DUMMY_USER_PASSWORD) - SecurityContextHolder.getContext().setAuthentication(userAuth) - - Optional importNet = this.filterRunner.createImportFiltersNet() - Optional exportNet = this.filterRunner.createExportFiltersNet() - assert importNet.isPresent() - assert exportNet.isPresent() - - importCase = this.workflowService.searchOne( - QCase.case$.processIdentifier.eq(IMPORT_NET_IDENTIFIER).and(QCase.case$.author.email.eq(DUMMY_USER_MAIL)) - ) - exportCase = this.workflowService.searchOne( - QCase.case$.processIdentifier.eq(EXPORT_NET_IDENTIFIER).and(QCase.case$.author.email.eq(DUMMY_USER_MAIL)) - ) - assert importCase != null - assert exportCase != null - } - - @Test - @Disabled("Fixne DJ") - void createImportExportFiltersNet() { - List filterCases = this.userFilterSearchService.autocompleteFindFilters("") - assert filterCases.size() == DEFAULT_FILTERS_SIZE - - Set exportFiltersIds = filterCases.stream() - .filter({ filterCase -> filterCase.title in FILTERS_TO_EXPORT }) - .map({ filterCase -> filterCase.stringId }) - .collect(Collectors.toSet()) - FileFieldValue exportedFiltersField = this.importExportService.exportFiltersToFile(exportFiltersIds) - File exportedFiltersFile = new File(exportedFiltersField.getPath()) - assert exportedFiltersFile.exists() - - importCase.dataSet.get(UPLOAD_FILE_FIELD).value = exportedFiltersField - this.workflowService.save(importCase) - List importedTasksIds = this.importExportService.importFilters() - assert importedTasksIds.size() == FILTERS_TO_EXPORT.size() - - validateFilterXML(new FileInputStream(exportedFiltersField.getPath())) - importedTasksIds.forEach({ taskId -> - Task filterTask = this.taskService.findOne(taskId) - this.dataService.setData(filterTask, ImportHelper.populateDataset([ - (VISIBILITY_FIELD): [ - "type" : "enumeration_map", - "value": FILTER_VISIBILITY_PRIVATE - ], - (NEW_TITLE_FIELD) : [ - "type" : "text", - "value": this.workflowService.findOne(filterTask.caseId).title + " new" - ] - ])) - }) - Task importTask = this.taskService.searchOne(QTask.task.caseId.eq(importCase.stringId).and(QTask.task.transitionId.eq("importFilter"))) - this.dataService.setData(importTask, ImportHelper.populateDataset([ - (IMPORTED_FILTERS_FIELD): [ - "type" : "taskRef", - "value": importedTasksIds - ] - ])) - this.taskService.finishTask(importTask, dummyUser) - Thread.sleep(1000) - filterCases = this.userFilterSearchService.autocompleteFindFilters("") - List filterCasesNames = filterCases.stream().map({ filterCase -> filterCase.title }).collect(Collectors.toList()) - assert filterCases.size() == DEFAULT_FILTERS_SIZE + FILTERS_TO_EXPORT.size() - - for (String filterName : FILTERS_TO_EXPORT_NEW) { - assert filterName in filterCasesNames - } - for (Case filterCase : filterCases) { - if (filterCase.title in FILTERS_TO_EXPORT_NEW) { - assert filterCase.dataSet.get(VISIBILITY_FIELD).value == FILTER_VISIBILITY_PRIVATE - } - } - for (int i = 0; i < FILTERS_TO_EXPORT.size(); i++) { - Case filterCase1 = filterCases.get(filterCasesNames.indexOf(FILTERS_TO_EXPORT[i])) - DataField filterField1 = filterCase1.dataSet.get(FILTER_FIELD) - Case filterCase2 = filterCases.get(filterCasesNames.indexOf(FILTERS_TO_EXPORT_NEW[i])) - DataField filterField2 = filterCase2.dataSet.get(FILTER_FIELD) - assert filterCase1.icon == filterCase2.icon - assert filterField1.value == filterField2.value - assert filterField1.allowedNets == filterField2.allowedNets - assert filterField1.filterMetadata == filterField2.filterMetadata - } - } - - @Test - void importExportChainedFilters() { - Optional filter1Opt = defaultFiltersRunner.createCaseFilter( - "Link 1", - "home", - FILTER_VISIBILITY_PUBLIC, - "(author:<>)", - [], - ["predicateMetadata" : [ - [[ - "category" : "case_author", - "configuration": [ - "operator": "equals" - ], - "values" : [[ - "text" : "search.category.userMe", - "value": ["<>"] - ]] - ]] - ], "searchCategories": ["case_author"]], - [:] - ) - assert filter1Opt.isPresent() - Case filter1 = filter1Opt.get() - Optional filter2Opt = defaultFiltersRunner.createCaseFilter( - "Link 2", - "home", - FILTER_VISIBILITY_PUBLIC, - "(author:<>)", - [], - ["predicateMetadata" : [ - [[ - "category" : "case_author", - "configuration": [ - "operator": "equals" - ], - "values" : [[ - "text" : "search.category.userMe", - "value": ["<>"] - ]] - ]] - ], "searchCategories": ["case_author"]], - [:], - true, - true, - filter1.getStringId() - ) - assert filter2Opt.isPresent() - Case filter2 = filter2Opt.get() - Optional filter3Opt = defaultFiltersRunner.createCaseFilter( - "Link 3", - "home", - FILTER_VISIBILITY_PUBLIC, - "(author:<>)", - [], - ["predicateMetadata" : [ - [[ - "category" : "case_author", - "configuration": [ - "operator": "equals" - ], - "values" : [[ - "text" : "search.category.userMe", - "value": ["<>"] - ]] - ]] - ], "searchCategories": ["case_author"]], - [:], - true, - true, - filter1.getStringId() - ) - assert filter3Opt.isPresent() - Case filter3 = filter3Opt.get() - - FilterImportExportList exported = importExportService.exportFilters([filter2.getStringId(), filter3.getStringId()]) - assert exported != null - assert exported.filters != null - assert exported.filters.size() == 3 - assert exported.filters.get(0).caseId == filter1.getStringId() - assert exported.filters.get(0).parentCaseId == null - assert exported.filters.get(0).parentViewId == null - assert exported.filters.get(1).caseId == filter2.getStringId() - assert exported.filters.get(1).parentCaseId == filter1.getStringId() - assert exported.filters.get(1).parentViewId == null - assert exported.filters.get(2).caseId == filter3.getStringId() - assert exported.filters.get(2).parentCaseId == filter1.getStringId() - assert exported.filters.get(2).parentViewId == null - - Map imported = importExportService.importFilters(exported) - assert imported != null - assert imported.size() == 3 - - List importedTasks = taskService.findAllById(new ArrayList(imported.values())) - assert importedTasks.size() == 3 - - Map invertedMap = imported.collectEntries { k, v -> [v, k] } - Map newToOldCaseId = importedTasks.collectEntries { t -> [t.caseId, invertedMap.get(t.getStringId())] } - Map idToOldCase = [filter1, filter2, filter3].collectEntries { c -> [c.stringId, c] } - - - List importedCases = workflowService.findAllById(new ArrayList(newToOldCaseId.keySet())) - assert importedCases.size() == 3 - - importedCases.forEach({ c -> - Case oldCase = idToOldCase.get(newToOldCaseId.get(c.getStringId())) - assert oldCase != null - assert newToOldCaseId.get(c.dataSet.get("origin_view_id").value) == oldCase.dataSet.get("origin_view_id").value - assert newToOldCaseId.get(c.dataSet.get("parent_filter_id").value) == oldCase.dataSet.get("parent_filter_id").value - }) - } - - private User createDummyUser() { - def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) - return importHelper.createUser(new User(name: "Dummy", surname: "User", email: DUMMY_USER_MAIL, password: DUMMY_USER_PASSWORD, state: UserState.ACTIVE), - [auths.get("user")] as Authority[], - [] as ProcessRole[]) - } - - private static void validateFilterXML(InputStream xml) throws IllegalFilterFileException { - try { - SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI) - Schema schema = factory.newSchema(new ClassPathResource("/petriNets/filter_export_schema.xsd").URL) - Validator validator = schema.newValidator() - validator.validate(new StreamSource(xml)) - } catch (Exception ex) { - throw new IllegalFilterFileException(ex) - } - } - - void createTestFilter() { - defaultFiltersRunner.createCaseFilter("Test filter", "filter_alt", FILTER_VISIBILITY_PUBLIC, - "((((dataSet.number.numberValue:5) AND (processIdentifier:6139e51308215f25b0a498c2_all_data)) OR ((dataSet.number.numberValue:[10 TO 100.548]) AND " + - "(processIdentifier:6139e51308215f25b0a498c2_all_data)) OR ((dataSet.text.fulltextValue:*asdad*) AND (processIdentifier:6139e51308215f25b0a498c2_all_data)) " + - "OR ((dataSet.enumeration.fulltextValue:*asdasd*) AND (processIdentifier:6139e51308215f25b0a498c2_all_data)) OR ((dataSet.enumeration_map.fulltextValue:*asdasd*) " + - "AND (processIdentifier:6139e51308215f25b0a498c2_all_data)) OR ((dataSet.multichoice.fulltextValue:*asdasd*) AND (processIdentifier:6139e51308215f25b0a498c2_all_data)) " + - "OR ((dataSet.boolean.booleanValue:true) AND (processIdentifier:6139e51308215f25b0a498c2_all_data)) OR ((dataSet.boolean.booleanValue:false) AND " + - "(processIdentifier:6139e51308215f25b0a498c2_all_data)) OR ((dataSet.date.timestampValue:[1631138400000 TO 1631224800000}) AND " + - "(processIdentifier:6139e51308215f25b0a498c2_all_data)) OR ((dataSet.date.timestampValue:[1631138400000 TO 1631311200000}) AND " + - "(processIdentifier:6139e51308215f25b0a498c2_all_data)) OR ((dataSet.file.fileNameValue:*asdasd*) AND (processIdentifier:6139e51308215f25b0a498c2_all_data)) OR " + - "((dataSet.fileList.fileNameValue:*asdasd*) AND (processIdentifier:6139e51308215f25b0a498c2_all_data)) OR ((dataSet.user.userIdValue:<>) AND " + - "(processIdentifier:6139e51308215f25b0a498c2_all_data)) OR ((dataSet.user.userIdValue:7) AND (processIdentifier:6139e51308215f25b0a498c2_all_data)) " + - "OR ((dataSet.datetime.timestampValue:[1631184300000 TO 1631184360000}) AND (processIdentifier:6139e51308215f25b0a498c2_all_data)) OR " + - "((dataSet.datetime.timestampValue:[1631184360000 TO 1631270820000}) AND (processIdentifier:6139e51308215f25b0a498c2_all_data))) AND (title:*asdasd*) AND " + - "((creationDateSortable:[1631138400000 TO 1631224800000}) OR (creationDateSortable:[1631138400000 TO 1631311200000})) AND " + - "((creationDateSortable:[1631184360000 TO 1631184420000}) OR (creationDateSortable:[1631184360000 TO 1631270820000})) AND " + - "(processIdentifier:6139e51308215f25b0a498c2_all_data) AND ((taskIds:1) AND (processIdentifier:6139e51308215f25b0a498c2_all_data)) AND " + - "((author:<>) OR (!(author:7))) AND (visualId:*asdad*) AND (stringId:*asdasd*))", ["all_data", "test_net"], - ["predicateMetadata": [ - [[ - "category" : "case_dataset", - "configuration": [ - "operator" : "equals", - "datafield": "number#Number" - ], - "values" : [5] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "in_range", - "datafield": "number#Number" - ], - "values" : [10, 100.548] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "substring", - "datafield": "text#Text" - ], - "values" : ["asdad"] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "substring", - "datafield": "enumeration#Enumeration" - ], - "values" : ["asdasd"] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "substring", - "datafield": "enumeration_map#Enumeration Map" - ], - "values" : ["asdasd"] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "substring", - "datafield": "multichoice#Multichoice" - ], - "values" : ["asdasd"] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "equals", - "datafield": "boolean#Boolean" - ], - "values" : [true] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "equals", - "datafield": "boolean#Boolean" - ], - "values" : [false] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "equals_date", - "datafield": "date#Date" - ], - "values" : [1631138400000] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "in_range_date", - "datafield": "date#Date" - ], - "values" : [1631138400000, 1631224800000] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "substring", - "datafield": "file#File" - ], - "values" : ["asdasd"] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "substring", - "datafield": "fileList#File List" - ], - "values" : ["asdasd"] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "equals", - "datafield": "user#User" - ], - "values" : [[ - "text" : "search.category.userMe", - "value": ["<>"] - ]] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "equals", - "datafield": "user#User" - ], - "values" : [[ - "text" : "Admin Netgrif", - "value": [7] - ]] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "equals_date_time", - "datafield": "dateTime#Datetime" - ], - "values" : [1631184356623] - ], [ - "category" : "case_dataset", - "configuration": [ - "operator" : "in_range_date_time", - "datafield": "dateTime#Datetime" - ], - "values" : [1631184364266, 1631270767000] - ]], - [[ - "category" : "case_title", - "configuration": [ - "operator": "substring" - ], - "values" : ["asdasd"] - ]], - [[ - "category" : "case_creation_date", - "configuration": [ - "operator": "equals_date" - ], - "values" : [1631138400000] - ], [ - "category" : "case_creation_date", - "configuration": [ - "operator": "in_range_date" - ], - "values" : [1631138400000, 1631224800000] - ]], - [[ - "category" : "case_creation_date_time", - "configuration": [ - "operator": "equals_date_time" - ], - "values" : [1631184402526] - ], [ - "category" : "case_creation_date_time", - "configuration": [ - "operator": "in_range_date_time" - ], - "values" : [1631184408995, 1631270810000] - ]], - [[ - "category" : "case_process", - "configuration": [ - "operator": "equals" - ], - "values" : ["All Data"] - ]], - [[ - "category" : "case_task", - "configuration": [ - "operator": "equals" - ], - "values" : ["Task - editable"] - ]], - [[ - "category" : "case_author", - "configuration": [ - "operator": "equals" - ], - "values" : [[ - "text" : "search.category.userMe", - "value": ["<>"] - ]] - ], [ - "category" : "case_author", - "configuration": [ - "operator": "not_equals" - ], - "values" : [[ - "text" : "Admin Netgrif", - "value": [7] - ]] - ]], - [[ - "category" : "case_visual_id", - "configuration": [ - "operator": "substring" - ], - "values" : ["asdad"] - ]], - [[ - "category" : "case_string_id", - "configuration": [ - "operator": "substring" - ], - "values" : ["asdasd"] - ]] - ], - "searchCategories" : ["case_dataset", "case_title", "case_creation_date", "case_creation_date_time", "case_process", "case_task", "case_author", "case_visual_id", "case_string_id"]], - [ - (GERMAN_ISO_3166_CODE): "Testfilter", - (SLOVAK_ISO_3166_CODE): "Testovaci filter" - ] - ) - } - -} diff --git a/src/test/groovy/com/netgrif/application/engine/filters/FilterTest.groovy b/src/test/groovy/com/netgrif/application/engine/filters/FilterTest.groovy new file mode 100644 index 00000000000..b05c90887cf --- /dev/null +++ b/src/test/groovy/com/netgrif/application/engine/filters/FilterTest.groovy @@ -0,0 +1,87 @@ +package com.netgrif.application.engine.filters + +import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.auth.service.UserService +import com.netgrif.application.engine.petrinet.domain.PetriNet +import com.netgrif.application.engine.petrinet.domain.VersionType +import com.netgrif.application.engine.petrinet.domain.dataset.CaseFilterField +import com.netgrif.application.engine.petrinet.domain.dataset.ProcessFilterField +import com.netgrif.application.engine.petrinet.domain.dataset.TaskFilterField +import com.netgrif.application.engine.petrinet.service.PetriNetService +import com.netgrif.application.engine.startup.ImportHelper +import com.netgrif.application.engine.startup.SuperCreator +import com.netgrif.application.engine.workflow.domain.Case +import com.netgrif.application.engine.workflow.service.interfaces.IDataService +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension + +import static org.testng.AssertJUnit.assertEquals + +@SpringBootTest +@ActiveProfiles(["test"]) +@ExtendWith(SpringExtension.class) +class FilterTest { + + private static final String PROCESS_PATH = "src/test/resources/petriNets/filter_test.xml" + + @Autowired + private TestHelper testHelper + + @Autowired + private IWorkflowService workflowService + + @Autowired + private ImportHelper importHelper + + @Autowired + private UserService userService + + @Autowired + private IDataService dataService + + @Autowired + private PetriNetService petriNetService + + @Autowired + private SuperCreator superCreator + + @BeforeEach + void beforeEach() { + testHelper.truncateDbs() + } + + @Test + void filterTest() { + InputStream inputStream = new FileInputStream(PROCESS_PATH) + PetriNet testProcess = petriNetService.importPetriNet(inputStream, VersionType.MAJOR, superCreator.loggedSuper).getNet() + inputStream.close() + assertEquals(CaseFilterField.class, testProcess.getField("filter_field").get().class) + assertEquals(CaseFilterField.class, testProcess.getField("case_filter_field").get().class) + assertEquals(TaskFilterField.class, testProcess.getField("task_filter_field").get().class) + assertEquals(ProcessFilterField.class, testProcess.getField("process_filter_field").get().class) + + Case testCase = workflowService.createCase(testProcess.stringId, "", "", superCreator.loggedSuper).case + assertEquals("someQueryString", testCase.getFieldValue("filter_field")) + assertEquals("someQueryString", testCase.getFieldValue("case_filter_field")) + assertEquals("someQueryString", testCase.getFieldValue("task_filter_field")) + assertEquals("someQueryString", testCase.getFieldValue("process_filter_field")) + + String taskId = testCase.tasks.find { taskPair -> taskPair.transition == "t1" }.task + testCase = dataService.setData(taskId, ImportHelper.populateDataset([ + "filter_field": ["type": "filter", "value": "newQueryString"], + "case_filter_field": ["type": "caseFilter", "value": "newQueryString"], + "task_filter_field": ["type": "taskFilter", "value": "newQueryString"], + "process_filter_field": ["type": "processFilter", "value": "newQueryString"], + ])).case + assertEquals("newQueryString", testCase.getFieldValue("filter_field")) + assertEquals("newQueryString", testCase.getFieldValue("case_filter_field")) + assertEquals("newQueryString", testCase.getFieldValue("task_filter_field")) + assertEquals("newQueryString", testCase.getFieldValue("process_filter_field")) + } +} diff --git a/src/test/groovy/com/netgrif/application/engine/ipc/CaseApiTest.groovy b/src/test/groovy/com/netgrif/application/engine/ipc/CaseApiTest.groovy index b72b0ea628d..fca9f9cc425 100644 --- a/src/test/groovy/com/netgrif/application/engine/ipc/CaseApiTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/ipc/CaseApiTest.groovy @@ -77,17 +77,18 @@ class CaseApiTest { assert testNet.getNet() != null List cases = [] + def caseTitle = "CaseApiTestCase " 5.times { index -> - cases << importHelper.createCase("Case $index" as String, testNet.getNet()) + cases << importHelper.createCase("CaseApiTestCase $index" as String, testNet.getNet()) } importHelper.assignTaskToSuper("Task", cases[0].stringId) importHelper.finishTaskAsSuper("Task", cases[0].stringId) cases = caseRepository.findAll() - assert cases.find { it.title == "Case 1" }.dataSet["field"].value != 0 - assert cases.findAll { it.title != "Case 1" && it.processIdentifier == "test" }.every { it.dataSet["field"].value == 0 } - assert cases.find { it.title == "Case 0" }.dataSet["count"].value == 5 - assert cases.find { it.title == "Case 0" }.dataSet["paged"].value == 1 + assert cases.find { it.title == "CaseApiTestCase 1" }.dataSet["field"].value != 0 + assert cases.findAll { it.title != "CaseApiTestCase 1" && it.processIdentifier == "test" }.every { it.dataSet["field"].value == 0 } + assert cases.find { it.title == "CaseApiTestCase 0" }.dataSet["count"].value == 5 + assert cases.find { it.title == "CaseApiTestCase 0" }.dataSet["paged"].value == 1 } } \ No newline at end of file diff --git a/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy b/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy index cf6e0108859..98d06dc2b94 100644 --- a/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy @@ -15,11 +15,9 @@ import com.netgrif.application.engine.workflow.domain.QCase import com.netgrif.application.engine.workflow.domain.QTask import com.netgrif.application.engine.workflow.domain.Task import com.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes.SetDataEventOutcome -import com.netgrif.application.engine.workflow.domain.menu.MenuAndFilters +import com.netgrif.application.engine.menu.domain.MenuAndFilters import com.netgrif.application.engine.workflow.domain.repositories.CaseRepository -import com.netgrif.application.engine.workflow.service.UserFilterSearchService import com.netgrif.application.engine.workflow.service.interfaces.IDataService -import com.netgrif.application.engine.workflow.service.interfaces.IMenuImportExportService import com.netgrif.application.engine.workflow.service.interfaces.ITaskService import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService import org.junit.jupiter.api.BeforeEach @@ -72,10 +70,7 @@ class MenuImportExportTest { "Menu entry \"My tasks\": OK\n" @Autowired - IMenuImportExportService menuImportExportService - - @Autowired - FilterRunner filterRunner + MenuRunner menuRunner @Autowired TestHelper testHelper @@ -89,12 +84,6 @@ class MenuImportExportTest { @Autowired ImportHelper importHelper - @Autowired - DefaultFiltersRunner defaultFiltersRunner - - @Autowired - UserFilterSearchService userFilterSearchService - @Autowired ITaskService taskService @@ -123,7 +112,6 @@ class MenuImportExportTest { @BeforeEach void beforeTest() { this.testHelper.truncateDbs(); - this.defaultFiltersRunner.run() this.dummyUser = createDummyUser(); } diff --git a/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/DynamicEnumerationTest.groovy b/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/DynamicEnumerationTest.groovy index 315d33ba5c8..f855f3ac1ed 100644 --- a/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/DynamicEnumerationTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/DynamicEnumerationTest.groovy @@ -59,7 +59,7 @@ class DynamicEnumerationTest { assert optNet.getNet() != null; def net = optNet.getNet() - def aCase = importHelper.createCase("Case", net) + def aCase = importHelper.createCase("TestCase", net) assert aCase != null Task task = taskService.findByCases(new FullPageRequest(), Collections.singletonList(aCase.getStringId())).stream().collect(Collectors.toList()).get(0); @@ -67,7 +67,7 @@ class DynamicEnumerationTest { dataService.setData(task.stringId, ImportHelper.populateDataset([ "autocomplete": [ - "value": "Case", + "value": "TestCase", "type" : "enumeration" ] ])) @@ -78,6 +78,6 @@ class DynamicEnumerationTest { def field = aCase.dataSet["autocomplete"] assert field.choices.size() == 1 - assert field.choices.find { it.defaultValue == "Case" } + assert field.choices.find { it.defaultValue == "TestCase" } } } diff --git a/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileFieldTest.groovy b/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileFieldTest.groovy index efbf77af576..d137fa6d47a 100644 --- a/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileFieldTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileFieldTest.groovy @@ -14,6 +14,9 @@ import com.netgrif.application.engine.startup.SuperCreator import com.netgrif.application.engine.workflow.domain.Case import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService import com.netgrif.application.engine.workflow.web.requestbodies.file.FileFieldRequest +import io.minio.BucketExistsArgs +import io.minio.MakeBucketArgs +import io.minio.MinioClient import org.assertj.core.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -56,6 +59,9 @@ class FileFieldTest { public static final String USER_EMAIL = "super@netgrif.com" public static final String MOCK_FILE_NAME = "hello.txt" + public static final String BUCKET = "default" + + static MinioClient mc; @Value('${admin.password:password}') private String userPassword @@ -91,6 +97,17 @@ class FileFieldTest { @BeforeEach void setup() { testHelper.truncateDbs() + + mc = MinioClient.builder() + .endpoint("http://127.0.0.1:9000") + .credentials("root", "password") + .build(); + + boolean exists = mc.bucketExists(BucketExistsArgs.builder().bucket(BUCKET).build()); + if (!exists) { + mc.makeBucket(MakeBucketArgs.builder().bucket(BUCKET).build()); + } + mockMvc = MockMvcBuilders .webAppContextSetup(context) .apply(SecurityMockMvcConfigurers.springSecurity()) diff --git a/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileListFieldTest.groovy b/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileListFieldTest.groovy index 6cc02b28c37..422e7f4b9b8 100644 --- a/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileListFieldTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileListFieldTest.groovy @@ -14,6 +14,9 @@ import com.netgrif.application.engine.startup.SuperCreator import com.netgrif.application.engine.workflow.domain.Case import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService import com.netgrif.application.engine.workflow.web.requestbodies.file.FileFieldRequest +import io.minio.BucketExistsArgs +import io.minio.MakeBucketArgs +import io.minio.MinioClient import org.assertj.core.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -54,6 +57,9 @@ class FileListFieldTest { public static final String TASK_TITLE = "Task" public static final String USER_EMAIL = "super@netgrif.com" public static final String MOCK_FILE_NAME = "hello.txt" + public static final String BUCKET = "default" + + static MinioClient mc; @Value('${admin.password:password}') private String userPassword @@ -89,6 +95,17 @@ class FileListFieldTest { @BeforeEach void setup() { testHelper.truncateDbs() + + mc = MinioClient.builder() + .endpoint("http://127.0.0.1:9000") + .credentials("root", "password") + .build(); + + boolean exists = mc.bucketExists(BucketExistsArgs.builder().bucket(BUCKET).build()); + if (!exists) { + mc.makeBucket(MakeBucketArgs.builder().bucket(BUCKET).build()); + } + mockMvc = MockMvcBuilders .webAppContextSetup(context) .apply(SecurityMockMvcConfigurers.springSecurity()) diff --git a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy new file mode 100644 index 00000000000..944266e13e6 --- /dev/null +++ b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy @@ -0,0 +1,158 @@ +package com.netgrif.application.engine.pfql + +import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.petrinet.domain.PetriNet +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType +import com.netgrif.application.engine.startup.ImportHelper +import com.netgrif.application.engine.startup.SuperCreator +import com.netgrif.application.engine.workflow.domain.Case +import com.netgrif.application.engine.workflow.service.interfaces.IDataService +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension + +import static org.junit.jupiter.api.Assertions.assertEquals +import static org.junit.jupiter.api.Assertions.assertThrows + +@SpringBootTest +@ActiveProfiles(["test"]) +@ExtendWith(SpringExtension.class) +class QueryLangActionTest { + + private static final String TRANS_ID = "t1" + private static final String QUERY_FIELD_ID = "input_query" + private static final String RESULT_FIELD_ID = "result" + private static final String RESULT_CLASS_FIELD_ID = "result_class" + private static final String SEARCH_GENERAL_SEARCH_FIELD_ID = "general_search" + private static final String SEARCH_GENERAL_COUNT_FIELD_ID = "general_count" + private static final String SEARCH_GENERAL_EXISTS_FIELD_ID = "general_exists" + + private static final String SEARCH_ONE_TEMPLATE = "%s_search_one" + private static final String SEARCH_TEMPLATE = "%s_search" + private static final String SEARCH_PAGED_TEMPLATE = "%s_paged_search" + + @Autowired + private TestHelper testHelper + + @Autowired + private ImportHelper importHelper + + @Autowired + private IDataService dataService + + @Autowired + private SuperCreator superCreator + + private PetriNet testProcess + private Case testCase + private String testCaseTaskId + + @BeforeEach + void beforeEach() { + testHelper.truncateDbs() + testProcess = importHelper.createNet("/query_lang_test.xml").get() + testCase = importHelper.createCase("test", testProcess) + testCaseTaskId = testCase.tasks.find { taskPair -> taskPair.transition == TRANS_ID }.task + } + + @Test + void generalTest() { + updateQuery("process: identifier == '" + testProcess.identifier + "'") + + pressButton(SEARCH_GENERAL_EXISTS_FIELD_ID) + assertEquals(true, testCase.getFieldValue(RESULT_FIELD_ID)) + + pressButton(SEARCH_GENERAL_COUNT_FIELD_ID) + assertEquals(1, testCase.getFieldValue(RESULT_FIELD_ID)) + + pressButton(SEARCH_GENERAL_SEARCH_FIELD_ID) + assertEquals("class com.netgrif.application.engine.petrinet.domain.PetriNet", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("processes: identifier == '" + testProcess.identifier + "'") + pressButton(SEARCH_GENERAL_SEARCH_FIELD_ID) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + } + + @Test + void processTest() { + updateQuery("process: identifier == '" + testProcess.identifier + "'") + pressButton(String.format(SEARCH_ONE_TEMPLATE, "process")) + assertEquals("class com.netgrif.application.engine.petrinet.domain.PetriNet", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("processes: identifier == '" + testProcess.identifier + "'") + pressButton(String.format(SEARCH_TEMPLATE, "process")) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + pressButton(String.format(SEARCH_PAGED_TEMPLATE, "process")) + assertEquals("class org.springframework.data.domain.PageImpl", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("case: processIdentifier == '" + testProcess.identifier + "'") + assertThrows(IllegalArgumentException.class, () -> pressButton(String.format(SEARCH_PAGED_TEMPLATE, "process"))) + } + + @Test + void caseTest() { + updateQuery("case: processIdentifier == '" + testProcess.identifier + "'") + pressButton(String.format(SEARCH_ONE_TEMPLATE, "case")) + assertEquals("class com.netgrif.application.engine.workflow.domain.Case", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("cases: processIdentifier == '" + testProcess.identifier + "'") + pressButton(String.format(SEARCH_TEMPLATE, "case")) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + pressButton(String.format(SEARCH_PAGED_TEMPLATE, "case")) + assertEquals("class org.springframework.data.domain.PageImpl", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("process: identifier == '" + testProcess.identifier + "'") + assertThrows(IllegalArgumentException.class, () -> pressButton(String.format(SEARCH_PAGED_TEMPLATE, "case"))) + } + + @Test + void taskTest() { + updateQuery("task: caseId == '" + testCase.stringId + "'") + pressButton(String.format(SEARCH_ONE_TEMPLATE, "task")) + assertEquals("class com.netgrif.application.engine.workflow.domain.Task", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("tasks: caseId == '" + testCase.stringId + "'") + pressButton(String.format(SEARCH_TEMPLATE, "task")) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + pressButton(String.format(SEARCH_PAGED_TEMPLATE, "task")) + assertEquals("class org.springframework.data.domain.PageImpl", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("process: identifier == '" + testProcess.identifier + "'") + assertThrows(IllegalArgumentException.class, () -> pressButton(String.format(SEARCH_PAGED_TEMPLATE, "task"))) + } + + @Test + void userTest() { + updateQuery("user: email == '" + superCreator.superUser.email + "'") + pressButton(String.format(SEARCH_ONE_TEMPLATE, "user")) + assertEquals("class com.netgrif.application.engine.auth.domain.User", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("users: email == '" + superCreator.superUser.email + "'") + pressButton(String.format(SEARCH_TEMPLATE, "user")) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + pressButton(String.format(SEARCH_PAGED_TEMPLATE, "user")) + assertEquals("class org.springframework.data.domain.PageImpl", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("process: identifier == '" + testProcess.identifier + "'") + assertThrows(IllegalArgumentException.class, () -> pressButton(String.format(SEARCH_PAGED_TEMPLATE, "user"))) + } + + private void setData(Map> dataSet) { + testCase = dataService.setData(testCaseTaskId, ImportHelper.populateDataset(dataSet)).case + } + + private void pressButton(String buttonFieldId) { + setData([(buttonFieldId): ["value": 0, "type": FieldType.BUTTON.name]]) + } + + private void updateQuery(String query) { + setData([(QUERY_FIELD_ID): ["value": query, "type": FieldType.TEXT.name]]) + } +} diff --git a/src/test/groovy/com/netgrif/application/engine/workflow/CasePermissionsTest.groovy b/src/test/groovy/com/netgrif/application/engine/workflow/CasePermissionsTest.groovy new file mode 100644 index 00000000000..50563bfa453 --- /dev/null +++ b/src/test/groovy/com/netgrif/application/engine/workflow/CasePermissionsTest.groovy @@ -0,0 +1,476 @@ +package com.netgrif.application.engine.workflow + +import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.auth.domain.IUser +import com.netgrif.application.engine.auth.domain.User +import com.netgrif.application.engine.auth.domain.UserState +import com.netgrif.application.engine.auth.service.interfaces.IUserService +import com.netgrif.application.engine.petrinet.domain.PetriNet +import com.netgrif.application.engine.petrinet.domain.VersionType +import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.ActionDelegate +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService +import com.netgrif.application.engine.workflow.domain.Case +import com.netgrif.application.engine.workflow.service.interfaces.IDataService +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService +import groovy.json.JsonOutput +import groovy.json.JsonSlurper +import groovy.util.logging.Slf4j +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.context.i18n.LocaleContextHolder +import org.springframework.hateoas.MediaTypes +import org.springframework.http.MediaType +import org.springframework.mock.web.MockHttpServletRequest +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.Authentication +import org.springframework.security.web.authentication.WebAuthenticationDetails +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.MvcResult +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.web.context.WebApplicationContext + +import java.nio.charset.StandardCharsets + +import static org.junit.jupiter.api.Assertions.assertFalse +import static org.junit.jupiter.api.Assertions.assertTrue +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post + +@Slf4j +@SpringBootTest +@ActiveProfiles(["test"]) +@ExtendWith(SpringExtension.class) +class CasePermissionsTest { + + @Autowired + private IWorkflowService workflowService + + @Autowired + private IPetriNetService petriNetService + + @Autowired + private IUserService userService + + @Autowired + private IDataService dataService + + @Autowired + private TestHelper testHelper + + @Autowired + private ActionDelegate actionDelegate + + @Autowired + private WebApplicationContext wac + + private MockMvc mvc + + private static final String CREATE_CASE_PERMISSION_DIRECTORY = "src/test/resources/petriNets/permissions/case/default/create" + private static final String CREATE_CASE_PERMISSION_DEFAULT_DISABLED_DIRECTORY = "src/test/resources/petriNets/permissions/case/defaultDisabled/create" + + private static final String DELETE_CASE_PERMISSION_DIRECTORY = "src/test/resources/petriNets/permissions/case/default/delete" + private static final String DELETE_CASE_PERMISSION_DEFAULT_DISABLED_DIRECTORY = "src/test/resources/petriNets/permissions/case/defaultDisabled/delete" + + private static final String VIEW_CASE_PERMISSION_DIRECTORY = "src/test/resources/petriNets/permissions/case/default/view" + private static final String VIEW_CASE_PERMISSION_DEFAULT_DISABLED_DIRECTORY = "src/test/resources/petriNets/permissions/case/defaultDisabled/view" + + private static final String CORRECT_PERMISSIONS_CSV_FILEPATH = "src/test/resources/csv/permissions - correct.csv" + private static final String CORRECT_PERMISSIONS_DEFAULT_DISABLED_CSV_FILEPATH = "src/test/resources/csv/permissions - correct default disabled.csv" + + private static final String CREATE_CASE_CORRECT_PERMISSIONS_CSV_FILEPATH = "src/test/resources/csv/create case permissions - correct.csv" + private static final String CREATE_CASE_CORRECT_PERMISSIONS_DEFAULT_DISABLED_CSV_FILEPATH = "src/test/resources/csv/create case permissions - correct default disabled.csv" + + private static final String CASE_BASE_URL = "/api/workflow/case" + + private static Map testUsers = [:] + private static List withRoles = [] + private static List inUserRef = [] + private static Map>> correctResults = new HashMap<>() + + @BeforeEach + void init() { + testHelper.truncateDbs() + testUsers.clear() + actionDelegate.outcomes = [] + mvc = MockMvcBuilders + .webAppContextSetup(wac) + .apply(springSecurity()) + .build() + + [ + new User("no_permissions@mail.com", "password", "No", "Permissions"), + new User("has_role@mail.com", "password", "Has", "Role"), + new User("in_userRef@mail.com", "password", "In", "UserRef"), + new User("both_permissions@mail.com", "password", "Both", "Permissions") + ].each { + it.setState(UserState.ACTIVE) + testUsers.put(it.getEmail(), userService.saveNew(it)) + } + + withRoles = [testUsers.get("has_role@mail.com"), testUsers.get("both_permissions@mail.com")] + inUserRef = [testUsers.get("in_userRef@mail.com").stringId, testUsers.get("both_permissions@mail.com").stringId] + correctResults = permissionsCsvToExpectedMap() + } + + @Test + void testCreateCasePermission() { + testUsers.remove("in_userRef@mail.com") + testUsers.remove("both_permissions@mail.com") + withRoles.removeIf { it.email == "both_permissions@mail.com" } + Map testNets = importTestNets(CREATE_CASE_PERMISSION_DIRECTORY) + Map testNetsNoDefault = importTestNets(CREATE_CASE_PERMISSION_DEFAULT_DISABLED_DIRECTORY) + Map>> results = [:] + testUsers.forEach((email, user) -> { + Authentication auth = new UsernamePasswordAuthenticationToken(email, "password") + auth.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) + + results.put(email, [ + "Default enabled" : resolveCreateWithNets(testNets, auth), + "Default DISabled": resolveCreateWithNets(testNetsNoDefault, auth) + ]) + }) + correctResults = importCorrectResultsForCreatePermission() + compareTestResultsToExpected(results, "Create") + } + + @Test + void testDeleteCasePermission() { + Map testNets = importTestNets(DELETE_CASE_PERMISSION_DIRECTORY) + Map testNetsNoDefault = importTestNets(DELETE_CASE_PERMISSION_DEFAULT_DISABLED_DIRECTORY) + Map>> results = [:] + // TODO: NAE-2447 fix + testUsers.forEach((email, user) -> { + + results.put(email, [ + "Default enabled" : resolveDeleteWithNets(testNets, user), + "Default DISabled": resolveDeleteWithNets(testNetsNoDefault, user) + ]) + }) + compareTestResultsToExpected(results, "Delete") + } + + @Test + void testViewCasePermission() { + // TODO: NAE-2447 fix + Map testNets = importTestNets(VIEW_CASE_PERMISSION_DIRECTORY) + createTestCases(testNets) + Map testNetsNoDefault = importTestNets(VIEW_CASE_PERMISSION_DEFAULT_DISABLED_DIRECTORY) + createTestCases(testNetsNoDefault) + Map>> resultsElastic = [:] + + testUsers.forEach((email, user) -> { + + resultsElastic.put(email, [ + "Default enabled" : resolveViewWithNets(testNets, user), + "Default DISabled": resolveViewWithNets(testNetsNoDefault, user) + ]) + }) + compareTestResultsToExpected(resultsElastic, "View") + } + + void createTestCases(Map testNets) { + testNets.forEach((identifier, net) -> { + def content = JsonOutput.toJson([ + netId: net.stringId + ]) + Authentication authSuper = new UsernamePasswordAuthenticationToken("super@netgrif.com", "password") + authSuper.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) + String caseId = parseResult(performRequest(buildCreateCaseRequest(content, authSuper))).outcome.aCase.stringId + Case createdCase = workflowService.findOne(caseId) + actionDelegate.setData("t_001", createdCase, [ + "users": [ + "type" : "userList", + "value": inUserRef + ] + ]).getCase() + }) + } + + List resolveViewWithNets(Map testNets, IUser user) { + Authentication auth = new UsernamePasswordAuthenticationToken(user.email, "password") + auth.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) + List found = [] + testNets.forEach((identifier, net) -> { + MockHttpServletRequestBuilder request = buildElasticSearchCaseRequest(net.identifier, auth) + + if (performSearchRequestAndResolveSuccess(request, net.identifier)) { + found.add(net.identifier) + } + }) + return found.sort() + } + + List resolveDeleteWithNets(Map testNets, IUser user) { + Authentication auth = new UsernamePasswordAuthenticationToken(user.email, "password") + auth.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) + List deleted = [] + testNets.forEach((identifier, net) -> { + def content = JsonOutput.toJson([ + netId: net.stringId + ]) + + Authentication authSuper = new UsernamePasswordAuthenticationToken("super@netgrif.com", "password") + authSuper.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) + String caseId = parseResult(performRequest(buildCreateCaseRequest(content, authSuper))).outcome.aCase.stringId + Case createdCase = workflowService.findOne(caseId) + actionDelegate.setData("t_001", createdCase, [ + "users": [ + "type" : "userList", + "value": inUserRef + ] + ]).getCase() + + if (performRequestAndResolveSuccess(buildDeleteCaseRequest(caseId, auth))) { + deleted.add(net.identifier) + } + }) + return deleted.sort() + } + + List resolveCreateWithNets(Map testNets, Authentication auth) { + List created = [] + testNets.forEach((identifier, net) -> { + def content = JsonOutput.toJson([ + netId: net.stringId + ]) + + boolean result = performRequestAndResolveSuccess(buildCreateCaseRequest(content, auth)) + if (result) { + created.add(net.identifier) + } + }) + return created.sort() + } + + static MockHttpServletRequestBuilder buildCreateCaseRequest(String content, Authentication auth) { + return post(CASE_BASE_URL) + .content(content) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .accept(MediaTypes.HAL_JSON_VALUE) + .locale(LocaleContextHolder.getLocale()) + .with(csrf().asHeader()) + .with(authentication(auth)) + } + + static MockHttpServletRequestBuilder buildDeleteCaseRequest(String caseId, Authentication auth) { + return delete(CASE_BASE_URL.concat("/${caseId}")) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .accept(MediaTypes.HAL_JSON_VALUE) + .locale(LocaleContextHolder.getLocale()) + .with(csrf().asHeader()) + .with(authentication(auth)) + } + + static MockHttpServletRequestBuilder buildElasticSearchCaseRequest(String netIdentifier, Authentication auth) { + return post(CASE_BASE_URL.concat("/search")) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .accept(MediaTypes.HAL_JSON_VALUE) + .content(JsonOutput.toJson([ + "process": [ + "identifier": netIdentifier + ], + ])) + .locale(LocaleContextHolder.getLocale()) + .with(csrf().asHeader()) + .with(authentication(auth)) + } + + boolean performRequestAndResolveSuccess(MockHttpServletRequestBuilder request) { + MvcResult result = performRequest(request) + if (result.getResponse().getStatus() == 200) { + return true + } + return false + } + + boolean performSearchRequestAndResolveSuccess(MockHttpServletRequestBuilder request, String netIdentifier) { + MvcResult result = performRequest(request) + Object parsedResult = parseResult(result) + if (parsedResult.page.totalElements == 1 && parsedResult._embedded.cases[0].processIdentifier == netIdentifier) { + return true + } + return false + } + + MvcResult performRequest(MockHttpServletRequestBuilder request) { + return mvc.perform(request).andReturn() + } + + Map importTestNets(String testNetDirectory) { + File testNetsDirectory = new File(testNetDirectory) + assertTrue(testNetsDirectory.isDirectory()) + File[] testNets = testNetsDirectory.listFiles() + assertTrue(testNets.size() > 0) + Map importedNets = [:] + for (File testNet in testNets) { + try (FileInputStream fis = new FileInputStream(testNet)) { + PetriNet net = petriNetService.importPetriNet(fis, VersionType.MAJOR, userService.getLoggedOrSystem().transformToLoggedUser()).getNet() + withRoles.forEach { user -> + assignRoleToUser(net, user) + } + importedNets.put(net.identifier, net) + } catch (FileNotFoundException e) { + log.error("Could not import net [${testNet.name}]", e) + throw new IllegalArgumentException(e) + } + } + return importedNets + } + + void assignRoleToUser(PetriNet net, IUser user) { + testUsers.put(user.email, userService.addRole(user, net.roles.values().find { role -> role.importId == "process_role" }.stringId)) + } + + static Map>> permissionsCsvToExpectedMap() { + Map>> expectedMap = initializeExpectedPermissionsMap() + + addPermissionsCsvToExpectedMap( + CORRECT_PERMISSIONS_CSV_FILEPATH, + "Default enabled", + expectedMap + ) + + addPermissionsCsvToExpectedMap( + CORRECT_PERMISSIONS_DEFAULT_DISABLED_CSV_FILEPATH, + "Default DISabled", + expectedMap + ) + + return expectedMap + } + + static void addPermissionsCsvToExpectedMap(String csvFilePath, + String defaultRoleKey, + Map>> expectedMap, + Map userColumns = [:] + ) { + File csvFile = new File(csvFilePath) + if (userColumns.isEmpty()) { + userColumns = this.userColumns() + } + + List lines = csvFile.readLines("UTF-8").findAll { it?.trim() } + assertFalse(lines.isEmpty(), "CSV file is empty: ${csvFilePath}") + List header = lines.first().split(",", -1)*.trim() + + int transitionTitleIndex = header.indexOf("Transition title") + assertTrue(transitionTitleIndex >= 0, "Missing required column 'Transition title' in ${csvFilePath}") + + lines.tail().each { line -> + List columns = line.split(",", -1)*.trim() + String title = columns[transitionTitleIndex] + + userColumns.each { csvColumnName, userEmail -> + int permissionIndex = header.indexOf(csvColumnName) + assertTrue(permissionIndex >= 0, "Missing required column '${csvColumnName}' in ${csvFilePath}") + + String permissionValue = columns[permissionIndex] + .replace(".", "") + .trim() + .toUpperCase() + + if (permissionValue == "TRUE") { + expectedMap[userEmail][defaultRoleKey] << title.replace("=", "_").replace(" ", "_") + } + } + } + + expectedMap.values().each { Map> permissionsByDefaultRole -> + permissionsByDefaultRole[defaultRoleKey].sort() + } + } + + static Map>> initializeExpectedPermissionsMap() { + return userColumns().values().collectEntries { String email -> + [ + email, + [ + "Default enabled" : [], + "Default DISabled": [] + ] + ] + } + } + + static Map userColumns() { + return [ + "No permissions" : "no_permissions@mail.com", + "Has role" : "has_role@mail.com", + "Is in userList" : "in_userRef@mail.com", + "Has role and is in userList": "both_permissions@mail.com" + ] + } + + static void compareTestResultsToExpected(Map>> testResultMap, String permissionType) { + println("\n========== ${permissionType} permissions comparison ==========") + + testUsers.keySet().each { String userEmail -> + println("\nUser: ${userEmail}") + + ["Default enabled", "Default DISabled"].each { String defaultRoleKey -> + Set actualNetIdentifiersIds = new TreeSet<>( + testResultMap.get(userEmail)?.get(defaultRoleKey) ?: [] + ) + + Set expectedNetIdentifiers = new TreeSet<>( + correctResults.get(userEmail)?.get(defaultRoleKey)?.collect { permissionType.toLowerCase().concat("_case_").concat(it) } ?: [] + ) + + Set presentInBoth = new TreeSet<>(actualNetIdentifiersIds) + presentInBoth.retainAll(expectedNetIdentifiers) + + Set presentOnlyInMap = new TreeSet<>(actualNetIdentifiersIds) + presentOnlyInMap.removeAll(expectedNetIdentifiers) + + Set presentOnlyInCorrectResultsWithDefaultRoleMap = new TreeSet<>(expectedNetIdentifiers) + presentOnlyInCorrectResultsWithDefaultRoleMap.removeAll(actualNetIdentifiersIds) + + println("\n${defaultRoleKey}:") + println("Present in both results (${presentInBoth.size()}): ${presentInBoth}") + println("Present only in test results (${presentOnlyInMap.size()}): ${presentOnlyInMap}") + println("Present only in correct results (${presentOnlyInCorrectResultsWithDefaultRoleMap.size()}): ${presentOnlyInCorrectResultsWithDefaultRoleMap}") + + assertTrue(presentInBoth.size() == actualNetIdentifiersIds.size() && presentInBoth.size() == expectedNetIdentifiers.size()) + } + } + + println("\n=================================================") + } + + static importCorrectResultsForCreatePermission() { + Map>> expectedMap = initializeExpectedPermissionsMap() + + Map userColumns = userColumns() + userColumns.remove("Is in userList") + userColumns.remove("Has role and is in userList") + + + addPermissionsCsvToExpectedMap( + CREATE_CASE_CORRECT_PERMISSIONS_CSV_FILEPATH, + "Default enabled", + expectedMap, userColumns + ) + + addPermissionsCsvToExpectedMap( + CREATE_CASE_CORRECT_PERMISSIONS_DEFAULT_DISABLED_CSV_FILEPATH, + "Default DISabled", + expectedMap, userColumns + ) + + return expectedMap + } + + @SuppressWarnings("GrMethodMayBeStatic") + private def parseResult(MvcResult result) { + return (new JsonSlurper()).parseText(result.response.getContentAsString(StandardCharsets.UTF_8)) + } +} \ No newline at end of file diff --git a/src/test/groovy/com/netgrif/application/engine/workflow/CaseSearchTest.groovy b/src/test/groovy/com/netgrif/application/engine/workflow/CaseSearchTest.groovy index 4c78adbe4a6..33407446074 100644 --- a/src/test/groovy/com/netgrif/application/engine/workflow/CaseSearchTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/workflow/CaseSearchTest.groovy @@ -9,7 +9,7 @@ import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetServi import com.netgrif.application.engine.startup.ImportHelper import com.netgrif.application.engine.startup.SuperCreator import com.netgrif.application.engine.workflow.domain.Case -import com.netgrif.application.engine.workflow.service.CaseSearchService +import com.netgrif.application.engine.workflow.service.LegacyCaseSearchService import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test @@ -179,10 +179,10 @@ class CaseSearchTest { String buildRequestBody(String fullText) { def map = [ - (CaseSearchService.PETRINET): [ - (CaseSearchService.PETRINET_IDENTIFIER): "case_search_test.xml" + (LegacyCaseSearchService.PETRINET): [ + (LegacyCaseSearchService.PETRINET_IDENTIFIER): "case_search_test.xml" ], - (CaseSearchService.FULLTEXT): fullText + (LegacyCaseSearchService.FULLTEXT): fullText ] ObjectMapper mapper = new ObjectMapper() diff --git a/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy b/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy index e30f8af7f85..89f55920109 100644 --- a/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy @@ -21,7 +21,7 @@ import com.netgrif.application.engine.utils.FullPageRequest import com.netgrif.application.engine.workflow.domain.Case import com.netgrif.application.engine.workflow.domain.DataField import com.netgrif.application.engine.workflow.domain.Task -import com.netgrif.application.engine.workflow.service.TaskSearchService +import com.netgrif.application.engine.workflow.service.LegacyTaskSearchService import com.netgrif.application.engine.workflow.service.TaskService import com.netgrif.application.engine.workflow.service.interfaces.IDataService import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService @@ -50,7 +50,7 @@ class TaskControllerTest { private TaskService taskService @Autowired - private TaskSearchService taskSearchService + private LegacyTaskSearchService taskSearchService @Autowired private IElasticTaskService elasticTaskService diff --git a/src/test/groovy/com/netgrif/application/engine/workflow/TaskPermissionsTest.groovy b/src/test/groovy/com/netgrif/application/engine/workflow/TaskPermissionsTest.groovy new file mode 100644 index 00000000000..9c7980dffa1 --- /dev/null +++ b/src/test/groovy/com/netgrif/application/engine/workflow/TaskPermissionsTest.groovy @@ -0,0 +1,469 @@ +package com.netgrif.application.engine.workflow + +import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.auth.domain.IUser +import com.netgrif.application.engine.auth.domain.User +import com.netgrif.application.engine.auth.domain.UserState +import com.netgrif.application.engine.auth.service.interfaces.IUserService +import com.netgrif.application.engine.elastic.service.interfaces.IElasticTaskService +import com.netgrif.application.engine.elastic.web.requestbodies.ElasticTaskSearchRequest +import com.netgrif.application.engine.petrinet.domain.PetriNet +import com.netgrif.application.engine.petrinet.domain.VersionType +import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.ActionDelegate +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService +import com.netgrif.application.engine.workflow.domain.Case +import com.netgrif.application.engine.workflow.domain.Task +import com.netgrif.application.engine.workflow.service.interfaces.IDataService +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService +import com.netgrif.application.engine.workflow.web.requestbodies.taskSearch.TaskSearchCaseRequest +import groovy.json.JsonOutput +import groovy.json.JsonSlurper +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.context.i18n.LocaleContextHolder +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import org.springframework.hateoas.MediaTypes +import org.springframework.http.MediaType +import org.springframework.mock.web.MockHttpServletRequest +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.Authentication +import org.springframework.security.web.authentication.WebAuthenticationDetails +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.MvcResult +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.web.context.WebApplicationContext + +import java.nio.charset.StandardCharsets +import java.util.stream.Collectors + +import static org.junit.jupiter.api.Assertions.assertFalse +import static org.junit.jupiter.api.Assertions.assertTrue +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post + +@SpringBootTest +@ActiveProfiles(["test"]) +@ExtendWith(SpringExtension.class) +class TaskPermissionsTest { + + @Autowired + private IElasticTaskService elasticTaskService + + @Autowired + private ITaskService taskService + + @Autowired + private IWorkflowService workflowService + + @Autowired + private IPetriNetService petriNetService + + @Autowired + private IUserService userService + + @Autowired + private IDataService dataService + + @Autowired + private ActionDelegate actionDelegate + + @Autowired + private TestHelper testHelper + + @Autowired + private WebApplicationContext wac + + private MockMvc mvc + + private static final String VIEW_TEST_NET = "view_permission_combinations.xml" + private static final String VIEW_TEST_NET_NO_DEFAULT = "view_permission_combinations_no_default.xml" + + private static final String ASSIGN_TEST_NET = "assign_permission_combinations.xml" + private static final String ASSIGN_TEST_NET_NO_DEFAULT = "assign_permission_combinations_no_default.xml" + + private static final String FINISH_TEST_NET = "finish_permission_combinations.xml" + private static final String FINISH_TEST_NET_NO_DEFAULT = "finish_permission_combinations_no_default.xml" + + private static final String CANCEL_TEST_NET = "cancel_permission_combinations.xml" + private static final String CANCEL_TEST_NET_NO_DEFAULT = "cancel_permission_combinations_no_default.xml" + + private static final String CORRECT_PERMISSIONS_CSV_FILEPATH = "src/test/resources/csv/permissions - correct.csv" + private static final String CORRECT_PERMISSIONS_DEFAULT_DISABLED_CSV_FILEPATH = "src/test/resources/csv/permissions - correct default disabled.csv" + + private static Map testUsers = [:] + private static List withRoles = [] + private static List inUserRef = [] + private static Map>> correctResults = new HashMap<>() + + private static final Closure TASK_ASSIGN_URL = { id -> "/api/task/assign/$id" as String } + private static final Closure TASK_FINISH_URL = { id -> "/api/task/finish/$id" as String } + private static final Closure TASK_CANCEL_URL = { id -> "/api/task/cancel/$id" as String } + private static final String TASK_SEARCH_URL = "/api/task/search?sort=priority" + private static final String ELASTIC_TASK_SEARCH_URL = "/api/task/search_es?sort=priority" + + @BeforeEach() + void init() { + testHelper.truncateDbs() + actionDelegate.outcomes = [] + testUsers.clear() + correctResults.clear() + mvc = MockMvcBuilders + .webAppContextSetup(wac) + .apply(springSecurity()) + .build() + + [ + new User("no_permissions@mail.com", "password", "No", "Permissions"), + new User("has_role@mail.com", "password", "Has", "Role"), + new User("in_userRef@mail.com", "password", "In", "UserRef"), + new User("both_permissions@mail.com", "password", "Both", "Permissions") + ].each { + it.setState(UserState.ACTIVE) + testUsers.put(it.getEmail(), userService.saveNew(it)) + } + + withRoles = [testUsers.get("has_role@mail.com"), testUsers.get("both_permissions@mail.com")] + inUserRef = [testUsers.get("in_userRef@mail.com").stringId, testUsers.get("both_permissions@mail.com").stringId] + + correctResults = permissionsCsvToExpectedMap() + } + + @Test + void testViewPermissions() { + Case testCase = prepareTestCase("src/test/resources/petriNets/permissions/" + VIEW_TEST_NET, "t_001") + Case testCaseNoDefault = prepareTestCase("src/test/resources/petriNets/permissions/" + VIEW_TEST_NET_NO_DEFAULT, "t_007") + + Thread.sleep(1000) + + def mapElastic = [:] + def mapMongo = [:] + ElasticTaskSearchRequest request = new ElasticTaskSearchRequest() + request.useCase = [new TaskSearchCaseRequest(testCase.stringId, testCase.title)] + + ElasticTaskSearchRequest request2 = new ElasticTaskSearchRequest() + request2.useCase = [new TaskSearchCaseRequest(testCaseNoDefault.stringId, testCaseNoDefault.title)] + + testUsers.forEach((key, value) -> { +// Elastic task search + Page tasks = elasticTaskService.search([request], + value.transformToLoggedUser(), + Pageable.unpaged(), LocaleContextHolder.getLocale(), false) + List list = new ArrayList<>(tasks.content).stream().map(task -> task.transitionId).collect(Collectors.toList()).sort() + + + Page tasks2 = elasticTaskService.search([request2], + value.transformToLoggedUser(), + Pageable.unpaged(), LocaleContextHolder.getLocale(), false) + List list2 = new ArrayList<>(tasks2.content).stream().map(task -> task.transitionId).collect(Collectors.toList()).sort() + +// Mongo task search + Page mongoTasksDefault = taskService.search([request], Pageable.unpaged(), value.transformToLoggedUser(), LocaleContextHolder.getLocale(), false) + List mongoListDefault = new ArrayList<>(mongoTasksDefault.content).stream().map(task -> task.transitionId).collect(Collectors.toList()).sort() + Page mongoTasksNoDefault = taskService.search([request2], Pageable.unpaged(), value.transformToLoggedUser(), LocaleContextHolder.getLocale(), false) + List mongoListNoDefault = new ArrayList<>(mongoTasksNoDefault.content).stream().map(task -> task.transitionId).collect(Collectors.toList()).sort() + + mapElastic.put(key, [ + "Default enabled" : list, + "Default DISabled": list2 + ]) + + mapMongo.put(key, [ + "Default enabled" : mongoListDefault, + "Default DISabled": mongoListNoDefault + ]) + }) + compareTestResultsToExpected(mapElastic, "Elastic search", "View") + compareTestResultsToExpected(mapMongo, "Mongo search", "View") + } + + @Test + void testViewPermissionsMVC() { + Case testCase = prepareTestCase("src/test/resources/petriNets/permissions/" + VIEW_TEST_NET, "t_001") + Case testCaseNoDefault = prepareTestCase("src/test/resources/petriNets/permissions/" + VIEW_TEST_NET_NO_DEFAULT, "t_007") + + Thread.sleep(1000) + + def mapElastic = [:] + def mapMongo = [:] + + def content1 = JsonOutput.toJson([ + case: [ + id: testCase.stringId + ] + ]) + def content2 = JsonOutput.toJson([ + case: [ + id: testCaseNoDefault.stringId + ] + ]) + + testUsers.forEach((key, value) -> { + Authentication auth = new UsernamePasswordAuthenticationToken(key, "password") + auth.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) +// Elastic task search + mapElastic.put(key, [ + "Default enabled" : performSearch(ELASTIC_TASK_SEARCH_URL, content1, auth), + "Default DISabled": performSearch(ELASTIC_TASK_SEARCH_URL, content2, auth) + ]) + +// Mongo task search + mapMongo.put(key, [ + "Default enabled" : performSearch(TASK_SEARCH_URL, content1, auth), + "Default DISabled": performSearch(TASK_SEARCH_URL, content2, auth) + ]) + }) + compareTestResultsToExpected(mapElastic, "Elastic search MVC", "View") + compareTestResultsToExpected(mapMongo, "Mongo search MVC", "View") + } + + @Test + void testAssignPermission() { + // TODO: NAE-2447 fix + Case testCase = prepareTestCase("src/test/resources/petriNets/permissions/" + ASSIGN_TEST_NET, "t_001") + Case testCaseNoDefault = prepareTestCase("src/test/resources/petriNets/permissions/" + ASSIGN_TEST_NET_NO_DEFAULT, "t_007") + + Thread.sleep(1000) + + def resultMap = [:] + + testUsers.forEach((key, value) -> { + + Authentication auth = new UsernamePasswordAuthenticationToken(key, "password") + auth.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) + + resultMap.put(key, [ + "Default enabled" : performRequest(testCase, TASK_ASSIGN_URL, auth), + "Default DISabled": performRequest(testCaseNoDefault, TASK_ASSIGN_URL, auth) + ]) + }) + compareTestResultsToExpected(resultMap, "MockMvc", "Assign") + } + + @Test + void testFinishPermission() { + // TODO: NAE-2447 fix + Case testCase = prepareTestCase("src/test/resources/petriNets/permissions/" + FINISH_TEST_NET, "t_001") + Case testCaseNoDefault = prepareTestCase("src/test/resources/petriNets/permissions/" + FINISH_TEST_NET_NO_DEFAULT, "t_007") + + Thread.sleep(1000) + + def resultMap = [:] + + testUsers.forEach((key, value) -> { + + Authentication auth = new UsernamePasswordAuthenticationToken(key, "password") + auth.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) + + performRequest(testCase, TASK_ASSIGN_URL, auth) + performRequest(testCaseNoDefault, TASK_ASSIGN_URL, auth) + + resultMap.put(key, [ + "Default enabled" : performRequest(testCase, TASK_FINISH_URL, auth), + "Default DISabled": performRequest(testCaseNoDefault, TASK_FINISH_URL, auth) + ]) + }) + compareTestResultsToExpected(resultMap, "MockMvc", "FINISH") + } + + @Test + void testCancelPermission() { + // TODO: NAE-2447 fix + Case testCase = prepareTestCase("src/test/resources/petriNets/permissions/" + CANCEL_TEST_NET, "t_001") + Case testCaseNoDefault = prepareTestCase("src/test/resources/petriNets/permissions/" + CANCEL_TEST_NET_NO_DEFAULT, "t_007") + + Thread.sleep(1000) + + def resultMap = [:] + + testUsers.forEach((key, value) -> { + + Authentication auth = new UsernamePasswordAuthenticationToken(key, "password") + auth.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) + + performRequest(testCase, TASK_ASSIGN_URL, auth) + performRequest(testCaseNoDefault, TASK_ASSIGN_URL, auth) + + resultMap.put(key, [ + "Default enabled" : performRequest(testCase, TASK_CANCEL_URL, auth), + "Default DISabled": performRequest(testCaseNoDefault, TASK_CANCEL_URL, auth) + ]) + }) + compareTestResultsToExpected(resultMap, "MockMvc", "CANCEL") + } + + List performSearch(String searchUrl, String content, Authentication auth) { + List list = [] + MvcResult result = mvc.perform(post(searchUrl) + .content(content) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .accept(MediaTypes.HAL_JSON_VALUE) + .locale(LocaleContextHolder.getLocale()) + .with(csrf().asHeader()) + .with(authentication(auth))) + .andReturn() + if (result.getResponse().getStatus() == 200) { + def parsedResult = parseResult(result)._embedded + parsedResult == null ?: list.addAll(parsedResult.tasks?.collect { it.transitionId }.sort()) + } + return list + } + + List performRequest(Case testCase, Closure request, Authentication auth) { + List resultTransitions = [] + testCase.tasks.forEach((taskPair) -> { + MvcResult result = mvc.perform(get(request(taskPair.getTask())) + .accept(MediaTypes.HAL_JSON_VALUE) + .locale(LocaleContextHolder.getLocale()) + .with(csrf().asHeader()) + .with(authentication(auth))) + .andReturn() + if (result.getResponse().getStatus() != 200) { + return + } + resultTransitions.add(taskPair.getTransition()) + }) + return resultTransitions.sort() + } + + Case prepareTestCase(String netFilePath, String transId) { + PetriNet net = petriNetService.importPetriNet(new FileInputStream(netFilePath), VersionType.MAJOR, userService.getLoggedOrSystem().transformToLoggedUser()).getNet() + Case testCase = workflowService.createCaseByIdentifier(net.identifier, "Test case", "", userService.getLoggedOrSystem().transformToLoggedUser()).getCase() + withRoles.forEach { + testUsers.put(it.getEmail(), userService.addRole(testUsers.get(it.getEmail()), net.roles.values().find { role -> role.importId == "process_role" }.stringId)) + } + testCase = actionDelegate.setData(transId, testCase, [ + "users": [ + "type" : "userList", + "value": inUserRef + ] + ]).getCase() + return testCase + } + + static Map>> permissionsCsvToExpectedMap() { + Map>> expectedMap = initializeExpectedPermissionsMap() + + addPermissionsCsvToExpectedMap( + CORRECT_PERMISSIONS_CSV_FILEPATH, + "Default enabled", + expectedMap + ) + + addPermissionsCsvToExpectedMap( + CORRECT_PERMISSIONS_DEFAULT_DISABLED_CSV_FILEPATH, + "Default DISabled", + expectedMap + ) + + return expectedMap + } + + static void addPermissionsCsvToExpectedMap(String csvFilePath, + String defaultRoleKey, + Map>> expectedMap) { + File csvFile = new File(csvFilePath) + Map userColumns = userColumns() + + List lines = csvFile.readLines("UTF-8").findAll { it?.trim() } + assertFalse(lines.isEmpty(), "CSV file is empty: ${csvFilePath}") + List header = lines.first().split(",", -1)*.trim() + + int transitionIdIndex = header.indexOf("Transition ID") + assertTrue(transitionIdIndex >= 0, "Missing required column 'Transition ID' in ${csvFilePath}") + + lines.tail().each { line -> + List columns = line.split(",", -1)*.trim() + String transitionId = columns[transitionIdIndex] + + userColumns.each { csvColumnName, userEmail -> + int permissionIndex = header.indexOf(csvColumnName) + assertTrue(permissionIndex >= 0, "Missing required column '${csvColumnName}' in ${csvFilePath}") + + String permissionValue = columns[permissionIndex] + .replace(".", "") + .trim() + .toUpperCase() + + if (permissionValue == "TRUE") { + expectedMap[userEmail][defaultRoleKey] << transitionId + } + } + } + + expectedMap.values().each { Map> permissionsByDefaultRole -> + permissionsByDefaultRole[defaultRoleKey].sort() + } + } + + static Map>> initializeExpectedPermissionsMap() { + return userColumns().values().collectEntries { String email -> + [ + email, + [ + "Default enabled" : [], + "Default DISabled": [] + ] + ] + } + } + + static Map userColumns() { + return [ + "No permissions" : "no_permissions@mail.com", + "Has role" : "has_role@mail.com", + "Is in userList" : "in_userRef@mail.com", + "Has role and is in userList": "both_permissions@mail.com" + ] + } + + static void compareTestResultsToExpected(Map>> testResultMap, String searchType, String permissionType) { + println("\n========== ${searchType} - ${permissionType} permissions comparison ==========") + + testUsers.keySet().each { String userEmail -> + println("\nUser: ${userEmail}") + + ["Default enabled", "Default DISabled"].each { String defaultRoleKey -> + Set actualTransitionIds = new TreeSet<>( + testResultMap.get(userEmail)?.get(defaultRoleKey) ?: [] + ) + + Set expectedTransitionIds = new TreeSet<>( + correctResults.get(userEmail)?.get(defaultRoleKey) ?: [] + ) + + Set presentInBoth = new TreeSet<>(actualTransitionIds) + presentInBoth.retainAll(expectedTransitionIds) + + Set presentOnlyInMap = new TreeSet<>(actualTransitionIds) + presentOnlyInMap.removeAll(expectedTransitionIds) + + Set presentOnlyInCorrectResultsWithDefaultRoleMap = new TreeSet<>(expectedTransitionIds) + presentOnlyInCorrectResultsWithDefaultRoleMap.removeAll(actualTransitionIds) + + println("\n${searchType} - ${defaultRoleKey}:") + println("Present in both results (${presentInBoth.size()}): ${presentInBoth}") + println("Present only in test results (${presentOnlyInMap.size()}): ${presentOnlyInMap}") + println("Present only in correct results (${presentOnlyInCorrectResultsWithDefaultRoleMap.size()}): ${presentOnlyInCorrectResultsWithDefaultRoleMap}") + + assertTrue(presentInBoth.size() == actualTransitionIds.size() && presentInBoth.size() == expectedTransitionIds.size()) + } + } + + println("\n=================================================") + } + + @SuppressWarnings("GrMethodMayBeStatic") + private def parseResult(MvcResult result) { + return (new JsonSlurper()).parseText(result.response.getContentAsString(StandardCharsets.UTF_8)) + } +} \ No newline at end of file diff --git a/src/test/java/com/netgrif/application/engine/MockService.java b/src/test/java/com/netgrif/application/engine/MockService.java index 524cb597c38..c20b33fd769 100644 --- a/src/test/java/com/netgrif/application/engine/MockService.java +++ b/src/test/java/com/netgrif/application/engine/MockService.java @@ -3,12 +3,14 @@ import com.netgrif.application.engine.auth.domain.Authority; import com.netgrif.application.engine.auth.domain.LoggedUser; import com.netgrif.application.engine.auth.service.interfaces.IAuthorityService; +import com.netgrif.application.engine.petrinet.service.interfaces.IProcessRoleService; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import java.util.Collections; +import java.util.Set; @Component @Profile("test") @@ -17,8 +19,13 @@ public class MockService { @Autowired private IAuthorityService authorityService; + @Autowired + private IProcessRoleService processRoleService; + public LoggedUser mockLoggedUser() { Authority authorityUser = authorityService.getOrCreate(Authority.user); - return new LoggedUser(new ObjectId().toString(), "super@netgrif.com", "password", Collections.singleton(authorityUser)); + LoggedUser loggedUser = new LoggedUser(new ObjectId().toString(), "super@netgrif.com", "password", Collections.singleton(authorityUser)); + loggedUser.setProcessRoles(Set.of(processRoleService.defaultRole().getStringId())); + return loggedUser; } } diff --git a/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java new file mode 100644 index 00000000000..a6872e39c75 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/menu/MenuItemServiceTest.java @@ -0,0 +1,767 @@ +package com.netgrif.application.engine.menu; + +import com.mongodb.client.MongoCollection; +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.service.interfaces.IUserService; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; +import com.netgrif.application.engine.menu.domain.*; +import com.netgrif.application.engine.menu.domain.configurations.*; +import com.netgrif.application.engine.menu.domain.templates.CustomViewTemplate; +import com.netgrif.application.engine.menu.domain.templates.SimpleTaskViewTemplate; +import com.netgrif.application.engine.menu.domain.templates.TabbedCaseViewTemplate; +import com.netgrif.application.engine.menu.domain.templates.Template; +import com.netgrif.application.engine.menu.service.MenuItemService; +import com.netgrif.application.engine.menu.service.MenuItemTemplateHolder; +import com.netgrif.application.engine.menu.utils.MenuItemUtils; +import com.netgrif.application.engine.petrinet.domain.DataGroup; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.UriNode; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import com.netgrif.application.engine.petrinet.domain.throwable.TransitionNotExecutableException; +import com.netgrif.application.engine.petrinet.service.interfaces.IUriService; +import com.netgrif.application.engine.startup.ImportHelper; +import com.netgrif.application.engine.startup.SuperCreator; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.DataField; +import com.netgrif.application.engine.workflow.domain.TaskPair; +import com.netgrif.application.engine.workflow.domain.repositories.CaseRepository; +import com.netgrif.application.engine.workflow.service.interfaces.IDataService; +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class MenuItemServiceTest { + + @Autowired + private TestHelper testHelper; + + @Autowired + private MenuItemService menuItemService; + + @Autowired + private SuperCreator superCreator; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private CaseRepository caseRepository; + + @Autowired + private IUriService uriService; + + @Autowired + private IWorkflowService workflowService; + + @Autowired + private IUserService userService; + + @Autowired + private IDataService dataService; + + @Autowired + private ITaskService taskService; + + @BeforeEach + public void beforeEach() { + testHelper.truncateDbs(); + } + + @Test + public void ensureDatabaseIndexesTest() { + mongoTemplate.getDb().drop(); + + MongoCollection collection = mongoTemplate.getDb().getCollection("case", Case.class); + long indexCountBefore = collection.listIndexes().into(new java.util.ArrayList<>()).size(); + assertEquals(0, indexCountBefore, "Expected no indexes after dropping database"); + + menuItemService.ensureDatabaseIndexes(); + + long indexCountAfter = collection.listIndexes().into(new java.util.ArrayList<>()).size(); + assertTrue(indexCountAfter > indexCountBefore, "Expected indexes to be created after calling ensureDatabaseIndexes"); + } + + @Test + public void createMenuItemTest() throws TransitionNotExecutableException { + assertThrows(IllegalArgumentException.class, () -> menuItemService.createMenuItem(null)); + MenuItemBody emptyBody = new MenuItemBody(); + assertThrows(IllegalArgumentException.class, () -> menuItemService.createMenuItem(emptyBody)); + emptyBody.setUri("/"); + assertThrows(IllegalArgumentException.class, () -> menuItemService.createMenuItem(emptyBody)); + emptyBody.setIdentifier("xxx"); + assertDoesNotThrow(() -> menuItemService.createMenuItem(emptyBody)); + emptyBody.setUri("/xxx2"); + emptyBody.setIdentifier("xxx3"); + assertDoesNotThrow(() -> menuItemService.createMenuItem(emptyBody)); + emptyBody.setUri("/xxx4"); + emptyBody.setIdentifier("xxx4"); + assertThrows(IllegalArgumentException.class, () -> menuItemService.createMenuItem(emptyBody)); + + Map templateOptions = MenuItemTemplateHolder.transformToOptions(); + templateOptions.keySet().forEach((templateIdentifier) -> { + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(templateIdentifier, + uriService.getRoot().getUriPath(), templateIdentifier); + assertTrue(menuItemBodyOpt.isPresent()); + try { + createByTemplateAndAssert(menuItemBodyOpt.get()); + } catch (TransitionNotExecutableException e) { + throw new RuntimeException(e); + } + }); + + createAndAssertDetailed(); + } + + @SuppressWarnings("unchecked") + private void createByTemplateAndAssert(MenuItemBody menuItemBody) throws TransitionNotExecutableException { + caseRepository.deleteAll(); + Case menuItemCase = menuItemService.createMenuItem(menuItemBody); + + UriNode uriNode = uriService.findByUri(menuItemBody.getUri()); + assertEquals(uriNode.getStringId(), menuItemCase.getUriNodeId()); + + assertEquals(menuItemBody.getIdentifier(), menuItemCase.getFieldValue(MenuItemConstants.FIELD_IDENTIFIER)); + I18nString actualMenuName = (I18nString) menuItemCase.getFieldValue(MenuItemConstants.FIELD_MENU_NAME); + assertTrue(actualMenuName.equals(menuItemBody.getMenuName()) || actualMenuName.equals(new I18nString(menuItemBody.getIdentifier()))); + assertEquals(menuItemBody.isUseTabbedView(), menuItemCase.getFieldValue(MenuItemConstants.FIELD_USE_TABBED_VIEW)); + assertEquals(menuItemBody.isUseCustomView(), menuItemCase.getFieldValue(MenuItemConstants.FIELD_USE_CUSTOM_VIEW)); + assertEquals(menuItemBody.getConfigurationTemplateIdentifier(), menuItemCase.getFieldValue(MenuItemConstants.FIELD_CONFIGURATION_TEMPLATES)); + assertEquals((menuItemBody.getUri() + uriService.getUriSeparator() + menuItemBody.getIdentifier()).replaceAll("//", uriService.getUriSeparator()), + menuItemCase.getFieldValue(MenuItemConstants.FIELD_NODE_PATH)); + + if (menuItemBody.getView() == null) { + assertEquals(1 + 1, caseRepository.count()); + + List viewConfigurationIdValue = (List) menuItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID); + assertTrue(viewConfigurationIdValue == null || viewConfigurationIdValue.isEmpty()); + + List viewConfigurationFormValue = (List) menuItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_FORM); + assertTrue(viewConfigurationFormValue == null || viewConfigurationFormValue.isEmpty()); + + List viewConfigurationAllDataFormValue = (List) menuItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM); + assertTrue(viewConfigurationAllDataFormValue == null || viewConfigurationAllDataFormValue.isEmpty()); + } else { + List viewConfigurationIdValue = (List) menuItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID); + assertEquals(1, viewConfigurationIdValue.size()); + Case viewCase = workflowService.findOne(viewConfigurationIdValue.get(0)); + + List viewConfigurationFormValue = (List) menuItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_FORM); + assertEquals(1, viewConfigurationFormValue.size()); + assertTrue(viewConfigurationFormValue.contains(getTaskId(viewCase, ViewConstants.TRANS_SETTINGS_ID))); + + List viewConfigurationAllDataFormValue = (List) menuItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM); + assertEquals(1, viewConfigurationAllDataFormValue.size()); + assertTrue(viewConfigurationAllDataFormValue.contains(getTaskId(viewCase, ViewConstants.TRANS_ALL_MENU_DATA_ID))); + + if (!menuItemBody.getView().hasAssociatedView()) { + assertEquals(2 + 1, caseRepository.count()); + } else { + assertEquals(3 + 1, caseRepository.count()); + + List nextViewConfigurationIdValue = (List) viewCase.getFieldValue(ViewConstants.FIELD_VIEW_CONFIGURATION_ID); + assertEquals(1, nextViewConfigurationIdValue.size()); + Case nextViewCase = workflowService.findOne(nextViewConfigurationIdValue.get(0)); + + List nextViewConfigurationFormValue = (List) viewCase.getFieldValue(ViewConstants.FIELD_VIEW_CONFIGURATION_FORM); + assertEquals(1, nextViewConfigurationFormValue.size()); + assertTrue(nextViewConfigurationFormValue.contains(getTaskId(nextViewCase, ViewConstants.TRANS_SETTINGS_ID))); + + List nextViewConfigurationAllDataFormValue = (List) viewCase.getFieldValue(ViewConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM); + assertEquals(1, nextViewConfigurationAllDataFormValue.size()); + assertTrue(nextViewConfigurationAllDataFormValue.contains(getTaskId(nextViewCase, ViewConstants.TRANS_ALL_MENU_DATA_ID))); + } + } + } + + @SuppressWarnings("unchecked") + private void createAndAssertDetailed() throws TransitionNotExecutableException { + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER); + assertTrue(menuItemBodyOpt.isPresent()); + MenuItemBody menuItemBody = menuItemBodyOpt.get(); + menuItemBody.setUri("/netgrif/test"); + menuItemBody.setIdentifier("new_menu_item"); + menuItemBody.setMenuIcon("device_hub"); + assertNotNull(menuItemBody.getView()); + assertNotNull(menuItemBody.getView().getFilterBody()); + menuItemBody.getView().getFilterBody().setQuery("processIdentifier:menu_item"); + menuItemBody.getView().getFilterBody().setType(FieldType.CASE_FILTER); + ((CaseViewBody) menuItemBody.getView()).setDefaultHeaders(List.of("meta-title", "meta-processIdentifier")); + assertNotNull(menuItemBody.getView().getAssociatedViewBody()); + ((TaskViewBody) menuItemBody.getView().getAssociatedViewBody()).setDefaultHeaders(List.of("meta-title")); + menuItemBody.setAllowedRoles(menuItemService.collectRoles(Map.of("admin", "menu_item"))); + menuItemBody.setBannedRoles(menuItemService.collectRoles(Map.of("system", "menu_item"))); + + Case menuItemCase = menuItemService.createMenuItem(menuItemBody); + + // MENU ITEM + UriNode leafNode = uriService.findByUri("/netgrif/test/new_menu_item"); + assertNotNull(leafNode); + assertEquals(menuItemCase.getUriNodeId(), uriService.findByUri("/netgrif/test").getStringId()); + assertEquals("device_hub", menuItemCase.getFieldValue(MenuItemConstants.FIELD_MENU_ICON)); + I18nString menuName = (I18nString) menuItemCase.getFieldValue(MenuItemConstants.FIELD_MENU_NAME); + assertEquals(new I18nString("new_menu_item"), menuName); + assertEquals("new_menu_item", menuItemCase.getFieldValue(MenuItemConstants.FIELD_IDENTIFIER).toString()); + + Map bannedRolesOptions = menuItemCase.getDataSet().get(MenuItemConstants.FIELD_BANNED_ROLES).getOptions(); + assertTrue(bannedRolesOptions.containsKey("system:menu_item")); + Map allowedRolesOptions = menuItemCase.getDataSet().get(MenuItemConstants.FIELD_ALLOWED_ROLES).getOptions(); + assertTrue(allowedRolesOptions.containsKey("admin:menu_item")); + + // CASE VIEW + assertEquals(true, menuItemCase.getFieldValue(MenuItemConstants.FIELD_USE_TABBED_VIEW)); + assertEquals(MenuItemViewType.CASE_VIEW.getIdentifier(), menuItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE)); + + String caseViewId = MenuItemUtils.getCaseIdFromCaseRef(menuItemCase, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID); + assertNotNull(caseViewId); + Case caseView = workflowService.findOne(caseViewId); + DataField filterDataField = caseView.getDataField(CaseViewConstants.FIELD_FILTER); + assertEquals("processIdentifier:menu_item", filterDataField.getValue()); + assertEquals(true, caseView.getFieldValue(CaseViewConstants.FIELD_ALL_ALLOWED_NETS)); + assertEquals(true, caseView.getFieldValue(CaseViewConstants.FIELD_INHERIT_ALLOWED_NETS)); + + List caseDefaultHeaders = (List) caseView.getFieldValue(CaseViewConstants.FIELD_DEFAULT_HEADERS); + assertEquals(2, caseDefaultHeaders.size()); + assertTrue(caseDefaultHeaders.containsAll(List.of("meta-title", "meta-processIdentifier"))); + assertEquals(MenuItemViewType.TASK_VIEW.getIdentifier(), caseView.getFieldValue(CaseViewConstants.FIELD_CONFIGURATION_TYPE)); + + // TASK VIEW + String taskViewId = MenuItemUtils.getCaseIdFromCaseRef(caseView, CaseViewConstants.FIELD_VIEW_CONFIGURATION_ID); + assertNotNull(taskViewId); + Case taskView = workflowService.findOne(taskViewId); + List taskDefaultHeaders = (List) taskView.getFieldValue(TaskViewConstants.FIELD_DEFAULT_HEADERS); + assertEquals(1, taskDefaultHeaders.size()); + assertTrue(taskDefaultHeaders.contains("meta-title")); + + // FOLDERS + Case testFolder = findMenuItem("test"); + Case netgrifFolder = findMenuItem("netgrif"); + + UriNode testNode = uriService.findByUri("/netgrif"); + UriNode netgrifNode = uriService.getRoot(); + + Case rootFolder = findMenuItem(""); + + assertNotNull(testFolder); + assertNotNull(testNode); + assertEquals(testNode.getStringId(), testFolder.getUriNodeId()); + + String testFolderParentId = MenuItemUtils.getCaseIdFromCaseRef(testFolder, MenuItemConstants.FIELD_PARENT_ID); + assertEquals(netgrifFolder.getStringId(), testFolderParentId); + + List testFolderChildIds = MenuItemUtils.getCaseIdsFromCaseRef(testFolder, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + assertNotNull(testFolderChildIds); + assertTrue(testFolderChildIds.contains(menuItemCase.getStringId())); + + String itemParentId = MenuItemUtils.getCaseIdFromCaseRef(menuItemCase, MenuItemConstants.FIELD_PARENT_ID); + assertEquals(testFolder.getStringId(), itemParentId); + + assertEquals(netgrifNode.getStringId(), netgrifFolder.getUriNodeId()); + + String netgrifFolderParentId = MenuItemUtils.getCaseIdFromCaseRef(netgrifFolder, MenuItemConstants.FIELD_PARENT_ID); + assertEquals(rootFolder.getStringId(), netgrifFolderParentId); + + List netgrifFolderChildIds = MenuItemUtils.getCaseIdsFromCaseRef(netgrifFolder, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + assertNotNull(netgrifFolderChildIds); + assertTrue(netgrifFolderChildIds.contains(testFolder.getStringId())); + + String rootFolderParentId = MenuItemUtils.getCaseIdFromCaseRef(rootFolder, MenuItemConstants.FIELD_PARENT_ID); + assertNull(rootFolderParentId); + + List rootFolderChildIds = MenuItemUtils.getCaseIdsFromCaseRef(rootFolder, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + assertNotNull(rootFolderChildIds); + assertTrue(rootFolderChildIds.contains(netgrifFolder.getStringId())); + } + + @Test + public void updateMenuItemTest() throws TransitionNotExecutableException { + assertThrows(IllegalArgumentException.class, () -> menuItemService.updateMenuItem(null, null)); + + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(SimpleTaskViewTemplate.IDENTIFIER); + assertTrue(menuItemBodyOpt.isPresent()); + MenuItemBody menuItemBody = menuItemBodyOpt.get(); + menuItemBody.setUri(uriService.getRoot().getUriPath()); + menuItemBody.setIdentifier("test"); + Case menuItemCase = menuItemService.createMenuItem(menuItemBody); + + String oldMenuItemCaseId = menuItemCase.getStringId(); + String oldViewCaseId = MenuItemUtils.getCaseIdFromCaseRef(menuItemCase, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID); + Case simpleTaskViewCase = workflowService.findOne(oldViewCaseId); + assertEquals("task_view_configuration", simpleTaskViewCase.getProcessIdentifier()); + assertNull(MenuItemUtils.getCaseIdFromCaseRef(simpleTaskViewCase, ViewConstants.FIELD_VIEW_CONFIGURATION_ID)); + + assertThrows(IllegalArgumentException.class, () -> menuItemService.updateMenuItem(menuItemCase, null)); + assertThrows(IllegalArgumentException.class, () -> menuItemService.updateMenuItem(menuItemCase, new MenuItemBody())); + + menuItemBodyOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER, uriService.getRoot().getUriPath(), + "test"); + assertTrue(menuItemBodyOpt.isPresent()); + + Case updatedMenuItemCase = menuItemService.updateMenuItem(menuItemCase, menuItemBodyOpt.get()); + + assertThrows(IllegalArgumentException.class, () -> workflowService.findOne(oldMenuItemCaseId)); + assertThrows(IllegalArgumentException.class, () -> workflowService.findOne(oldViewCaseId)); + + String newMenuItemCaseId = updatedMenuItemCase.getStringId(); + assertNotEquals(oldMenuItemCaseId, newMenuItemCaseId); + String newViewCaseId = MenuItemUtils.getCaseIdFromCaseRef(updatedMenuItemCase, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID); + assertNotEquals(oldViewCaseId, newViewCaseId); + Case caseViewCase = workflowService.findOne(newViewCaseId); + assertEquals("case_view_configuration", caseViewCase.getProcessIdentifier()); + String taskViewCaseId = MenuItemUtils.getCaseIdFromCaseRef(caseViewCase, ViewConstants.FIELD_VIEW_CONFIGURATION_ID); + assertNotNull(taskViewCaseId); + Case taskViewCase = workflowService.findOne(taskViewCaseId); + assertEquals("task_view_configuration", taskViewCase.getProcessIdentifier()); + } + + @Test + public void createOrUpdateMenuItemTest() throws TransitionNotExecutableException { + assertThrows(IllegalArgumentException.class, () -> menuItemService.createOrUpdateMenuItem(null)); + + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER, + uriService.getRoot().getUriPath(), "test"); + assertTrue(menuItemBodyOpt.isPresent()); + + Case firstMenuItemCase = menuItemService.createOrUpdateMenuItem(menuItemBodyOpt.get()); + Case secondMenuItemCase = menuItemService.createOrUpdateMenuItem(menuItemBodyOpt.get()); + + assertNotEquals(firstMenuItemCase.getStringId(), secondMenuItemCase.getStringId()); + assertThrows(IllegalArgumentException.class, () -> workflowService.findOne(firstMenuItemCase.getStringId())); + } + + @Test + public void createOrIgnoreMenuItemTest() throws TransitionNotExecutableException { + assertThrows(IllegalArgumentException.class, () -> menuItemService.createOrIgnoreMenuItem(null)); + + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER, + uriService.getRoot().getUriPath(), "test"); + assertTrue(menuItemBodyOpt.isPresent()); + + Case firstMenuItemCase = menuItemService.createOrIgnoreMenuItem(menuItemBodyOpt.get()); + Case secondMenuItemCase = menuItemService.createOrIgnoreMenuItem(menuItemBodyOpt.get()); + + assertEquals(firstMenuItemCase.getStringId(), secondMenuItemCase.getStringId()); + } + + @Test + public void findMenuItemByIdentifierTest() throws TransitionNotExecutableException { + assertNull(menuItemService.findMenuItem(null)); + assertNull(menuItemService.findMenuItem("wrong")); + + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER); + assertTrue(menuItemBodyOpt.isPresent()); + MenuItemBody menuItemBody = menuItemBodyOpt.get(); + menuItemBody.setUri(uriService.getRoot().getUriPath()); + String identifier = "test"; + menuItemBody.setIdentifier(identifier); + + Case createdMenuItemCase = menuItemService.createMenuItem(menuItemBody); + Case foundMenuItemCase = menuItemService.findMenuItem(identifier); + assertEquals(createdMenuItemCase.getStringId(), foundMenuItemCase.getStringId()); + } + + @Test + public void findFolderCaseTest() throws TransitionNotExecutableException { + assertThrows(IllegalArgumentException.class, () -> menuItemService.findFolderCase(null)); + assertNull(menuItemService.findFolderCase(new UriNode())); + + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER, "/folderik", "test"); + assertTrue(menuItemBodyOpt.isPresent()); + + Case createdMenuItemCase = menuItemService.createMenuItem(menuItemBodyOpt.get()); + String parentFolderCaseId = MenuItemUtils.getCaseIdFromCaseRef(createdMenuItemCase, MenuItemConstants.FIELD_PARENT_ID); + assertNotNull(parentFolderCaseId); + UriNode node = uriService.findById(createdMenuItemCase.getUriNodeId()); + Case folderCase = menuItemService.findFolderCase(node); + assertEquals(parentFolderCaseId, folderCase.getStringId()); + } + + @Test + public void existsMenuItemTest() throws TransitionNotExecutableException { + assertFalse(menuItemService.existsMenuItem(null)); + assertFalse(menuItemService.existsMenuItem("wrong")); + + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER); + assertTrue(menuItemBodyOpt.isPresent()); + MenuItemBody menuItemBody = menuItemBodyOpt.get(); + menuItemBody.setUri(uriService.getRoot().getUriPath()); + String identifier = "test"; + menuItemBody.setIdentifier(identifier); + + menuItemService.createMenuItem(menuItemBody); + assertTrue(menuItemService.existsMenuItem(identifier)); + } + + @Test + public void moveItemTest() throws TransitionNotExecutableException { + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER); + assertTrue(menuItemBodyOpt.isPresent()); + MenuItemBody menuItemBody = menuItemBodyOpt.get(); + menuItemBody.setUri("/netgrif/test"); + menuItemBody.setIdentifier("new_menu_item"); + Case newMenuItemCase = menuItemService.createMenuItem(menuItemBody); + menuItemBody.setUri("/netgrif2/test2"); + menuItemBody.setIdentifier("new_menu_item2"); + Case newMenuItem2Case = menuItemService.createMenuItem(menuItemBody); + + assertThrows(IllegalArgumentException.class, () -> menuItemService.moveItem(null, "/mypath")); + Case finalNewMenuItemCase = newMenuItemCase; + assertThrows(IllegalArgumentException.class, () -> menuItemService.moveItem(finalNewMenuItemCase, null)); + + menuItemService.moveItem(newMenuItemCase, "/netgrif2"); + + newMenuItemCase = workflowService.findOne(newMenuItemCase.getStringId()); + UriNode netgrif2Node = uriService.findByUri("/netgrif2"); + assertEquals(netgrif2Node.getStringId(), newMenuItemCase.getUriNodeId()); + Case netgrif2FolderCase = findMenuItem("netgrif2"); + List netgrif2ChildIds = MenuItemUtils.getCaseIdsFromCaseRef(netgrif2FolderCase, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + assertNotNull(netgrif2ChildIds); + assertEquals(2, netgrif2ChildIds.size()); + assertTrue(netgrif2ChildIds.contains(newMenuItemCase.getStringId())); + + Case finalNetgrif2FolderCase = netgrif2FolderCase; + assertThrows(IllegalArgumentException.class, () -> menuItemService.moveItem(finalNetgrif2FolderCase, "/netgrif2/cyclic")); + + menuItemService.moveItem(netgrif2FolderCase, "/netgrif/test3"); + Case test3FolderCase = findMenuItem("test3"); + Case netgrifFolderCase = findMenuItem("netgrif"); + + String test3ParentCaseId = MenuItemUtils.getCaseIdFromCaseRef(test3FolderCase, MenuItemConstants.FIELD_PARENT_ID); + assertNotNull(test3ParentCaseId); + assertEquals(test3ParentCaseId, netgrifFolderCase.getStringId()); + + List netgrifFolderChildIds = MenuItemUtils.getCaseIdsFromCaseRef(netgrifFolderCase, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + assertNotNull(netgrifFolderChildIds); + assertTrue(netgrifFolderChildIds.contains(test3FolderCase.getStringId())); + + netgrif2FolderCase = workflowService.findOne(netgrif2FolderCase.getStringId()); + UriNode test3Node = uriService.findByUri("/netgrif/test3"); + assertEquals(test3Node.getStringId(), netgrif2FolderCase.getUriNodeId()); + assertEquals("/netgrif/test3/netgrif2", netgrif2FolderCase.getFieldValue(MenuItemConstants.FIELD_NODE_PATH)); + netgrif2ChildIds = MenuItemUtils.getCaseIdsFromCaseRef(netgrif2FolderCase, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + assertNotNull(netgrif2ChildIds); + assertEquals(2, netgrif2ChildIds.size()); + + Case test2FolderCase = findMenuItem("test2"); + netgrif2Node = uriService.findByUri("/netgrif/test3/netgrif2"); + assertEquals(netgrif2Node.getStringId(), test2FolderCase.getUriNodeId()); + assertEquals("/netgrif/test3/netgrif2/test2", test2FolderCase.getFieldValue(MenuItemConstants.FIELD_NODE_PATH)); + + newMenuItem2Case = workflowService.findOne(newMenuItem2Case.getStringId()); + UriNode test2Node = uriService.findByUri("/netgrif/test3/netgrif2/test2"); + assertEquals(test2Node.getStringId(), newMenuItem2Case.getUriNodeId()); + } + + @Test + public void duplicateFolderItemTest() throws TransitionNotExecutableException { + String starterUri = "/netgrif/test"; + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER); + assertTrue(menuItemBodyOpt.isPresent()); + MenuItemBody menuItemBody = menuItemBodyOpt.get(); + menuItemBody.setUri(starterUri); + menuItemBody.setIdentifier("new_menu_item"); + menuItemService.createMenuItem(menuItemBody); + Case originFolderCase = findMenuItem("test"); + + String newTitle = "New title"; + String newIdentifier = "new_identifier"; + + String duplicateTaskId = MenuItemUtils.findTaskIdInCase(originFolderCase, "duplicate_item"); + taskService.assignTask(duplicateTaskId); + + originFolderCase.getDataField(MenuItemConstants.FIELD_DUPLICATE_TITLE).setValue(new I18nString("")); + originFolderCase.getDataField(MenuItemConstants.FIELD_DUPLICATE_IDENTIFIER).setValue(newIdentifier); + originFolderCase = workflowService.save(originFolderCase); + assertThrows(IllegalArgumentException.class, () -> taskService.finishTask(duplicateTaskId)); + + originFolderCase.getDataField(MenuItemConstants.FIELD_DUPLICATE_TITLE).setValue(new I18nString(newTitle)); + originFolderCase.getDataField(MenuItemConstants.FIELD_DUPLICATE_IDENTIFIER).setValue("new_menu_item"); + originFolderCase = workflowService.save(originFolderCase); + assertThrows(IllegalArgumentException.class, () -> taskService.finishTask(duplicateTaskId)); + + originFolderCase.getDataField(MenuItemConstants.FIELD_DUPLICATE_TITLE).setValue(new I18nString(newTitle)); + originFolderCase.getDataField(MenuItemConstants.FIELD_DUPLICATE_IDENTIFIER).setValue(newIdentifier); + originFolderCase = workflowService.save(originFolderCase); + taskService.finishTask(duplicateTaskId); + + Case duplicatedFolderCase = findMenuItem(newIdentifier); + assertNotNull(duplicatedFolderCase); + + UriNode leafNode = uriService.findByUri("/netgrif/" + newIdentifier); + + assertNotNull(leafNode); + assertEquals(duplicatedFolderCase.getUriNodeId(), originFolderCase.getUriNodeId()); + assertEquals(new I18nString(""), duplicatedFolderCase.getFieldValue(MenuItemConstants.FIELD_DUPLICATE_TITLE)); + assertEquals("", duplicatedFolderCase.getFieldValue(MenuItemConstants.FIELD_DUPLICATE_IDENTIFIER)); + assertEquals(newTitle, duplicatedFolderCase.getTitle()); + assertEquals(new I18nString(newTitle), duplicatedFolderCase.getFieldValue(MenuItemConstants.FIELD_MENU_NAME)); + assertEquals(newIdentifier, duplicatedFolderCase.getFieldValue(MenuItemConstants.FIELD_IDENTIFIER)); + assertEquals("/netgrif/" + newIdentifier, duplicatedFolderCase.getFieldValue(MenuItemConstants.FIELD_NODE_PATH)); + List duplicatedChildIds = MenuItemUtils.getCaseIdsFromCaseRef(duplicatedFolderCase, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + assertNotNull(duplicatedChildIds); + assertEquals(0, duplicatedChildIds.size()); + assertFalse((Boolean) duplicatedFolderCase.getFieldValue(MenuItemConstants.FIELD_HAS_CHILDREN)); + assertEquals(1, duplicatedFolderCase.getActivePlaces().get("initialized")); + } + + @Test + @SuppressWarnings("unchecked") + public void duplicateLeafItemTest() throws TransitionNotExecutableException { + String starterUri = "/netgrif/test"; + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER); + assertTrue(menuItemBodyOpt.isPresent()); + MenuItemBody menuItemBody = menuItemBodyOpt.get(); + menuItemBody.setUri(starterUri); + menuItemBody.setIdentifier("new_menu_item"); + Case originLeafItemCase = menuItemService.createMenuItem(menuItemBody); + + String newTitle = "New title"; + String newIdentifier = "new_identifier"; + + Case duplicatedLeafItemCase = menuItemService.duplicateItem(originLeafItemCase, new I18nString(newTitle), newIdentifier); + assertEquals(duplicatedLeafItemCase.getUriNodeId(), originLeafItemCase.getUriNodeId()); + assertEquals(duplicatedLeafItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE), + originLeafItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE)); + + String duplicatedCaseViewId = MenuItemUtils.getCaseIdFromCaseRef(duplicatedLeafItemCase, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID); + assertNotNull(duplicatedCaseViewId); + String originCaseViewId = MenuItemUtils.getCaseIdFromCaseRef(originLeafItemCase, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID); + assertNotNull(originCaseViewId); + assertNotEquals(duplicatedCaseViewId, originCaseViewId); + + List duplicatedFormValue = (List) duplicatedLeafItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_FORM); + assertNotNull(duplicatedFormValue); + assertEquals(1, duplicatedFormValue.size()); + List originFormValue = (List) originLeafItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_FORM); + assertNotNull(originFormValue); + assertEquals(1, originFormValue.size()); + assertNotEquals(duplicatedFormValue.get(0), originFormValue.get(0)); + + List duplicatedAllFormValue = (List) duplicatedLeafItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM); + assertNotNull(duplicatedAllFormValue); + assertEquals(1, duplicatedAllFormValue.size()); + List originAllFormValue = (List) originLeafItemCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM); + assertNotNull(originAllFormValue); + assertEquals(1, originAllFormValue.size()); + assertNotEquals(duplicatedAllFormValue.get(0), originAllFormValue.get(0)); + + Case duplicatedCaseViewCase = workflowService.findOne(duplicatedCaseViewId); + Case originCaseViewCase = workflowService.findOne(originCaseViewId); + assertEquals(duplicatedCaseViewCase.getProcessIdentifier(), originCaseViewCase.getProcessIdentifier()); + + assertEquals(duplicatedCaseViewCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE), + originCaseViewCase.getFieldValue(MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE)); + + String duplicatedTaskViewId = MenuItemUtils.getCaseIdFromCaseRef(duplicatedCaseViewCase, ViewConstants.FIELD_VIEW_CONFIGURATION_ID); + assertNotNull(duplicatedTaskViewId); + String originTaskViewId = MenuItemUtils.getCaseIdFromCaseRef(originCaseViewCase, ViewConstants.FIELD_VIEW_CONFIGURATION_ID); + assertNotNull(originTaskViewId); + assertNotEquals(duplicatedTaskViewId, originTaskViewId); + } + + @Test + public void removeChildItemFromParentTest() throws TransitionNotExecutableException { + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(CustomViewTemplate.IDENTIFIER); + assertTrue(menuItemBodyOpt.isPresent()); + MenuItemBody menuItemBody = menuItemBodyOpt.get(); + menuItemBody.setUri("/folderik"); + menuItemBody.setIdentifier("test"); + + Case testItemCase = menuItemService.createMenuItem(menuItemBody); + + Case folderikCase = findMenuItem("folderik"); + assertNotNull(folderikCase); + List childrenCaseIds = MenuItemUtils.getCaseIdsFromCaseRef(folderikCase, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + assertNotNull(childrenCaseIds); + assertEquals(1, childrenCaseIds.size()); + assertTrue(childrenCaseIds.contains(testItemCase.getStringId())); + assertTrue((Boolean) folderikCase.getFieldValue(MenuItemConstants.FIELD_HAS_CHILDREN)); + + folderikCase = menuItemService.removeChildItemFromParent(folderikCase.getStringId(), testItemCase); + + childrenCaseIds = MenuItemUtils.getCaseIdsFromCaseRef(folderikCase, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + assertNotNull(childrenCaseIds); + assertEquals(0, childrenCaseIds.size()); + assertFalse((Boolean) folderikCase.getFieldValue(MenuItemConstants.FIELD_HAS_CHILDREN)); + } + + @Test + @SuppressWarnings("unchecked") + public void handleConfigurationTemplateTest() throws TransitionNotExecutableException { + Case menuItemCase = workflowService.createCaseByIdentifier(MenuItemConstants.PROCESS_IDENTIFIER, "", "", + userService.getSystem().transformToLoggedUser()).getCase(); + + menuItemCase = dataService.setData(MenuItemUtils.findTaskIdInCase(menuItemCase, MenuItemConstants.TRANS_INIT_ID), + ImportHelper.populateDataset( + Map.of(MenuItemConstants.FIELD_CONFIGURATION_TEMPLATES, + Map.of("type", FieldType.ENUMERATION_MAP.getName(), "value", TabbedCaseViewTemplate.IDENTIFIER)) + ) + ).getCase(); + + ConfigurationTemplateOutcome outcome = menuItemService.handleConfigurationTemplate(menuItemCase); + + assertTrue((Boolean) outcome.getMapping().get(MenuItemConstants.FIELD_USE_TABBED_VIEW)); + assertFalse((Boolean) outcome.getMapping().get(MenuItemConstants.FIELD_USE_CUSTOM_VIEW)); + assertEquals(MenuItemViewType.CASE_VIEW.getIdentifier(), outcome.getMapping().get(MenuItemConstants.FIELD_VIEW_CONFIGURATION_TYPE)); + List viewCaseIdAsList = (List) outcome.getMapping().get(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID); + assertEquals(1, viewCaseIdAsList.size()); + Case viewCase = workflowService.findOne(viewCaseIdAsList.get(0)); + assertEquals("case_view_configuration", viewCase.getProcessIdentifier()); + List viewFormTaskIdAsList = (List) outcome.getMapping().get(MenuItemConstants.FIELD_VIEW_CONFIGURATION_FORM); + assertEquals(1, viewFormTaskIdAsList.size()); + assertEquals(MenuItemUtils.findTaskIdInCase(viewCase, ViewConstants.TRANS_SETTINGS_ID), viewFormTaskIdAsList.get(0)); + List viewAllFormTaskIdAsList = (List) outcome.getMapping().get(MenuItemConstants.FIELD_VIEW_CONFIGURATION_ALL_DATA_FORM); + assertEquals(1, viewAllFormTaskIdAsList.size()); + assertEquals(MenuItemUtils.findTaskIdInCase(viewCase, ViewConstants.TRANS_ALL_MENU_DATA_ID), viewAllFormTaskIdAsList.get(0)); + } + + @Test + public void getAvailableViewsAsOptionsByIsPrimaryTest() { + + Map options = menuItemService.getAvailableViewsAsOptions(true, true); + assertNotNull(options); + assertEquals(4, options.size()); + assertTrue(options.containsKey(MenuItemViewType.CASE_VIEW.getIdentifier())); + assertTrue(options.containsKey(MenuItemViewType.TASK_VIEW.getIdentifier())); + assertTrue(options.containsKey(MenuItemViewType.TABBED_TICKET_VIEW.getIdentifier())); + assertTrue(options.containsKey(MenuItemViewType.SINGLE_TASK_VIEW.getIdentifier())); + + options = menuItemService.getAvailableViewsAsOptions(true, false); + assertNotNull(options); + assertEquals(0, options.size()); + + options = menuItemService.getAvailableViewsAsOptions(false, false); + assertNotNull(options); + assertEquals(0, options.size()); + + options = menuItemService.getAvailableViewsAsOptions(false, true); + assertNotNull(options); + assertEquals(3, options.size()); + assertTrue(options.containsKey(MenuItemViewType.CASE_VIEW.getIdentifier())); + assertTrue(options.containsKey(MenuItemViewType.TASK_VIEW.getIdentifier())); + assertTrue(options.containsKey(MenuItemViewType.SINGLE_TASK_VIEW.getIdentifier())); + } + + @Test + public void getAvailableViewsAsOptionsByViewIdentifierTest() { + Map options = menuItemService.getAvailableViewsAsOptions(true, MenuItemViewType.CASE_VIEW.getIdentifier()); + assertNotNull(options); + assertEquals(1, options.size()); + assertTrue(options.containsKey(MenuItemViewType.TASK_VIEW.getIdentifier())); + + options = menuItemService.getAvailableViewsAsOptions(true, MenuItemViewType.TABBED_TICKET_VIEW.getIdentifier()); + assertNotNull(options); + assertEquals(1, options.size()); + assertTrue(options.containsKey(MenuItemViewType.SINGLE_TASK_VIEW.getIdentifier())); + + options = menuItemService.getAvailableViewsAsOptions(false, MenuItemViewType.TABBED_TICKET_VIEW.getIdentifier()); + assertNotNull(options); + assertEquals(1, options.size()); + assertTrue(options.containsKey(MenuItemViewType.SINGLE_TASK_VIEW.getIdentifier())); + + options = menuItemService.getAvailableViewsAsOptions(true, MenuItemViewType.TASK_VIEW.getIdentifier()); + assertNotNull(options); + assertEquals(0, options.size()); + } + + @Test + void testRemoveMenuItem() throws TransitionNotExecutableException, InterruptedException { + Optional menuItemBodyOpt = MenuItemTemplateHolder.get(TabbedCaseViewTemplate.IDENTIFIER); + assertTrue(menuItemBodyOpt.isPresent()); + MenuItemBody menuItemBody = menuItemBodyOpt.get(); + menuItemBody.setUri("/netgrif/test"); + menuItemBody.setIdentifier("new_menu_item"); + Case leafItemCase = menuItemService.createMenuItem(menuItemBody); + + Case testFolder = findMenuItem("test"); + String netgrifFolderId = MenuItemUtils.getCaseIdFromCaseRef(testFolder, MenuItemConstants.FIELD_PARENT_ID); + + Case netgrifFolder = workflowService.findOne(netgrifFolderId); + List childIds = MenuItemUtils.getCaseIdsFromCaseRef(netgrifFolder, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + assertNotNull(childIds); + assertTrue(childIds.contains(testFolder.getStringId())); + assertDoesNotThrow(() -> workflowService.findOne(testFolder.getStringId())); + assertDoesNotThrow(() -> workflowService.findOne(leafItemCase.getStringId())); + String tabbedCaseViewId = MenuItemUtils.getCaseIdFromCaseRef(leafItemCase, MenuItemConstants.FIELD_VIEW_CONFIGURATION_ID); + assertNotNull(tabbedCaseViewId); + Case tabbedCaseView = workflowService.findOne(tabbedCaseViewId); + String tabbedTaskViewId = MenuItemUtils.getCaseIdFromCaseRef(tabbedCaseView, CaseViewConstants.FIELD_VIEW_CONFIGURATION_ID); + assertNotNull(tabbedTaskViewId); + assertDoesNotThrow(() -> workflowService.findOne(tabbedTaskViewId)); + + workflowService.deleteCase(testFolder); + Thread.sleep(2000); + netgrifFolder = workflowService.findOne(netgrifFolderId); + List updatedChildIds = MenuItemUtils.getCaseIdsFromCaseRef(netgrifFolder, MenuItemConstants.FIELD_CHILD_ITEM_IDS); + assertNotNull(updatedChildIds); + assertFalse(updatedChildIds.contains(testFolder.getStringId())); + assertThrows(IllegalArgumentException.class, () -> workflowService.findOne(testFolder.getStringId())); + assertThrows(IllegalArgumentException.class, () -> workflowService.findOne(leafItemCase.getStringId())); + assertThrows(IllegalArgumentException.class, () -> workflowService.findOne(tabbedCaseViewId)); + assertThrows(IllegalArgumentException.class, () -> workflowService.findOne(tabbedTaskViewId)); + } + + private Case createDefaultMenuItem(String identifier, I18nString name) throws TransitionNotExecutableException { + FilterBody filterBody = new FilterBody(); + filterBody.setQuery("processIdentifier:process1"); + filterBody.setType(FieldType.CASE_FILTER); + + CaseViewBody caseView = new CaseViewBody(); + caseView.setFilterBody(filterBody); + caseView.setRequireTitleInCreation(false); + caseView.setChainedView(new TaskViewBody()); + + MenuItemBody menuItemBody = new MenuItemBody(); + menuItemBody.setUri("/"); + menuItemBody.setIdentifier(identifier); + menuItemBody.setMenuIcon("home"); + menuItemBody.setMenuName(name); + menuItemBody.setTabIcon("folder"); + menuItemBody.setTabName(name); + menuItemBody.setView(caseView); + + return menuItemService.createMenuItem(menuItemBody); + } + + private void login() { + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(superCreator.getLoggedSuper(), null)); + } + + private String getTaskId(Case useCase, String transId) { + return useCase.getTasks().stream() + .filter((taskPair -> taskPair.getTransition().equals(transId))) + .map(TaskPair::getTask) + .findFirst().orElse(null); + } + + private Case findMenuItem(String identifier) { + Query query = Query.query( + Criteria.where("processIdentifier").is(MenuItemConstants.PROCESS_IDENTIFIER) + .and(String.format("dataSet.%s.value", MenuItemConstants.FIELD_IDENTIFIER)).is(identifier) + ); + query.withHint(MenuItemConstants.IDENTIFIER_INDEX_NAME); + List caseAsList = mongoTemplate.find(query, Case.class); + Optional caseOptional = caseAsList.stream().findFirst(); + return caseOptional.map(aCase -> workflowService.findOne(aCase.getStringId())).orElse(null); + } +} diff --git a/src/test/java/com/netgrif/application/engine/pfql/CaseSearchServiceTest.java b/src/test/java/com/netgrif/application/engine/pfql/CaseSearchServiceTest.java new file mode 100644 index 00000000000..6d34428a50a --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/pfql/CaseSearchServiceTest.java @@ -0,0 +1,187 @@ +package com.netgrif.application.engine.pfql; + +import com.netgrif.application.engine.MockService; +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.domain.LoggedUser; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.caseresource.CaseSearchService; +import com.netgrif.application.engine.startup.ImportHelper; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.TaskPair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class CaseSearchServiceTest { + + private static final String QUERY_FIELD_ID = "input_query"; + + @Autowired + private TestHelper testHelper; + + @Autowired + private CaseSearchService caseSearchService; + + @Autowired + private ImportHelper importHelper; + + @Autowired + private MockService mockService; + + private Case testCase; + + @BeforeEach + protected void beforeEach() { + testHelper.truncateDbs(); + Optional createdNetOpt = importHelper.createNet("/query_lang_test.xml"); + assertTrue(createdNetOpt.isPresent()); + testCase = importHelper.createCase("test", createdNetOpt.get()); + } + + @Test + public void queryResourceTypeTest() { + assertEquals(QueryType.CASE, caseSearchService.getQueryResourceType()); + } + + @Test + public void searchOneTest() throws InterruptedException { + assertThrows(IllegalArgumentException.class, () -> caseSearchService.searchOne((String) null)); + assertThrows(IllegalArgumentException.class, () -> caseSearchService.searchOne("cases: title eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> caseSearchService.searchOne("process: identifier eq 'query_lang_test'")); + + Case result = caseSearchService.searchOne("case: title eq 'test'"); + assertNotNull(result); + assertNotNull(result.getPetriNet()); + assertEquals(testCase.getStringId(), result.getStringId()); + + login(mockService.mockLoggedUser()); + result = caseSearchService.searchOne("case: title eq 'test'"); + assertNull(result); + logout(); + + setData(Map.of(QUERY_FIELD_ID, Map.of("value", "xxx", "type", FieldType.TEXT.getName()))); + Thread.sleep(2000); + result = caseSearchService.searchOne("case: data." + QUERY_FIELD_ID + ".value eq 'xxx'"); + assertNotNull(result); + assertNotNull(result.getPetriNet()); + assertEquals(testCase.getStringId(), result.getStringId()); + + result = caseSearchService.searchOne("case: title eq 'wrong'"); + assertNull(result); + } + + @Test + public void searchAllTest() throws InterruptedException { + assertThrows(IllegalArgumentException.class, () -> caseSearchService.searchAll((String) null)); + assertThrows(IllegalArgumentException.class, () -> caseSearchService.searchAll("case: title eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> caseSearchService.searchAll("processes: identifier eq 'query_lang_test'")); + + Page result = caseSearchService.searchAll("cases: title eq 'test'"); + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertNotNull(result.getContent().get(0).getPetriNet()); + assertEquals(testCase.getStringId(), result.getContent().get(0).getStringId()); + + login(mockService.mockLoggedUser()); + result = caseSearchService.searchAll("cases: title eq 'test'"); + assertNotNull(result); + assertEquals(0, result.getTotalElements()); + logout(); + + setData(Map.of(QUERY_FIELD_ID, Map.of("value", "xxx", "type", FieldType.TEXT.getName()))); + Thread.sleep(2000); + result = caseSearchService.searchAll("cases: data." + QUERY_FIELD_ID + ".value eq 'xxx'"); + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertNotNull(result.getContent().get(0).getPetriNet()); + assertEquals(testCase.getStringId(), result.getContent().get(0).getStringId()); + + result = caseSearchService.searchAll("cases: title eq 'wrong'"); + assertNotNull(result); + assertEquals(0, result.getTotalElements()); + } + + @Test + public void countTest() throws InterruptedException { + assertThrows(IllegalArgumentException.class, () -> caseSearchService.count((String) null)); + assertThrows(IllegalArgumentException.class, () -> caseSearchService.count("process: identifier eq 'query_lang_test'")); + + long result = caseSearchService.count("case: title eq 'test'"); + assertEquals(1, result); + + login(mockService.mockLoggedUser()); + result = caseSearchService.count("case: title eq 'test'"); + assertEquals(0, result); + logout(); + + result = caseSearchService.count("cases: title eq 'test'"); + assertEquals(1, result); + + setData(Map.of(QUERY_FIELD_ID, Map.of("value", "xxx", "type", FieldType.TEXT.getName()))); + Thread.sleep(2000); + result = caseSearchService.count("cases: data." + QUERY_FIELD_ID + ".value eq 'xxx'"); + assertEquals(1, result); + + result = caseSearchService.count("cases: title eq 'wrong'"); + assertEquals(0, result); + } + + @Test + public void existsTest() throws InterruptedException { + assertThrows(IllegalArgumentException.class, () -> caseSearchService.exists((String) null)); + assertThrows(IllegalArgumentException.class, () -> caseSearchService.exists("process: identifier eq 'query_lang_test'")); + + boolean result = caseSearchService.exists("case: title eq 'test'"); + assertTrue(result); + + login(mockService.mockLoggedUser()); + result = caseSearchService.exists("case: title eq 'test'"); + assertFalse(result); + logout(); + + result = caseSearchService.exists("cases: title eq 'test'"); + assertTrue(result); + + setData(Map.of(QUERY_FIELD_ID, Map.of("value", "xxx", "type", FieldType.TEXT.getName()))); + Thread.sleep(2000); + result = caseSearchService.exists("cases: data." + QUERY_FIELD_ID + ".value eq 'xxx'"); + assertTrue(result); + + result = caseSearchService.exists("cases: title eq 'wrong'"); + assertFalse(result); + } + + private void setData(Map> dataSet) { + String taskId = testCase.getTasks().stream() + .filter(taskPair -> taskPair.getTransition().equals("t1")) + .map(TaskPair::getTask) + .findFirst().get(); + testCase = importHelper.setTaskData(taskId, dataSet).getCase(); + } + + private void login(LoggedUser loggedUser) { + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(loggedUser, null)); + } + + private void logout() { + SecurityContextHolder.getContext().setAuthentication(null); + } + +} diff --git a/src/test/java/com/netgrif/application/engine/pfql/ProcessSearchServiceTest.java b/src/test/java/com/netgrif/application/engine/pfql/ProcessSearchServiceTest.java new file mode 100644 index 00000000000..44fefe6f187 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/pfql/ProcessSearchServiceTest.java @@ -0,0 +1,120 @@ +package com.netgrif.application.engine.pfql; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.processresource.ProcessSearchService; +import com.netgrif.application.engine.startup.ImportHelper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class ProcessSearchServiceTest { + + @Autowired + private TestHelper testHelper; + + @Autowired + private ProcessSearchService processSearchService; + + @Autowired + private ImportHelper importHelper; + + private PetriNet testNet; + + @BeforeEach + protected void beforeEach() { + testHelper.truncateDbs(); + Optional createdNetOpt = importHelper.createNet("/query_lang_test.xml"); + assertTrue(createdNetOpt.isPresent()); + testNet = createdNetOpt.get(); + } + + @Test + public void queryResourceTypeTest() { + assertEquals(QueryType.PROCESS, processSearchService.getQueryResourceType()); + } + + @Test + public void searchOneTest() { + assertThrows(IllegalArgumentException.class, () -> processSearchService.searchOne((String) null)); + assertThrows(IllegalArgumentException.class, () -> processSearchService.searchOne("processes: identifier eq 'xxx'")); + assertThrows(IllegalArgumentException.class, () -> processSearchService.searchOne("case: processIdentifier eq 'xxx'")); + + PetriNet result = processSearchService.searchOne("process: identifier eq 'query_lang_test'"); + assertNotNull(result); + assertEquals(testNet.getStringId(), result.getStringId()); + + result = processSearchService.searchOne("process: identifier eq 'wrong'"); + assertNull(result); + } + + @Test + public void searchAllTest() { + assertThrows(IllegalArgumentException.class, () -> processSearchService.searchAll((String) null)); + assertThrows(IllegalArgumentException.class, () -> processSearchService.searchAll("process: identifier eq 'xxx'")); + assertThrows(IllegalArgumentException.class, () -> processSearchService.searchAll("cases: processIdentifier eq 'xxx'")); + + Page result = processSearchService.searchAll("processes: identifier eq 'query_lang_test'"); + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertEquals(20, result.getPageable().getPageSize()); + assertEquals(testNet.getStringId(), result.getContent().get(0).getStringId()); + + result = processSearchService.searchAll("processes: identifier eq 'query_lang_test' page 0 size 67"); + assertNotNull(result); + assertEquals(67, result.getPageable().getPageSize()); + + result = processSearchService.searchAll("processes: identifier eq 'wrong'"); + assertNotNull(result); + assertEquals(0, result.getTotalElements()); + } + + @Test + public void countTest() { + assertThrows(IllegalArgumentException.class, () -> processSearchService.count((String) null)); + assertThrows(IllegalArgumentException.class, () -> processSearchService.count("case: identifier eq 'xxx'")); + + long result = processSearchService.count("process: identifier eq 'query_lang_test'"); + assertEquals(1, result); + + result = processSearchService.count("processes: identifier eq 'query_lang_test'"); + assertEquals(1, result); + + result = processSearchService.count("processes: identifier eq 'query_lang_test'"); + assertEquals(1, result); + + result = processSearchService.count("processes: identifier eq 'wrong'"); + assertEquals(0, result); + } + + @Test + public void existsTest() { + assertThrows(IllegalArgumentException.class, () -> processSearchService.exists((String) null)); + assertThrows(IllegalArgumentException.class, () -> processSearchService.exists("case: identifier eq 'xxx'")); + + boolean result = processSearchService.exists("process: identifier eq 'query_lang_test'"); + assertTrue(result); + + result = processSearchService.exists("processes: identifier eq 'query_lang_test'"); + assertTrue(result); + + result = processSearchService.exists("processes: identifier eq 'query_lang_test'"); + assertTrue(result); + + result = processSearchService.exists("processes: identifier eq 'wrong'"); + assertFalse(result); + } +} diff --git a/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java new file mode 100644 index 00000000000..929f7e242a9 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java @@ -0,0 +1,2128 @@ +package com.netgrif.application.engine.pfql; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.domain.QUser; +import com.netgrif.application.engine.auth.domain.User; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.QPetriNet; +import com.netgrif.application.engine.petrinet.domain.version.Version; +import com.netgrif.application.engine.pfql.service.ISearchService; +import com.netgrif.application.engine.pfql.utils.MongoDbUtils; +import com.netgrif.application.engine.startup.ImportHelper; +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.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.StringPath; +import org.bson.Document; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class QueryLangTest { + + public static final ObjectId GENERIC_OBJECT_ID = ObjectId.get(); + + @Autowired + private MongoOperations mongoOperations; + + @Autowired + private ISearchService searchService; + + @Autowired + private ImportHelper helper; + + @Autowired + private TestHelper testHelper; + + @Test + @SuppressWarnings("unchecked") + @Disabled + public void testSearchService() { + testHelper.truncateDbs(); + + Optional optionalPetriNet = helper.createNet("/pfql.xml"); + assertNotNull(optionalPetriNet); + + Object processAsObject = searchService.search("process: identifier == 'query_test'"); + assertNotNull(processAsObject); + assertEquals(PetriNet.class, processAsObject.getClass()); + + PetriNet process = (PetriNet) processAsObject; + for (int i = 0; i < 10; i++) { + Map params = new HashMap<>(); + params.put("id", String.valueOf(i)); + helper.createCase(String.format("Test %02d", i), process, params); + } + + Object cases = searchService.search("cases: processIdentifier eq 'query_test' page 1 size 5 sort by title desc"); + assertNotNull(cases); + assertEquals(PageImpl.class, cases.getClass()); + assertEquals(10, ((Page) cases).getTotalElements()); + + Object case3 = searchService.search("case: processIdentifier eq 'query_test' and data.number_0.value == 3"); + assertNotNull(case3); + assertEquals(Case.class, case3.getClass()); + assertEquals("Test 03", ((Case) case3).getTitle()); + assertEquals(3, ((Case) case3).getFieldValue("number_0")); + + Object case4 = searchService.search("case: processIdentifier eq 'query_test' and data.text_0.value == '4'"); + assertNotNull(case4); + assertEquals(Case.class, case4.getClass()); + assertEquals("4", ((Case) case4).getFieldValue("text_0")); + + Object case5 = searchService.search("case: processIdentifier eq 'query_test' and data.boolean_0.value == true"); + assertNotNull(case5); + assertEquals(Case.class, case5.getClass()); + assertEquals(true, ((Case) case5).getFieldValue("boolean_0")); + + cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true"); + assertEquals(5, ((Page) cases).getTotalElements()); + + cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true and data.text_0.value != '4'"); + assertEquals(4, ((Page) cases).getTotalElements()); + } + + @Test + public void testSimpleMongodbProcessQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, PetriNet.class); + + // without comparison + Predicate actual = evaluateQuery("processes").getFullMongoQuery(); + Predicate expected = new BooleanBuilder(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // id comparison + actual = evaluateQuery(String.format("process: id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet._id.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("process: id in('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet._id.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // identifier comparison + checkStringComparison(mongoDbUtils, "process", "identifier", QPetriNet.petriNet.identifier); + + // version comparison + actual = evaluateQuery("process: version eq 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.eq(new Version(1, 1, 1)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version lt 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.major.lt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.lt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.lt(1)))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version lte 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.major.loe(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.loe(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.loe(1)))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version gt 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.major.gt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.gt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.gt(1)))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version gte 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.major.goe(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.goe(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.goe(1)))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + Version v1 = new Version(1, 1, 1); + Version v2 = new Version(2, 2, 2); + Version v3 = new Version(3, 3, 3); + actual = evaluateQuery("process: version in(1.1.1, 2.2.2, 3.3.3)").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.in(List.of(v1, v2, v3)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version not in (1.1.1, 2.2.2, 3.3.3)").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.in(List.of(v1, v2, v3)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version in(1.1.1:2.2.2)").getFullMongoQuery(); + BooleanBuilder builder = new BooleanBuilder(); + builder.and(QPetriNet.petriNet.version.major.gt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.gt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.gt(1))))); + builder.and(QPetriNet.petriNet.version.major.lt(2) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.lt(2))) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.eq(2L).and(QPetriNet.petriNet.version.patch.lt(2))))); + expected = builder; + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version in[1.1.1 :2.2.2]").getFullMongoQuery(); + builder = new BooleanBuilder(); + builder.and(QPetriNet.petriNet.version.major.goe(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.goe(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.goe(1))))); + builder.and(QPetriNet.petriNet.version.major.loe(2) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.loe(2))) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.eq(2L).and(QPetriNet.petriNet.version.patch.loe(2))))); + expected = builder; + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version not in (1.1.1 : 2.2.2]").getFullMongoQuery(); + builder = new BooleanBuilder(); + builder.and(QPetriNet.petriNet.version.major.gt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.gt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.gt(1))))); + builder.and(QPetriNet.petriNet.version.major.loe(2) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.loe(2))) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.eq(2L).and(QPetriNet.petriNet.version.patch.loe(2))))); + expected = builder.not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // title comparison + checkStringComparison(mongoDbUtils, "process", "title", QPetriNet.petriNet.title.defaultValue); + + // creationDate comparison + checkDateComparison(mongoDbUtils, "process", "creationDate", QPetriNet.petriNet.creationDate); + } + + @Test + public void testComplexMongodbProcessQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, PetriNet.class); + + // not comparison + Predicate actual = evaluateQuery(String.format("process: id not eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QPetriNet.petriNet._id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("process: id neq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet._id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and comparison + actual = evaluateQuery(String.format("process: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet._id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and not comparison + actual = evaluateQuery(String.format("process: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet._id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("process: id eq '%s' and title != 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet._id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or comparison + actual = evaluateQuery(String.format("process: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet._id.eq(GENERIC_OBJECT_ID).or(QPetriNet.petriNet.title.defaultValue.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or not comparison + actual = evaluateQuery(String.format("process: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet._id.eq(GENERIC_OBJECT_ID).or(QPetriNet.petriNet.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis comparison + actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet._id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test").or(QPetriNet.petriNet.title.defaultValue.eq("test1"))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis not comparison + actual = evaluateQuery(String.format("process: id eq '%s' and not (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet._id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test").or(QPetriNet.petriNet.title.defaultValue.eq("test1")).not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or (title eq 'test1' and identifier eq 'test'))", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet._id.eq(GENERIC_OBJECT_ID) + .and(QPetriNet.petriNet.title.defaultValue.eq("test") + .or(QPetriNet.petriNet.title.defaultValue.eq("test1").and(QPetriNet.petriNet.identifier.eq("test")))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testSimpleMongodbCaseQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, Case.class); + + // without comparison + Predicate actual = evaluateQuery("cases").getFullMongoQuery(); + Predicate expected = new BooleanBuilder(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // id comparison + actual = evaluateQuery(String.format("case: id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$._id.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("case: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$._id.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // processId comparison + actual = evaluateQuery(String.format("case: processId eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.petriNetObjectId.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("case: processId in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.petriNetObjectId.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // processIdentifier comparison + checkStringComparison(mongoDbUtils, "case", "processIdentifier", QCase.case$.processIdentifier); + + // title comparison + checkStringComparison(mongoDbUtils, "case", "title", QCase.case$.title); + + // creationDate comparison + checkDateComparison(mongoDbUtils, "case", "creationDate", QCase.case$.creationDate); + + // author comparison + actual = evaluateQuery("case: author eq 'test'").getFullMongoQuery(); + expected = QCase.case$.author.id.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: author contains 'test'").getFullMongoQuery(); + expected = QCase.case$.author.id.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: author in ('test', 'test1')").getFullMongoQuery(); + expected = QCase.case$.author.id.in("test", "test1"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // only available for elastic query + // places comparison + actual = evaluateQuery("case: places.p1.marking eq 1").getFullMongoQuery(); + assertNull(actual); + + // task state comparison + actual = evaluateQuery("case: tasks.t1.state eq enabled").getFullMongoQuery(); + assertNull(actual); + + // task userId comparison + actual = evaluateQuery("case: tasks.t1.userId eq 'test'").getFullMongoQuery(); + assertNull(actual); + + // data value comparison + actual = evaluateQuery("case: data.field1.value eq 'test'").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.name.value eq 'test'").getFullMongoQuery(); // name is a reserved keyword + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value contains 'test'").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value eq 1").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value lt 1").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value lte 1").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value gt 1").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value gte 1").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value eq 2011-12-03T10:15:30").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value lt 2011-12-03T10:15:30").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value lte 2011-12-03T10:15:30").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value gt 2011-12-03T10:15:30").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value gte 2011-12-03T10:15:30").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value eq true").getFullMongoQuery(); + assertNull(actual); + + // data options comparison + actual = evaluateQuery("case: data.field1.options eq 'test'").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.options contains 'test'").getFullMongoQuery(); + assertNull(actual); + } + + @Test + public void testComplexMongodbCaseQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, Case.class); + + // not comparison + Predicate actual = evaluateQuery(String.format("case: id not eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QCase.case$._id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("case: id neq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$._id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and comparison + actual = evaluateQuery(String.format("case: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$._id.eq(GENERIC_OBJECT_ID).and(QCase.case$.title.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and not comparison + actual = evaluateQuery(String.format("case: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$._id.eq(GENERIC_OBJECT_ID).and(QCase.case$.title.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("case: id eq '%s' and title != 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$._id.eq(GENERIC_OBJECT_ID).and(QCase.case$.title.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or comparison + actual = evaluateQuery(String.format("case: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$._id.eq(GENERIC_OBJECT_ID).or(QCase.case$.title.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or not comparison + actual = evaluateQuery(String.format("case: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$._id.eq(GENERIC_OBJECT_ID).or(QCase.case$.title.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("case: id eq '%s' or title neq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$._id.eq(GENERIC_OBJECT_ID).or(QCase.case$.title.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis comparison + actual = evaluateQuery(String.format("case: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$._id.eq(GENERIC_OBJECT_ID).and(QCase.case$.title.eq("test").or(QCase.case$.title.eq("test1"))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("case: id eq '%s' and (title eq 'test' or (title eq 'test1' and processIdentifier eq 'test'))", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$._id.eq(GENERIC_OBJECT_ID) + .and(QCase.case$.title.eq("test") + .or(QCase.case$.title.eq("test1").and(QCase.case$.processIdentifier.eq("test")))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testSimpleMongodbTaskQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, Task.class); + + // without comparison + Predicate actual = evaluateQuery("tasks").getFullMongoQuery(); + Predicate expected = new BooleanBuilder(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // id comparison + actual = evaluateQuery(String.format("task: id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task._id.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("task: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task._id.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // transitionId comparison + checkStringComparison(mongoDbUtils, "task", "transitionId", QTask.task.transitionId); + + // title comparison + checkStringComparison(mongoDbUtils, "task", "title", QTask.task.title.defaultValue); + + // state comparison +// TODO: fix +// actual = evaluateQuery("task: state eq enabled").getFullMongoQuery(); +// expected = QTask.task.state.eq(State.ENABLED); + +// compareMongoQueries(mongoDbUtils, actual, expected); + +// actual = evaluateQuery("task: state eq disabled").getFullMongoQuery(); +// expected = QTask.task.state.eq(State.DISABLED); + +// compareMongoQueries(mongoDbUtils, actual, expected); + + // userId comparison + actual = evaluateQuery("task: userId eq 'test'").getFullMongoQuery(); + expected = QTask.task.userId.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: userId contains 'test'").getFullMongoQuery(); + expected = QTask.task.userId.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: userId in ('test', 'test1')").getFullMongoQuery(); + expected = QTask.task.userId.in("test", "test1"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // caseId comparison + actual = evaluateQuery("task: caseId eq 'test'").getFullMongoQuery(); + expected = QTask.task.caseId.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: caseId contains 'test'").getFullMongoQuery(); + expected = QTask.task.caseId.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: caseId in ('test', 'test1')").getFullMongoQuery(); + expected = QTask.task.caseId.in("test", "test1"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // processId comparison + actual = evaluateQuery("task: processId eq 'test'").getFullMongoQuery(); + expected = QTask.task.processId.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: processId contains 'test'").getFullMongoQuery(); + expected = QTask.task.processId.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: processId in ('test', 'test1')").getFullMongoQuery(); + expected = QTask.task.processId.in("test", "test1"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // lastAssign comparison +// TODO: fix +// checkDateComparison(mongoDbUtils, "task", "lastAssign", QTask.task.lastAssigned); + + // lastFinish comparison +// checkDateComparison(mongoDbUtils, "task", "lastFinish", QTask.task.lastFinished); + } + + @Test + public void testComplexMongodbTaskQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, Task.class); + + // not comparison + Predicate actual = evaluateQuery(String.format("task: id not eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QTask.task._id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("task: id != '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task._id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and comparison + actual = evaluateQuery(String.format("task: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task._id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and not comparison + actual = evaluateQuery(String.format("task: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task._id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("task: id eq '%s' and title neq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task._id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or comparison + actual = evaluateQuery(String.format("task: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task._id.eq(GENERIC_OBJECT_ID).or(QTask.task.title.defaultValue.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or not comparison + actual = evaluateQuery(String.format("task: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task._id.eq(GENERIC_OBJECT_ID).or(QTask.task.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("task: id eq '%s' or title neq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task._id.eq(GENERIC_OBJECT_ID).or(QTask.task.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis comparison + actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task._id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test").or(QTask.task.title.defaultValue.eq("test1"))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis not comparison + actual = evaluateQuery(String.format("task: id eq '%s' and not (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task._id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test").or(QTask.task.title.defaultValue.eq("test1")).not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or (title eq 'test1' and processId eq 'test'))", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task._id.eq(GENERIC_OBJECT_ID) + .and(QTask.task.title.defaultValue.eq("test") + .or(QTask.task.title.defaultValue.eq("test1").and(QTask.task.processId.eq("test")))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testSimpleMongodbUserQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, User.class); + + // without comparison + Predicate actual = evaluateQuery("users").getFullMongoQuery(); + Predicate expected = new BooleanBuilder(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // id comparison + actual = evaluateQuery(String.format("user: id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user._id.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("user: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user._id.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // name comparison + checkStringComparison(mongoDbUtils, "user", "name", QUser.user.name); + + // surname comparison + checkStringComparison(mongoDbUtils, "user", "surname", QUser.user.surname); + + // email comparison + checkStringComparison(mongoDbUtils, "user", "email", QUser.user.email); + } + + @Test + public void testComplexMongodbUserQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, User.class); + + // not comparison + Predicate actual = evaluateQuery(String.format("user: id not eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QUser.user._id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("user: id != '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user._id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and comparison + actual = evaluateQuery(String.format("user: id eq '%s' and email eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user._id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and not comparison + actual = evaluateQuery(String.format("user: id eq '%s' and email not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user._id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("user: id eq '%s' and email neq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user._id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or comparison + actual = evaluateQuery(String.format("user: id eq '%s' or email eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user._id.eq(GENERIC_OBJECT_ID).or(QUser.user.email.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or not comparison + actual = evaluateQuery(String.format("user: id eq '%s' or email not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user._id.eq(GENERIC_OBJECT_ID).or(QUser.user.email.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("user: id eq '%s' or email != 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user._id.eq(GENERIC_OBJECT_ID).or(QUser.user.email.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis comparison + actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user._id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test").or(QUser.user.email.eq("test1"))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis not comparison + actual = evaluateQuery(String.format("user: id eq '%s' and not (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user._id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test").or(QUser.user.email.eq("test1")).not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or (email eq 'test1' and name eq 'test'))", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user._id.eq(GENERIC_OBJECT_ID) + .and(QUser.user.email.eq("test") + .or(QUser.user.email.eq("test1").and(QUser.user.name.eq("test")))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testSimpleElasticProcessQuery() { + // without comparison + String actual = evaluateQuery("processes").getFullElasticQuery(); + assertEquals("*", actual); + + // elastic query should be always null + // id comparison + actual = evaluateQuery(String.format("process: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // identifier comparison + actual = evaluateQuery("process: identifier eq 'test'").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("process: identifier contains 'test'").getFullElasticQuery(); + assertNull(actual); + + // version comparison + actual = evaluateQuery("process: version eq 1.1.1").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("process: version lt 1.1.1").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("process: version lte 1.1.1").getFullElasticQuery(); + assertNull(actual); + + + actual = evaluateQuery("process: version gt 1.1.1").getFullElasticQuery(); + assertNull(actual); + + + actual = evaluateQuery("process: version gte 1.1.1").getFullElasticQuery(); + assertNull(actual); + + + // title comparison + actual = evaluateQuery("process: title eq 'test'").getFullElasticQuery(); + assertNull(actual); + + + actual = evaluateQuery("process: title contains 'test'").getFullElasticQuery(); + assertNull(actual); + + + // creationDate comparison + actual = evaluateQuery("process: creationDate eq 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + + actual = evaluateQuery("process: creationDate lt 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + + actual = evaluateQuery("process: creationDate lte 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + + actual = evaluateQuery("process: creationDate gt 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + + actual = evaluateQuery("process: creationDate gte 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + } + + @Test + public void testComplexElasticProcessQuery() { + // elastic query should be always null + // not comparison + String actual = evaluateQuery(String.format("process: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + assertNull(actual); + + actual = evaluateQuery(String.format("process: id neq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // and comparison + actual = evaluateQuery(String.format("process: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // and not comparison + actual = evaluateQuery(String.format("process: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery(String.format("process: id eq '%s' and title != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // or comparison + actual = evaluateQuery(String.format("process: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // or not comparison + actual = evaluateQuery(String.format("process: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery(String.format("process: id eq '%s' or title != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // parenthesis comparison + actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // parenthesis not comparison + actual = evaluateQuery(String.format("process: id eq '%s' and not (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + + // nested parenthesis comparison + actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or (title eq 'test1' and identifier eq 'test'))", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + } + + @Test + public void testSimpleElasticCaseQuery() { + // without comparison + String actual = evaluateQuery("cases").getFullElasticQuery(); + String expected = "*"; + assertEquals(expected, actual); + + // id comparison + actual = evaluateQuery(String.format("case: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("case: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + // processId comparison + actual = evaluateQuery(String.format("case: processId eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("processId:%s", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("case: processId in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("processId:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + // processIdentifier comparison + checkStringComparisonElastic("case", "processIdentifier", "processIdentifier"); + + // title comparison + checkStringComparisonElastic("case", "title", "title"); + + // creationDate comparison + checkDateComparisonElastic("case", "creationDate", "creationDateSortable"); + + // author comparison + actual = evaluateQuery("case: author eq 'test'").getFullElasticQuery(); + expected = "author:test"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: author contains 'test'").getFullElasticQuery(); + expected = "author:*test*"; + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("case: author in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("author:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + // places comparison + checkNumberComparisonElastic("case", "places.p1.marking", "places.p1.marking"); + + // task state comparison +// TODO: fix +// actual = evaluateQuery("case: tasks.t1.state eq enabled").getFullElasticQuery(); +// expected = String.format("tasks.t1.state:%s", State.ENABLED); +// assertEquals(expected, actual); +// actual = evaluateQuery("case: tasks.t1.state eq disabled").getFullElasticQuery(); +// expected = String.format("tasks.t1.state:%s", State.DISABLED); +// assertEquals(expected, actual); + + // task userId comparison + actual = evaluateQuery("case: tasks.t1.userId eq 'test'").getFullElasticQuery(); + expected = "tasks.t1.userId:test"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: tasks.t1.userId contains 'test'").getFullElasticQuery(); + expected = "tasks.t1.userId:*test*"; + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("case: tasks.t1.userId in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("tasks.t1.userId:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + // data value comparison + checkStringComparisonElastic("case", "data.field1.value", "dataSet.field1.textValue"); + + checkNumberComparisonElastic("case", "data.field2.value", "dataSet.field2.numberValue"); + + checkDateComparisonElastic("case", "data.field3.value", "dataSet.field3.timestampValue"); + + actual = evaluateQuery("case: data.field1.value eq true").getFullElasticQuery(); + expected = "dataSet.field1.booleanValue:true"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: data.field1.value eq false").getFullElasticQuery(); + expected = "dataSet.field1.booleanValue:false"; + assertEquals(expected, actual); + + // data options comparison + checkStringComparisonElastic("case", "data.field1.options", "dataSet.field1.options"); + } + + @Test + public void testComplexElasticCaseQuery() { + // not comparison + String actual = evaluateQuery(String.format("case: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + String expected = String.format("NOT stringId:%s", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("case: id neq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("NOT stringId:%s", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + // and comparison + actual = evaluateQuery(String.format("case: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND title:test", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + // and not comparison + actual = evaluateQuery(String.format("case: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND NOT title:test", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("case: id eq '%s' and title != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND NOT title:test", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + // or comparison + actual = evaluateQuery(String.format("case: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s OR title:test", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + // or not comparison + actual = evaluateQuery(String.format("case: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s OR NOT title:test", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("case: id eq '%s' or title neq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s OR NOT title:test", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + // parenthesis comparison + actual = evaluateQuery(String.format("case: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND (title:test OR title:test1)", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("case: id eq '%s' and (title eq 'test' or (title eq 'test1' and processIdentifier eq 'test'))", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND (title:test OR (title:test1 AND processIdentifier:test))", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + } + + @Test + public void testSimpleElasticTaskQuery() { + // without comparison + String actual = evaluateQuery("tasks").getFullElasticQuery(); + assertEquals("*", actual); + + // elastic query should be always null + // id comparison + actual = evaluateQuery(String.format("task: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // transitionId comparison + actual = evaluateQuery("task: transitionId eq 'test'").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: transitionId contains 'test'").getFullElasticQuery(); + assertNull(actual); + + // title comparison + actual = evaluateQuery("task: title eq 'test'").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: title contains 'test'").getFullElasticQuery(); + assertNull(actual); + + // state comparison + actual = evaluateQuery("task: state eq enabled").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: state eq disabled").getFullElasticQuery(); + assertNull(actual); + + // userId comparison + actual = evaluateQuery("task: userId eq 'test'").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: userId contains 'test'").getFullElasticQuery(); + assertNull(actual); + + // caseId comparison + actual = evaluateQuery("task: caseId eq 'test'").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: caseId contains 'test'").getFullElasticQuery(); + assertNull(actual); + + // processId comparison + actual = evaluateQuery("task: processId eq 'test'").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: processId contains 'test'").getFullElasticQuery(); + assertNull(actual); + + // lastAssign comparison + actual = evaluateQuery("task: lastAssign eq 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: lastAssign lt 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: lastAssign lte 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: lastAssign gt 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: lastAssign gte 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + // lastFinish comparison + actual = evaluateQuery("task: lastFinish eq 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: lastFinish lt 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: lastFinish lte 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: lastFinish gt 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("task: lastFinish gte 2011-12-03T10:15:30").getFullElasticQuery(); + assertNull(actual); + } + + @Test + public void testComplexElasticTaskQuery() { + // elastic query should be always null + // not comparison + String actual = evaluateQuery(String.format("task: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery(String.format("task: id neq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // and comparison + actual = evaluateQuery(String.format("task: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // and not comparison + actual = evaluateQuery(String.format("task: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery(String.format("task: id eq '%s' and title != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // or comparison + actual = evaluateQuery(String.format("task: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // or not comparison + actual = evaluateQuery(String.format("task: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery(String.format("task: id eq '%s' or title neq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // parenthesis comparison + actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // parenthesis not comparison + actual = evaluateQuery(String.format("task: id eq '%s' and not (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or (title eq 'test1' and processId eq 'test'))", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + } + + @Test + public void testSimpleElasticUserQuery() { + // without comparison + String actual = evaluateQuery("users").getFullElasticQuery(); + assertEquals("*", actual); + + // elastic query should be always null + // id comparison + actual = evaluateQuery(String.format("user: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // name comparison + actual = evaluateQuery("user: name eq 'test'").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("user: name contains 'test'").getFullElasticQuery(); + assertNull(actual); + + // surname comparison + actual = evaluateQuery("user: surname eq 'test'").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("user: surname contains 'test'").getFullElasticQuery(); + assertNull(actual); + + // email comparison + actual = evaluateQuery("user: email eq 'test'").getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery("user: email contains 'test'").getFullElasticQuery(); + assertNull(actual); + } + + @Test + public void testComplexElasticUserQuery() { + // elastic query should be always null + // not comparison + String actual = evaluateQuery(String.format("user: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery(String.format("user: id != '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // and comparison + actual = evaluateQuery(String.format("user: id eq '%s' and email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // and not comparison + actual = evaluateQuery(String.format("user: id eq '%s' and email not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery(String.format("user: id eq '%s' and email neq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // or comparison + actual = evaluateQuery(String.format("user: id eq '%s' or email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // or not comparison + actual = evaluateQuery(String.format("user: id eq '%s' or email not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + actual = evaluateQuery(String.format("user: id eq '%s' or email != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // parenthesis comparison + actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // parenthesis not comparison + actual = evaluateQuery(String.format("user: id eq '%s' and not (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or (email eq 'test1' and name eq 'test'))", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assertNull(actual); + } + + @Test + public void testPagingQuery() { + Pageable pageable = evaluateQuery("cases: processIdentifier eq 'test'").getPageable(); + assertEquals(0, pageable.getPageNumber()); + assertEquals(20, pageable.getPageSize()); + + pageable = evaluateQuery("cases: processIdentifier eq 'test' page 2").getPageable(); + assertEquals(2, pageable.getPageNumber()); + assertEquals(20, pageable.getPageSize()); + + pageable = evaluateQuery("cases: processIdentifier eq 'test' page 2 size 4").getPageable(); + assertEquals(2, pageable.getPageNumber()); + assertEquals(4, pageable.getPageSize()); + + pageable = evaluateQuery("cases: page 2 size 4").getPageable(); + assertEquals(2, pageable.getPageNumber()); + assertEquals(4, pageable.getPageSize()); + } + + @Test + public void testProcessSortingQuery() { + // default (no sort) + Pageable actual = evaluateQuery("processes: identifier eq 'test'").getPageable(); + assertFalse(actual.getSort().isSorted()); + + // default ordering asc + actual = evaluateQuery("processes: identifier eq 'test' sort by id").getPageable(); + assertTrue(actual.getSort().isSorted()); + List orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + + // set ordering + actual = evaluateQuery("processes: identifier eq 'test' sort by id desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("processes: identifier eq 'test' sort by identifier desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("identifier", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("processes: identifier eq 'test' sort by title asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("title.defaultValue", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + actual = evaluateQuery("processes: identifier eq 'test' sort by version asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("version", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + actual = evaluateQuery("processes: identifier eq 'test' sort by creationDate asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("creationDate", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + // complex set ordering + actual = evaluateQuery("processes: identifier eq 'test' sort by id asc, title desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.defaultValue", orders.get(1).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(1).getDirection()); + + // complex default ordering + actual = evaluateQuery("processes: identifier eq 'test' sort by id asc, title").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.defaultValue", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); + + // complex default ordering + actual = evaluateQuery("processes: sort by id asc, title").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.defaultValue", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); + } + + @Test + public void testCaseSortingMongoDbQuery() { + // default (no sort) + Pageable actual = evaluateQuery("cases: processIdentifier eq 'test'").getPageable(); + assertFalse(actual.getSort().isSorted()); + + // default ordering asc + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id").getPageable(); + assertTrue(actual.getSort().isSorted()); + List orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + + // set ordering + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by processIdentifier desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("processIdentifier", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by title asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("title", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by processId asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("petriNetObjectId", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by creationDate asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("creationDate", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by author desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("author.id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + // complex set ordering + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id asc, title desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title", orders.get(1).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(1).getDirection()); + + // complex default ordering + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id asc, title").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); + + // complex default ordering + actual = evaluateQuery("cases: sort by id asc, title").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); + } + + @Test + public void testCaseSortingElasticQuery() { + // default (no sort) + Pageable actual = evaluateQuery("cases: data.field1.value eq 'test'").getPageable(); + assertFalse(actual.getSort().isSorted()); + + // default ordering asc + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id").getPageable(); + assertTrue(actual.getSort().isSorted()); + List orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("stringId.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + + // set ordering + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("stringId.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by processIdentifier desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("processIdentifier.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by title asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("title.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by processId asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("processId.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by creationDate asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("creationDateSortable", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by author desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("author.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by places.p1.marking desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("places.p1.marking", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by tasks.t1.state desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("tasks.t1.state.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by tasks.t1.userId desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("tasks.t1.userId.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + // complex set ordering + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id asc, title desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("stringId.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.keyword", orders.get(1).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(1).getDirection()); + + // complex default ordering + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id asc, title").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("stringId.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.keyword", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); + } + + @Test + public void testTaskSortingQuery() { + // default (no sort) + Pageable actual = evaluateQuery("tasks: title eq 'test'").getPageable(); + assertFalse(actual.getSort().isSorted()); + + // default ordering asc + actual = evaluateQuery("tasks: title eq 'test' sort by id").getPageable(); + assertTrue(actual.getSort().isSorted()); + List orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + + // set ordering + actual = evaluateQuery("tasks: title eq 'test' sort by id desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("tasks: title eq 'test' sort by transitionId desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("transitionId", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("tasks: title eq 'test' sort by title asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("title.defaultValue", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + actual = evaluateQuery("tasks: title eq 'test' sort by processId asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("processId", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + actual = evaluateQuery("tasks: title eq 'test' sort by caseId asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("caseId", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + actual = evaluateQuery("tasks: title eq 'test' sort by userId asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("userId", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + actual = evaluateQuery("tasks: title eq 'test' sort by lastAssign asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("lastAssigned", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + + actual = evaluateQuery("tasks: title eq 'test' sort by lastFinish desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("lastFinished", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + // complex set ordering + actual = evaluateQuery("tasks: title eq 'test' sort by id asc, title desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.defaultValue", orders.get(1).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(1).getDirection()); + + // complex default ordering + actual = evaluateQuery("tasks: title eq 'test' sort by id asc, title").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.defaultValue", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); + + // complex default ordering + actual = evaluateQuery("tasks: sort by id asc, title").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.defaultValue", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); + } + + @Test + public void testUserSortingQuery() { + // default (no sort) + Pageable actual = evaluateQuery("users: name eq 'test'").getPageable(); + assertFalse(actual.getSort().isSorted()); + + // default ordering asc + actual = evaluateQuery("users: name eq 'test' sort by id").getPageable(); + assertTrue(actual.getSort().isSorted()); + List orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + + // set ordering + actual = evaluateQuery("users: name eq 'test' sort by id desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("users: name eq 'test' sort by name desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("name", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); + + actual = evaluateQuery("users: name eq 'test' sort by surname asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("surname", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + actual = evaluateQuery("users: name eq 'test' sort by email asc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(1, orders.size()); + assertEquals("email", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + + // complex set ordering + actual = evaluateQuery("users: name eq 'test' sort by id asc, name desc").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("name", orders.get(1).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(1).getDirection()); + + // complex default ordering + actual = evaluateQuery("users: name eq 'test' sort by id asc, name").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("name", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); + + // complex default ordering + actual = evaluateQuery("users: sort by id asc, name").getPageable(); + assertTrue(actual.getSort().isSorted()); + orders = actual.getSort().toList(); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("name", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); + } + + @Test + public void testProcessQueriesFail() { + // using case, task, user attributes + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: processId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: processIdentifier eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: author eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: places.p1.marking eq 1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: tasks.t1.state eq enabled")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: tasks.t1.userId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: data.field1.value eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: data.field1.options contains 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: transitionId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: state eq enabled")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: userId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: caseId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: lastAssign eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: lastFinish eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: name eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: surname eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: email eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process email eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process page 2")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process:")); + } + + @Test + public void testCaseQueriesFail() { + // using process, task, user attributes + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: version eq 1.1.1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: transitionId eq 1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: state eq enabled")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: userId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: caseId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: lastAssign eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: lastFinish eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: name eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: surname eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: email eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case email eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case page 2")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case:")); + } + + @Test + public void testTaskQueriesFail() { + // using process, case, user attributes + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: identifier eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: version eq 1.1.1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: creationDate eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: processIdentifier eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: places.p1.marking eq 1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: tasks.t1.state eq enabled")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: tasks.t1.userId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: data.field1.value eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: data.field1.options contains 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: name eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: surname eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: email eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task email eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task page 2")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task:")); + } + + @Test + public void testUserQueriesFail() { + // using process, case, task attributes + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: identifier eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: version eq 1.1.1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: creationDate eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: processId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: processIdentifier eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: author eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: places.p1.marking eq 1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: tasks.t1.state eq enabled")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: tasks.t1.userId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: data.field1.value eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: data.field1.options contains 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: transitionId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: state eq enabled")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: userId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: caseId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: lastAssign eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: lastFinish eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user lastFinish eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user page 2")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user:")); + } + + @Test + public void testComparisonTypeFail() { + // id comparison + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id contains 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id lt 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id lte 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id gt 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id gte 'test'")); + + // number comparison + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: places.p1.marking contains 1")); + + // date/datetime comparison + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: creationDate contains 2020-03-03")); + + // boolean comparison + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value contains true")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value lt true")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value lte true")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value gt true")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value gte true")); + } + + private static void checkStringComparison(MongoDbUtils mongoDbUtils, String resource, String attribute, StringPath stringPath) { + Predicate actual = evaluateQuery(String.format("%s: %s eq 'test'", resource, attribute)).getFullMongoQuery(); + Predicate expected = stringPath.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s contains 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s lt 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.lt("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s lte 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.loe("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s gt 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.gt("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s gte 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.goe("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in ('test1', 'test2', 'test3')", resource, attribute)).getFullMongoQuery(); + expected = stringPath.in(List.of("test1", "test2", "test3")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in ('test1', 'test2', 'test3')", resource, attribute)).getFullMongoQuery(); + expected = stringPath.in(List.of("test1", "test2", "test3")).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in ('test1' : 'test2')", resource, attribute)).getFullMongoQuery(); + expected = stringPath.gt("test1").and(stringPath.lt("test2")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in ['test1' : 'test2']", resource, attribute)).getFullMongoQuery(); + expected = stringPath.goe("test1").and(stringPath.loe("test2")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in ('test1' : 'test2']", resource, attribute)).getFullMongoQuery(); + expected = stringPath.gt("test1").and(stringPath.loe("test2")).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + private static void checkDateComparison(MongoDbUtils mongoDbUtils, String resource, String attribute, DateTimePath dateTimePath) { + LocalDateTime date1 = LocalDateTime.of(2011, 12, 3, 10, 15, 30); + LocalDateTime date2 = LocalDateTime.of(2011, 12, 3, 11, 15, 30); + LocalDateTime date3 = LocalDateTime.of(2011, 12, 3, 12, 15, 30); + LocalDateTime date4 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + LocalDateTime date5 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + LocalDateTime date6 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + + Predicate actual = evaluateQuery(String.format("%s: %s eq 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + Predicate expected = dateTimePath.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s lt 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s lte 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.loe(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s gt 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s gte 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.goe(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03T10:15:30, 2011-12-03T11:15:30, 2011-12-03T12:15:30)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.in(List.of(date1, date2, date3)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30, 2011-12-03T11:15:30, 2011-12-03T12:15:30)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.in(List.of(date1, date2, date3)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03T10:15:30 : 2011-12-03T11:15:30)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(date1).and(dateTimePath.lt(date2)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in [2011-12-03T10:15:30 : 2011-12-03T11:15:30]", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.goe(date1).and(dateTimePath.loe(date2)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30 : 2011-12-03T11:15:30]", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(date1).and(dateTimePath.loe(date2)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03, 2011-12-03, 2011-12-03)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.in(List.of(date4, date5, date6)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03, 2011-12-03, 2011-12-03)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.in(List.of(date4, date5, date6)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03 : 2011-12-03)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(date4).and(dateTimePath.lt(date5)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in [2011-12-03 : 2011-12-03]", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.goe(date4).and(dateTimePath.loe(date5)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03 : 2011-12-03]", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(date4).and(dateTimePath.loe(date5)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + private static void checkStringComparisonElastic(String resource, String attribute, String resultAttribute) { + String actual = evaluateQuery(String.format("%s: %s eq 'test'", resource, attribute)).getFullElasticQuery(); + String expected = String.format("%s:test", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s contains 'test'", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:*test*", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s lt 'test'", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:test", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s gte 'test'", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>=test", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s in ('test1', 'test2', 'test3')", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:(test1 OR test2 OR test3)", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s not in ('test1', 'test2', 'test3')", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT %s:(test1 OR test2 OR test3)", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s in ('test1' : 'test2')", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>test1 AND %s:=test1 AND %s:<=test2)", resultAttribute, resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s not in ('test1' : 'test2']", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT (%s:>test1 AND %s:<=test2)", resultAttribute, resultAttribute); + + assertEquals(expected, actual); + } + + private static void checkNumberComparisonElastic(String resource, String attribute, String resultAttribute) { + String actual = evaluateQuery(String.format("%s: %s eq 1", resource, attribute)).getFullElasticQuery(); + String expected = String.format("%s:1", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s lt 1", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<1", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s lte 1", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<=1", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s gt 1", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>1", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s gte 1", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>=1", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s in (1, 2, 3)", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:(1 OR 2 OR 3)", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s not in (1, 2, 3)", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT %s:(1 OR 2 OR 3)", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s in (1 : 2)", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>1 AND %s:<2)", resultAttribute, resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s in [1 : 2]", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>=1 AND %s:<=2)", resultAttribute, resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s not in (1 : 2]", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT (%s:>1 AND %s:<=2)", resultAttribute, resultAttribute); + + assertEquals(expected, actual); + } + + private static void checkDateComparisonElastic(String resource, String attribute, String resultAttribute) { + LocalDateTime date1 = LocalDateTime.of(2011, 12, 3, 10, 15, 30); + LocalDateTime date2 = LocalDateTime.of(2011, 12, 3, 11, 15, 30); + LocalDateTime date3 = LocalDateTime.of(2011, 12, 3, 12, 15, 30); + LocalDateTime date4 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + LocalDateTime date5 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + LocalDateTime date6 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + + String actual = evaluateQuery(String.format("%s: %s eq 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + String expected = String.format("%s:%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s lt 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s lte 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<=%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s gt 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s gte 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>=%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03T10:15:30, 2011-12-03T11:15:30, 2011-12-03T12:15:30)", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:(%s OR %s OR %s)", resultAttribute, Timestamp.valueOf(date1).getTime(), Timestamp.valueOf(date2).getTime(), Timestamp.valueOf(date3).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30, 2011-12-03T11:15:30, 2011-12-03T12:15:30)", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT %s:(%s OR %s OR %s)", resultAttribute, Timestamp.valueOf(date1).getTime(), Timestamp.valueOf(date2).getTime(), Timestamp.valueOf(date3).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03T10:15:30 : 2011-12-03T11:15:30)", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>%s AND %s:<%s)", resultAttribute, Timestamp.valueOf(date1).getTime(), resultAttribute, Timestamp.valueOf(date2).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s in [2011-12-03T10:15:30 : 2011-12-03T11:15:30]", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>=%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date1).getTime(), resultAttribute, Timestamp.valueOf(date2).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30 : 2011-12-03T11:15:30]", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT (%s:>%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date1).getTime(), resultAttribute, Timestamp.valueOf(date2).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03, 2011-12-03, 2011-12-03)", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:(%s OR %s OR %s)", resultAttribute, Timestamp.valueOf(date4).getTime(), Timestamp.valueOf(date5).getTime(), Timestamp.valueOf(date6).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03, 2011-12-03, 2011-12-03)", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT %s:(%s OR %s OR %s)", resultAttribute, Timestamp.valueOf(date4).getTime(), Timestamp.valueOf(date5).getTime(), Timestamp.valueOf(date6).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03 : 2011-12-03)", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>%s AND %s:<%s)", resultAttribute, Timestamp.valueOf(date4).getTime(), resultAttribute, Timestamp.valueOf(date5).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s in [2011-12-03 : 2011-12-03]", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>=%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date4).getTime(), resultAttribute, Timestamp.valueOf(date5).getTime()); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03 : 2011-12-03]", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT (%s:>%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date4).getTime(), resultAttribute, Timestamp.valueOf(date5).getTime()); + + assertEquals(expected, actual); + } + + private static void compareMongoQueries(MongoDbUtils mongoDbUtils, Predicate actual, Predicate expected) { + Document actualDocument = mongoDbUtils.convertPredicateToDocument(actual); + Document expectedDocument = mongoDbUtils.convertPredicateToDocument(expected); + + assertEquals(expectedDocument, actualDocument); + } +} diff --git a/src/test/java/com/netgrif/application/engine/pfql/TaskSearchServiceTest.java b/src/test/java/com/netgrif/application/engine/pfql/TaskSearchServiceTest.java new file mode 100644 index 00000000000..3166f9329df --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/pfql/TaskSearchServiceTest.java @@ -0,0 +1,155 @@ +package com.netgrif.application.engine.pfql; + +import com.netgrif.application.engine.MockService; +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.domain.LoggedUser; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.taskresource.TaskSearchService; +import com.netgrif.application.engine.startup.ImportHelper; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.Task; +import com.netgrif.application.engine.workflow.domain.TaskPair; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class TaskSearchServiceTest { + + @Autowired + private TestHelper testHelper; + + @Autowired + private TaskSearchService taskSearchService; + + @Autowired + private ImportHelper importHelper; + + @Autowired + private MockService mockService; + + private String testTaskId; + + @BeforeEach + protected void beforeEach() { + testHelper.truncateDbs(); + Optional createdNetOpt = importHelper.createNet("/query_lang_test.xml"); + assertTrue(createdNetOpt.isPresent()); + Case testCase = importHelper.createCase("test", createdNetOpt.get()); + testTaskId = testCase.getTasks().stream() + .filter(taskPair -> taskPair.getTransition().equals("t1")) + .map(TaskPair::getTask) + .findFirst().get(); + } + + @Test + public void queryResourceTypeTest() { + assertEquals(QueryType.TASK, taskSearchService.getQueryResourceType()); + } + + @Test + public void searchOneTest() { + assertThrows(IllegalArgumentException.class, () -> taskSearchService.searchOne((String) null)); + assertThrows(IllegalArgumentException.class, () -> taskSearchService.searchOne("tasks: title eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> taskSearchService.searchOne("process: identifier eq 'query_lang_test'")); + + Task result = taskSearchService.searchOne("task: id eq '" + testTaskId + "'"); + assertNotNull(result); + assertEquals(testTaskId, result.getStringId()); + + login(mockService.mockLoggedUser()); + result = taskSearchService.searchOne("task: id eq '" + testTaskId + "'"); + assertNull(result); + logout(); + + result = taskSearchService.searchOne("task: id eq '" + new ObjectId() + "'"); + assertNull(result); + } + + @Test + public void searchAllTest() { + assertThrows(IllegalArgumentException.class, () -> taskSearchService.searchAll((String) null)); + assertThrows(IllegalArgumentException.class, () -> taskSearchService.searchAll("task: title eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> taskSearchService.searchAll("processes: identifier eq 'query_lang_test'")); + + Page result = taskSearchService.searchAll("tasks: id eq '" + testTaskId + "'"); + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertEquals(testTaskId, result.getContent().get(0).getStringId()); + + login(mockService.mockLoggedUser()); + result = taskSearchService.searchAll("tasks: id eq '" + testTaskId + "'"); + assertNotNull(result); + assertEquals(0, result.getTotalElements()); + logout(); + + result = taskSearchService.searchAll("tasks: id eq '" + new ObjectId() + "'"); + assertNotNull(result); + assertEquals(0, result.getTotalElements()); + } + + @Test + public void countTest() { + assertThrows(IllegalArgumentException.class, () -> taskSearchService.count((String) null)); + assertThrows(IllegalArgumentException.class, () -> taskSearchService.count("process: identifier eq 'query_lang_test'")); + + long result = taskSearchService.count("task: id eq '" + testTaskId + "'"); + assertEquals(1, result); + + login(mockService.mockLoggedUser()); + result = taskSearchService.count("task: id eq '" + testTaskId + "'"); + assertEquals(0, result); + logout(); + + result = taskSearchService.count("tasks: id eq '" + testTaskId + "'"); + assertEquals(1, result); + + result = taskSearchService.count("tasks: id eq '" + new ObjectId() + "'"); + assertEquals(0, result); + } + + @Test + public void existsTest() { + assertThrows(IllegalArgumentException.class, () -> taskSearchService.exists((String) null)); + assertThrows(IllegalArgumentException.class, () -> taskSearchService.exists("process: identifier eq 'query_lang_test'")); + + boolean result = taskSearchService.exists("task: id eq '" + testTaskId + "'"); + assertTrue(result); + + login(mockService.mockLoggedUser()); + result = taskSearchService.exists("task: id eq '" + testTaskId + "'"); + assertFalse(result); + logout(); + + result = taskSearchService.exists("tasks: id eq '" + testTaskId + "'"); + assertTrue(result); + + result = taskSearchService.exists("tasks: id eq '" + new ObjectId() + "'"); + assertFalse(result); + } + + private void login(LoggedUser loggedUser) { + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(loggedUser, null)); + } + + private void logout() { + SecurityContextHolder.getContext().setAuthentication(null); + } + +} diff --git a/src/test/java/com/netgrif/application/engine/pfql/UserSearchServiceTest.java b/src/test/java/com/netgrif/application/engine/pfql/UserSearchServiceTest.java new file mode 100644 index 00000000000..4f3c0a9ec41 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/pfql/UserSearchServiceTest.java @@ -0,0 +1,96 @@ +package com.netgrif.application.engine.pfql; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.userresource.UserSearchService; +import com.netgrif.application.engine.startup.SuperCreator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class UserSearchServiceTest { + + @Autowired + private TestHelper testHelper; + + @Autowired + private UserSearchService userSearchService; + + @Autowired + private SuperCreator superCreator; + + @BeforeEach + protected void beforeEach() { + testHelper.truncateDbs(); + } + + @Test + public void queryResourceTypeTest() { + assertEquals(QueryType.USER, userSearchService.getQueryResourceType()); + } + + @Test + public void searchOneTest() { + assertThrows(IllegalArgumentException.class, () -> userSearchService.searchOne((String) null)); + assertThrows(IllegalArgumentException.class, () -> userSearchService.searchOne("users: title eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> userSearchService.searchOne("process: identifier eq 'query_lang_test'")); + + IUser result = userSearchService.searchOne("user: email eq '" + superCreator.getSuperUser().getEmail() + "'"); + assertNotNull(result); + assertEquals(superCreator.getSuperUser().getStringId(), result.getStringId()); + + result = userSearchService.searchOne("user: email eq 'wrong'"); + assertNull(result); + } + + @Test + public void searchAllTest() { + assertThrows(IllegalArgumentException.class, () -> userSearchService.searchAll((String) null)); + assertThrows(IllegalArgumentException.class, () -> userSearchService.searchAll("user: title eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> userSearchService.searchAll("processes: identifier eq 'query_lang_test'")); + + Page result = userSearchService.searchAll("users: email eq '" + superCreator.getSuperUser().getEmail() + "'"); + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertEquals(superCreator.getSuperUser().getStringId(), result.getContent().get(0).getStringId()); + + result = userSearchService.searchAll("users: email eq 'wrong'"); + assertNotNull(result); + assertEquals(0, result.getTotalElements()); + } + + @Test + public void countTest() { + assertThrows(IllegalArgumentException.class, () -> userSearchService.count((String) null)); + assertThrows(IllegalArgumentException.class, () -> userSearchService.count("process: identifier eq 'query_lang_test'")); + + long result = userSearchService.count("users: email eq '" + superCreator.getSuperUser().getEmail() + "'"); + assertEquals(1, result); + + result = userSearchService.count("users: email eq 'wrong'"); + assertEquals(0, result); + } + + @Test + public void existsTest() { + assertThrows(IllegalArgumentException.class, () -> userSearchService.exists((String) null)); + assertThrows(IllegalArgumentException.class, () -> userSearchService.exists("process: identifier eq 'query_lang_test'")); + + boolean result = userSearchService.exists("users: email eq '" + superCreator.getSuperUser().getEmail() + "'"); + assertTrue(result); + + result = userSearchService.exists("users: email eq 'wrong'"); + assertFalse(result); + } +} diff --git a/src/test/java/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java b/src/test/java/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java new file mode 100644 index 00000000000..5fc8d04ffe5 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java @@ -0,0 +1,18 @@ +package com.netgrif.application.engine.pfql.utils; + +import com.querydsl.core.types.Predicate; + +import org.bson.Document; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.repository.support.SpringDataMongodbQuery; + +public class MongoDbUtils extends SpringDataMongodbQuery { + + public MongoDbUtils(MongoOperations operations, Class type) { + super(operations, type, operations.getCollectionName(type)); + } + + public Document convertPredicateToDocument(Predicate predicate) { + return this.createQuery(predicate); + } +} diff --git a/src/test/java/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java b/src/test/java/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java new file mode 100644 index 00000000000..b4ed91b129e --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java @@ -0,0 +1,58 @@ +package com.netgrif.application.engine.pfql.utils; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class SearchTestUtils { + + public static T convertToObject(Object object, Class targetClass) { + assert targetClass.isInstance(object); + return targetClass.cast(object); + } + + public static List convertToObjectList(Object objectList, Class targetClass) { + assert objectList instanceof List; + for (Object object : (List) objectList) { + assert targetClass.isInstance(object); + } + + return (List) objectList; + } + + public static void compareById(T actual, T expected, Function getId) { + assert getId.apply(actual).equals(getId.apply(expected)); + } + + public static void compareById(T actual, List expected, Function getId) { + List expectedIds = expected.stream() + .map(getId) + .collect(Collectors.toList()); + + assert expectedIds.contains(getId.apply(actual)); + } + + public static void compareById(List actual, List expected, Function getId) { + List actualIds = actual.stream() + .map(getId) + .collect(Collectors.toList()); + List expectedIds = expected.stream() + .map(getId) + .collect(Collectors.toList()); + + actualIds.sort(String::compareTo); + expectedIds.sort(String::compareTo); + assert actualIds.equals(expectedIds); + } + + public static void compareByIdInOrder(List actual, List expected, Function getId) { + List actualIds = actual.stream() + .map(getId) + .collect(Collectors.toList()); + List expectedIds = expected.stream() + .map(getId) + .collect(Collectors.toList()); + + assert actualIds.equals(expectedIds); + } +} diff --git a/src/test/java/com/netgrif/application/engine/workflow/service/TaskServiceTest.java b/src/test/java/com/netgrif/application/engine/workflow/service/TaskServiceTest.java index ee48a912435..5fa6c01ec7f 100644 --- a/src/test/java/com/netgrif/application/engine/workflow/service/TaskServiceTest.java +++ b/src/test/java/com/netgrif/application/engine/workflow/service/TaskServiceTest.java @@ -1,5 +1,6 @@ package com.netgrif.application.engine.workflow.service; +import com.netgrif.application.engine.TestHelper; import com.netgrif.application.engine.auth.domain.Authority; import com.netgrif.application.engine.auth.domain.LoggedUser; import com.netgrif.application.engine.auth.domain.User; @@ -57,33 +58,24 @@ public class TaskServiceTest { @Autowired private TaskRepository taskRepository; - @Autowired - private MongoTemplate mongoTemplate; - @Autowired private UserRepository userRepository; @Autowired private IAuthorityService authorityService; - @Autowired - private SystemUserRunner userRunner; - - @Autowired - private UriRunner uriRunner; - @Autowired private IPetriNetService petriNetService; @Autowired private SuperCreator superCreator; + @Autowired + private TestHelper testHelper; + @BeforeEach public void setUp() throws Exception { - mongoTemplate.getDb().drop(); - taskRepository.deleteAll(); - userRunner.run(""); - uriRunner.run(); + testHelper.truncateDbs(); petriNetService.importPetriNet(new FileInputStream("src/test/resources/prikladFM.xml"), VersionType.MAJOR, superCreator.getLoggedSuper()); PetriNet net = petriNetRepository.findAll().get(0); diff --git a/src/test/resources/actionref_test.xml b/src/test/resources/actionref_test.xml index a9693d9cbdc..6c788bd23f1 100644 --- a/src/test/resources/actionref_test.xml +++ b/src/test/resources/actionref_test.xml @@ -2,6 +2,7 @@ actionref_test.xml + 1.0.0 TST Test diff --git a/src/test/resources/agreement.xml b/src/test/resources/agreement.xml index 4d585489a22..64822739d66 100644 --- a/src/test/resources/agreement.xml +++ b/src/test/resources/agreement.xml @@ -1,6 +1,7 @@ Agreement - Agr + 1.0.0 + Agr Agreement device_hub true diff --git a/src/test/resources/all_data.xml b/src/test/resources/all_data.xml index b1038511029..4b603eaa738 100644 --- a/src/test/resources/all_data.xml +++ b/src/test/resources/all_data.xml @@ -1,8 +1,10 @@ all_data + 1.0.0 All Data ALL + true process_role diff --git a/src/test/resources/all_data_pdf.xml b/src/test/resources/all_data_pdf.xml index 408e03836b3..a2d4b956f41 100644 --- a/src/test/resources/all_data_pdf.xml +++ b/src/test/resources/all_data_pdf.xml @@ -1,8 +1,10 @@ all_data + 1.0.0 All Data ALL + true process_role diff --git a/src/test/resources/application-test-ldap.properties b/src/test/resources/application-test-ldap.properties index 1e3afef7ccb..d9cd4fd7246 100644 --- a/src/test/resources/application-test-ldap.properties +++ b/src/test/resources/application-test-ldap.properties @@ -61,7 +61,7 @@ logging.level.com.netgrif.application.engine.admin.AdminConsoleRunner=debug logging.level.com.netgrif.application.engine.petrinet.domain.dataset.logic.action.runner.CaseFieldsExpressionRunner=debug nae.actions.imports=org.bson.types.ObjectId,com.netgrif.application.engine.petrinet.domain.version.Version,com.netgrif.application.engine.petrinet.domain.PetriNet,com.netgrif.application.engine.petrinet.domain.I18nString -nae.actions.star-imports=java.time +nae.actions.star-imports=java.time,com.netgrif.application.engine.petrinet.domain.version,com.netgrif.application.engine.petrinet.domain nae.actions.static-star-imports=java.time.LocalDate # case field expression runner diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties index 23248333d50..8c6675c79b7 100644 --- a/src/test/resources/application-test.properties +++ b/src/test/resources/application-test.properties @@ -61,7 +61,7 @@ logging.level.com.netgrif.application.engine.admin.AdminConsoleRunner=debug logging.level.com.netgrif.application.engine.petrinet.domain.dataset.logic.action.runner.CaseFieldsExpressionRunner=debug nae.actions.imports=org.bson.types.ObjectId,com.netgrif.application.engine.petrinet.domain.version.Version,com.netgrif.application.engine.petrinet.domain.PetriNet,com.netgrif.application.engine.petrinet.domain.I18nString -nae.actions.star-imports=java.time +nae.actions.star-imports=java.time,com.netgrif.application.engine.petrinet.domain.version,com.netgrif.application.engine.petrinet.domain nae.actions.static-star-imports=java.time.LocalDate nae.ldap.enabled=false diff --git a/src/test/resources/arc_order_test.xml b/src/test/resources/arc_order_test.xml index 8a63dff1e68..93295fb348c 100644 --- a/src/test/resources/arc_order_test.xml +++ b/src/test/resources/arc_order_test.xml @@ -2,6 +2,7 @@ arc_order_test.xml + 1.0.0 TST Test diff --git a/src/test/resources/arc_reference_invalid_test.xml b/src/test/resources/arc_reference_invalid_test.xml index 92d83316250..c433f155403 100644 --- a/src/test/resources/arc_reference_invalid_test.xml +++ b/src/test/resources/arc_reference_invalid_test.xml @@ -3,6 +3,7 @@ xsi:noNamespaceSchemaLocation="https://petriflow.org/petriflow.schema.xsd"> arc_ref_invalid_test + 1.0.0 ARi Arc ref invalid test diff --git a/src/test/resources/arc_reference_test.xml b/src/test/resources/arc_reference_test.xml index 8ddfde6422d..63011ca025c 100644 --- a/src/test/resources/arc_reference_test.xml +++ b/src/test/resources/arc_reference_test.xml @@ -2,6 +2,7 @@ arc_ref_test + 1.0.0 ART Arc ref test diff --git a/src/test/resources/assignRoleMainNet_test_.xml b/src/test/resources/assignRoleMainNet_test_.xml index 53a21a1703e..fb78f145a49 100644 --- a/src/test/resources/assignRoleMainNet_test_.xml +++ b/src/test/resources/assignRoleMainNet_test_.xml @@ -2,6 +2,7 @@ main + 1.0.0 ORG main business diff --git a/src/test/resources/assignRoleSecondaryNet_test.xml b/src/test/resources/assignRoleSecondaryNet_test.xml index 493dfab39a2..022c174c202 100644 --- a/src/test/resources/assignRoleSecondaryNet_test.xml +++ b/src/test/resources/assignRoleSecondaryNet_test.xml @@ -2,6 +2,7 @@ secondary + 1.0.0 ORG secondary business diff --git a/src/test/resources/assign_cancel_finish_with_Case_test.xml b/src/test/resources/assign_cancel_finish_with_Case_test.xml index eb87a06e4c7..87ce988db0a 100644 --- a/src/test/resources/assign_cancel_finish_with_Case_test.xml +++ b/src/test/resources/assign_cancel_finish_with_Case_test.xml @@ -2,6 +2,7 @@ assign_cancel_finish_with_Case_net + 1.0.0 TST Test false diff --git a/src/test/resources/autotrigger_taskref.xml b/src/test/resources/autotrigger_taskref.xml index eaa031db8cb..f7ae164ade1 100644 --- a/src/test/resources/autotrigger_taskref.xml +++ b/src/test/resources/autotrigger_taskref.xml @@ -2,7 +2,8 @@ Praca - PRC + 1.0.0 + PRC Práca true false diff --git a/src/test/resources/button_set_data_test.xml b/src/test/resources/button_set_data_test.xml index 50707a280d9..7ae8316ef43 100644 --- a/src/test/resources/button_set_data_test.xml +++ b/src/test/resources/button_set_data_test.xml @@ -1,6 +1,7 @@ button_set_data - SDB + 1.0.0 + SDB Set Data Button v3 device_hub true diff --git a/src/test/resources/case_choices_test.xml b/src/test/resources/case_choices_test.xml index 8c9f355a071..75430bc76ec 100644 --- a/src/test/resources/case_choices_test.xml +++ b/src/test/resources/case_choices_test.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test true diff --git a/src/test/resources/case_name_change_test.xml b/src/test/resources/case_name_change_test.xml index d204b49b11e..3ccc401ef30 100644 --- a/src/test/resources/case_name_change_test.xml +++ b/src/test/resources/case_name_change_test.xml @@ -1,6 +1,7 @@ case_name_change_test - CNC + 1.0.0 + CNC Case name change Test device_hub true diff --git a/src/test/resources/case_search_test.xml b/src/test/resources/case_search_test.xml index 7c8db1daaca..d15f21a59dc 100644 --- a/src/test/resources/case_search_test.xml +++ b/src/test/resources/case_search_test.xml @@ -1,6 +1,7 @@ case_search_test.xml + 1.0.0 TST Test true diff --git a/src/test/resources/caseref_test.xml b/src/test/resources/caseref_test.xml index 3df3f896cf7..330f125d3d4 100644 --- a/src/test/resources/caseref_test.xml +++ b/src/test/resources/caseref_test.xml @@ -2,6 +2,7 @@ caseref_test.xml + 1.0.0 TST Test diff --git a/src/test/resources/change_allowed_nets_action_test.xml b/src/test/resources/change_allowed_nets_action_test.xml index 9c9400a16c6..1b2c5669971 100644 --- a/src/test/resources/change_allowed_nets_action_test.xml +++ b/src/test/resources/change_allowed_nets_action_test.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test true diff --git a/src/test/resources/change_behavior_update.xml b/src/test/resources/change_behavior_update.xml index 90c91201fb9..4cda64ee483 100644 --- a/src/test/resources/change_behavior_update.xml +++ b/src/test/resources/change_behavior_update.xml @@ -1,6 +1,7 @@ new_model - NEW + 1.0.0 + NEW New Model device_hub true diff --git a/src/test/resources/change_caseref_value_action_test.xml b/src/test/resources/change_caseref_value_action_test.xml index 10ebcae9a95..88f3933adb5 100644 --- a/src/test/resources/change_caseref_value_action_test.xml +++ b/src/test/resources/change_caseref_value_action_test.xml @@ -2,6 +2,7 @@ change_value + 1.0.0 TST Test true diff --git a/src/test/resources/constructor_destructor.xml b/src/test/resources/constructor_destructor.xml index 25629c073a7..262c7000894 100644 --- a/src/test/resources/constructor_destructor.xml +++ b/src/test/resources/constructor_destructor.xml @@ -2,6 +2,7 @@ constructor_destructor + 1.0.0 CAD Constructor and Destructor business diff --git a/src/test/resources/create_case_locale.xml b/src/test/resources/create_case_locale.xml index 00676b3b6a6..7091aeab520 100644 --- a/src/test/resources/create_case_locale.xml +++ b/src/test/resources/create_case_locale.xml @@ -2,6 +2,7 @@ currency_test.xml + 1.0.0 TST Test Slovenský preklad diff --git a/src/test/resources/csv/create case permissions - correct default disabled.csv b/src/test/resources/csv/create case permissions - correct default disabled.csv new file mode 100644 index 00000000000..c4f6fe948fd --- /dev/null +++ b/src/test/resources/csv/create case permissions - correct default disabled.csv @@ -0,0 +1,4 @@ +Transition title,roleRef,No permissions,Has role +role=true,true,false,true +role=false,false,false,false +role=undefined,undefined,false,false \ No newline at end of file diff --git a/src/test/resources/csv/create case permissions - correct.csv b/src/test/resources/csv/create case permissions - correct.csv new file mode 100644 index 00000000000..743aa3ea3d8 --- /dev/null +++ b/src/test/resources/csv/create case permissions - correct.csv @@ -0,0 +1,10 @@ +Transition title,default role,roleRef,No permissions,Has role +role=true default=true,true,true,true,true +role=true default=false,false,true,false,false +role=true default=undefined,undefined,true,false,true +role=false default=true,true,false,true,false +role=false default=false,false,false,false,false +role=false default=undefined,undefined,false,false,false +role=undefined default=true,true,undefined,true,true +role=undefined default=false,false,undefined,false,false +role=undefined default=undefined,undefined,undefined,true,true \ No newline at end of file diff --git a/src/test/resources/csv/permissions - correct default disabled.csv b/src/test/resources/csv/permissions - correct default disabled.csv new file mode 100644 index 00000000000..bba1da1c6cf --- /dev/null +++ b/src/test/resources/csv/permissions - correct default disabled.csv @@ -0,0 +1,10 @@ +Transition ID,Transition title,roleRef,userRef,No permissions,Has role,Is in userList,Has role and is in userList +t_007,role=true user=true,TRUE,TRUE,FALSE,TRUE,TRUE,TRUE +t_008,role=true user=false,TRUE,FALSE,FALSE,TRUE,FALSE,FALSE +t_009,role=true user=undefined,TRUE,undefined,FALSE,TRUE,FALSE,TRUE +t_016,role=false user=true,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE +t_017,role=false user=false,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE +t_018,role=false user=undefined,FALSE,undefined,FALSE,FALSE,FALSE,FALSE +t_025,role=undefined user=true,undefined,TRUE,FALSE,FALSE,TRUE,TRUE +t_026,role=undefined user=false,undefined,FALSE,FALSE,FALSE,FALSE,FALSE +t_027,role=undefined user=undefined,undefined,undefined,FALSE,FALSE,FALSE,FALSE \ No newline at end of file diff --git a/src/test/resources/csv/permissions - correct.csv b/src/test/resources/csv/permissions - correct.csv new file mode 100644 index 00000000000..feb3ef6f842 --- /dev/null +++ b/src/test/resources/csv/permissions - correct.csv @@ -0,0 +1,28 @@ +Transition ID,Transition title,default role,roleRef,userRef,No permissions,Has role,Is in userList,Has role and is in userList +t_005,role=true default=false user=false,false,true,false,FALSE,FALSE,FALSE,FALSE +t_014,role=false default=false user=false,false,false,false,FALSE,FALSE,FALSE,FALSE +t_023,role=undefined default=false user=false,false,undefined,false,FALSE,FALSE,FALSE,FALSE +t_004,role=true default=false user=true,false,true,true,FALSE,FALSE,TRUE,TRUE +t_013,role=false default=false user=true,false,false,true,FALSE,FALSE,TRUE,TRUE +t_022,role=undefined default=false user=true,false,undefined,true,FALSE,FALSE,TRUE,TRUE +t_006,role=true default=false user=undefined,false,true,undefined,FALSE,FALSE,FALSE,FALSE +t_015,role=false default=false user=undefined,false,false,undefined,FALSE,FALSE,FALSE,FALSE +t_024,role=undefined default=false user=undefined,false,undefined,undefined,FALSE,FALSE,FALSE,FALSE +t_002,role=true default=true user=false,true,true,false,TRUE,TRUE,FALSE,FALSE +t_011,role=false default=true user=false,true,false,false,TRUE,FALSE,FALSE,FALSE +t_020,role=undefined default=true user=false,true,undefined,false,TRUE,TRUE,FALSE,FALSE +t_001,role=true default=true user=true,true,true,true,TRUE,TRUE,TRUE,TRUE +t_010,role=false default=true user=true,true,false,true,TRUE,FALSE,TRUE,TRUE +t_019,role=undefined default=true user=true,true,undefined,true,TRUE,TRUE,TRUE,TRUE +t_003,role=true default=true user=undefined,true,true,undefined,TRUE,TRUE,TRUE,TRUE +t_012,role=false default=true user=undefined,true,false,undefined,TRUE,FALSE,TRUE,FALSE +t_021,role=undefined default=true user=undefined,true,undefined,undefined,TRUE,TRUE,TRUE,TRUE +t_008,role=true default=undefined user=false,undefined,true,false,FALSE,TRUE,FALSE,FALSE +t_017,role=false default=undefined user=false,undefined,false,false,FALSE,FALSE,FALSE,FALSE +t_026,role=undefined default=undefined user=false,undefined,undefined,false,FALSE,FALSE,FALSE,FALSE +t_007,role=true default=undefined user=true,undefined,true,true,FALSE,TRUE,TRUE,TRUE +t_016,role=false default=undefined user=true,undefined,false,true,FALSE,FALSE,TRUE,TRUE +t_025,role=undefined default=undefined user=true,undefined,undefined,true,FALSE,FALSE,TRUE,TRUE +t_009,role=true default=undefined user=undefined,undefined,true,undefined,FALSE,TRUE,FALSE,TRUE +t_018,role=false default=undefined user=undefined,undefined,false,undefined,FALSE,FALSE,FALSE,FALSE +t_027,role=undefined default=undefined user=undefined,undefined,undefined,undefined,TRUE,TRUE,TRUE,TRUE \ No newline at end of file diff --git a/src/test/resources/currency_test.xml b/src/test/resources/currency_test.xml index f806c113078..781abc5a7f0 100644 --- a/src/test/resources/currency_test.xml +++ b/src/test/resources/currency_test.xml @@ -2,6 +2,7 @@ currency_test.xml + 1.0.0 TST Test diff --git a/src/test/resources/dashboard_management_test.xml b/src/test/resources/dashboard_management_test.xml new file mode 100644 index 00000000000..5a6483016fe --- /dev/null +++ b/src/test/resources/dashboard_management_test.xml @@ -0,0 +1,56 @@ + + dashboard_import + 1.0.0 + MEI + Dashboard import + device_hub + true + true + false + + + new_model_upload + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/data_button_test.xml b/src/test/resources/data_button_test.xml index 95d4c80ca9d..8fe2b829cdc 100644 --- a/src/test/resources/data_button_test.xml +++ b/src/test/resources/data_button_test.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test true diff --git a/src/test/resources/data_map.xml b/src/test/resources/data_map.xml index b87ee09ec38..cf58969cbf7 100644 --- a/src/test/resources/data_map.xml +++ b/src/test/resources/data_map.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test true diff --git a/src/test/resources/data_map_2.xml b/src/test/resources/data_map_2.xml index 29fdf4f8d4f..d788e685741 100644 --- a/src/test/resources/data_map_2.xml +++ b/src/test/resources/data_map_2.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test true diff --git a/src/test/resources/data_test.xml b/src/test/resources/data_test.xml index 6fe0ca71154..f41fde0f60e 100644 --- a/src/test/resources/data_test.xml +++ b/src/test/resources/data_test.xml @@ -1,6 +1,7 @@ 1 + 1.0.0 TES Data test true diff --git a/src/test/resources/data_text_validation.xml b/src/test/resources/data_text_validation.xml index cf90b3a361e..06f6a5b75be 100644 --- a/src/test/resources/data_text_validation.xml +++ b/src/test/resources/data_text_validation.xml @@ -2,6 +2,7 @@ F test + 1.0.0 TST Test true diff --git a/src/test/resources/datagroup_test.xml b/src/test/resources/datagroup_test.xml index ab041058138..021eaafb9b3 100644 --- a/src/test/resources/datagroup_test.xml +++ b/src/test/resources/datagroup_test.xml @@ -1,6 +1,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/datagroup_test_layout.xml b/src/test/resources/datagroup_test_layout.xml index 29202ba0c28..ff8bef95dca 100644 --- a/src/test/resources/datagroup_test_layout.xml +++ b/src/test/resources/datagroup_test_layout.xml @@ -2,6 +2,7 @@ datagroup_test + 1.0.0 DGT Data group test true diff --git a/src/test/resources/enum_list.xml b/src/test/resources/enum_list.xml index f9f5893bffa..5042a939a37 100644 --- a/src/test/resources/enum_list.xml +++ b/src/test/resources/enum_list.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test true diff --git a/src/test/resources/enumeration_multichoice_options.xml b/src/test/resources/enumeration_multichoice_options.xml index e0bcfc26be5..389ef5d75ef 100644 --- a/src/test/resources/enumeration_multichoice_options.xml +++ b/src/test/resources/enumeration_multichoice_options.xml @@ -1,8 +1,9 @@ enumeration_multichoice_options - Enumeration/multichoice options + 1.0.0 EMO + Enumeration/multichoice options true diff --git a/src/test/resources/event_test.xml b/src/test/resources/event_test.xml index 49715c88f7b..47cc06b0d01 100644 --- a/src/test/resources/event_test.xml +++ b/src/test/resources/event_test.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/field_view.xml b/src/test/resources/field_view.xml index 4a771ee1860..bbc8a13a29f 100644 --- a/src/test/resources/field_view.xml +++ b/src/test/resources/field_view.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/file_test.xml b/src/test/resources/file_test.xml index 7557663904a..58438bca1ed 100644 --- a/src/test/resources/file_test.xml +++ b/src/test/resources/file_test.xml @@ -1,6 +1,7 @@ test + 1.0.0 TST Test true diff --git a/src/test/resources/file_test_net.xml b/src/test/resources/file_test_net.xml index 247c8fdf1cb..966ff8416f1 100644 --- a/src/test/resources/file_test_net.xml +++ b/src/test/resources/file_test_net.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/flow.xml b/src/test/resources/flow.xml index 4a21ac59be5..1f2436d11c3 100644 --- a/src/test/resources/flow.xml +++ b/src/test/resources/flow.xml @@ -1,5 +1,6 @@ new_model + 1.0.0 NEW New Model device_hub diff --git a/src/test/resources/initial_behavior.xml b/src/test/resources/initial_behavior.xml index 26842135573..8a0451f2227 100644 --- a/src/test/resources/initial_behavior.xml +++ b/src/test/resources/initial_behavior.xml @@ -1,6 +1,7 @@ initial_behavior + 1.0.0 Initial behavior IBH true diff --git a/src/test/resources/insurance_portal_demo_test.xml b/src/test/resources/insurance_portal_demo_test.xml index b37cb65c50d..70b0851dce0 100644 --- a/src/test/resources/insurance_portal_demo_test.xml +++ b/src/test/resources/insurance_portal_demo_test.xml @@ -1,6 +1,7 @@ test + 1.0.0 TST Test true diff --git a/src/test/resources/invalid_data_ref_layout.xml b/src/test/resources/invalid_data_ref_layout.xml index e438bc9371c..bd34c87810c 100644 --- a/src/test/resources/invalid_data_ref_layout.xml +++ b/src/test/resources/invalid_data_ref_layout.xml @@ -1,6 +1,7 @@ invalid_data_ref_layout - IDR + 1.0.0 + IDR Invalid data ref in grid layout device_hub true diff --git a/src/test/resources/ipc_bulk.xml b/src/test/resources/ipc_bulk.xml index f6e150eeeba..e569220b401 100644 --- a/src/test/resources/ipc_bulk.xml +++ b/src/test/resources/ipc_bulk.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test true diff --git a/src/test/resources/ipc_createCase.xml b/src/test/resources/ipc_createCase.xml index 09a03611dcd..c7b60fa62c4 100644 --- a/src/test/resources/ipc_createCase.xml +++ b/src/test/resources/ipc_createCase.xml @@ -2,6 +2,7 @@ create_case_net + 1.0.0 TST Test true diff --git a/src/test/resources/ipc_data.xml b/src/test/resources/ipc_data.xml index 4a99b3de73c..f34cfeead25 100644 --- a/src/test/resources/ipc_data.xml +++ b/src/test/resources/ipc_data.xml @@ -2,8 +2,10 @@ test + 1.0.0 TST Test + true data_text diff --git a/src/test/resources/ipc_group.xml b/src/test/resources/ipc_group.xml index 85f837c1c53..8d115b21556 100644 --- a/src/test/resources/ipc_group.xml +++ b/src/test/resources/ipc_group.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/ipc_set_data.xml b/src/test/resources/ipc_set_data.xml index 1e94beee85c..b90181e3053 100644 --- a/src/test/resources/ipc_set_data.xml +++ b/src/test/resources/ipc_set_data.xml @@ -2,8 +2,10 @@ test + 1.0.0 TST Test + true data_text diff --git a/src/test/resources/ipc_task_search.xml b/src/test/resources/ipc_task_search.xml index c9211dbde03..4683f01f3aa 100644 --- a/src/test/resources/ipc_task_search.xml +++ b/src/test/resources/ipc_task_search.xml @@ -2,6 +2,7 @@ create_case_net + 1.0.0 TST Test true diff --git a/src/test/resources/ipc_transition_role.xml b/src/test/resources/ipc_transition_role.xml index 696d51769e1..2c786a9f3c6 100644 --- a/src/test/resources/ipc_transition_role.xml +++ b/src/test/resources/ipc_transition_role.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test false diff --git a/src/test/resources/ipc_where.xml b/src/test/resources/ipc_where.xml index c9d6991a51f..7922f8ac1b8 100644 --- a/src/test/resources/ipc_where.xml +++ b/src/test/resources/ipc_where.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test true @@ -32,7 +33,7 @@ field: f.field; execute "synchronized" where { - it.title.eq("Case 1") + it.title.eq("CaseApiTestCase 1") } with ([ "field": [ value: 69.0, @@ -45,7 +46,7 @@ field: f.field; change field about { - findCase({it.title.eq("Case 1")}); + findCase({it.title.eq("CaseApiTestCase 1")}); return 0 as Double; } @@ -54,7 +55,7 @@ field: f.count; - def cases = findCases({it.title.startsWith("Case ")}); + def cases = findCases({it.title.startsWith("CaseApiTestCase ")}); change field value { return cases.size(); } @@ -63,7 +64,7 @@ field: f.paged; - def cases = findCases({ it.title.startsWith("Case ")}, org.springframework.data.domain.PageRequest.of(1, 4)); + def cases = findCases({ it.title.startsWith("CaseApiTestCase ")}, org.springframework.data.domain.PageRequest.of(1, 4)); change field value { return cases.size(); } diff --git a/src/test/resources/mapping_test.xml b/src/test/resources/mapping_test.xml index 960b0f5f94b..25635cdcbd2 100755 --- a/src/test/resources/mapping_test.xml +++ b/src/test/resources/mapping_test.xml @@ -3,6 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://petriflow.org/petriflow.schema.xsd"> test + 1.0.0 TST Test false diff --git a/src/test/resources/nae-1272_datagroup_divider_improvement.xml b/src/test/resources/nae-1272_datagroup_divider_improvement.xml index 82ebc005f2e..8e60fba34df 100644 --- a/src/test/resources/nae-1272_datagroup_divider_improvement.xml +++ b/src/test/resources/nae-1272_datagroup_divider_improvement.xml @@ -2,6 +2,7 @@ nae_1272 + 1.0.0 NAE NAE-1272 Datagroup divider improvement true diff --git a/src/test/resources/net_clone.xml b/src/test/resources/net_clone.xml index 4f54118ad3e..e1fb2199c29 100644 --- a/src/test/resources/net_clone.xml +++ b/src/test/resources/net_clone.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/pdf_run_action.xml b/src/test/resources/pdf_run_action.xml index cba7da838c1..b89caead343 100644 --- a/src/test/resources/pdf_run_action.xml +++ b/src/test/resources/pdf_run_action.xml @@ -1,6 +1,7 @@ 1 - TES + 1.0.0 + TES PDF test true false diff --git a/src/test/resources/pdf_test_1.xml b/src/test/resources/pdf_test_1.xml index fd4222adc5e..beeab79b98d 100644 --- a/src/test/resources/pdf_test_1.xml +++ b/src/test/resources/pdf_test_1.xml @@ -2,6 +2,7 @@ 1 + 1.0.0 TES Data test true diff --git a/src/test/resources/pdf_test_2.xml b/src/test/resources/pdf_test_2.xml index 15831035c50..dd0a9a717f9 100644 --- a/src/test/resources/pdf_test_2.xml +++ b/src/test/resources/pdf_test_2.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test true diff --git a/src/test/resources/pdf_test_3.xml b/src/test/resources/pdf_test_3.xml index 4755336cbac..af32844348b 100644 --- a/src/test/resources/pdf_test_3.xml +++ b/src/test/resources/pdf_test_3.xml @@ -6,6 +6,7 @@ PER Personal Information contacts + true Surname Question diff --git a/src/test/resources/petriNets/NAE-1290_Export_actions.xml b/src/test/resources/petriNets/NAE-1290_Export_actions.xml index 33794373055..f8856553877 100644 --- a/src/test/resources/petriNets/NAE-1290_Export_actions.xml +++ b/src/test/resources/petriNets/NAE-1290_Export_actions.xml @@ -1,5 +1,6 @@ export_test + 1.0.0 EXT Export actions test true diff --git a/src/test/resources/petriNets/NAE-1616_duplicate_action_id.xml b/src/test/resources/petriNets/NAE-1616_duplicate_action_id.xml index e8bf6688c6f..8176318378b 100644 --- a/src/test/resources/petriNets/NAE-1616_duplicate_action_id.xml +++ b/src/test/resources/petriNets/NAE-1616_duplicate_action_id.xml @@ -1,5 +1,6 @@ NAE_1616 + 1.0.0 NAE NAE-1616 - Cannot upload process with action id bug_report diff --git a/src/test/resources/petriNets/NAE-2390_action_api_improvements.xml b/src/test/resources/petriNets/NAE-2390_action_api_improvements.xml new file mode 100644 index 00000000000..4cd61d922b0 --- /dev/null +++ b/src/test/resources/petriNets/NAE-2390_action_api_improvements.xml @@ -0,0 +1,214 @@ + + action_api_improvements + 1.0.0 + NEW + Action API Improvements Test + device_hub + true + true + false + + caseRef + + <allowedNets> + <allowedNet>action_api_improvements</allowedNet> + </allowedNets> + </data> + <data type="enumeration"> + <id>enumeration</id> + <title/> + </data> + <data type="enumeration_map"> + <id>enumeration_map</id> + <title/> + </data> + <data type="multichoice"> + <id>multichoice</id> + <title/> + </data> + <data type="multichoice_map"> + <id>multichoice_map</id> + <title/> + </data> + <data type="stringCollection"> + <id>stringCollection</id> + <title/> + </data> + <data type="taskRef"> + <id>taskRef</id> + <title/> + </data> + <data type="text"> + <id>text</id> + <title/> + </data> + <transition> + <id>t1</id> + <x>336</x> + <y>112</y> + <label/> + <dataGroup> + <id>t1_0</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>enumeration_map</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>caseRef</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>2</x> + <y>0</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>taskRef</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>stringCollection</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>enumeration</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>2</x> + <y>2</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>multichoice</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>3</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>text</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>2</x> + <y>3</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>multichoice_map</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>2</x> + <y>4</y> + <rows>1</rows> + <cols>2</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + </transition> + <transition> + <id>t2</id> + <x>208</x> + <y>240</y> + <label/> + </transition> + <transition> + <id>t3</id> + <x>336</x> + <y>240</y> + <label/> + </transition> + <transition> + <id>t4</id> + <x>464</x> + <y>240</y> + <label/> + </transition> + <place> + <id>p1</id> + <x>208</x> + <y>112</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>p2</id> + <x>464</x> + <y>112</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>regular</type> + <sourceId>p1</sourceId> + <destinationId>t1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>t1</sourceId> + <destinationId>p2</destinationId> + <multiplicity>1</multiplicity> + </arc> +</document> \ No newline at end of file diff --git a/src/test/resources/petriNets/NAE_1305_Loading_na_set_data_pre_button.xml b/src/test/resources/petriNets/NAE_1305_Loading_na_set_data_pre_button.xml index 729ea089a57..20151d11349 100644 --- a/src/test/resources/petriNets/NAE_1305_Loading_na_set_data_pre_button.xml +++ b/src/test/resources/petriNets/NAE_1305_Loading_na_set_data_pre_button.xml @@ -2,6 +2,7 @@ <document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://petriflow.org/petriflow.schema.xsd"> <id>NAE_1305_Loading_na_set_data_pre_button</id> + <version>1.0.0</version> <initials>ALL</initials> <title>NAE_1305_Loading_na_set_data_pre_button Test default case name diff --git a/src/test/resources/petriNets/NAE_1382_first_trans_auto.xml b/src/test/resources/petriNets/NAE_1382_first_trans_auto.xml index ea2f20a7173..a59752e2c88 100644 --- a/src/test/resources/petriNets/NAE_1382_first_trans_auto.xml +++ b/src/test/resources/petriNets/NAE_1382_first_trans_auto.xml @@ -1,7 +1,8 @@ NAE_1382_first_trans_auto - ERR + 1.0.0 + ERR New Model true false diff --git a/src/test/resources/petriNets/NAE_1382_first_trans_auto_2.xml b/src/test/resources/petriNets/NAE_1382_first_trans_auto_2.xml index e2bf2005b9e..d238cc59779 100644 --- a/src/test/resources/petriNets/NAE_1382_first_trans_auto_2.xml +++ b/src/test/resources/petriNets/NAE_1382_first_trans_auto_2.xml @@ -1,7 +1,8 @@ NAE_1382_first_trans_auto - ERR + 1.0.0 + ERR New Model true false diff --git a/src/test/resources/petriNets/all_data_refs.xml b/src/test/resources/petriNets/all_data_refs.xml index c261c14cbd1..25bf011971d 100644 --- a/src/test/resources/petriNets/all_data_refs.xml +++ b/src/test/resources/petriNets/all_data_refs.xml @@ -1,7 +1,8 @@ all_data - All Data + 1.0.0 + All Data ALL diff --git a/src/test/resources/petriNets/change_field_value_init.xml b/src/test/resources/petriNets/change_field_value_init.xml index 6bef2b17ba0..7fe06b7d06f 100644 --- a/src/test/resources/petriNets/change_field_value_init.xml +++ b/src/test/resources/petriNets/change_field_value_init.xml @@ -1,6 +1,7 @@ change_field_value_init + 1.0.0 change_field_value_init true CFVI diff --git a/src/test/resources/petriNets/changed_fields_allowed_nets.xml b/src/test/resources/petriNets/changed_fields_allowed_nets.xml index 1f3c66814f0..8c5bc649c8e 100644 --- a/src/test/resources/petriNets/changed_fields_allowed_nets.xml +++ b/src/test/resources/petriNets/changed_fields_allowed_nets.xml @@ -2,6 +2,7 @@ changed_fields_allowed_nets + 1.0.0 NEW New Model true diff --git a/src/test/resources/petriNets/dynamic_case_name_test.xml b/src/test/resources/petriNets/dynamic_case_name_test.xml index 21594887fa6..44ed7ae8170 100644 --- a/src/test/resources/petriNets/dynamic_case_name_test.xml +++ b/src/test/resources/petriNets/dynamic_case_name_test.xml @@ -1,6 +1,7 @@ dynamic_choices + 1.0.0 dynamic_choices true DVT diff --git a/src/test/resources/petriNets/dynamic_choices.xml b/src/test/resources/petriNets/dynamic_choices.xml index 655bcccdc15..93e36de6b71 100644 --- a/src/test/resources/petriNets/dynamic_choices.xml +++ b/src/test/resources/petriNets/dynamic_choices.xml @@ -1,6 +1,7 @@ dynamic_choices + 1.0.0 dynamic_choices true DVT diff --git a/src/test/resources/petriNets/dynamic_init.xml b/src/test/resources/petriNets/dynamic_init.xml index 586c88c14e0..ee0a40cdac0 100644 --- a/src/test/resources/petriNets/dynamic_init.xml +++ b/src/test/resources/petriNets/dynamic_init.xml @@ -1,9 +1,10 @@ dynamic_init + 1.0.0 + DVT dynamic_init true - DVT text diff --git a/src/test/resources/petriNets/dynamic_validations.xml b/src/test/resources/petriNets/dynamic_validations.xml index 0e6c6da2745..59c0857498c 100644 --- a/src/test/resources/petriNets/dynamic_validations.xml +++ b/src/test/resources/petriNets/dynamic_validations.xml @@ -1,6 +1,7 @@ dynamic_validations + 1.0.0 dynamic_validations true DVT diff --git a/src/test/resources/petriNets/dynamic_validations_performance_test.xml b/src/test/resources/petriNets/dynamic_validations_performance_test.xml index d33ba5dc10f..93e25c01e58 100644 --- a/src/test/resources/petriNets/dynamic_validations_performance_test.xml +++ b/src/test/resources/petriNets/dynamic_validations_performance_test.xml @@ -1,6 +1,7 @@ dynamic_validations_performance + 1.0.0 dynamic_validations_performance true DVT diff --git a/src/test/resources/petriNets/dynamic_validations_performance_test_comparison.xml b/src/test/resources/petriNets/dynamic_validations_performance_test_comparison.xml index b9a7973bd9f..02234928dce 100644 --- a/src/test/resources/petriNets/dynamic_validations_performance_test_comparison.xml +++ b/src/test/resources/petriNets/dynamic_validations_performance_test_comparison.xml @@ -1,6 +1,7 @@ dynamic_validations_performance_comparison + 1.0.0 dynamic_validations_performance_comparison true DVT diff --git a/src/test/resources/petriNets/filter_api_test.xml b/src/test/resources/petriNets/filter_api_test.xml deleted file mode 100644 index 5bb13019442..00000000000 --- a/src/test/resources/petriNets/filter_api_test.xml +++ /dev/null @@ -1,270 +0,0 @@ - - filter_api_test - FTT - filter_api_test - device_hub - true - true - false - - - role_1 - Role 1 - - - - role_2 - Role 2 - - - - - upload - - - createCase("filter_api_test") - - - - - - - - menu_stringId - - </data> - <data type="text"> - <id>filter_stringId</id> - <title/> - </data> - - <data type="text"> - <id>query</id> - <title/> - <init>processIdentifier:"filter_api_test"</init> - </data> - <data type="text"> - <id>icon</id> - <title/> - <init>device_hub</init> - </data> - <data type="text"> - <id>uri</id> - <title/> - </data> - <data type="text"> - <id>allowed_nets</id> - <title/> - </data> - <data type="text"> - <id>title</id> - <title/> - </data> - <data type="text"> - <id>identifier</id> - <title/> - </data> - <data type="text"> - <id>group</id> - <title>Skupina - - - - create_filter_and_menu - create filter and menu - - - - - menu_stringId: f.menu_stringId, - filter_stringId: f.filter_stringId, - uri: f.uri, - title: f.title, - allowed_nets: f.allowed_nets, - query: f.query, - identifier: f.identifier, - icon: f.icon; - - def filter = createCaseFilter(title.value, query.value, allowed_nets.value.split(",") as List, icon.value) - def item = createMenuItem(uri.value, identifier.value, title.value, icon.value, filter, - ["role_1": "filter_api_test"], ["role_2": "filter_api_test"], - ["meta-title,meta-title"], ["meta-title,meta-title"]) - - change menu_stringId value { item.stringId } - change filter_stringId value { filter.stringId } - - - - - - change_filter_and_menu - change filter and menu - - - - - uri: f.uri, - title: f.title, - allowed_nets: f.allowed_nets, - query: f.query, - group: f.group, - identifier: f.identifier, - icon: f.icon; - - def item = findMenuItem(identifier.value) - def filter = getFilterFromMenuItem(item) - - changeFilter filter query { query.value } - changeFilter filter allowedNets { allowed_nets.value.split(",") as List } - changeFilter filter icon { icon.value } - changeFilter filter title { title.value } - changeFilter filter filterMetadata { [ - "searchCategories" : [], - "predicateMetadata" : [], - "filterType" : type, - "defaultSearchCategories": false, - "inheritAllowedNets" : false - ] } - - changeMenuItem item filter { filter } - changeMenuItem item allowedRoles { ["role_2": "filter_api_test"] } - changeMenuItem item uri { uri.value } - changeMenuItem item title { title.value } - changeMenuItem item caseDefaultHeaders { "meta-title,meta-title,meta-title" } - changeMenuItem item taskDefaultHeaders { "meta-title,meta-title,meta-title" } - - - - - - delete_filter_and_menu - delete filter and menu - - - - - menu_stringId: f.menu_stringId, - identifier: f.identifier; - - def item = workflowService.findOne(menu_stringId.value) - def filter = getFilterFromMenuItem(item) - - deleteMenuItem(item) - deleteFilter(filter) - - - - - - create_filter_in_group - create filter in group - - - - - menu_stringId: f.menu_stringId, - filter_stringId: f.filter_stringId, - uri: f.uri, - title: f.title, - allowed_nets: f.allowed_nets, - query: f.query, - group: f.group, - identifier: f.identifier, - icon: f.icon; - - def item = createFilterInMenu( - uri.value, - identifier.value, - title.value, - query.value, - type.value, - allowed_nets.value.split(",") as List, - ["role_1": "filter_api_test"], - [:], - icon.value - ) - def filter = getFilterFromMenuItem(item) - - change menu_stringId value { item.stringId } - change filter_stringId value { filter.stringId } - - - - - - - find_filter - find filter - - - - - title: f.title, - found_filter: f.found_filter; - - def filter = findFilter(title.value) - change found_filter value { filter.stringId } - - - - - - found_filter - found filter - - - - move_item - move item - - - - move_dest_uri: f.move_dest_uri, - move_item_id: f.move_item_id, - move_folder_path: f.move_folder_path; - - def item - if (move_item_id.value) { - item = workflowService.findOne(move_item_id.value) - } else if (move_folder_path.value) { - def node = uriService.findByUri(move_folder_path.value) - item = findFolderCase(node) - } - - moveMenuItem(item, move_dest_uri.value) - - - - - - move_dest_uri - found filter - - - move_item_id - View item id to find - - - move_folder_path - Folder item path to find - - - - t1 - 700 - 180 - - - t1_assign - - - t1_finish - - - t1_cancel - - - t1_delegate - - - \ No newline at end of file diff --git a/src/test/resources/petriNets/filter_api_test_0.xml b/src/test/resources/petriNets/filter_api_test_0.xml index b2f05113506..9a5d1293cdd 100644 --- a/src/test/resources/petriNets/filter_api_test_0.xml +++ b/src/test/resources/petriNets/filter_api_test_0.xml @@ -1,6 +1,7 @@ netgrif/test/filter_api_test - FTT + 1.0.0 + FTT filter_api_test device_hub true diff --git a/src/test/resources/petriNets/filter_test.xml b/src/test/resources/petriNets/filter_test.xml new file mode 100644 index 00000000000..37780267820 --- /dev/null +++ b/src/test/resources/petriNets/filter_test.xml @@ -0,0 +1,69 @@ + + filter_test + 1.0.0 + FTT + filter_test + device_hub + true + false + false + + + filter_field + + <init>someQueryString</init> + </data> + + <data type="caseFilter"> + <id>case_filter_field</id> + <title/> + <init>someQueryString</init> + </data> + + <data type="taskFilter"> + <id>task_filter_field</id> + <title/> + <init>someQueryString</init> + </data> + + <data type="processFilter"> + <id>process_filter_field</id> + <title/> + <init>someQueryString</init> + </data> + + <transition> + <id>t1</id> + <x>700</x> + <y>180</y> + <label>test</label> + <dataGroup> + <id>t1_0</id> + <layout>legacy</layout> + <dataRef> + <id>filter_field</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>case_filter_field</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>task_filter_field</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>process_filter_field</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + </dataGroup> + </transition> +</document> \ No newline at end of file diff --git a/src/test/resources/petriNets/function_overloading.xml b/src/test/resources/petriNets/function_overloading.xml index a3e58babc96..8398eb846c3 100644 --- a/src/test/resources/petriNets/function_overloading.xml +++ b/src/test/resources/petriNets/function_overloading.xml @@ -2,6 +2,7 @@ <document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://petriflow.org/petriflow.schema.xsd"> <id>function_overloading</id> + <version>1.0.0</version> <initials>FOF</initials> <title>Test diff --git a/src/test/resources/petriNets/function_overloading_fail.xml b/src/test/resources/petriNets/function_overloading_fail.xml index 5df7b1cb633..b70fcb96e49 100644 --- a/src/test/resources/petriNets/function_overloading_fail.xml +++ b/src/test/resources/petriNets/function_overloading_fail.xml @@ -2,6 +2,7 @@ function_overloading_fail + 1.0.0 FOF Test diff --git a/src/test/resources/petriNets/function_overloading_fail_v2.xml b/src/test/resources/petriNets/function_overloading_fail_v2.xml index 88966c403df..fd1a422e223 100644 --- a/src/test/resources/petriNets/function_overloading_fail_v2.xml +++ b/src/test/resources/petriNets/function_overloading_fail_v2.xml @@ -2,6 +2,7 @@ function_overloading_fail + 1.0.0 FOF Test diff --git a/src/test/resources/petriNets/function_overloading_v2.xml b/src/test/resources/petriNets/function_overloading_v2.xml index 3648f5c1180..b2ea0972670 100644 --- a/src/test/resources/petriNets/function_overloading_v2.xml +++ b/src/test/resources/petriNets/function_overloading_v2.xml @@ -2,6 +2,7 @@ function_overloading + 1.0.0 FOF Test diff --git a/src/test/resources/petriNets/function_res.xml b/src/test/resources/petriNets/function_res.xml index 6faa882219f..2e014720730 100644 --- a/src/test/resources/petriNets/function_res.xml +++ b/src/test/resources/petriNets/function_res.xml @@ -2,6 +2,7 @@ function_res + 1.0.0 SFR Test diff --git a/src/test/resources/petriNets/function_res_v2.xml b/src/test/resources/petriNets/function_res_v2.xml index d2c665b384e..1c9ae5c616b 100644 --- a/src/test/resources/petriNets/function_res_v2.xml +++ b/src/test/resources/petriNets/function_res_v2.xml @@ -2,6 +2,7 @@ function_res + 1.0.0 SFR Test diff --git a/src/test/resources/petriNets/function_test.xml b/src/test/resources/petriNets/function_test.xml index 90d20fe2fcd..30ee3cf7000 100644 --- a/src/test/resources/petriNets/function_test.xml +++ b/src/test/resources/petriNets/function_test.xml @@ -2,6 +2,7 @@ function_test + 1.0.0 SFT Test diff --git a/src/test/resources/petriNets/function_test_v2.xml b/src/test/resources/petriNets/function_test_v2.xml index 26976d76708..3f60a686505 100644 --- a/src/test/resources/petriNets/function_test_v2.xml +++ b/src/test/resources/petriNets/function_test_v2.xml @@ -2,6 +2,7 @@ function_test + 1.0.0 SFT Test diff --git a/src/test/resources/petriNets/groovy_shell_test.xml b/src/test/resources/petriNets/groovy_shell_test.xml index cc257a4f00f..4f4564e6539 100644 --- a/src/test/resources/petriNets/groovy_shell_test.xml +++ b/src/test/resources/petriNets/groovy_shell_test.xml @@ -1,7 +1,8 @@ new_model - NEW + 1.0.0 + NEW New Model true false diff --git a/src/test/resources/petriNets/impersonation_test.xml b/src/test/resources/petriNets/impersonation_test.xml index 5ed2917f12a..037c5ec6336 100644 --- a/src/test/resources/petriNets/impersonation_test.xml +++ b/src/test/resources/petriNets/impersonation_test.xml @@ -12,6 +12,13 @@ true true + true + + + + default + + true diff --git a/src/test/resources/petriNets/importer_upsert.xml b/src/test/resources/petriNets/importer_upsert.xml index e3db46a1cae..20df06c0558 100644 --- a/src/test/resources/petriNets/importer_upsert.xml +++ b/src/test/resources/petriNets/importer_upsert.xml @@ -1,6 +1,7 @@ importer_upsert + 1.0.0 importer_upsert IMP diff --git a/src/test/resources/petriNets/mortgage_net.xml b/src/test/resources/petriNets/mortgage_net.xml index 98204cb74ad..3e809ddbb13 100644 --- a/src/test/resources/petriNets/mortgage_net.xml +++ b/src/test/resources/petriNets/mortgage_net.xml @@ -1,6 +1,7 @@ mortgage + 1.0.0 MOR Mortgage false diff --git a/src/test/resources/petriNets/nae_1276_Init_value_as_choice.xml b/src/test/resources/petriNets/nae_1276_Init_value_as_choice.xml index 743de9c89de..f12e42cfa7c 100644 --- a/src/test/resources/petriNets/nae_1276_Init_value_as_choice.xml +++ b/src/test/resources/petriNets/nae_1276_Init_value_as_choice.xml @@ -2,6 +2,7 @@ new_init_test + 1.0.0 NIT New init test true diff --git a/src/test/resources/petriNets/permissions/assign_permission_combinations.xml b/src/test/resources/petriNets/permissions/assign_permission_combinations.xml new file mode 100644 index 00000000000..aa31e751c6c --- /dev/null +++ b/src/test/resources/petriNets/permissions/assign_permission_combinations.xml @@ -0,0 +1,532 @@ + + + assign_permission_combinations + 1.0.0 + APC + Assign Permission Combinations + true + + + process_role + Process Role + + + + users + Users + + + + t_001 + 100 + 100 + + + process_role + + true + + + + default + + true + + + + users + + true + + + + + + t_002 + 300 + 100 + + + process_role + + true + + + + default + + true + + + + users + + false + + + + + + t_003 + 500 + 100 + + + process_role + + true + + + + default + + true + + + + + + t_004 + 700 + 100 + + + process_role + + true + + + + default + + false + + + + users + + true + + + + + + t_005 + 900 + 100 + + + process_role + + true + + + + default + + false + + + + users + + false + + + + + + t_006 + 1100 + 100 + + + process_role + + true + + + + default + + false + + + + + + t_007 + 1300 + 100 + + + process_role + + true + + + + users + + true + + + + + + t_008 + 1500 + 100 + + + process_role + + true + + + + users + + false + + + + + + t_009 + 1700 + 100 + + + process_role + + true + + + + + + t_010 + 100 + 300 + + + process_role + + false + + + + default + + true + + + + users + + true + + + + + + t_011 + 300 + 300 + + + process_role + + false + + + + default + + true + + + + users + + false + + + + + + t_012 + 500 + 300 + + + process_role + + false + + + + default + + true + + + + + + t_013 + 700 + 300 + + + process_role + + false + + + + default + + false + + + + users + + true + + + + + + t_014 + 900 + 300 + + + process_role + + false + + + + default + + false + + + + users + + false + + + + + + t_015 + 1100 + 300 + + + process_role + + false + + + + default + + false + + + + + + t_016 + 1300 + 300 + + + process_role + + false + + + + users + + true + + + + + + t_017 + 1500 + 300 + + + process_role + + false + + + + users + + false + + + + + + t_018 + 1700 + 300 + + + process_role + + false + + + + + + t_019 + 100 + 500 + + + default + + true + + + + users + + true + + + + + + t_020 + 300 + 500 + + + default + + true + + + + users + + false + + + + + + t_021 + 500 + 500 + + + default + + true + + + + + + t_022 + 700 + 500 + + + default + + false + + + + users + + true + + + + + + t_023 + 900 + 500 + + + default + + false + + + + users + + false + + + + + + t_024 + 1100 + 500 + + + default + + false + + + + + + t_025 + 1300 + 500 + + + users + + true + + + + + + t_026 + 1500 + 500 + + + users + + false + + + + + + t_027 + 1700 + 500 + + + \ No newline at end of file diff --git a/src/test/resources/petriNets/permissions/assign_permission_combinations_no_default.xml b/src/test/resources/petriNets/permissions/assign_permission_combinations_no_default.xml new file mode 100644 index 00000000000..6f818fc1558 --- /dev/null +++ b/src/test/resources/petriNets/permissions/assign_permission_combinations_no_default.xml @@ -0,0 +1,154 @@ + + + assign_permission_combinations_no_default + 1.0.0 + APC + Assign Permission Combinations With Default Role Disabled + false + + + process_role + Process Role + + + + users + Users + + + + t_007 + 1300 + 100 + + + process_role + + true + + + + users + + true + + + + + + t_008 + 1500 + 100 + + + process_role + + true + + + + users + + false + + + + + + t_009 + 1700 + 100 + + + process_role + + true + + + + + + t_016 + 1300 + 300 + + + process_role + + false + + + + users + + true + + + + + + t_017 + 1500 + 300 + + + process_role + + false + + + + users + + false + + + + + + t_018 + 1700 + 300 + + + process_role + + false + + + + + + t_025 + 1300 + 500 + + + users + + true + + + + + + t_026 + 1500 + 500 + + + users + + false + + + + + + t_027 + 1700 + 500 + + + \ No newline at end of file diff --git a/src/test/resources/petriNets/permissions/cancel_permission_combinations.xml b/src/test/resources/petriNets/permissions/cancel_permission_combinations.xml new file mode 100644 index 00000000000..aedbc7bf646 --- /dev/null +++ b/src/test/resources/petriNets/permissions/cancel_permission_combinations.xml @@ -0,0 +1,586 @@ + + + cancel_permission_combinations + 1.0.0 + CPC + Cancel Permission Combinations + true + + + process_role + Process Role + + + + users + Users + + + + t_001 + 100 + 100 + + + process_role + + true + true + + + + default + + true + true + + + + users + + true + true + + + + + + t_002 + 300 + 100 + + + process_role + + true + true + + + + default + + true + true + + + + users + + false + true + + + + + + t_003 + 500 + 100 + + + process_role + + true + true + + + + default + + true + true + + + + + + t_004 + 700 + 100 + + + process_role + + true + true + + + + default + + false + true + + + + users + + true + true + + + + + + t_005 + 900 + 100 + + + process_role + + true + true + + + + default + + false + true + + + + users + + false + true + + + + + + t_006 + 1100 + 100 + + + process_role + + true + true + + + + default + + false + true + + + + + + t_007 + 1300 + 100 + + + process_role + + true + true + + + + users + + true + true + + + + + + t_008 + 1500 + 100 + + + process_role + + true + true + + + + users + + false + true + + + + + + t_009 + 1700 + 100 + + + process_role + + true + true + + + + + + t_010 + 100 + 300 + + + process_role + + false + true + + + + default + + true + true + + + + users + + true + true + + + + + + t_011 + 300 + 300 + + + process_role + + false + true + + + + default + + true + true + + + + users + + false + true + + + + + + t_012 + 500 + 300 + + + process_role + + false + true + + + + default + + true + true + + + + + + t_013 + 700 + 300 + + + process_role + + false + true + + + + default + + false + true + + + + users + + true + true + + + + + + t_014 + 900 + 300 + + + process_role + + false + true + + + + default + + false + true + + + + users + + false + true + + + + + + t_015 + 1100 + 300 + + + process_role + + false + true + + + + default + + false + true + + + + + + t_016 + 1300 + 300 + + + process_role + + false + true + + + + users + + true + true + + + + + + t_017 + 1500 + 300 + + + process_role + + false + true + + + + users + + false + true + + + + + + t_018 + 1700 + 300 + + + process_role + + false + true + + + + + + t_019 + 100 + 500 + + + default + + true + true + + + + users + + true + true + + + + + + t_020 + 300 + 500 + + + default + + true + true + + + + users + + false + true + + + + + + t_021 + 500 + 500 + + + default + + true + true + + + + + + t_022 + 700 + 500 + + + default + + false + true + + + + users + + true + true + + + + + + t_023 + 900 + 500 + + + default + + false + true + + + + users + + false + true + + + + + + t_024 + 1100 + 500 + + + default + + false + true + + + + + + t_025 + 1300 + 500 + + + users + + true + true + + + + + + t_026 + 1500 + 500 + + + users + + false + true + + + + + + t_027 + 1700 + 500 + + + \ No newline at end of file diff --git a/src/test/resources/petriNets/permissions/cancel_permission_combinations_no_default.xml b/src/test/resources/petriNets/permissions/cancel_permission_combinations_no_default.xml new file mode 100644 index 00000000000..36b651e945b --- /dev/null +++ b/src/test/resources/petriNets/permissions/cancel_permission_combinations_no_default.xml @@ -0,0 +1,166 @@ + + + cancel_permission_combinations_no_default + 1.0.0 + CPC + Cancel Permission Combinations With Default Role Disabled + false + + + process_role + Process Role + + + + users + Users + + + + t_007 + 1300 + 100 + + + process_role + + true + true + + + + users + + true + true + + + + + + t_008 + 1500 + 100 + + + process_role + + true + true + + + + users + + false + true + + + + + + t_009 + 1700 + 100 + + + process_role + + true + true + + + + + + t_016 + 1300 + 300 + + + process_role + + false + true + + + + users + + true + true + + + + + + t_017 + 1500 + 300 + + + process_role + + false + true + + + + users + + false + true + + + + + + t_018 + 1700 + 300 + + + process_role + + false + true + + + + + + t_025 + 1300 + 500 + + + users + + true + true + + + + + + t_026 + 1500 + 500 + + + users + + false + true + + + + + + t_027 + 1700 + 500 + + + \ No newline at end of file diff --git a/src/test/resources/petriNets/permissions/case/default/create/create_case_role_false_default_false.xml b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_false_default_false.xml new file mode 100644 index 00000000000..38a405b860a --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_false_default_false.xml @@ -0,0 +1,29 @@ + + + create_case_role_false_default_false + 1.0.0 + CDFDF + Create Case Role False Default False + assignment + true + + + process_role + Process Role + + + + process_role + + false + + + + + default + + false + + + diff --git a/src/test/resources/petriNets/permissions/case/default/create/create_case_role_false_default_true.xml b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_false_default_true.xml new file mode 100644 index 00000000000..05c6da83208 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_false_default_true.xml @@ -0,0 +1,29 @@ + + + create_case_role_false_default_true + 1.0.0 + CDFDT + Create Case Role False Default True + assignment + true + + + process_role + Process Role + + + + process_role + + false + + + + + default + + true + + + diff --git a/src/test/resources/petriNets/permissions/case/default/create/create_case_role_false_default_undefined.xml b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_false_default_undefined.xml new file mode 100644 index 00000000000..6a2d6cc1e8e --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_false_default_undefined.xml @@ -0,0 +1,22 @@ + + + create_case_role_false_default_undefined + 1.0.0 + CDFDU + Create Case Role False Default Undefined + assignment + true + + + process_role + Process Role + + + + process_role + + false + + + diff --git a/src/test/resources/petriNets/permissions/case/default/create/create_case_role_true_default_false.xml b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_true_default_false.xml new file mode 100644 index 00000000000..7778ade3320 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_true_default_false.xml @@ -0,0 +1,29 @@ + + + create_case_role_true_default_false + 1.0.0 + CDTDF + Create Case Role True Default False + assignment + true + + + process_role + Process Role + + + + process_role + + true + + + + + default + + false + + + diff --git a/src/test/resources/petriNets/permissions/case/default/create/create_case_role_true_default_true.xml b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_true_default_true.xml new file mode 100644 index 00000000000..4062ac81205 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_true_default_true.xml @@ -0,0 +1,29 @@ + + + create_case_role_true_default_true + 1.0.0 + CDTDT + Create Case Role True Default True + assignment + true + + + process_role + Process Role + + + + process_role + + true + + + + + default + + true + + + diff --git a/src/test/resources/petriNets/permissions/case/default/create/create_case_role_true_default_undefined.xml b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_true_default_undefined.xml new file mode 100644 index 00000000000..e812f401751 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_true_default_undefined.xml @@ -0,0 +1,22 @@ + + + create_case_role_true_default_undefined + 1.0.0 + CDTDU + Create Case Role True Default Undefined + assignment + true + + + process_role + Process Role + + + + process_role + + true + + + diff --git a/src/test/resources/petriNets/permissions/case/default/create/create_case_role_undefined_default_false.xml b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_undefined_default_false.xml new file mode 100644 index 00000000000..5fd88fa209f --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_undefined_default_false.xml @@ -0,0 +1,22 @@ + + + create_case_role_undefined_default_false + 1.0.0 + CDUDF + Create Case Role Undefined Default False + assignment + true + + + process_role + Process Role + + + + default + + false + + + diff --git a/src/test/resources/petriNets/permissions/case/default/create/create_case_role_undefined_default_true.xml b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_undefined_default_true.xml new file mode 100644 index 00000000000..67122ee9e42 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_undefined_default_true.xml @@ -0,0 +1,22 @@ + + + create_case_role_undefined_default_true + 1.0.0 + CDUDT + Create Case Role Undefined Default True + assignment + true + + + process_role + Process Role + + + + default + + true + + + diff --git a/src/test/resources/petriNets/permissions/case/default/create/create_case_role_undefined_default_undefined.xml b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_undefined_default_undefined.xml new file mode 100644 index 00000000000..6aa7a6317dd --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/create/create_case_role_undefined_default_undefined.xml @@ -0,0 +1,15 @@ + + + create_case_role_undefined_default_undefined + 1.0.0 + CDUDU + Create Case Role Undefined Default Undefined + assignment + true + + + process_role + Process Role + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_false_user_false.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_false_user_false.xml new file mode 100644 index 00000000000..aaf85608a47 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_false_user_false.xml @@ -0,0 +1,44 @@ + + + delete_case_role_false_default_false_user_false + 1.0.0 + DCP + Delete Case Role false Default false User false + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + default + + false + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_false_user_true.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_false_user_true.xml new file mode 100644 index 00000000000..2ffd96b7e7e --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_false_user_true.xml @@ -0,0 +1,44 @@ + + + delete_case_role_false_default_false_user_true + 1.0.0 + DCP + Delete Case Role false Default false User true + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + default + + false + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_false_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_false_user_undefined.xml new file mode 100644 index 00000000000..590fa333352 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_false_user_undefined.xml @@ -0,0 +1,38 @@ + + + delete_case_role_false_default_false_user_undefined + 1.0.0 + DCP + Delete Case Role false Default false User undefined + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + default + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_true_user_false.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_true_user_false.xml new file mode 100644 index 00000000000..bf036aaeaf5 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_true_user_false.xml @@ -0,0 +1,44 @@ + + + delete_case_role_false_default_true_user_false + 1.0.0 + DCP + Delete Case Role false Default true User false + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + default + + true + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_true_user_true.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_true_user_true.xml new file mode 100644 index 00000000000..101d6c1ded8 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_true_user_true.xml @@ -0,0 +1,44 @@ + + + delete_case_role_false_default_true_user_true + 1.0.0 + DCP + Delete Case Role false Default true User true + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + default + + true + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_true_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_true_user_undefined.xml new file mode 100644 index 00000000000..7d36b5225e8 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_true_user_undefined.xml @@ -0,0 +1,38 @@ + + + delete_case_role_false_default_true_user_undefined + 1.0.0 + DCP + Delete Case Role false Default true User undefined + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + default + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_undefined_user_false.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_undefined_user_false.xml new file mode 100644 index 00000000000..d3391a9ba29 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_undefined_user_false.xml @@ -0,0 +1,38 @@ + + + delete_case_role_false_default_undefined_user_false + 1.0.0 + DCP + Delete Case Role false Default undefined User false + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_undefined_user_true.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_undefined_user_true.xml new file mode 100644 index 00000000000..8ef4fb7affb --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_undefined_user_true.xml @@ -0,0 +1,38 @@ + + + delete_case_role_false_default_undefined_user_true + 1.0.0 + DCP + Delete Case Role false Default undefined User true + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_undefined_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_undefined_user_undefined.xml new file mode 100644 index 00000000000..8da8b10831a --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_false_default_undefined_user_undefined.xml @@ -0,0 +1,32 @@ + + + delete_case_role_false_default_undefined_user_undefined + 1.0.0 + DCP + Delete Case Role false Default undefined User undefined + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_false_user_false.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_false_user_false.xml new file mode 100644 index 00000000000..7f0869d8061 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_false_user_false.xml @@ -0,0 +1,44 @@ + + + delete_case_role_true_default_false_user_false + 1.0.0 + DCP + Delete Case Role true Default false User false + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + default + + false + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_false_user_true.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_false_user_true.xml new file mode 100644 index 00000000000..ab2896afcff --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_false_user_true.xml @@ -0,0 +1,44 @@ + + + delete_case_role_true_default_false_user_true + 1.0.0 + DCP + Delete Case Role true Default false User true + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + default + + false + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_false_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_false_user_undefined.xml new file mode 100644 index 00000000000..7dc589fcd42 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_false_user_undefined.xml @@ -0,0 +1,38 @@ + + + delete_case_role_true_default_false_user_undefined + 1.0.0 + DCP + Delete Case Role true Default false User undefined + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + default + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_true_user_false.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_true_user_false.xml new file mode 100644 index 00000000000..41cc00cf966 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_true_user_false.xml @@ -0,0 +1,44 @@ + + + delete_case_role_true_default_true_user_false + 1.0.0 + DCP + Delete Case Role true Default true User false + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + default + + true + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_true_user_true.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_true_user_true.xml new file mode 100644 index 00000000000..a5c7faf5d63 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_true_user_true.xml @@ -0,0 +1,44 @@ + + + delete_case_role_true_default_true_user_true + 1.0.0 + DCP + Delete Case Role true Default true User true + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + default + + true + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_true_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_true_user_undefined.xml new file mode 100644 index 00000000000..c3dee880fb5 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_true_user_undefined.xml @@ -0,0 +1,38 @@ + + + delete_case_role_true_default_true_user_undefined + 1.0.0 + DCP + Delete Case Role true Default true User undefined + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + default + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_undefined_user_false.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_undefined_user_false.xml new file mode 100644 index 00000000000..b66f306c3fb --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_undefined_user_false.xml @@ -0,0 +1,38 @@ + + + delete_case_role_true_default_undefined_user_false + 1.0.0 + DCP + Delete Case Role true Default undefined User false + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_undefined_user_true.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_undefined_user_true.xml new file mode 100644 index 00000000000..b436d8ab0dc --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_undefined_user_true.xml @@ -0,0 +1,38 @@ + + + delete_case_role_true_default_undefined_user_true + 1.0.0 + DCP + Delete Case Role true Default undefined User true + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_undefined_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_undefined_user_undefined.xml new file mode 100644 index 00000000000..2652b13952d --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_true_default_undefined_user_undefined.xml @@ -0,0 +1,32 @@ + + + delete_case_role_true_default_undefined_user_undefined + 1.0.0 + DCP + Delete Case Role true Default undefined User undefined + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_false_user_false.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_false_user_false.xml new file mode 100644 index 00000000000..ac4fbc1013c --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_false_user_false.xml @@ -0,0 +1,38 @@ + + + delete_case_role_undefined_default_false_user_false + 1.0.0 + DCP + Delete Case Role undefined Default false User false + true + + + process_role + Process Role + + + + users + Users + + + + default + + false + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_false_user_true.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_false_user_true.xml new file mode 100644 index 00000000000..65bc202ff97 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_false_user_true.xml @@ -0,0 +1,38 @@ + + + delete_case_role_undefined_default_false_user_true + 1.0.0 + DCP + Delete Case Role undefined Default false User true + true + + + process_role + Process Role + + + + users + Users + + + + default + + false + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_false_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_false_user_undefined.xml new file mode 100644 index 00000000000..e93c1763062 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_false_user_undefined.xml @@ -0,0 +1,32 @@ + + + delete_case_role_undefined_default_false_user_undefined + 1.0.0 + DCP + Delete Case Role undefined Default false User undefined + true + + + process_role + Process Role + + + + users + Users + + + + default + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_true_user_false.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_true_user_false.xml new file mode 100644 index 00000000000..3af9eb143e3 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_true_user_false.xml @@ -0,0 +1,38 @@ + + + delete_case_role_undefined_default_true_user_false + 1.0.0 + DCP + Delete Case Role undefined Default true User false + true + + + process_role + Process Role + + + + users + Users + + + + default + + true + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_true_user_true.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_true_user_true.xml new file mode 100644 index 00000000000..e8e11b8de10 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_true_user_true.xml @@ -0,0 +1,38 @@ + + + delete_case_role_undefined_default_true_user_true + 1.0.0 + DCP + Delete Case Role undefined Default true User true + true + + + process_role + Process Role + + + + users + Users + + + + default + + true + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_true_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_true_user_undefined.xml new file mode 100644 index 00000000000..6e4a23fc552 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_true_user_undefined.xml @@ -0,0 +1,32 @@ + + + delete_case_role_undefined_default_true_user_undefined + 1.0.0 + DCP + Delete Case Role undefined Default true User undefined + true + + + process_role + Process Role + + + + users + Users + + + + default + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_undefined_user_false.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_undefined_user_false.xml new file mode 100644 index 00000000000..97585ebece7 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_undefined_user_false.xml @@ -0,0 +1,32 @@ + + + delete_case_role_undefined_default_undefined_user_false + 1.0.0 + DCP + Delete Case Role undefined Default undefined User false + true + + + process_role + Process Role + + + + users + Users + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_undefined_user_true.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_undefined_user_true.xml new file mode 100644 index 00000000000..74eedb7817b --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_undefined_user_true.xml @@ -0,0 +1,32 @@ + + + delete_case_role_undefined_default_undefined_user_true + 1.0.0 + DCP + Delete Case Role undefined Default undefined User true + true + + + process_role + Process Role + + + + users + Users + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_undefined_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_undefined_user_undefined.xml new file mode 100644 index 00000000000..97a8268ba6e --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/delete/delete_case_role_undefined_default_undefined_user_undefined.xml @@ -0,0 +1,25 @@ + + + delete_case_role_undefined_default_undefined_user_undefined + 1.0.0 + DCP + Delete Case Role undefined Default undefined User undefined + true + + + process_role + Process Role + + + + users + Users + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_false_user_false.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_false_user_false.xml new file mode 100644 index 00000000000..ae09929c23c --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_false_user_false.xml @@ -0,0 +1,44 @@ + + + view_case_role_false_default_false_user_false + 1.0.0 + DCP + View Case Role false Default false User false + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + default + + false + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_false_user_true.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_false_user_true.xml new file mode 100644 index 00000000000..77af09492de --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_false_user_true.xml @@ -0,0 +1,44 @@ + + + view_case_role_false_default_false_user_true + 1.0.0 + DCP + View Case Role false Default false User true + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + default + + false + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_false_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_false_user_undefined.xml new file mode 100644 index 00000000000..1aa7e1ba334 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_false_user_undefined.xml @@ -0,0 +1,38 @@ + + + view_case_role_false_default_false_user_undefined + 1.0.0 + DCP + View Case Role false Default false User undefined + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + default + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_true_user_false.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_true_user_false.xml new file mode 100644 index 00000000000..53065a9aaf6 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_true_user_false.xml @@ -0,0 +1,44 @@ + + + view_case_role_false_default_true_user_false + 1.0.0 + DCP + View Case Role false Default true User false + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + default + + true + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_true_user_true.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_true_user_true.xml new file mode 100644 index 00000000000..d9231a4556e --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_true_user_true.xml @@ -0,0 +1,44 @@ + + + view_case_role_false_default_true_user_true + 1.0.0 + DCP + View Case Role false Default true User true + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + default + + true + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_true_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_true_user_undefined.xml new file mode 100644 index 00000000000..1246e371b0c --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_true_user_undefined.xml @@ -0,0 +1,38 @@ + + + view_case_role_false_default_true_user_undefined + 1.0.0 + DCP + View Case Role false Default true User undefined + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + default + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_undefined_user_false.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_undefined_user_false.xml new file mode 100644 index 00000000000..ab6b6938130 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_undefined_user_false.xml @@ -0,0 +1,38 @@ + + + view_case_role_false_default_undefined_user_false + 1.0.0 + DCP + View Case Role false Default undefined User false + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_undefined_user_true.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_undefined_user_true.xml new file mode 100644 index 00000000000..5d31b2a7606 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_undefined_user_true.xml @@ -0,0 +1,38 @@ + + + view_case_role_false_default_undefined_user_true + 1.0.0 + DCP + View Case Role false Default undefined User true + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_undefined_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_undefined_user_undefined.xml new file mode 100644 index 00000000000..2d769047380 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_false_default_undefined_user_undefined.xml @@ -0,0 +1,32 @@ + + + view_case_role_false_default_undefined_user_undefined + 1.0.0 + DCP + View Case Role false Default undefined User undefined + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_false_user_false.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_false_user_false.xml new file mode 100644 index 00000000000..392077f2c20 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_false_user_false.xml @@ -0,0 +1,44 @@ + + + view_case_role_true_default_false_user_false + 1.0.0 + DCP + View Case Role true Default false User false + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + default + + false + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_false_user_true.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_false_user_true.xml new file mode 100644 index 00000000000..1050d95acfc --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_false_user_true.xml @@ -0,0 +1,44 @@ + + + view_case_role_true_default_false_user_true + 1.0.0 + DCP + View Case Role true Default false User true + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + default + + false + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_false_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_false_user_undefined.xml new file mode 100644 index 00000000000..53ba09d4429 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_false_user_undefined.xml @@ -0,0 +1,38 @@ + + + view_case_role_true_default_false_user_undefined + 1.0.0 + DCP + View Case Role true Default false User undefined + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + default + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_true_user_false.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_true_user_false.xml new file mode 100644 index 00000000000..3e2ad192512 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_true_user_false.xml @@ -0,0 +1,44 @@ + + + view_case_role_true_default_true_user_false + 1.0.0 + DCP + View Case Role true Default true User false + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + default + + true + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_true_user_true.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_true_user_true.xml new file mode 100644 index 00000000000..a4feda55330 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_true_user_true.xml @@ -0,0 +1,44 @@ + + + view_case_role_true_default_true_user_true + 1.0.0 + DCP + View Case Role true Default true User true + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + default + + true + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_true_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_true_user_undefined.xml new file mode 100644 index 00000000000..be126767550 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_true_user_undefined.xml @@ -0,0 +1,38 @@ + + + view_case_role_true_default_true_user_undefined + 1.0.0 + DCP + View Case Role true Default true User undefined + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + default + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_undefined_user_false.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_undefined_user_false.xml new file mode 100644 index 00000000000..7e41d645cb9 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_undefined_user_false.xml @@ -0,0 +1,38 @@ + + + view_case_role_true_default_undefined_user_false + 1.0.0 + DCP + View Case Role true Default undefined User false + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_undefined_user_true.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_undefined_user_true.xml new file mode 100644 index 00000000000..10c41d8edff --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_undefined_user_true.xml @@ -0,0 +1,38 @@ + + + view_case_role_true_default_undefined_user_true + 1.0.0 + DCP + View Case Role true Default undefined User true + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_undefined_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_undefined_user_undefined.xml new file mode 100644 index 00000000000..d7318d6dc9e --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_true_default_undefined_user_undefined.xml @@ -0,0 +1,32 @@ + + + view_case_role_true_default_undefined_user_undefined + 1.0.0 + DCP + View Case Role true Default undefined User undefined + true + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_false_user_false.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_false_user_false.xml new file mode 100644 index 00000000000..5e983c4a9c6 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_false_user_false.xml @@ -0,0 +1,38 @@ + + + view_case_role_undefined_default_false_user_false + 1.0.0 + DCP + View Case Role undefined Default false User false + true + + + process_role + Process Role + + + + users + Users + + + + default + + false + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_false_user_true.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_false_user_true.xml new file mode 100644 index 00000000000..6e90dd4a531 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_false_user_true.xml @@ -0,0 +1,38 @@ + + + view_case_role_undefined_default_false_user_true + 1.0.0 + DCP + View Case Role undefined Default false User true + true + + + process_role + Process Role + + + + users + Users + + + + default + + false + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_false_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_false_user_undefined.xml new file mode 100644 index 00000000000..543a6020779 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_false_user_undefined.xml @@ -0,0 +1,32 @@ + + + view_case_role_undefined_default_false_user_undefined + 1.0.0 + DCP + View Case Role undefined Default false User undefined + true + + + process_role + Process Role + + + + users + Users + + + + default + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_true_user_false.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_true_user_false.xml new file mode 100644 index 00000000000..b669ce95d63 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_true_user_false.xml @@ -0,0 +1,38 @@ + + + view_case_role_undefined_default_true_user_false + 1.0.0 + DCP + View Case Role undefined Default true User false + true + + + process_role + Process Role + + + + users + Users + + + + default + + true + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_true_user_true.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_true_user_true.xml new file mode 100644 index 00000000000..8c1b7734a47 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_true_user_true.xml @@ -0,0 +1,38 @@ + + + view_case_role_undefined_default_true_user_true + 1.0.0 + DCP + View Case Role undefined Default true User true + true + + + process_role + Process Role + + + + users + Users + + + + default + + true + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_true_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_true_user_undefined.xml new file mode 100644 index 00000000000..6a82bbd78c8 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_true_user_undefined.xml @@ -0,0 +1,32 @@ + + + view_case_role_undefined_default_true_user_undefined + 1.0.0 + DCP + View Case Role undefined Default true User undefined + true + + + process_role + Process Role + + + + users + Users + + + + default + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_undefined_user_false.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_undefined_user_false.xml new file mode 100644 index 00000000000..7a4de04ffed --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_undefined_user_false.xml @@ -0,0 +1,32 @@ + + + view_case_role_undefined_default_undefined_user_false + 1.0.0 + DCP + View Case Role undefined Default undefined User false + true + + + process_role + Process Role + + + + users + Users + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_undefined_user_true.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_undefined_user_true.xml new file mode 100644 index 00000000000..e002e4a7f23 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_undefined_user_true.xml @@ -0,0 +1,32 @@ + + + view_case_role_undefined_default_undefined_user_true + 1.0.0 + DCP + View Case Role undefined Default undefined User true + true + + + process_role + Process Role + + + + users + Users + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_undefined_user_undefined.xml b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_undefined_user_undefined.xml new file mode 100644 index 00000000000..64a1a75ea92 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/default/view/view_case_role_undefined_default_undefined_user_undefined.xml @@ -0,0 +1,25 @@ + + + view_case_role_undefined_default_undefined_user_undefined + 1.0.0 + DCP + View Case Role undefined Default undefined User undefined + true + + + process_role + Process Role + + + + users + Users + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/create/create_case_role_false.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/create/create_case_role_false.xml new file mode 100644 index 00000000000..a6b844dea31 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/create/create_case_role_false.xml @@ -0,0 +1,22 @@ + + + create_case_role_false + 1.0.0 + CDFDU + Create Case Role False + assignment + false + + + process_role + Process Role + + + + process_role + + false + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/create/create_case_role_true.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/create/create_case_role_true.xml new file mode 100644 index 00000000000..05a4e011d96 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/create/create_case_role_true.xml @@ -0,0 +1,22 @@ + + + create_case_role_true + 1.0.0 + CDTDU + Create Case Role True + assignment + false + + + process_role + Process Role + + + + process_role + + true + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/create/create_case_role_undefined.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/create/create_case_role_undefined.xml new file mode 100644 index 00000000000..a81f76ea559 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/create/create_case_role_undefined.xml @@ -0,0 +1,15 @@ + + + create_case_role_undefined + 1.0.0 + CDUDU + Create Case Role Undefined + assignment + false + + + process_role + Process Role + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_false_user_false.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_false_user_false.xml new file mode 100644 index 00000000000..3a7fad2bff7 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_false_user_false.xml @@ -0,0 +1,38 @@ + + + delete_case_role_false_user_false + 1.0.0 + DCP + Delete Case Role false User false + false + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_false_user_true.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_false_user_true.xml new file mode 100644 index 00000000000..1298b8b0b20 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_false_user_true.xml @@ -0,0 +1,38 @@ + + + delete_case_role_false_user_true + 1.0.0 + DCP + Delete Case Role false User true + false + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_false_user_undefined.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_false_user_undefined.xml new file mode 100644 index 00000000000..75dad997c66 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_false_user_undefined.xml @@ -0,0 +1,32 @@ + + + delete_case_role_false_user_undefined + 1.0.0 + DCP + Delete Case Role false User undefined + false + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_true_user_false.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_true_user_false.xml new file mode 100644 index 00000000000..26f0f6c1a53 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_true_user_false.xml @@ -0,0 +1,38 @@ + + + delete_case_role_true_user_false + 1.0.0 + DCP + Delete Case Role true User false + false + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_true_user_true.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_true_user_true.xml new file mode 100644 index 00000000000..8dffa394dbc --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_true_user_true.xml @@ -0,0 +1,38 @@ + + + delete_case_role_true_user_true + 1.0.0 + DCP + Delete Case Role true User true + false + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_true_user_undefined.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_true_user_undefined.xml new file mode 100644 index 00000000000..4e392b22355 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_true_user_undefined.xml @@ -0,0 +1,32 @@ + + + delete_case_role_true_user_undefined + 1.0.0 + DCP + Delete Case Role true User undefined + false + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_undefined_user_false.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_undefined_user_false.xml new file mode 100644 index 00000000000..b724b03bac0 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_undefined_user_false.xml @@ -0,0 +1,32 @@ + + + delete_case_role_undefined_user_false + 1.0.0 + DCP + Delete Case Role undefined User false + false + + + process_role + Process Role + + + + users + Users + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_undefined_user_true.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_undefined_user_true.xml new file mode 100644 index 00000000000..a089abd6cd9 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_undefined_user_true.xml @@ -0,0 +1,32 @@ + + + delete_case_role_undefined_user_true + 1.0.0 + DCP + Delete Case Role undefined User true + false + + + process_role + Process Role + + + + users + Users + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_undefined_user_undefined.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_undefined_user_undefined.xml new file mode 100644 index 00000000000..8184879d895 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/delete/delete_case_role_undefined_user_undefined.xml @@ -0,0 +1,25 @@ + + + delete_case_role_undefined_user_undefined + 1.0.0 + DCP + Delete Case Role undefined User undefined + false + + + process_role + Process Role + + + + users + Users + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_false_user_false.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_false_user_false.xml new file mode 100644 index 00000000000..dc16b396222 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_false_user_false.xml @@ -0,0 +1,38 @@ + + + view_case_role_false_user_false + 1.0.0 + DCP + View Case Role false User false + false + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_false_user_true.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_false_user_true.xml new file mode 100644 index 00000000000..712b608637e --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_false_user_true.xml @@ -0,0 +1,38 @@ + + + view_case_role_false_user_true + 1.0.0 + DCP + View Case Role false User true + false + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_false_user_undefined.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_false_user_undefined.xml new file mode 100644 index 00000000000..68c2068e0d2 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_false_user_undefined.xml @@ -0,0 +1,32 @@ + + + view_case_role_false_user_undefined + 1.0.0 + DCP + View Case Role false User undefined + false + + + process_role + Process Role + + + + users + Users + + + + process_role + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_true_user_false.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_true_user_false.xml new file mode 100644 index 00000000000..33e3a8ebea8 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_true_user_false.xml @@ -0,0 +1,38 @@ + + + view_case_role_true_user_false + 1.0.0 + DCP + View Case Role true User false + false + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_true_user_true.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_true_user_true.xml new file mode 100644 index 00000000000..7c11e2648f5 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_true_user_true.xml @@ -0,0 +1,38 @@ + + + view_case_role_true_user_true + 1.0.0 + DCP + View Case Role true User true + false + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_true_user_undefined.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_true_user_undefined.xml new file mode 100644 index 00000000000..cef7509f99a --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_true_user_undefined.xml @@ -0,0 +1,32 @@ + + + view_case_role_true_user_undefined + 1.0.0 + DCP + View Case Role true User undefined + false + + + process_role + Process Role + + + + users + Users + + + + process_role + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_undefined_user_false.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_undefined_user_false.xml new file mode 100644 index 00000000000..3a1ddcc5769 --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_undefined_user_false.xml @@ -0,0 +1,32 @@ + + + view_case_role_undefined_user_false + 1.0.0 + DCP + View Case Role undefined User false + false + + + process_role + Process Role + + + + users + Users + + + + users + + false + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_undefined_user_true.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_undefined_user_true.xml new file mode 100644 index 00000000000..d2c3451c5bc --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_undefined_user_true.xml @@ -0,0 +1,32 @@ + + + view_case_role_undefined_user_true + 1.0.0 + DCP + View Case Role undefined User true + false + + + process_role + Process Role + + + + users + Users + + + + users + + true + + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_undefined_user_undefined.xml b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_undefined_user_undefined.xml new file mode 100644 index 00000000000..8565a1043dc --- /dev/null +++ b/src/test/resources/petriNets/permissions/case/defaultDisabled/view/view_case_role_undefined_user_undefined.xml @@ -0,0 +1,25 @@ + + + view_case_role_undefined_user_undefined + 1.0.0 + DCP + View Case Role undefined User undefined + false + + + process_role + Process Role + + + + users + Users + + + t_001 + 200 + 200 + + + diff --git a/src/test/resources/petriNets/permissions/finish_permission_combinations.xml b/src/test/resources/petriNets/permissions/finish_permission_combinations.xml new file mode 100644 index 00000000000..1c4ad7a900c --- /dev/null +++ b/src/test/resources/petriNets/permissions/finish_permission_combinations.xml @@ -0,0 +1,586 @@ + + + finish_permission_combinations + 1.0.0 + FPC + Finish Permission Combinations + true + + + process_role + Process Role + + + + users + Users + + + + t_001 + 100 + 100 + + + process_role + + true + true + + + + default + + true + true + + + + users + + true + true + + + + + + t_002 + 300 + 100 + + + process_role + + true + true + + + + default + + true + true + + + + users + + false + true + + + + + + t_003 + 500 + 100 + + + process_role + + true + true + + + + default + + true + true + + + + + + t_004 + 700 + 100 + + + process_role + + true + true + + + + default + + false + true + + + + users + + true + true + + + + + + t_005 + 900 + 100 + + + process_role + + true + true + + + + default + + false + true + + + + users + + false + true + + + + + + t_006 + 1100 + 100 + + + process_role + + true + true + + + + default + + false + true + + + + + + t_007 + 1300 + 100 + + + process_role + + true + true + + + + users + + true + true + + + + + + t_008 + 1500 + 100 + + + process_role + + true + true + + + + users + + false + true + + + + + + t_009 + 1700 + 100 + + + process_role + + true + true + + + + + + t_010 + 100 + 300 + + + process_role + + false + true + + + + default + + true + true + + + + users + + true + true + + + + + + t_011 + 300 + 300 + + + process_role + + false + true + + + + default + + true + true + + + + users + + false + true + + + + + + t_012 + 500 + 300 + + + process_role + + false + true + + + + default + + true + true + + + + + + t_013 + 700 + 300 + + + process_role + + false + true + + + + default + + false + true + + + + users + + true + true + + + + + + t_014 + 900 + 300 + + + process_role + + false + true + + + + default + + false + true + + + + users + + false + true + + + + + + t_015 + 1100 + 300 + + + process_role + + false + true + + + + default + + false + true + + + + + + t_016 + 1300 + 300 + + + process_role + + false + true + + + + users + + true + true + + + + + + t_017 + 1500 + 300 + + + process_role + + false + true + + + + users + + false + true + + + + + + t_018 + 1700 + 300 + + + process_role + + false + true + + + + + + t_019 + 100 + 500 + + + default + + true + true + + + + users + + true + true + + + + + + t_020 + 300 + 500 + + + default + + true + true + + + + users + + false + true + + + + + + t_021 + 500 + 500 + + + default + + true + true + + + + + + t_022 + 700 + 500 + + + default + + false + true + + + + users + + true + true + + + + + + t_023 + 900 + 500 + + + default + + false + true + + + + users + + false + true + + + + + + t_024 + 1100 + 500 + + + default + + false + true + + + + + + t_025 + 1300 + 500 + + + users + + true + true + + + + + + t_026 + 1500 + 500 + + + users + + false + true + + + + + + t_027 + 1700 + 500 + + + \ No newline at end of file diff --git a/src/test/resources/petriNets/permissions/finish_permission_combinations_no_default.xml b/src/test/resources/petriNets/permissions/finish_permission_combinations_no_default.xml new file mode 100644 index 00000000000..d381c86678e --- /dev/null +++ b/src/test/resources/petriNets/permissions/finish_permission_combinations_no_default.xml @@ -0,0 +1,184 @@ + + + finish_permission_combinations_no_default + 1.0.0 + FPC + Finish Permission Combinations With Default Role Disabled + false + + + process_role + Process Role + + + + users + Users + + + + t_007 + 1300 + 100 + + + process_role + + true + true + + + + users + + true + true + + + + + + t_008 + 1500 + 100 + + + process_role + + true + true + + + + users + + false + true + + + + + + t_009 + 1700 + 100 + + + process_role + + true + true + + + + + + t_016 + 1300 + 300 + + + process_role + + false + true + + + + users + + true + true + + + + + + t_017 + 1500 + 300 + + + process_role + + false + true + + + + users + + false + true + + + + + + t_018 + 1700 + 300 + + + process_role + + false + true + + + + + + t_025 + 1300 + 500 + + + users + + true + true + + + + + + t_026 + 1500 + 500 + + + users + + false + true + + + + + + t_027 + 1700 + 500 + + + process_role + + true + + + + default + + true + + + + users + + true + + + + \ No newline at end of file diff --git a/src/test/resources/petriNets/permissions/view_permission_combinations.xml b/src/test/resources/petriNets/permissions/view_permission_combinations.xml new file mode 100644 index 00000000000..8cf5d10d3d7 --- /dev/null +++ b/src/test/resources/petriNets/permissions/view_permission_combinations.xml @@ -0,0 +1,532 @@ + + + view_permission_combinations + 1.0.0 + VPC + View Permission Combinations + true + + + process_role + Process Role + + + + users + Users + + + + t_001 + 100 + 100 + + + process_role + + true + + + + default + + true + + + + users + + true + + + + + + t_002 + 300 + 100 + + + process_role + + true + + + + default + + true + + + + users + + false + + + + + + t_003 + 500 + 100 + + + process_role + + true + + + + default + + true + + + + + + t_004 + 700 + 100 + + + process_role + + true + + + + default + + false + + + + users + + true + + + + + + t_005 + 900 + 100 + + + process_role + + true + + + + default + + false + + + + users + + false + + + + + + t_006 + 1100 + 100 + + + process_role + + true + + + + default + + false + + + + + + t_007 + 1300 + 100 + + + process_role + + true + + + + users + + true + + + + + + t_008 + 1500 + 100 + + + process_role + + true + + + + users + + false + + + + + + t_009 + 1700 + 100 + + + process_role + + true + + + + + + t_010 + 100 + 300 + + + process_role + + false + + + + default + + true + + + + users + + true + + + + + + t_011 + 300 + 300 + + + process_role + + false + + + + default + + true + + + + users + + false + + + + + + t_012 + 500 + 300 + + + process_role + + false + + + + default + + true + + + + + + t_013 + 700 + 300 + + + process_role + + false + + + + default + + false + + + + users + + true + + + + + + t_014 + 900 + 300 + + + process_role + + false + + + + default + + false + + + + users + + false + + + + + + t_015 + 1100 + 300 + + + process_role + + false + + + + default + + false + + + + + + t_016 + 1300 + 300 + + + process_role + + false + + + + users + + true + + + + + + t_017 + 1500 + 300 + + + process_role + + false + + + + users + + false + + + + + + t_018 + 1700 + 300 + + + process_role + + false + + + + + + t_019 + 100 + 500 + + + default + + true + + + + users + + true + + + + + + t_020 + 300 + 500 + + + default + + true + + + + users + + false + + + + + + t_021 + 500 + 500 + + + default + + true + + + + + + t_022 + 700 + 500 + + + default + + false + + + + users + + true + + + + + + t_023 + 900 + 500 + + + default + + false + + + + users + + false + + + + + + t_024 + 1100 + 500 + + + default + + false + + + + + + t_025 + 1300 + 500 + + + users + + true + + + + + + t_026 + 1500 + 500 + + + users + + false + + + + + + t_027 + 1700 + 500 + + + \ No newline at end of file diff --git a/src/test/resources/petriNets/permissions/view_permission_combinations_no_default.xml b/src/test/resources/petriNets/permissions/view_permission_combinations_no_default.xml new file mode 100644 index 00000000000..0da52e1f849 --- /dev/null +++ b/src/test/resources/petriNets/permissions/view_permission_combinations_no_default.xml @@ -0,0 +1,154 @@ + + + view_permission_combinations_no_default + 1.0.0 + VPC + View Permission Combinations With Default Role Disabled + false + + + process_role + Process Role + + + + users + Users + + + + t_007 + 1300 + 100 + + + process_role + + true + + + + users + + true + + + + + + t_008 + 1500 + 100 + + + process_role + + true + + + + users + + false + + + + + + t_009 + 1700 + 100 + + + process_role + + true + + + + + + t_016 + 1300 + 300 + + + process_role + + false + + + + users + + true + + + + + + t_017 + 1500 + 300 + + + process_role + + false + + + + users + + false + + + + + + t_018 + 1700 + 300 + + + process_role + + false + + + + + + t_025 + 1300 + 500 + + + users + + true + + + + + + t_026 + 1500 + 500 + + + users + + false + + + + + + t_027 + 1700 + 500 + + + \ No newline at end of file diff --git a/src/test/resources/petriNets/pfql.xml b/src/test/resources/petriNets/pfql.xml new file mode 100644 index 00000000000..1dd151d8199 --- /dev/null +++ b/src/test/resources/petriNets/pfql.xml @@ -0,0 +1,97 @@ + + query_test + 1.0.0 + NEW + New Model + device_hub + true + true + false + + boolean_0 + Boolean + (params["id"] as int) % 2 == 0 + + + dateTime_0 + Date Time + java.time.LocalDateTime.now().plusYears(params["id"] as int) + + + number_0 + Number + params["id"] as int + + + text_0 + Text + params["id"] + + + t1 + 176 + 112 + + + t1_0 + 4 + grid + + text_0 + + editable + + + 0 + 0 + 1 + 2 + + outline + + + + number_0 + + editable + + + 2 + 0 + 1 + 2 + + outline + + + + boolean_0 + + editable + + + 0 + 1 + 1 + 2 + + outline + + + + dateTime_0 + + editable + + + 2 + 1 + 1 + 2 + + outline + + + + + \ No newline at end of file diff --git a/src/test/resources/petriNets/query_lang_test.xml b/src/test/resources/petriNets/query_lang_test.xml new file mode 100644 index 00000000000..af088e7d270 --- /dev/null +++ b/src/test/resources/petriNets/query_lang_test.xml @@ -0,0 +1,266 @@ + + query_lang_test + 1.0.0 + QLT + query_lang_test + device_hub + true + false + false + + + system + System + + + + system + + true + true + + + + + input_query + + </data> + + <data type="text"> + <id>result_class</id> + <title/> + </data> + + <data type="text"> + <id>result</id> + <title/> + </data> + + <data type="button"> + <id>general_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { search(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>general_count</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result: f.result; + + change result value { count(input_query.value) } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>general_exists</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result: f.result; + + change result value { exists(input_query.value) } + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>user_search_one</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchUser(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>user_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchUsers(input_query.value)?.class?.toString()} + </action> + </actions> + </event> + </data> + <data type="button"> + <id>user_paged_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { pagedSearchUsers(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>task_search_one</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchTask(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>task_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchTasks(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>task_paged_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { pagedSearchTasks(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>process_search_one</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchProcess(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>process_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchProcesses(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>process_paged_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { pagedSearchProcesses(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>case_search_one</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchCase(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>case_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchCases(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>case_paged_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { pagedSearchCases(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + + <transition> + <id>t1</id> + <x>700</x> + <y>180</y> + <label>TEST API</label> + <roleRef> + <id>system</id> + <logic> + <perform>true</perform> + </logic> + </roleRef> + </transition> +</document> \ No newline at end of file diff --git a/src/test/resources/petriNets/role_assign_remove_test.xml b/src/test/resources/petriNets/role_assign_remove_test.xml index 32b2d2cc2f8..b17c82a0ce7 100644 --- a/src/test/resources/petriNets/role_assign_remove_test.xml +++ b/src/test/resources/petriNets/role_assign_remove_test.xml @@ -1,6 +1,7 @@ <document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://petriflow.org/petriflow.schema.xsd"> <id>role_assign_remove_test</id> + <version>1.0.0</version> <title>Role Assign And Remove RAR diff --git a/src/test/resources/petriNets/role_test.xml b/src/test/resources/petriNets/role_test.xml index 8d2ad743285..5a1f2619f73 100644 --- a/src/test/resources/petriNets/role_test.xml +++ b/src/test/resources/petriNets/role_test.xml @@ -2,6 +2,7 @@ role_test + 1.0.0 IMP role_test diff --git a/src/test/resources/petriNets/validation/valid_boolean.xml b/src/test/resources/petriNets/validation/valid_boolean.xml index bfccf851133..017b9769dc0 100644 --- a/src/test/resources/petriNets/validation/valid_boolean.xml +++ b/src/test/resources/petriNets/validation/valid_boolean.xml @@ -1,6 +1,7 @@ valid_boolean - VB1 + 1.0.0 + VB1 Validacia BooleanFieldu check_circle true diff --git a/src/test/resources/petriNets/validation/valid_date.xml b/src/test/resources/petriNets/validation/valid_date.xml index 4a16b5b7c52..837f6406ef2 100644 --- a/src/test/resources/petriNets/validation/valid_date.xml +++ b/src/test/resources/petriNets/validation/valid_date.xml @@ -1,6 +1,7 @@ valid_date + 1.0.0 VD1 Validacia DateFieldu check_circle diff --git a/src/test/resources/petriNets/validation/valid_number.xml b/src/test/resources/petriNets/validation/valid_number.xml index 658d78546a1..10b18579d6a 100644 --- a/src/test/resources/petriNets/validation/valid_number.xml +++ b/src/test/resources/petriNets/validation/valid_number.xml @@ -1,6 +1,7 @@ valid_number + 1.0.0 VN1 Validacia NumberFieldu check_circle diff --git a/src/test/resources/petriNets/validation/valid_regex.xml b/src/test/resources/petriNets/validation/valid_regex.xml index 3b9fa45e0d3..0c2aa68909a 100644 --- a/src/test/resources/petriNets/validation/valid_regex.xml +++ b/src/test/resources/petriNets/validation/valid_regex.xml @@ -1,6 +1,7 @@ valid_regex - VR1 + 1.0.0 + VR1 Validacia regexov check_circle true diff --git a/src/test/resources/petriNets/validation/valid_text.xml b/src/test/resources/petriNets/validation/valid_text.xml index f3f5477a87e..a64609aa4de 100644 --- a/src/test/resources/petriNets/validation/valid_text.xml +++ b/src/test/resources/petriNets/validation/valid_text.xml @@ -1,6 +1,7 @@ valid_text + 1.0.0 VT1 Validacia TextFieldu check_circle diff --git a/src/test/resources/poistenie.xml b/src/test/resources/poistenie.xml index bdcacd7c715..a963dc5f04c 100644 --- a/src/test/resources/poistenie.xml +++ b/src/test/resources/poistenie.xml @@ -1,6 +1,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/poistenie_rozsirene.xml b/src/test/resources/poistenie_rozsirene.xml index 518ed2b9c2d..205a891b76e 100644 --- a/src/test/resources/poistenie_rozsirene.xml +++ b/src/test/resources/poistenie_rozsirene.xml @@ -1,6 +1,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/poistenie_transaction.xml b/src/test/resources/poistenie_transaction.xml index 139d519e726..71550d9827a 100644 --- a/src/test/resources/poistenie_transaction.xml +++ b/src/test/resources/poistenie_transaction.xml @@ -1,6 +1,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/poistenie_triggers.xml b/src/test/resources/poistenie_triggers.xml index 4919206b18e..580f40fdcbf 100644 --- a/src/test/resources/poistenie_triggers.xml +++ b/src/test/resources/poistenie_triggers.xml @@ -1,6 +1,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_combined.xml b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_combined.xml index e3c19481edf..be2813921a0 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_combined.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_combined.xml @@ -1,7 +1,8 @@ permissions_test_anonymous_6 - PTA + 1.0.0 + PTA Permissions test anonymous 6 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_custom.xml b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_custom.xml index 8a95b6330c7..ded2ca14401 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_custom.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_custom.xml @@ -1,7 +1,8 @@ permissions_test_anonymous_4 - PTA + 1.0.0 + PTA Permissions test anonymous 4 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_defined.xml b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_defined.xml index 429117b420f..14ff211ba39 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_defined.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_defined.xml @@ -1,7 +1,8 @@ permissions_test_anonymous_2 - PTA + 1.0.0 + PTA Permissions test anonymous 2 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_disabled.xml b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_disabled.xml index 865ed55afe4..4ef2308d8ff 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_disabled.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_disabled.xml @@ -1,7 +1,8 @@ permissions_test_anonymous_10 - PTA + 1.0.0 + PTA Permissions test anonymous 10 diff --git a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_missing.xml b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_missing.xml index edae7d805d5..6669c650460 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_missing.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_missing.xml @@ -1,7 +1,8 @@ permissions_test_anonymous_7 - PTA + 1.0.0 + PTA Permissions test anonymous 7 diff --git a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_negative.xml b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_negative.xml index 1f9f219b1a1..26e6cab17b0 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_negative.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_negative.xml @@ -1,7 +1,8 @@ permissions_test_anonymous_5 - PTA + 1.0.0 + PTA Permissions test anonymous 5 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_reserved.xml b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_reserved.xml index a6c04bb3e8b..962ab163415 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_reserved.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_reserved.xml @@ -1,7 +1,8 @@ permissions_test_anonymous_8 - PTA + 1.0.0 + PTA Permissions test anonymous 8 diff --git a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_shadowed.xml b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_shadowed.xml index c6c7fc729db..7fe41a74691 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_shadowed.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_shadowed.xml @@ -1,7 +1,8 @@ permissions_test_anonymous_3 - PTA + 1.0.0 + PTA Permissions test anonymous 3 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_shadowed_userref.xml b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_shadowed_userref.xml index aebd7cc7e08..9f8c777863b 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_shadowed_userref.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_shadowed_userref.xml @@ -1,7 +1,8 @@ permissions_test_anonymous_9 - PTA + 1.0.0 + PTA Permissions test anonymous 9 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_shadowed_usersref.xml b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_shadowed_usersref.xml index 390626b56fc..6a590f24fe1 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_shadowed_usersref.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_anonymous_role_shadowed_usersref.xml @@ -1,7 +1,8 @@ permissions_test_anonymous_11 - PTA + 1.0.0 + PTA Permissions test anonymous 11 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_combined_roles_defined.xml b/src/test/resources/predefinedPermissions/role_permissions_combined_roles_defined.xml index a67f850b7fa..56cbc9d0c0a 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_combined_roles_defined.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_combined_roles_defined.xml @@ -1,7 +1,8 @@ permissions_test_combined_2 - PTC + 1.0.0 + PTC Permissions test combined 2 true true diff --git a/src/test/resources/predefinedPermissions/role_permissions_combined_roles_undefined.xml b/src/test/resources/predefinedPermissions/role_permissions_combined_roles_undefined.xml index 854bbe68bed..0f07c49f87b 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_combined_roles_undefined.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_combined_roles_undefined.xml @@ -1,7 +1,8 @@ permissions_test_combined_1 - PTC + 1.0.0 + PTC Permissions test combined 1 diff --git a/src/test/resources/predefinedPermissions/role_permissions_default_role_combined.xml b/src/test/resources/predefinedPermissions/role_permissions_default_role_combined.xml index e46e7cf7bf0..ac371e8cf48 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_default_role_combined.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_default_role_combined.xml @@ -1,7 +1,8 @@ permissions_test_default_6 - PTD + 1.0.0 + PTD Permissions test default 6 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_default_role_custom.xml b/src/test/resources/predefinedPermissions/role_permissions_default_role_custom.xml index 9938318bede..5e4e1b81e60 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_default_role_custom.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_default_role_custom.xml @@ -1,7 +1,8 @@ permissions_test_default_4 - PTD + 1.0.0 + PTD Permissions test default 4 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_default_role_defined.xml b/src/test/resources/predefinedPermissions/role_permissions_default_role_defined.xml index 2cede6a9f98..4f6c63480e1 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_default_role_defined.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_default_role_defined.xml @@ -1,7 +1,8 @@ permissions_test_default_2 - PTD + 1.0.0 + PTD Permissions test default 2 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_default_role_disabled.xml b/src/test/resources/predefinedPermissions/role_permissions_default_role_disabled.xml index 72a3c59eca4..8c9d9b9d3eb 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_default_role_disabled.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_default_role_disabled.xml @@ -1,7 +1,8 @@ permissions_test_default_10 - PTD + 1.0.0 + PTD Permissions test default 10 diff --git a/src/test/resources/predefinedPermissions/role_permissions_default_role_missing.xml b/src/test/resources/predefinedPermissions/role_permissions_default_role_missing.xml index a2ff5e4f30d..51a6f38ae4a 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_default_role_missing.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_default_role_missing.xml @@ -1,7 +1,8 @@ permissions_test_default_7 - PTD + 1.0.0 + PTD Permissions test default 7 diff --git a/src/test/resources/predefinedPermissions/role_permissions_default_role_negative.xml b/src/test/resources/predefinedPermissions/role_permissions_default_role_negative.xml index 27615b0c51e..f3886a3ac2c 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_default_role_negative.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_default_role_negative.xml @@ -1,7 +1,8 @@ permissions_test_default_5 - PTD + 1.0.0 + PTD Permissions test default 5 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_default_role_reserved.xml b/src/test/resources/predefinedPermissions/role_permissions_default_role_reserved.xml index 555b6a488dc..7a318c96c34 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_default_role_reserved.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_default_role_reserved.xml @@ -1,7 +1,8 @@ permissions_test_default_8 - PTD + 1.0.0 + PTD Permissions test default 8 diff --git a/src/test/resources/predefinedPermissions/role_permissions_default_role_shadowed.xml b/src/test/resources/predefinedPermissions/role_permissions_default_role_shadowed.xml index 930fa096140..94a1879aa77 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_default_role_shadowed.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_default_role_shadowed.xml @@ -1,7 +1,8 @@ permissions_test_default_3 - PTD + 1.0.0 + PTD Permissions test default 3 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_default_role_shadowed_userref.xml b/src/test/resources/predefinedPermissions/role_permissions_default_role_shadowed_userref.xml index 7e095f8533c..f4e1d5a6190 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_default_role_shadowed_userref.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_default_role_shadowed_userref.xml @@ -1,7 +1,8 @@ permissions_test_default_9 - PTD + 1.0.0 + PTD Permissions test default 9 true diff --git a/src/test/resources/predefinedPermissions/role_permissions_default_role_shadowed_usersref.xml b/src/test/resources/predefinedPermissions/role_permissions_default_role_shadowed_usersref.xml index 25298a9a7eb..66176bb31f6 100644 --- a/src/test/resources/predefinedPermissions/role_permissions_default_role_shadowed_usersref.xml +++ b/src/test/resources/predefinedPermissions/role_permissions_default_role_shadowed_usersref.xml @@ -1,7 +1,8 @@ permissions_test_default_11 - PTD + 1.0.0 + PTD Permissions test default 11 true diff --git a/src/test/resources/prikladFM.xml b/src/test/resources/prikladFM.xml index d26601a9d87..3aa205426c1 100644 --- a/src/test/resources/prikladFM.xml +++ b/src/test/resources/prikladFM.xml @@ -1,6 +1,7 @@ test + 1.0.0 TST Test false diff --git a/src/test/resources/prikladFM_import.xml b/src/test/resources/prikladFM_import.xml index 4628ff9889d..54e44b97081 100644 --- a/src/test/resources/prikladFM_import.xml +++ b/src/test/resources/prikladFM_import.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test false diff --git a/src/test/resources/prikladFM_test.xml b/src/test/resources/prikladFM_test.xml index 369a2e491b8..a874d74a9f7 100644 --- a/src/test/resources/prikladFM_test.xml +++ b/src/test/resources/prikladFM_test.xml @@ -2,6 +2,7 @@ prikladFM_test.xml + 1.0.0 TST Test false diff --git a/src/test/resources/priloha.xml b/src/test/resources/priloha.xml index 7e15542391a..78c943a103e 100644 --- a/src/test/resources/priloha.xml +++ b/src/test/resources/priloha.xml @@ -2,6 +2,7 @@ priloha + 1.0.0 IPR 1.0.0 Iné príloha diff --git a/src/test/resources/priority_test.xml b/src/test/resources/priority_test.xml index 816f8ba57b4..1085611b962 100644 --- a/src/test/resources/priority_test.xml +++ b/src/test/resources/priority_test.xml @@ -1,6 +1,7 @@ test + 1.0.0 TST Test false diff --git a/src/test/resources/process_delete_test.xml b/src/test/resources/process_delete_test.xml index 3859937941d..34790111f31 100644 --- a/src/test/resources/process_delete_test.xml +++ b/src/test/resources/process_delete_test.xml @@ -2,6 +2,7 @@ processDeleteTest + 1.0.0 PDT Process Delete Test false diff --git a/src/test/resources/process_search_test.xml b/src/test/resources/process_search_test.xml index 3f53f9069fa..955552c4935 100644 --- a/src/test/resources/process_search_test.xml +++ b/src/test/resources/process_search_test.xml @@ -2,6 +2,7 @@ processSearchTest + 1.0.0 PST Process Search Test Process Search Case Name diff --git a/src/test/resources/read_test.xml b/src/test/resources/read_test.xml index 105c6496e81..fee2c2154ec 100644 --- a/src/test/resources/read_test.xml +++ b/src/test/resources/read_test.xml @@ -1,6 +1,7 @@ test + 1.0.0 TST Test false diff --git a/src/test/resources/remoteFileField.xml b/src/test/resources/remoteFileField.xml index a77c644a0a1..95bf76df738 100644 --- a/src/test/resources/remoteFileField.xml +++ b/src/test/resources/remoteFileField.xml @@ -2,6 +2,7 @@ remote_file_field_net + 1.0.0 TST Test true diff --git a/src/test/resources/remoteFileListField.xml b/src/test/resources/remoteFileListField.xml index ac1da8b04ee..e85b442e716 100644 --- a/src/test/resources/remoteFileListField.xml +++ b/src/test/resources/remoteFileListField.xml @@ -2,6 +2,7 @@ remote_file_list_field_net + 1.0.0 TST Remote file list field test true diff --git a/src/test/resources/removeRole_test.xml b/src/test/resources/removeRole_test.xml index 3a7c78d7681..9849f0f29f8 100644 --- a/src/test/resources/removeRole_test.xml +++ b/src/test/resources/removeRole_test.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test false diff --git a/src/test/resources/reset_inhibitor_test.xml b/src/test/resources/reset_inhibitor_test.xml index 8bc2fd9a060..f6572748cd8 100644 --- a/src/test/resources/reset_inhibitor_test.xml +++ b/src/test/resources/reset_inhibitor_test.xml @@ -1,6 +1,7 @@ test + 1.0.0 TST Test false diff --git a/src/test/resources/role_all_data.xml b/src/test/resources/role_all_data.xml index 96c9048d849..6af3141615d 100644 --- a/src/test/resources/role_all_data.xml +++ b/src/test/resources/role_all_data.xml @@ -1,7 +1,8 @@ all_data - ALL + 1.0.0 + ALL All Data device_hub true diff --git a/src/test/resources/role_cancel_test.xml b/src/test/resources/role_cancel_test.xml index 54f10be23ac..5610c7e427e 100644 --- a/src/test/resources/role_cancel_test.xml +++ b/src/test/resources/role_cancel_test.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/rolref_view.xml b/src/test/resources/rolref_view.xml index 173725a088c..533b42a367f 100644 --- a/src/test/resources/rolref_view.xml +++ b/src/test/resources/rolref_view.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/set_data_test.xml b/src/test/resources/set_data_test.xml index d5612f1dd28..539ee90c16d 100644 --- a/src/test/resources/set_data_test.xml +++ b/src/test/resources/set_data_test.xml @@ -1,7 +1,8 @@ set_data_test - SDT + 1.0.0 + SDT Set data test net diff --git a/src/test/resources/simple_taskref.xml b/src/test/resources/simple_taskref.xml index 4278089966a..4e93f885d22 100644 --- a/src/test/resources/simple_taskref.xml +++ b/src/test/resources/simple_taskref.xml @@ -2,6 +2,7 @@ simple_taskref + 1.0.0 STR simple taskref true diff --git a/src/test/resources/task_authentication_service_test.xml b/src/test/resources/task_authentication_service_test.xml index fb6cb8c6cd3..17f7a3700cd 100644 --- a/src/test/resources/task_authentication_service_test.xml +++ b/src/test/resources/task_authentication_service_test.xml @@ -2,6 +2,7 @@ 1 + 1.0.0 TST TaskAuthenticationService test diff --git a/src/test/resources/task_authorization_service_test.xml b/src/test/resources/task_authorization_service_test.xml index 4ace8c27f14..f1f2cc59062 100644 --- a/src/test/resources/task_authorization_service_test.xml +++ b/src/test/resources/task_authorization_service_test.xml @@ -2,6 +2,7 @@ wst + 1.0.0 WST WorkflowAuthorizationService test diff --git a/src/test/resources/task_authorization_service_test_with_userRefs.xml b/src/test/resources/task_authorization_service_test_with_userRefs.xml index ef5c5f338d7..f1afc4edc90 100644 --- a/src/test/resources/task_authorization_service_test_with_userRefs.xml +++ b/src/test/resources/task_authorization_service_test_with_userRefs.xml @@ -2,6 +2,7 @@ wst_usersRef + 1.0.0 WSU WorkflowAuthorizationService test diff --git a/src/test/resources/task_cancel_net.xml b/src/test/resources/task_cancel_net.xml index d73f36eae39..9abddae55fa 100644 --- a/src/test/resources/task_cancel_net.xml +++ b/src/test/resources/task_cancel_net.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test diff --git a/src/test/resources/task_events.xml b/src/test/resources/task_events.xml index a74caa83bdd..c6acdc131e2 100644 --- a/src/test/resources/task_events.xml +++ b/src/test/resources/task_events.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test true diff --git a/src/test/resources/task_events_test.xml b/src/test/resources/task_events_test.xml index 7733ddecda6..7ea03b2a985 100644 --- a/src/test/resources/task_events_test.xml +++ b/src/test/resources/task_events_test.xml @@ -1,7 +1,8 @@ task_events_test - TTT + 1.0.0 + TTT Task events test true false diff --git a/src/test/resources/task_ref_data.xml b/src/test/resources/task_ref_data.xml index 3db8b66a9d4..d8031ff72b7 100644 --- a/src/test/resources/task_ref_data.xml +++ b/src/test/resources/task_ref_data.xml @@ -1,7 +1,8 @@ task_ref_data - TRD + 1.0.0 + TRD Task ref data true false diff --git a/src/test/resources/task_ref_set_data_test.xml b/src/test/resources/task_ref_set_data_test.xml index ceb04e1e8dd..68bec546b80 100644 --- a/src/test/resources/task_ref_set_data_test.xml +++ b/src/test/resources/task_ref_set_data_test.xml @@ -1,7 +1,8 @@ task_ref_set_data_test - TRS + 1.0.0 + TRS Task ref set data test true false diff --git a/src/test/resources/task_reindex_test.xml b/src/test/resources/task_reindex_test.xml index 925de789dd7..54c1a285d6d 100644 --- a/src/test/resources/task_reindex_test.xml +++ b/src/test/resources/task_reindex_test.xml @@ -2,6 +2,7 @@ task_reindex_test + 1.0.0 TST task_reindex_test diff --git a/src/test/resources/taskref_init.xml b/src/test/resources/taskref_init.xml index c91cd8351df..7360f4d7450 100644 --- a/src/test/resources/taskref_init.xml +++ b/src/test/resources/taskref_init.xml @@ -2,6 +2,7 @@ taskref_init_test + 1.0.0 TIT Task ref init test net true diff --git a/src/test/resources/test.xml b/src/test/resources/test.xml index 170e539af3c..cdf603ec65c 100644 --- a/src/test/resources/test.xml +++ b/src/test/resources/test.xml @@ -1,6 +1,7 @@ test + 1.0.0 TST Test false diff --git a/src/test/resources/test_autocomplete_dynamic.xml b/src/test/resources/test_autocomplete_dynamic.xml index 03d660ad8a8..e8ad6a3c726 100644 --- a/src/test/resources/test_autocomplete_dynamic.xml +++ b/src/test/resources/test_autocomplete_dynamic.xml @@ -1,8 +1,10 @@ test_autocomplete_dynamic - Autocomplete Dynamic Net + 1.0.0 ADN + Autocomplete Dynamic Net + true diff --git a/src/test/resources/test_icon_enum.xml b/src/test/resources/test_icon_enum.xml index f9313270fc6..cae3654199c 100644 --- a/src/test/resources/test_icon_enum.xml +++ b/src/test/resources/test_icon_enum.xml @@ -2,6 +2,7 @@ test_icon_enum + 1.0.0 Icon Enum Net IEN diff --git a/src/test/resources/test_inter_data_actions_dynamic.xml b/src/test/resources/test_inter_data_actions_dynamic.xml index 26635c21d79..79d81f1c4a8 100644 --- a/src/test/resources/test_inter_data_actions_dynamic.xml +++ b/src/test/resources/test_inter_data_actions_dynamic.xml @@ -2,6 +2,7 @@ test + 1.0.0 TST Test true diff --git a/src/test/resources/test_inter_data_actions_static.xml b/src/test/resources/test_inter_data_actions_static.xml index f9434af77b3..d9d36b5326a 100644 --- a/src/test/resources/test_inter_data_actions_static.xml +++ b/src/test/resources/test_inter_data_actions_static.xml @@ -2,6 +2,7 @@ test_inter_data_actions_static.xml + 1.0.0 TST Test true diff --git a/src/test/resources/test_setData.xml b/src/test/resources/test_setData.xml index 04c0563d5d9..a08163a4ec7 100644 --- a/src/test/resources/test_setData.xml +++ b/src/test/resources/test_setData.xml @@ -1,6 +1,7 @@ test_setData - TSD + 1.0.0 + TSD TestSetData device_hub true diff --git a/src/test/resources/this_kw_test.xml b/src/test/resources/this_kw_test.xml index 5ef7628e5e9..8db835fb5d1 100644 --- a/src/test/resources/this_kw_test.xml +++ b/src/test/resources/this_kw_test.xml @@ -2,6 +2,7 @@ this_kw_test + 1.0.0 TKW This keyword test net true diff --git a/src/test/resources/user_list.xml b/src/test/resources/user_list.xml index 3741fd56d8a..6227616eb3c 100644 --- a/src/test/resources/user_list.xml +++ b/src/test/resources/user_list.xml @@ -2,6 +2,7 @@ user_list + 1.0.0 User list ULT diff --git a/src/test/resources/userrefs_test.xml b/src/test/resources/userrefs_test.xml index 438ff582ea4..d5eb781c3db 100644 --- a/src/test/resources/userrefs_test.xml +++ b/src/test/resources/userrefs_test.xml @@ -1,6 +1,7 @@ testing_model + 1.0.0 TSM Testing Model false diff --git a/src/test/resources/variable_arc_test.xml b/src/test/resources/variable_arc_test.xml index 199883e5db9..94124c22026 100644 --- a/src/test/resources/variable_arc_test.xml +++ b/src/test/resources/variable_arc_test.xml @@ -1,8 +1,10 @@ variable_arc_test.xml + 1.0.0 TST Test + true diff --git a/src/test/resources/view_permission_test.xml b/src/test/resources/view_permission_test.xml index 3d4819fe424..afd0c0bb414 100644 --- a/src/test/resources/view_permission_test.xml +++ b/src/test/resources/view_permission_test.xml @@ -2,6 +2,7 @@ vpt + 1.0.0 VPT ViewPermissionTest test true diff --git a/src/test/resources/view_permission_with_userRefs_test.xml b/src/test/resources/view_permission_with_userRefs_test.xml index 1adbecfb8e0..6420907c6c0 100644 --- a/src/test/resources/view_permission_with_userRefs_test.xml +++ b/src/test/resources/view_permission_with_userRefs_test.xml @@ -2,6 +2,7 @@ vpt_userRefs + 1.0.0 VPT ViewPermissionTest test true diff --git a/src/test/resources/workflow_authorization_service_test.xml b/src/test/resources/workflow_authorization_service_test.xml index 064aa7d689b..425f6fcc7fc 100644 --- a/src/test/resources/workflow_authorization_service_test.xml +++ b/src/test/resources/workflow_authorization_service_test.xml @@ -2,6 +2,7 @@ wst + 1.0.0 WST WorkflowAuthorizationService test diff --git a/src/test/resources/workflow_authorization_service_test_with_userRefs.xml b/src/test/resources/workflow_authorization_service_test_with_userRefs.xml index 3801963e3b4..29791ad5b81 100644 --- a/src/test/resources/workflow_authorization_service_test_with_userRefs.xml +++ b/src/test/resources/workflow_authorization_service_test_with_userRefs.xml @@ -2,6 +2,7 @@ wst_usersRef + 1.0.0 WSU WorkflowAuthorizationService test diff --git a/src/test/resources/zaverecna_praca.xml b/src/test/resources/zaverecna_praca.xml index f8e40e4354c..82b0926c9bb 100644 --- a/src/test/resources/zaverecna_praca.xml +++ b/src/test/resources/zaverecna_praca.xml @@ -1,6 +1,7 @@ zaverecna_praca.xml + 1.0.0 TST Test