Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
af2f5cb
Release/6.4.1
machacjozef Jan 9, 2025
ea2bc25
Release/6.5.0
machacjozef Jan 27, 2025
d02feea
[NAE-2034] Otvorenie prvého viewu
Retoocs Jan 28, 2025
458b89e
[NAE-2033] Úvodný dashboard
camperko Feb 5, 2025
c61be81
Merge remote-tracking branch 'origin/NAE-2034' into NAE-2033
camperko Feb 5, 2025
8bf002e
[NAE-2033] Úvodný dashboard
camperko Feb 7, 2025
c7cb0d2
[NAE-2033] Úvodný dashboard
camperko Feb 7, 2025
16a3cd6
[NAE-2051] Implementovat moznost volby komponentu zobrazenia v menu i…
Retoocs Feb 10, 2025
12c6379
[NAE-2051] Implementovat moznost volby komponentu zobrazenia v menu i…
Retoocs Feb 10, 2025
860b0d0
[NAE-2051] Implementovat moznost volby komponentu zobrazenia v menu i…
Retoocs Feb 10, 2025
255340f
[NAE-2051] Implement configurable view in menu items
Retoocs Feb 11, 2025
2bde47c
[NAE-2051] Implement configurable view in menu items
Retoocs Feb 11, 2025
570a7ad
[NAE-2051] Implement configurable view in menu items
Retoocs Feb 12, 2025
6c71bdb
[NAE-2051] Implement configurable view in menu items
Retoocs Feb 12, 2025
06d484d
[NAE-2033] Úvodný dashboard
camperko Feb 13, 2025
a5e0350
[NAE-2052] Integrate ticket view with menu items
Retoocs Feb 13, 2025
1097ea3
[NAE-2033] Úvodný dashboard
camperko Feb 13, 2025
9dc653e
[NAE-2033] Úvodný dashboard
camperko Feb 13, 2025
dd06c6e
[NAE-2052] Integrate ticket view with menu items
Retoocs Feb 14, 2025
7971f6d
[NAE-2052] Integrate ticket view with menu items
Retoocs Feb 14, 2025
817cb99
[NAE-2033] Úvodný dashboard
camperko Feb 14, 2025
be3da23
[NAE-2039] Search in workflow view
Kovy95 Feb 14, 2025
e3f80ab
[NAE-2039] Search in workflow view
Kovy95 Feb 14, 2025
145e3e0
[NAE-2052] Integrate ticket view with menu items
Retoocs Feb 14, 2025
1c16250
[NAE-2033] Úvodný dashboard
camperko Feb 14, 2025
7211660
[NAE-2039] Search in workflow view
Kovy95 Feb 14, 2025
fa81b02
[NAE-2052] Integrate ticket view with menu items
Retoocs Feb 14, 2025
4940da2
Merge remote-tracking branch 'origin/NAE-2051' into NAE-2054
Kovy95 Feb 17, 2025
c550d27
[NAE-2054] Release 6.5.0 CE
Kovy95 Feb 17, 2025
9cebf5f
Merge remote-tracking branch 'origin/NAE-2039' into NAE-2054
Kovy95 Feb 17, 2025
c47db8d
Merge remote-tracking branch 'origin/NAE-2052' into NAE-2054
Kovy95 Feb 17, 2025
32c8a4b
[NAE-2055] Rework enum registry to service registry
Retoocs Feb 17, 2025
56d016a
Revert "[NAE-2055] Rework enum registry to service registry"
Retoocs Feb 17, 2025
afe468f
[NAE-2051] Implement configurable view in menu items
Retoocs Feb 17, 2025
438bb1d
Merge remote-tracking branch 'origin/NAE-2051' into NAE-2052
Retoocs Feb 17, 2025
32f4c1a
[NAE-2054] Release 6.5.0 CE
Kovy95 Feb 17, 2025
9606298
Merge remote-tracking branch 'origin/NAE-2052' into NAE-2054
Kovy95 Feb 17, 2025
1fa70e9
[NAE-2054] Release 6.5.0 CE
Retoocs Feb 17, 2025
282497a
[NAE-2033] Úvodný dashboard
camperko Feb 18, 2025
34657bb
Merge pull request #290 from netgrif/NAE-2054
mazarijuraj Feb 18, 2025
7f14cf6
Release/6.5.0
Retoocs Feb 18, 2025
d6cec3e
Release/6.5.0
Retoocs Feb 18, 2025
ea16ee2
[NAE-2063] Action API 6.5.0
martinkranec Apr 10, 2025
103ad7b
[NAE-2063] Action API 6.5.0
martinkranec Apr 15, 2025
e51f1f6
[NAE-2063] Action API 6.5.0
martinkranec Apr 15, 2025
77a175a
[NAE-2063] Action API 6.5.0
martinkranec Apr 15, 2025
73fb713
[NAE-2063] Action API 6.5.0
martinkranec Apr 16, 2025
318c517
[NAE-2063] Action API 6.5.0
martinkranec Apr 16, 2025
8478fdb
[NAE-2063] Action API 6.5.0
martinkranec Apr 16, 2025
8875df0
[NAE-2063] Action API 6.5.0
martinkranec Apr 16, 2025
1e41d46
[NAE-2063] Action API 6.5.0
martinkranec Apr 16, 2025
693438c
[NAE-2063] Action API 6.5.0
martinkranec Apr 16, 2025
1c98631
[NAE-2063] Action API 6.5.0
Retoocs Apr 17, 2025
7817b3e
Merge pull request #297 from netgrif/NAE-2063
mazarijuraj Apr 17, 2025
1171c1d
[NAE-2099] MenuItemService.appendChildCaseIdInDataSet not setting has…
martinkranec May 9, 2025
c6febb8
Merge pull request #305 from netgrif/NAE-2099
mazarijuraj May 12, 2025
9866500
release 6.5.0-rc.1
machacjozef Jun 26, 2025
76b93bd
release 6.5.0-rc.1
machacjozef Jun 26, 2025
cef4f14
7.0.0-RC4
machacjozef Jul 21, 2025
0be42bf
7.0.0-RC4
machacjozef Jul 21, 2025
f0dded0
debug github pipeline
tuplle Jul 21, 2025
bcbb9a1
7.0.0-RC4
machacjozef Jul 21, 2025
082883b
Merge branch 'release/6.5.0' into NAE-2147
martinkranec Jul 21, 2025
36abe63
[NAE-2147] Merge 6.5.0 into 7.0.0
martinkranec Jul 21, 2025
2a2c35f
Merge branch 'release/7.0.0-rev4' into NAE-2147
martinkranec Jul 21, 2025
f6dedf0
[NAE-2147] Merge 6.5.0 into 7.0.0
martinkranec Jul 21, 2025
41dacdc
[NAE-2147] Merge 6.5.0 into 7.0.0
martinkranec Jul 26, 2025
7a85f22
[NAE-2147] Merge 6.5.0 into 7.0.0
martinkranec Jul 26, 2025
608c86d
[NAE-2147] Merge 6.5.0 into 7.0.0
martinkranec Jul 28, 2025
490cbb7
[NAE-2147] Merge 6.5.0 into 7.0.0
martinkranec Jul 28, 2025
f1981c5
Merge remote-tracking branch 'refs/remotes/origin/release/7.0.0-rev5'…
martinkranec Jul 28, 2025
84dac05
[NAE-2147] Merge 6.5.0 into 7.0.0
martinkranec Jul 29, 2025
7ec0cc9
Merge branch 'refs/heads/release/7.0.0-rev4' into NAE-2147
martinkranec Jul 29, 2025
a65b028
[NAE-2147] Merge 6.5.0 into 7.0.0
martinkranec Jul 29, 2025
33ff704
[NAE-2147] Merge 6.5.0 into 7.0.0
tuplle Jul 29, 2025
4832d9d
[NAE-2147] Merge 6.5.0 into 7.0.0
tuplle Jul 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/nexus-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,20 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: DEBUG PRINT
run: echo "$MAVEN_USERNAME / $MAVEN_PASSWORD"
shell: bash
env:
MAVEN_USERNAME: ${{ secrets.NEXUS_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}

Comment on lines +39 to +45
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Credentials echoed to logs – remove immediately

echo "$MAVEN_USERNAME / $MAVEN_PASSWORD" leaks raw credentials into the Actions log. Even if GitHub masks secrets, any mis-configuration or later copy-paste can expose them.
Delete this debug step or output only a placeholder.

-      - name: DEBUG PRINT
-        run: echo "$MAVEN_USERNAME / $MAVEN_PASSWORD"
-        shell: bash
-        env:
-          MAVEN_USERNAME: ${{ secrets.NEXUS_USERNAME }}
-          MAVEN_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: DEBUG PRINT
run: echo "$MAVEN_USERNAME / $MAVEN_PASSWORD"
shell: bash
env:
MAVEN_USERNAME: ${{ secrets.NEXUS_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
🤖 Prompt for AI Agents
In .github/workflows/nexus-release.yml around lines 39 to 45, the step that
echoes MAVEN_USERNAME and MAVEN_PASSWORD outputs sensitive credentials to the
logs. Remove the entire debug print step or replace the echo command with a
placeholder message that does not reveal any secret values to prevent credential
leakage.


- name: Set up Maven Central Repository
uses: actions/setup-java@v4
with:
java-version: 21
distribution: 'temurin'
server-id: netgrif-nexus-snapshots
server-id: netgrif-nexus-releases
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD

Expand Down
15 changes: 14 additions & 1 deletion application-engine/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@ 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/v7.0.0](https://github.com/netgrif/application-engine/commits/v7.0.0)
- Full Changelog: [https://github.com/netgrif/application-engine/commits/v6.5.0](https://github.com/netgrif/application-engine/commits/v6.5.0)

## [6.5.0](https://github.com/netgrif/application-engine/releases/tag/v6.5.0) (2025-02-18)

### Added
- [NAE-2033] Welcome dashboard

### 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

- Full Changelog: [https://github.com/netgrif/application-engine/commits/v7.0.0](https://github.com/netgrif/application-engine/commits/v7.0.0)

## [7.0.0](https://github.com/netgrif/application-engine/releases/tag/v7.0.0) (2024-11-22)

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,22 @@ class ImportHelper {
superCreator.setAllToSuperUser()
}

Optional<PetriNet> 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<PetriNet> filterNet = this.createNet(netFileName, VersionType.MAJOR, ActorTransformer.toLoggedUser(userService.getSystem()))

if (!filterNet.isPresent()) {
log.error("Import of ${message} failed!")
}

return filterNet
}

static ObjectNode populateDataset(Map<String, Map<String, String>> data) {
ObjectMapper mapper = new ObjectMapper()
String json = mapper.writeValueAsString(data)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
package com.netgrif.application.engine.elastic.service;

import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryStringQuery;
import com.netgrif.application.engine.configuration.ElasticsearchConfiguration;
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.objects.auth.domain.LoggedUser;
import com.netgrif.application.engine.objects.elastic.domain.ElasticPetriNet;
import com.netgrif.application.engine.objects.petrinet.domain.PetriNet;
import com.netgrif.application.engine.objects.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.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.dao.InvalidDataAccessApiUsageException;
Comment on lines +18 to 19
Copy link

@coderabbitai coderabbitai bot Jul 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify if @lazy annotation is necessary.

The @lazy annotation on line 19 suggests potential circular dependency issues. Consider verifying if this is still needed after fixing the constructor injection.


🏁 Script executed:

#!/bin/bash
# Check for circular dependencies by finding where ElasticPetriNetService is injected
rg -A 5 "ElasticPetriNetService" --glob "*.java" | grep -E "@Autowired|constructor|@RequiredArgsConstructor"

Length of output: 457


🏁 Script executed:

#!/bin/bash
# Show the beginning of ElasticPetriNetService to see where and how @Lazy is applied
sed -n '1,200p' application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java

Length of output: 9587


Remove unused Lazy import

The import org.springframework.context.annotation.Lazy is not used anywhere in ElasticPetriNetService (no @Lazy annotations present). You can safely delete it:

  • File: application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java
    • Remove the line:
    import org.springframework.context.annotation.Lazy;
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java
at lines 18 to 19, remove the unused import statement 'import
org.springframework.context.annotation.Lazy;' since there are no @Lazy
annotations used in the file. This will clean up unnecessary imports.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The annotation is presnet at line 58.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.elasticsearch.core.SearchHitSupport;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;

import static org.springframework.data.elasticsearch.client.elc.Queries.termQuery;

@Service
@Slf4j
public class ElasticPetriNetService implements IElasticPetriNetService {
Expand All @@ -16,9 +42,23 @@ public class ElasticPetriNetService implements IElasticPetriNetService {

private final Executor executors;

public ElasticPetriNetService(ElasticPetriNetRepository repository, Executor executors) {
private IPetriNetService petriNetService;

Comment on lines +45 to +46
Copy link

@coderabbitai coderabbitai bot Jul 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add petriNetService to constructor injection.

The petriNetService field is declared but not initialized in the constructor, which will cause NullPointerException when used in the search method.

-    private IPetriNetService petriNetService;
+    private final IPetriNetService petriNetService;

-    public ElasticPetriNetService(ElasticPetriNetRepository repository, Executor executors, ElasticsearchTemplate template, ElasticsearchConfiguration elasticsearchConfiguration) {
+    public ElasticPetriNetService(ElasticPetriNetRepository repository, Executor executors, ElasticsearchTemplate template, ElasticsearchConfiguration elasticsearchConfiguration, IPetriNetService petriNetService) {
         this.repository = repository;
         this.executors = executors;
         this.template = template;
         this.elasticsearchConfiguration = elasticsearchConfiguration;
+        this.petriNetService = petriNetService;
     }

Also applies to: 51-56

🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java
around lines 45-46 and 51-56, the petriNetService field is declared but not
initialized via constructor injection. To fix this, add a parameter for
IPetriNetService to the constructor and assign it to the petriNetService field.
This ensures petriNetService is properly initialized and prevents
NullPointerException when used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

petriNetService properties is initialized with lazy setter at line 60.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tuplle Thank you for the clarification! You're absolutely right. I can see that petriNetService is properly initialized using the @Lazy and @Autowired setter injection pattern at line 60:

@Lazy
@Autowired
public void setPetriNetService(IPetriNetService petriNetService) {
    this.petriNetService = petriNetService;
}

This is a valid Spring dependency injection pattern, often used to resolve circular dependencies. The @Lazy annotation ensures the dependency is initialized only when needed, and Spring will properly inject the service before any methods that use it are called. My suggestion for constructor injection was unnecessary in this case.

  (\   /)
 ( ._. )
o_("_")_o

learns something new


✏️ Learnings added
Learnt from: tuplle
PR: netgrif/application-engine#331
File: application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java:45-46
Timestamp: 2025-07-29T17:19:18.213Z
Learning: In ElasticPetriNetService class, petriNetService is properly initialized using @Lazy setter injection rather than constructor injection. This pattern with @Lazy @Autowired setter methods is commonly used in Spring to resolve circular dependencies and is a valid alternative to constructor injection.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

private final ElasticsearchTemplate template;

protected ElasticsearchConfiguration elasticsearchConfiguration;

public ElasticPetriNetService(ElasticPetriNetRepository repository, Executor executors, ElasticsearchTemplate template, ElasticsearchConfiguration elasticsearchConfiguration) {
this.repository = repository;
this.executors = executors;
this.template = template;
this.elasticsearchConfiguration = elasticsearchConfiguration;
}

@Lazy
@Autowired
public void setPetriNetService(IPetriNetService petriNetService) {
this.petriNetService = petriNetService;
}

@Override
Expand Down Expand Up @@ -54,4 +94,111 @@ public void remove(String id) {
log.info("[" + id + "]: PetriNet \"" + id + "\" deleted");
});
}

/**
* 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<PetriNetReference> - page of PetriNetReferences
*/
@Override
public Page<PetriNetReference> 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());
// TODO: impersonation
// LoggedUser loggedOrImpersonated = user.getSelfOrImpersonated();
LoggedUser loggedOrImpersonated = user;
NativeQuery query = buildQuery(requests, loggedOrImpersonated, pageable, locale, isIntersection);
List<PetriNet> netPage;
long total;
if (query != null) {
SearchHits<ElasticPetriNet> hits = template.search(query, ElasticPetriNet.class, IndexCoordinates.of(elasticsearchConfiguration.elasticPetriNetIndex()));
Page<ElasticPetriNet> 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 NativeQuery buildQuery(PetriNetSearch request, LoggedUser user, Pageable pageable, Locale locale, Boolean isIntersection) {
List<BoolQuery.Builder> 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<BoolQuery.Builder> reductionOperation = isIntersection ? (a, b) -> a.must(b.build()._toQuery()) : (a, b) -> a.should(b.build()._toQuery());
BoolQuery.Builder query = singleQueries.stream().reduce(new BoolQuery.Builder(), reductionOperation);

NativeQueryBuilder builder = new NativeQueryBuilder();
return builder
.withQuery(query.build()._toQuery())
.withPageable(pageable)
.build();
}

protected BoolQuery.Builder buildSingleQuery(PetriNetSearch request, LoggedUser user, Locale locale) {
BoolQuery.Builder query = new BoolQuery.Builder();

buildFullTextQuery(request, query);
boolean resultAlwaysEmpty = buildGroupQuery(request, user, locale, query);
if (resultAlwaysEmpty) {
return null;
}
return query;
}

protected void buildFullTextQuery(PetriNetSearch request, BoolQuery.Builder query) {
if (request.getTitle() == null || request.getTitle().isEmpty()) {
return;
}

// TODO refactor to properties
String searchText = "*" + request.getTitle() + "*";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Sanitize search input to prevent query injection

The search text is directly concatenated with wildcards without escaping special characters. This could lead to query errors or injection issues if the title contains Elasticsearch query syntax characters.

Consider escaping special characters:

-String searchText = "*" + request.getTitle() + "*";
+String searchText = "*" + QueryStringQuery.escape(request.getTitle()) + "*";

Or use a more robust approach with match queries instead of wildcards for better performance:

-String searchText = "*" + request.getTitle() + "*";
-QueryStringQuery fullTextQuery = QueryStringQuery.of(builder -> builder.fields(fullTextFields).query(searchText));
+QueryStringQuery fullTextQuery = QueryStringQuery.of(builder -> 
+    builder.fields(fullTextFields)
+           .query(request.getTitle())
+           .defaultOperator(Operator.And)
+           .analyzeWildcard(true)
+           .fuzziness("AUTO"));

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java
at line 176, the searchText is constructed by directly concatenating wildcards
to the request title without escaping special characters, which risks query
injection or errors. Fix this by sanitizing the input string to escape
Elasticsearch special characters before adding wildcards, or refactor the query
to use a match query instead of wildcard queries for safer and more efficient
searching.

List<String> fullTextFields = List.of(
"title.textValue^2",
"identifier^1"
);

QueryStringQuery fullTextQuery = QueryStringQuery.of(builder -> builder.fields(fullTextFields).query(searchText));
query.must(fullTextQuery._toQuery());
}

protected boolean buildGroupQuery(PetriNetSearch request, LoggedUser user, Locale locale, BoolQuery.Builder query) {
if (request.getGroup() == null || request.getGroup().isEmpty()) {
return false;
}

PetriNetSearch processQuery = new PetriNetSearch();
processQuery.setGroup(request.getGroup());
List<PetriNetReference> groupProcesses = this.petriNetService.search(processQuery, user, new FullPageRequest(), locale).getContent();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add pagination limits to prevent memory issues.

Using FullPageRequest without limits could retrieve an unbounded number of results, potentially causing memory issues.

-List<PetriNetReference> groupProcesses = this.petriNetService.search(processQuery, user, new FullPageRequest(), locale).getContent();
+// Limit to reasonable number of group processes
+Page<PetriNetReference> groupProcessPage = this.petriNetService.search(processQuery, user, PageRequest.of(0, 1000), locale);
+if (groupProcessPage.getTotalElements() > 1000) {
+    log.warn("Large number of group processes found: {}", groupProcessPage.getTotalElements());
+}
+List<PetriNetReference> groupProcesses = groupProcessPage.getContent();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
List<PetriNetReference> groupProcesses = this.petriNetService.search(processQuery, user, new FullPageRequest(), locale).getContent();
// Limit to reasonable number of group processes
Page<PetriNetReference> groupProcessPage = this.petriNetService.search(
processQuery,
user,
PageRequest.of(0, 1000),
locale
);
if (groupProcessPage.getTotalElements() > 1000) {
log.warn("Large number of group processes found: {}", groupProcessPage.getTotalElements());
}
List<PetriNetReference> groupProcesses = groupProcessPage.getContent();
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java
at line 185, the code uses FullPageRequest without pagination limits, which can
cause memory issues by loading too many results at once. Replace FullPageRequest
with a pageable request that sets explicit limits on page size and number to
restrict the amount of data retrieved in one call, preventing excessive memory
consumption.

if (groupProcesses.isEmpty())
return true;

BoolQuery.Builder groupQuery = new BoolQuery.Builder();
groupProcesses.stream().map(PetriNetReference::getIdentifier)
.map(netIdentifier -> termQuery("identifier", netIdentifier))
.forEach(termQuery -> groupQuery.should(termQuery._toQuery()));
query.filter(groupQuery.build()._toQuery());
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package com.netgrif.application.engine.elastic.service.interfaces;

import com.netgrif.application.engine.objects.auth.domain.LoggedUser;
import com.netgrif.application.engine.objects.elastic.domain.ElasticPetriNet;
import com.netgrif.application.engine.objects.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.Locale;

public interface IElasticPetriNetService {

@Async
Expand All @@ -11,4 +18,7 @@ public interface IElasticPetriNetService {
void indexNow(ElasticPetriNet net);

void remove(String id);

Page<PetriNetReference> search(PetriNetSearch requests, LoggedUser user, Pageable pageable, Locale locale, Boolean isIntersection);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.netgrif.application.engine.menu.services;


import com.netgrif.application.engine.auth.service.UserService;
import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService;
import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest;
import com.netgrif.application.engine.menu.services.interfaces.DashboardItemService;
import com.netgrif.application.engine.objects.auth.domain.AbstractUser;
import com.netgrif.application.engine.objects.auth.domain.ActorTransformer;
import com.netgrif.application.engine.objects.auth.domain.LoggedUser;
import com.netgrif.application.engine.objects.petrinet.domain.throwable.TransitionNotExecutableException;
import com.netgrif.application.engine.objects.utils.MenuItemUtils;
import com.netgrif.application.engine.objects.workflow.domain.Case;
import com.netgrif.application.engine.objects.workflow.domain.Task;
import com.netgrif.application.engine.objects.workflow.domain.menu.ToDataSetOutcome;
import com.netgrif.application.engine.objects.workflow.domain.menu.dashboard.DashboardItemBody;
import com.netgrif.application.engine.objects.workflow.domain.menu.dashboard.DashboardItemConstants;
import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService;
import com.netgrif.application.engine.startup.ImportHelper;
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 UserService 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());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate the sanitized ID before proceeding.

The method sanitizes the ID but doesn't validate the result. Consider checking if the sanitized ID is valid (non-empty, follows expected format) before using it.

 public Case getOrCreate(DashboardItemBody body) throws TransitionNotExecutableException {
-    MenuItemUtils.sanitize(body.getId());
+    String sanitizedId = MenuItemUtils.sanitize(body.getId());
+    if (sanitizedId == null || sanitizedId.trim().isEmpty()) {
+        throw new IllegalArgumentException("Invalid dashboard item ID after sanitization");
+    }
     Case itemCase;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
MenuItemUtils.sanitize(body.getId());
public Case getOrCreate(DashboardItemBody body) throws TransitionNotExecutableException {
String sanitizedId = MenuItemUtils.sanitize(body.getId());
if (sanitizedId == null || sanitizedId.trim().isEmpty()) {
throw new IllegalArgumentException("Invalid dashboard item ID after sanitization");
}
Case itemCase;
// … rest of method unchanged …
}
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
at line 54, after sanitizing the ID using MenuItemUtils.sanitize(body.getId()),
add validation to check if the sanitized ID is non-empty and matches the
expected format before proceeding. If the validation fails, handle the error
appropriately, such as throwing an exception or returning an error response, to
prevent using an invalid ID.

Case itemCase;
Comment on lines +55 to +56
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Add null check after sanitization

The sanitize method might return null or throw an exception for invalid input. Consider adding validation after sanitization.


🏁 Script executed:

#!/bin/bash
# Check the implementation of MenuItemUtils.sanitize to understand its behavior
ast-grep --pattern 'sanitize($_) {
  $$$
}'

# Also search for the method signature
rg -A 10 "sanitize\(" --glob "**/MenuItemUtils.java"

Length of output: 1553


🏁 Script executed:

#!/bin/bash
# Show context around getOrCreate in DashboardItemServiceImpl
rg -A10 -B10 "getOrCreate" --glob "**/DashboardItemServiceImpl.java"

Length of output: 3247


Ensure the sanitized ID is used and validated

The call to MenuItemUtils.sanitize(body.getId()) currently discards its result—and since sanitize(null) returns null, you could end up passing a null ID to findById. Update the code in getOrCreate to:

  • Assign the sanitized value to a local variable
  • Check it for null (or throw an appropriate exception)
  • Use it (or reset it on the body) for subsequent calls

For example:

-        MenuItemUtils.sanitize(body.getId());
-        Case itemCase;
+        String sanitizedId = MenuItemUtils.sanitize(body.getId());
+        if (sanitizedId == null) {
+            throw new IllegalArgumentException("Dashboard item ID must not be null or empty");
+        }
+        body.setId(sanitizedId);
+        Case itemCase;
         
-        itemCase = findById(body.getId());
+        itemCase = findById(sanitizedId);
  • body.setId(...) is optional if you prefer to use the local sanitizedId everywhere.
  • Ensure any callers downstream receive or use the cleaned value.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
MenuItemUtils.sanitize(body.getId());
Case itemCase;
// Sanitize and validate the incoming ID
String sanitizedId = MenuItemUtils.sanitize(body.getId());
if (sanitizedId == null) {
throw new IllegalArgumentException("Dashboard item ID must not be null or empty");
}
body.setId(sanitizedId);
Case itemCase;
itemCase = findById(sanitizedId);
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
around lines 54-55, the sanitized ID returned by
MenuItemUtils.sanitize(body.getId()) is not assigned or used, risking passing
null to findById. Fix this by assigning the sanitized ID to a local variable,
checking if it is null and throwing an exception if so, then use this sanitized
ID for all subsequent calls or optionally update body.setId with it to ensure
downstream code uses the cleaned value.


itemCase = findById(body.getId());
if (itemCase != null) {
log.info("Dashboard item with id:{} already exists", body.getId());
return itemCase;
}

LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedOrSystem());
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());
Comment on lines +81 to +82
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null check for itemCase parameter and validate sanitized ID.

The method should validate input parameters and the sanitized ID.

 public Case update(Case itemCase, DashboardItemBody body) throws TransitionNotExecutableException {
+    if (itemCase == null) {
+        throw new IllegalArgumentException("itemCase cannot be null");
+    }
+    if (body == null) {
+        throw new IllegalArgumentException("body cannot be null");
+    }
-    MenuItemUtils.sanitize(body.getId());
+    String sanitizedId = MenuItemUtils.sanitize(body.getId());
+    if (sanitizedId == null || sanitizedId.trim().isEmpty()) {
+        throw new IllegalArgumentException("Invalid dashboard item ID after sanitization");
+    }
     ToDataSetOutcome outcome = body.toDataSet();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public Case update(Case itemCase, DashboardItemBody body) throws TransitionNotExecutableException {
MenuItemUtils.sanitize(body.getId());
public Case update(Case itemCase, DashboardItemBody body) throws TransitionNotExecutableException {
if (itemCase == null) {
throw new IllegalArgumentException("itemCase cannot be null");
}
if (body == null) {
throw new IllegalArgumentException("body cannot be null");
}
String sanitizedId = MenuItemUtils.sanitize(body.getId());
if (sanitizedId == null || sanitizedId.trim().isEmpty()) {
throw new IllegalArgumentException("Invalid dashboard item ID after sanitization");
}
ToDataSetOutcome outcome = body.toDataSet();
// …rest of method unchanged…
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
around lines 80 to 81, add a null check for the itemCase parameter at the start
of the update method to prevent NullPointerExceptions. Also, after sanitizing
the ID from the body, validate that the sanitized ID is not null or empty before
proceeding. If either check fails, throw an appropriate exception or handle the
error to ensure input parameters are valid.

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);
Comment on lines +95 to +98
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Sanitize the identifier to prevent potential query injection.

The identifier is directly inserted into the query string without sanitization, which could lead to query injection vulnerabilities if the identifier contains special characters or malicious input.

 public Case findById(String identifier) {
+    if (identifier == null || identifier.trim().isEmpty()) {
+        return null;
+    }
+    String sanitizedIdentifier = MenuItemUtils.sanitize(identifier);
     String query = String.format("processIdentifier:%s AND dataSet.%s.textValue.keyword:\"%s\"",
-            DashboardItemConstants.PROCESS_IDENTIFIER, DashboardItemConstants.FIELD_ID, identifier);
+            DashboardItemConstants.PROCESS_IDENTIFIER, DashboardItemConstants.FIELD_ID, sanitizedIdentifier);
     return findCase(DashboardItemConstants.PROCESS_IDENTIFIER, query);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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);
public Case findById(String identifier) {
if (identifier == null || identifier.trim().isEmpty()) {
return null;
}
String sanitizedIdentifier = MenuItemUtils.sanitize(identifier);
String query = String.format(
"processIdentifier:%s AND dataSet.%s.textValue.keyword:\"%s\"",
DashboardItemConstants.PROCESS_IDENTIFIER,
DashboardItemConstants.FIELD_ID,
sanitizedIdentifier);
return findCase(DashboardItemConstants.PROCESS_IDENTIFIER, query);
}
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
around lines 94 to 97, the identifier is directly embedded into the query string
without sanitization, risking query injection. To fix this, sanitize or escape
the identifier to neutralize special characters before including it in the query
string, ensuring it cannot alter the query structure or introduce malicious
input.

}

protected Case setData(Case useCase, String transId, Map<String, Map<String, Object>> dataSet) {
String taskId = MenuItemUtils.findTaskIdInCase(useCase, transId);
return dataService.setData(taskId, ImportHelper.populateDataset((Map) dataSet)).getCase();
}
Comment on lines +101 to +104
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Document or remove unused method.

The setData method appears to be unused within this class. If it's intended for use by subclasses, please add appropriate documentation. Otherwise, consider removing it to reduce code complexity.


🏁 Script executed:

#!/bin/bash
# Search for usage of setData method in the codebase
rg -A 2 "setData\(" --type java | grep -B 2 -A 2 "DashboardItemServiceImpl"

Length of output: 1756


🏁 Script executed:

#!/bin/bash
# Show the beginning of MenuItemService to identify its type and declaration of setData
sed -n '1,200p' application-engine/src/main/java/com/netgrif/application/engine/menu/services/MenuItemService.java
echo '---'
# Show the beginning of DashboardItemServiceImpl to see its class signature and overrides
sed -n '1,200p' application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java

Length of output: 15957


Remove or document unused setData helper in DashboardItemServiceImpl

The protected setData(Case, String, Map<String,Map<String,Object>>) method isn’t referenced anywhere in DashboardItemServiceImpl.
• If this helper is intended for future subclass use, please add Javadoc (e.g. explain its purpose and visibility).
• Otherwise, remove the unused method to simplify the class.

🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
around lines 100 to 103, the protected setData method is unused in the class.
Either remove this method to simplify the class or, if it is intended for future
subclass use, add Javadoc explaining its purpose and why it is protected to
clarify its role.


@SuppressWarnings({"unchecked", "rawtypes"})
protected Case setDataWithExecute(Case useCase, String transId, Map<String, Map<String, Object>> dataSet) throws TransitionNotExecutableException {
AbstractUser 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<Case> resultPage = elasticCaseService.search(java.util.List.of(request), ActorTransformer.toLoggedUser(userService.getLoggedOrSystem()),
PageRequest.of(0, 1), Locale.getDefault(), false);

return resultPage.hasContent() ? resultPage.getContent().get(0) : null;
}
}
Loading
Loading