Skip to content

Commit

Permalink
feat(engine): like-queries for candidate groups
Browse files Browse the repository at this point in the history
- adds functionality to filter tasks on a candidate group using a like-statement

related to #4540
  • Loading branch information
jordy-cantaert-vaph authored and joaquinfelici committed Sep 17, 2024
1 parent 16f5002 commit e9bf0e5
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ public class TaskQueryDto extends AbstractQueryDto<TaskQuery> {
private String[] assigneeNotIn;
private String candidateGroup;
private String candidateGroupExpression;
private String candidateGroupLike;
private String candidateUser;
private String candidateUserExpression;
private Boolean includeAssignedTasks;
Expand Down Expand Up @@ -347,6 +348,11 @@ public void setCandidateGroupExpression(String candidateGroupExpression) {
this.candidateGroupExpression = candidateGroupExpression;
}

@CamundaQueryParam("candidateGroupLike")
public void setCandidateGroupLike(String candidateGroupLike) {
this.candidateGroupLike = candidateGroupLike;
}

@CamundaQueryParam(value = "withCandidateGroups", converter = BooleanConverter.class)
public void setWithCandidateGroups(Boolean withCandidateGroups) {
this.withCandidateGroups = withCandidateGroups;
Expand Down Expand Up @@ -821,6 +827,10 @@ public String getCandidateGroupExpression() {
return candidateGroupExpression;
}

public String getCandidateGroupLike() {
return candidateGroupLike;
}

public String getCandidateUser() {
return candidateUser;
}
Expand Down Expand Up @@ -1169,6 +1179,9 @@ protected void applyFilters(TaskQuery query) {
if (candidateGroupExpression != null) {
query.taskCandidateGroupExpression(candidateGroupExpression);
}
if (candidateGroupLike != null) {
query.taskCandidateGroupLike(candidateGroupLike);
}
if (withCandidateGroups != null && withCandidateGroups) {
query.withCandidateGroups();
}
Expand Down Expand Up @@ -1570,6 +1583,7 @@ public static TaskQueryDto fromQuery(Query<?, ?> query, boolean isOrQueryActive)

dto.candidateUser = taskQuery.getCandidateUser();
dto.candidateGroup = taskQuery.getCandidateGroup();
dto.candidateGroupLike = taskQuery.getCandidateGroupLike();
dto.candidateGroups = taskQuery.getCandidateGroupsInternal();
dto.includeAssignedTasks = taskQuery.isIncludeAssignedTasksInternal();
dto.withCandidateGroups = taskQuery.isWithCandidateGroups();
Expand Down Expand Up @@ -1876,4 +1890,4 @@ else if (VariableInstanceQueryProperty.VARIABLE_TYPE.equals(property)) {
}
return parameters;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,7 @@ private Map<String, String> getCompleteStringQueryParameters() {
parameters.put("assignee", "anAssignee");
parameters.put("assigneeLike", "anAssigneeLike");
parameters.put("candidateGroup", "aCandidateGroup");
parameters.put("candidateGroupLike", "aCandidateGroupLike");
parameters.put("candidateUser", "aCandidate");
parameters.put("includeAssignedTasks", "false");
parameters.put("taskId", "aTaskId");
Expand Down Expand Up @@ -566,6 +567,7 @@ private void verifyStringParameterQueryInvocations() {
verify(mockQuery).taskAssignee(stringQueryParameters.get("assignee"));
verify(mockQuery).taskAssigneeLike(stringQueryParameters.get("assigneeLike"));
verify(mockQuery).taskCandidateGroup(stringQueryParameters.get("candidateGroup"));
verify(mockQuery).taskCandidateGroupLike(stringQueryParameters.get("candidateGroupLike"));
verify(mockQuery).taskCandidateUser(stringQueryParameters.get("candidateUser"));
verify(mockQuery).taskDefinitionKey(stringQueryParameters.get("taskDefinitionKey"));
verify(mockQuery).taskDefinitionKeyLike(stringQueryParameters.get("taskDefinitionKeyLike"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public class TaskQueryImpl extends AbstractQuery<TaskQuery, Task> implements Tas
protected DelegationState delegationState;
protected String candidateUser;
protected String candidateGroup;
protected String candidateGroupLike;
protected List<String> candidateGroups;
protected Boolean withCandidateGroups;
protected Boolean withoutCandidateGroups;
Expand Down Expand Up @@ -451,6 +452,18 @@ public TaskQuery taskCandidateGroupExpression(String candidateGroupExpression) {
return this;
}

@Override
public TaskQuery taskCandidateGroupLike(String candidateGroup) {
ensureNotNull("Candidate group like", candidateGroup);

if (!isOrQueryActive && (candidateUser != null || expressions.containsKey("taskCandidateUser"))) {
throw new ProcessEngineException("Invalid query usage: cannot set both candidateGroup and candidateUser");
}

this.candidateGroupLike = candidateGroup;
return this;
}

@Override
public TaskQuery taskCandidateGroupIn(List<String> candidateGroups) {
ensureNotEmpty("Candidate group list", candidateGroups);
Expand Down Expand Up @@ -482,10 +495,11 @@ public TaskQuery taskCandidateGroupInExpression(String candidateGroupsExpression

@Override
public TaskQuery includeAssignedTasks() {
if (candidateUser == null && candidateGroup == null && candidateGroups == null && !isWithCandidateGroups() && !isWithoutCandidateGroups() && !isWithCandidateUsers() && !isWithoutCandidateUsers()
if (candidateUser == null && candidateGroup == null && candidateGroupLike == null && candidateGroups == null
&& !isWithCandidateGroups() && !isWithoutCandidateGroups() && !isWithCandidateUsers() && !isWithoutCandidateUsers()
&& !expressions.containsKey("taskCandidateUser") && !expressions.containsKey("taskCandidateGroup")
&& !expressions.containsKey("taskCandidateGroupIn")) {
throw new ProcessEngineException("Invalid query usage: candidateUser, candidateGroup, candidateGroupIn, withCandidateGroups, withoutCandidateGroups, withCandidateUsers, withoutCandidateUsers has to be called before 'includeAssignedTasks'.");
throw new ProcessEngineException("Invalid query usage: candidateUser, candidateGroup, candidateLike, candidateGroupIn, withCandidateGroups, withoutCandidateGroups, withCandidateUsers, withoutCandidateUsers has to be called before 'includeAssignedTasks'.");
}

includeAssignedTasks = true;
Expand Down Expand Up @@ -1578,6 +1592,10 @@ public String getCandidateGroup() {
return candidateGroup;
}

public String getCandidateGroupLike() {
return candidateGroupLike;
}

public boolean isIncludeAssignedTasks() {
return includeAssignedTasks != null ? includeAssignedTasks : false;
}
Expand Down Expand Up @@ -1929,6 +1947,13 @@ else if (this.getCandidateGroup() != null) {
extendedQuery.taskCandidateGroup(this.getCandidateGroup());
}

if (extendingQuery.getCandidateGroupLike() != null) {
extendedQuery.taskCandidateGroupLike(extendingQuery.getCandidateGroupLike());
}
else if (this.getCandidateGroupLike() != null) {
extendedQuery.taskCandidateGroupLike(this.getCandidateGroupLike());
}

if (extendingQuery.isWithCandidateGroups() || this.isWithCandidateGroups()) {
extendedQuery.withCandidateGroups();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public class JsonTaskQueryConverter extends JsonObjectConverter<TaskQuery> {
public static final String DELEGATION_STATE = "delegationState";
public static final String CANDIDATE_USER = "candidateUser";
public static final String CANDIDATE_GROUP = "candidateGroup";
public static final String CANDIDATE_GROUP_LIKE = "candidateGroupLike";
public static final String CANDIDATE_GROUPS = "candidateGroups";
public static final String WITH_CANDIDATE_GROUPS = "withCandidateGroups";
public static final String WITHOUT_CANDIDATE_GROUPS = "withoutCandidateGroups";
Expand Down Expand Up @@ -166,6 +167,7 @@ public JsonObject toJsonObject(TaskQuery taskQuery, boolean isOrQueryActive) {
JsonUtil.addField(json, DELEGATION_STATE, query.getDelegationStateString());
JsonUtil.addField(json, CANDIDATE_USER, query.getCandidateUser());
JsonUtil.addField(json, CANDIDATE_GROUP, query.getCandidateGroup());
JsonUtil.addField(json, CANDIDATE_GROUP_LIKE, query.getCandidateGroupLike());
JsonUtil.addListField(json, CANDIDATE_GROUPS, query.getCandidateGroupsInternal());
JsonUtil.addDefaultField(json, WITH_CANDIDATE_GROUPS, false, query.isWithCandidateGroups());
JsonUtil.addDefaultField(json, WITHOUT_CANDIDATE_GROUPS, false, query.isWithoutCandidateGroups());
Expand Down Expand Up @@ -363,6 +365,9 @@ protected TaskQuery toObject(JsonObject json, boolean isOrQuery) {
if (json.has(CANDIDATE_GROUP)) {
query.taskCandidateGroup(JsonUtil.getString(json, CANDIDATE_GROUP));
}
if (json.has(CANDIDATE_GROUP_LIKE)) {
query.taskCandidateGroupLike(JsonUtil.getString(json, CANDIDATE_GROUP_LIKE));
}
if (json.has(CANDIDATE_GROUPS) && !json.has(CANDIDATE_USER) && !json.has(CANDIDATE_GROUP)) {
query.taskCandidateGroupIn(getList(JsonUtil.getArray(json, CANDIDATE_GROUPS)));
}
Expand Down
19 changes: 19 additions & 0 deletions engine/src/main/java/org/camunda/bpm/engine/task/TaskQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,25 @@ public interface TaskQuery extends Query<TaskQuery, Task> {
*/
TaskQuery taskCandidateGroupExpression(String candidateGroupExpression);

/**
* Only select tasks for which users in the given group are candidates.
* The syntax is that of SQL: for example usage: nameLike(%camunda%)
*
* <p>
* Per default it only selects tasks which are not already assigned
* to a user. To also include assigned task in the result specify
* {@link #includeAssignedTasks()} in your query.
* </p>
*
* @throws ProcessEngineException <ul><li>When query is executed and {@link #taskCandidateUser(String)} or
* {@link #taskCandidateUserExpression(String)} (List)} has been executed on the
* "and query" instance. <br>
* No exception is thrown when query is executed and {@link #taskCandidateUser(String)} or
* {@link #taskCandidateUserExpression(String)} has been executed on the "or query" instance.</li>
* <li>When passed group is <code>null</code>.</li></ul>
*/
TaskQuery taskCandidateGroupLike(String candidateGroup);

/**
* Only select tasks for which the 'candidateGroup' is one of the given groups.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@
<if test="query.isOrQueryActive">
<bind name="JOIN_TYPE" value="'left join'" />
</if>
<if test="query != null &amp;&amp; (query.candidateUser != null || query.candidateGroups != null || query.involvedUser != null || query.withCandidateGroups || query.withCandidateUsers)">
<if test="query != null &amp;&amp; (query.candidateUser != null || query.candidateGroups != null || query.candidateGroupLike != null || query.involvedUser != null || query.withCandidateGroups || query.withCandidateUsers)">
<bind name="I_JOIN" value="true" />
</if>
<!-- the process definition table is joined if
Expand Down Expand Up @@ -577,14 +577,14 @@
<if test="query.isWithoutTenantId">
${queryType} RES.TENANT_ID_ is null
</if>
<if test="query.candidateUser != null || query.candidateGroups != null || query.withCandidateGroups || query.withCandidateUsers">
<if test="query.candidateUser != null || query.candidateGroups != null || query.candidateGroupLike != null || query.withCandidateGroups || query.withCandidateUsers">
${queryType}
<trim prefixOverrides="and" prefix="(" suffix=")">
<if test="!query.includeAssignedTasks">
and RES.ASSIGNEE_ is null
</if>
and I.TYPE_ = 'candidate'
<if test="query.candidateUser != null || query.candidateGroups != null">
<if test="query.candidateUser != null || query.candidateGroups != null || query.candidateGroupLike != null">
and
(
<if test="query.candidateUser != null">
Expand All @@ -600,6 +600,14 @@
#{group}
</foreach>
</if>
<!-- If we need to add the candidateGroupsLike statement and there have been previous statements
in de candidates block, then we need to add an "or" first -->
<if test="query.candidateGroupLike != null &amp;&amp; (query.candidateUser != null || (query.candidateGroups != null &amp;&amp; query.candidateGroups.size &gt; 0))">
or
</if>
<if test="query.candidateGroupLike != null">
I.GROUP_ID_ LIKE #{candidateGroupLike}
</if>
)
</if>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ public void testTaskQuery() {
query.taskUnassigned();
query.taskAssigned();
query.taskDelegationState(testDelegationState);
query.taskCandidateGroupLike(testString);
query.taskCandidateGroupIn(testCandidateGroups);
query.taskCandidateGroupInExpression(testString);
query.withCandidateGroups();
Expand Down Expand Up @@ -290,6 +291,7 @@ public void testTaskQuery() {
assertTrue(query.isUnassigned());
assertTrue(query.isAssigned());
assertEquals(testDelegationState, query.getDelegationState());
assertEquals(testString, query.getCandidateGroupLike());
assertEquals(testCandidateGroups, query.getCandidateGroups());
assertTrue(query.isWithCandidateGroups());
assertTrue(query.isWithoutCandidateGroups());
Expand Down Expand Up @@ -657,6 +659,17 @@ public void testTaskQueryCandidateGroup() {
assertEquals(testGroup.getId(), query.getExpressions().get("taskCandidateGroup"));
}

@Test
public void testTaskQueryCandidateGroupLike() {
TaskQueryImpl query = new TaskQueryImpl();
query.taskCandidateGroupLike(testGroup.getId());

saveQuery(query);
query = filter.getQuery();

assertEquals(testGroup.getId(), query.getCandidateGroupLike());
}

@Test
public void testTaskQueryCandidateUserIncludeAssignedTasks() {
TaskQueryImpl query = new TaskQueryImpl();
Expand Down Expand Up @@ -709,6 +722,19 @@ public void testTaskQueryCandidateGroupExpressionIncludeAssignedTasks() {
assertTrue(query.isIncludeAssignedTasks());
}

@Test
public void testTaskQueryCandidateGroupLikeIncludeAssignedTasks() {
TaskQueryImpl query = new TaskQueryImpl();
query.taskCandidateGroupLike(testGroup.getId());
query.includeAssignedTasks();

saveQuery(query);
query = filter.getQuery();

assertEquals(testGroup.getId(), query.getCandidateGroupLike());
assertTrue(query.isIncludeAssignedTasks());
}

@Test
public void testTaskQueryCandidateGroupsIncludeAssignedTasks() {
TaskQueryImpl query = new TaskQueryImpl();
Expand Down Expand Up @@ -823,6 +849,63 @@ public void testExtendingTaskQueryWithAssigneeNotIn() {
assertEquals(0, extendingQueryTasks.size());
}

@Test
public void testExtendingEmptyTaskQueryWithCandidateGroupLike() {
TaskQuery query = taskService.createTaskQuery();

saveQuery(query);

List<Task> tasks = filterService.list(filter.getId());
// 3 test tasks have been created during setup (no filters -> we expect all tasks to be found)
assertEquals(3, tasks.size());

// We extend the query with a "candidate group like"
TaskQuery extendingQuery = taskService.createTaskQuery();
extendingQuery.taskCandidateGroupLike("%count%");

tasks = filterService.list(filter.getId(), extendingQuery);
// There is 1 unassigned task with the candidate group "accounting"
assertEquals(1, tasks.size());
}

@Test
public void testExtendingCandidateGroupLikeTaskQueryWithEmpty() {
TaskQuery query = taskService.createTaskQuery().taskCandidateGroupLike("%count%");

saveQuery(query);

List<Task> tasks = filterService.list(filter.getId());
// 3 test tasks have been created, 1 is unassigned and matches the "like" (candidate group "accounting")
assertEquals(1, tasks.size());

// We extend the query with an empty query
TaskQuery extendingQuery = taskService.createTaskQuery();

tasks = filterService.list(filter.getId(), extendingQuery);
// The empty query should have been ignored in favor of the existing value for "candidate group like"
assertEquals(1, tasks.size());
}

@Test
public void testExtendingCandidateGroupLikeTaskQueryWithCandidateGroupLike() {
TaskQuery query = taskService.createTaskQuery().taskCandidateGroupLike("HR");

saveQuery(query);

List<Task> tasks = filterService.list(filter.getId());
// 3 test tasks have been created, none match with the query "HR"
assertTrue(tasks.isEmpty());

// We extend the query with a "candidate groups like" query
TaskQuery extendingQuery = taskService.createTaskQuery();
extendingQuery.taskCandidateGroupLike("acc%");

tasks = filterService.list(filter.getId(), extendingQuery);
// The existing query should have been overwritten with our new value, which matches 1 unassigned task
// with candidate group "accounting"
assertEquals(1, tasks.size());
}

@Test
public void testExtendingTaskQueryListWithCandidateGroups() {
TaskQuery query = taskService.createTaskQuery();
Expand Down
Loading

0 comments on commit e9bf0e5

Please sign in to comment.