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 3, 2023
1 parent 3d46f7b commit 49d2ee0
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public static StringBuilder whereIn(String collection, String column, StringBuil
.append("</choose>");
if (column.matches("t.CUSTOM_\\d+")) {
sb.append("<if test='" + collection + "ContainsNull'> OR " + column + " IS NULL </if>");
} else if (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,23 @@ 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().ownerIn(nullArray).list();

assertThat(list).containsExactly(taskSummary4);
}

@WithAccessId(user = "user-1-1")
@Test
void should_ReturnTaskWithOwnerNullAndUser11_When_QueryingForOwnerIn() {
List<TaskSummary> list = taskService.createTaskQuery().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,23 @@ 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 hasOwnerIsNullParam = queryString != null && queryString.contains(queryParameter);
if (hasOwnerIsNullParam) {
Pattern pattern = Pattern.compile("\\b" + queryParameter + "(&|$)");
// Create a Matcher object to perform the match
Matcher matcher = pattern.matcher(queryString);

// Check if the "owner-is-null" substring is present in the query string
boolean hasExactOwnerIsNullParam = matcher.find();
if (!hasExactOwnerIsNullParam) {
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 @@ -740,6 +740,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 +1266,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 +1423,7 @@ public TaskQueryFilterParameter(
String[] ownerNotIn,
String[] ownerLike,
String[] ownerNotLike,
String ownerNull,
ObjectReference[] primaryObjectReferenceIn,
String[] porCompanyIn,
String[] porCompanyNotIn,
Expand Down Expand Up @@ -1570,6 +1579,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 +1855,8 @@ public Void apply(TaskQuery query) {
.map(this::wrapElementsInLikeStatement)
.ifPresent(query::parentBusinessProcessIdNotLike);

Optional.ofNullable(ownerIn).ifPresent(query::ownerIn);
String[] ownerInAndNull = addNullToOwnerIn();
Optional.ofNullable(ownerInAndNull).ifPresent(query::ownerIn);
Optional.ofNullable(ownerNotIn).ifPresent(query::ownerNotIn);
Optional.ofNullable(ownerLike)
.map(this::wrapElementsInLikeStatement)
Expand Down Expand Up @@ -2184,4 +2195,19 @@ 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};
}
String[] newArray;
int nullPosition;
newArray = Arrays.copyOf(this.ownerIn, this.ownerIn.length + 1);
nullPosition = this.ownerIn.length;
newArray[nullPosition] = null;
return newArray;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
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 All @@ -55,21 +57,16 @@
/** Test Task Controller. */
@TaskanaSpringBootTest
class TaskControllerIntTest {

@Autowired TaskanaConfiguration taskanaConfiguration;
private static final ParameterizedTypeReference<TaskSummaryPagedRepresentationModel>
TASK_SUMMARY_PAGE_MODEL_TYPE = new ParameterizedTypeReference<>() {};

private static final ParameterizedTypeReference<TaskSummaryCollectionRepresentationModel>
TASK_SUMMARY_COLLECTION_MODEL_TYPE = new ParameterizedTypeReference<>() {};

private static final ParameterizedTypeReference<TaskRepresentationModel> TASK_MODEL_TYPE =
ParameterizedTypeReference.forType(TaskRepresentationModel.class);

private final RestHelper restHelper;
private final DataSource dataSource;

private final String schemaName;
@Autowired TaskanaConfiguration taskanaConfiguration;

@Autowired
TaskControllerIntTest(
Expand Down Expand Up @@ -1165,6 +1162,78 @@ void should_KeepFiltersInTheLinkOfTheResponse_When_GettingTasks() {
+ "&sort-by=POR_VALUE&order=DESCENDING");
}

@Test
void should_ReturnTasksWithOwnerUser11_When_GettingTasks() {
String url = restHelper.toUrl(RestEndpoints.URL_TASKS) + "?owner=user-1-1";
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(10);
}

@Test
void should_ReturnTasksWithOwnerNull_When_GettingTasks() {
String url = restHelper.toUrl(RestEndpoints.URL_TASKS) + "?owner-is-null";
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(65);
}

@Test
void should_ReturnTasksWithOwnerNullOrUser11_When_GettingTasks() {
String url = restHelper.toUrl(RestEndpoints.URL_TASKS) + "?owner-is-null&owner=user-1-1";
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(75);
}

@Test
void should_ReturnTasksWithReadyStateAndOwnerNullOrUser11_When_GettingTasks() {
String url =
restHelper.toUrl(RestEndpoints.URL_TASKS) + "?state=READY&owner-is-null&owner=user-1-1";
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(56);
}

@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(HttpClientErrorException.class);
};
return DynamicTest.stream(list.iterator(), Pair::getLeft, testOwnerIsNull);
}

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

0 comments on commit 49d2ee0

Please sign in to comment.