Skip to content

Commit

Permalink
Closes #2338 - Add query of tasks without owner
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesrdi committed Aug 10, 2023
1 parent cd9f160 commit 05187b0
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static StringBuilder whereIn(String collection, String column, StringBuil
.append("</when>")
.append("<otherwise>0=1</otherwise>")
.append("</choose>");
if (column.matches("t.CUSTOM_\\d+")) {
if (column.matches("t.CUSTOM_\\d+") || column.matches("t.OWNER")) {
sb.append("<if test='" + collection + "ContainsNull'> OR " + column + " IS NULL </if>");
}
return sb.append(")</if> ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1814,6 +1814,7 @@ class Owner {
TaskSummary taskSummary1;
TaskSummary taskSummary2;
TaskSummary taskSummary3;
TaskSummary taskSummary4;

@WithAccessId(user = "user-1-1")
@BeforeAll
Expand All @@ -1822,6 +1823,7 @@ void setup() throws Exception {
taskSummary1 = taskInWorkbasket(wb).owner("user-2-1").buildAndStoreAsSummary(taskService);
taskSummary2 = taskInWorkbasket(wb).owner("user-1-2").buildAndStoreAsSummary(taskService);
taskSummary3 = taskInWorkbasket(wb).owner("user-1-3").buildAndStoreAsSummary(taskService);
taskSummary4 = taskInWorkbasket(wb).owner(null).buildAndStoreAsSummary(taskService);
}

@WithAccessId(user = "user-1-1")
Expand Down Expand Up @@ -1859,6 +1861,29 @@ void should_ApplyFilter_When_QueryingForOwnerNotLike() {

assertThat(list).containsExactly(taskSummary1);
}

@WithAccessId(user = "user-1-1")
@Test
void should_ReturnTaskWithOwnerNull_When_QueryingForOwnerIn() {
String[] nullArray = {null};
List<TaskSummary> list =
taskService.createTaskQuery().workbasketIdIn(wb.getId()).ownerIn(nullArray).list();

assertThat(list).containsExactly(taskSummary4);
}

@WithAccessId(user = "user-1-1")
@Test
void should_ReturnTaskWithOwnerNullAndUser11_When_QueryingForOwnerIn() {
List<TaskSummary> list =
taskService
.createTaskQuery()
.workbasketIdIn(wb.getId())
.ownerIn("user-1-2", null)
.list();

assertThat(list).containsExactlyInAnyOrder(taskSummary2, taskSummary4);
}
}

@Nested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ public class TaskQueryImpl implements TaskQuery {
private String[] parentBusinessProcessIdLike;
private String[] parentBusinessProcessIdNotLike;
private String[] ownerIn;
private boolean ownerInContainsNull;
private String[] ownerNotIn;
private String[] ownerLike;
private String[] ownerNotLike;
Expand Down Expand Up @@ -865,7 +866,15 @@ public TaskQuery orderByParentBusinessProcessId(SortDirection sortDirection) {

@Override
public TaskQuery ownerIn(String... owners) {
this.ownerIn = owners;
List<String> conditionList = new ArrayList<>(Arrays.asList(owners));
boolean containsNull = conditionList.contains(null);
if (containsNull) {
conditionList.remove(null);
ownerInContainsNull = true;
this.ownerIn = conditionList.toArray(new String[owners.length - 1]);
} else {
this.ownerIn = owners;
}
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import pro.taskana.common.api.exceptions.InvalidArgumentException;

public class QueryParamsValidator {

Expand All @@ -32,5 +35,21 @@ public static void validateParams(HttpServletRequest request, Class<?>... filter
if (!providedParams.isEmpty()) {
throw new IllegalArgumentException("Unknown request parameters found: " + providedParams);
}
checkExactParam(request, "owner-is-null");
}

private static void checkExactParam(HttpServletRequest request, String queryParameter) {
String queryString = request.getQueryString();
boolean containParam = queryString != null && queryString.contains(queryParameter);
if (containParam) {
Pattern pattern = Pattern.compile("\\b" + queryParameter + "(&|$)");
Matcher matcher = pattern.matcher(queryString);

boolean hasExactParam = matcher.find();
if (!hasExactParam) {
throw new InvalidArgumentException(
"It is prohibited to use the param " + queryParameter + " with values.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import java.beans.ConstructorProperties;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import pro.taskana.common.api.IntInterval;
import pro.taskana.common.api.KeyDomain;
Expand Down Expand Up @@ -740,6 +742,13 @@ public class TaskQueryFilterParameter implements QueryParameter<TaskQuery, Void>
*/
@JsonProperty("owner-not-like")
private final String[] ownerNotLike;

/**
* Filter by tasks that have no owner. The parameter should exactly be "owner-is-null" without
* being followed by "=..."
*/
@JsonProperty("owner-is-null")
private final String ownerNull;
// endregion
// region primaryObjectReference
/**
Expand Down Expand Up @@ -1259,6 +1268,7 @@ public class TaskQueryFilterParameter implements QueryParameter<TaskQuery, Void>
"owner-not",
"owner-like",
"owner-not-like",
"owner-is-null",
"por",
"por-company",
"por-company-not",
Expand Down Expand Up @@ -1415,6 +1425,7 @@ public TaskQueryFilterParameter(
String[] ownerNotIn,
String[] ownerLike,
String[] ownerNotLike,
String ownerNull,
ObjectReference[] primaryObjectReferenceIn,
String[] porCompanyIn,
String[] porCompanyNotIn,
Expand Down Expand Up @@ -1570,6 +1581,7 @@ public TaskQueryFilterParameter(
this.ownerNotIn = ownerNotIn;
this.ownerLike = ownerLike;
this.ownerNotLike = ownerNotLike;
this.ownerNull = ownerNull;
this.primaryObjectReferenceIn = primaryObjectReferenceIn;
this.porCompanyIn = porCompanyIn;
this.porCompanyNotIn = porCompanyNotIn;
Expand Down Expand Up @@ -1845,7 +1857,8 @@ public Void apply(TaskQuery query) {
.map(this::wrapElementsInLikeStatement)
.ifPresent(query::parentBusinessProcessIdNotLike);

Optional.ofNullable(ownerIn).ifPresent(query::ownerIn);
String[] ownerInIncludingNull = addNullToOwnerIn();
Optional.ofNullable(ownerInIncludingNull).ifPresent(query::ownerIn);
Optional.ofNullable(ownerNotIn).ifPresent(query::ownerNotIn);
Optional.ofNullable(ownerLike)
.map(this::wrapElementsInLikeStatement)
Expand Down Expand Up @@ -2184,4 +2197,16 @@ private void validateFilterParameters() throws InvalidArgumentException {
"provided value of the property 'without-attachment' must be 'true'");
}
}

private String[] addNullToOwnerIn() {
if (this.ownerNull == null) {
return this.ownerIn;
}
if (this.ownerIn == null) {
return new String[]{null};
}
List<String> ownerInAsList = new ArrayList(Arrays.asList(this.ownerIn));
ownerInAsList.add(null);
return ownerInAsList.toArray(new String[ownerInAsList.size()]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.function.ThrowingConsumer;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
Expand All @@ -37,6 +39,7 @@
import org.springframework.web.client.HttpStatusCodeException;
import pro.taskana.TaskanaConfiguration;
import pro.taskana.classification.rest.models.ClassificationSummaryRepresentationModel;
import pro.taskana.common.internal.util.Pair;
import pro.taskana.common.rest.RestEndpoints;
import pro.taskana.rest.test.RestHelper;
import pro.taskana.rest.test.TaskanaSpringBootTest;
Expand Down Expand Up @@ -1165,6 +1168,46 @@ void should_KeepFiltersInTheLinkOfTheResponse_When_GettingTasks() {
+ "&sort-by=POR_VALUE&order=DESCENDING");
}

@ParameterizedTest
@CsvSource({
"owner=user-1-1, 10",
"owner-is-null, 65",
"owner-is-null&owner=user-1-1, 75",
"state=READY&owner-is-null&owner=user-1-1, 56",
})
void should_ReturnTasksWithVariousOwnerParameters_When_GettingTasks(
String queryParams, int expectedSize) {
String url = restHelper.toUrl(RestEndpoints.URL_TASKS) + "?" + queryParams;
HttpEntity<Object> auth = new HttpEntity<>(RestHelper.generateHeadersForUser("admin"));
ResponseEntity<TaskSummaryPagedRepresentationModel> response =
TEMPLATE.exchange(url, HttpMethod.GET, auth, TASK_SUMMARY_PAGE_MODEL_TYPE);

assertThat(response.getBody()).isNotNull();
assertThat((response.getBody()).getLink(IanaLinkRelations.SELF)).isNotNull();
assertThat((response.getBody()).getContent()).hasSize(expectedSize);
}

@TestFactory
Stream<DynamicTest> should_ThrowException_When_OwnerIsNullParamNotStrict() {
List<Pair<String, String>> list =
List.of(
Pair.of("When owner-is-null=", "?owner-is-null="),
Pair.of("When owner-is-null=anyValue", "?owner-is-null=anyValue1,anyValue2"));
ThrowingConsumer<Pair<String, String>> testOwnerIsNull =
t -> {
String url = restHelper.toUrl(RestEndpoints.URL_TASKS) + t.getRight();
HttpEntity<Object> auth = new HttpEntity<>(RestHelper.generateHeadersForUser("admin"));

assertThatThrownBy(
() ->
TEMPLATE.exchange(url, HttpMethod.GET, auth, TASK_SUMMARY_PAGE_MODEL_TYPE))
.isInstanceOf(HttpStatusCodeException.class)
.hasMessageContaining(
"It is prohibited to use the param owner-is-null with values.");
};
return DynamicTest.stream(list.iterator(), Pair::getLeft, testOwnerIsNull);
}

@Test
void should_GetAllTasks_For_GettingLastTaskSummaryPageSortedByPorValue() {
String url =
Expand Down

0 comments on commit 05187b0

Please sign in to comment.