diff --git a/lib/taskana-core-test/src/test/java/acceptance/task/query/TaskQueryImplAccTest.java b/lib/taskana-core-test/src/test/java/acceptance/task/query/TaskQueryImplAccTest.java index 7601ba92b..4911d247d 100644 --- a/lib/taskana-core-test/src/test/java/acceptance/task/query/TaskQueryImplAccTest.java +++ b/lib/taskana-core-test/src/test/java/acceptance/task/query/TaskQueryImplAccTest.java @@ -944,6 +944,8 @@ class TaskQueryImplAccTest { WorkbasketSummary wb; TaskSummary taskSummary1; TaskSummary taskSummary2; + TaskSummary taskSummary3; + @WithAccessId(user = "user-1-1") @BeforeAll @@ -951,6 +953,8 @@ class TaskQueryImplAccTest { wb = createWorkbasketWithPermission(); taskSummary1 = taskInWorkbasket(wb).priority(1).buildAndStoreAsSummary(taskService); taskSummary2 = taskInWorkbasket(wb).priority(2).buildAndStoreAsSummary(taskService); + taskSummary3 = taskInWorkbasket(wb).priority(4).buildAndStoreAsSummary(taskService); + } @WithAccessId(user = "user-1-1") @@ -968,7 +972,33 @@ class TaskQueryImplAccTest { List list = taskService.createTaskQuery().workbasketIdIn(wb.getId()).priorityNotIn(1).list(); - assertThat(list).containsExactly(taskSummary2); + assertThat(list).containsExactlyInAnyOrder(taskSummary2, taskSummary3); + } + + @WithAccessId(user = "user-1-1") + @Test + void should_ApplyFilter_When_QueryingForPriorityWithin() { + List list = + taskService + .createTaskQuery() + .workbasketIdIn(wb.getId()) + .priorityWithin(new IntInterval(2, 4)) + .list(); + + assertThat(list).containsExactlyInAnyOrder(taskSummary2, taskSummary3); + } + + @WithAccessId(user = "user-1-1") + @Test + void should_ApplyFilter_When_QueryingForPriorityNotWithin() { + List list = + taskService + .createTaskQuery() + .workbasketIdIn(wb.getId()) + .priorityNotWithin(new IntInterval(2, 4)) + .list(); + + assertThat(list).containsExactly(taskSummary1); } } diff --git a/lib/taskana-core/src/main/java/pro/taskana/task/api/TaskQuery.java b/lib/taskana-core/src/main/java/pro/taskana/task/api/TaskQuery.java index 5ddbde4c1..371daeef2 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/task/api/TaskQuery.java +++ b/lib/taskana-core/src/main/java/pro/taskana/task/api/TaskQuery.java @@ -481,6 +481,24 @@ public interface TaskQuery extends BaseQuery { */ TaskQuery priorityNotIn(int... priorities); + /** + * Includes the priorities in the provided range (inclusive) to your query (eg. priorities = [1,3] + * includes 1,2,3). + * + * @param priorities as an integer intervals + * @return the query + */ + TaskQuery priorityWithin(IntInterval... priorities); + + /** + * Excludes the priorities in the provided range (inclusive) to your query (eg. priorities = [1,3] + * excludes 1,2,3). + * + * @param priorities as an integer intervals + * @return the query + */ + TaskQuery priorityNotWithin(IntInterval... priorities); + /** * This method sorts the query result according to the priority. * diff --git a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskQueryImpl.java b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskQueryImpl.java index dceb06fef..0007e1def 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskQueryImpl.java +++ b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskQueryImpl.java @@ -102,6 +102,8 @@ public class TaskQueryImpl implements TaskQuery { private String[] descriptionNotLike; private int[] priority; private int[] priorityNotIn; + private IntInterval[] priorityWithin; + private IntInterval[] priorityNotWithin; private TaskState[] stateIn; private TaskState[] stateNotIn; private String[] classificationIdIn; @@ -602,6 +604,18 @@ public class TaskQueryImpl implements TaskQuery { return this; } + @Override + public TaskQuery priorityWithin(IntInterval[] priorities) { + this.priorityWithin = priorities; + return this; + } + + @Override + public TaskQuery priorityNotWithin(IntInterval[] priorities) { + this.priorityNotWithin = priorities; + return this; + } + @Override public TaskQuery orderByPriority(SortDirection sortDirection) { return addOrderCriteria("PRIORITY", sortDirection); @@ -2328,6 +2342,10 @@ public class TaskQueryImpl implements TaskQuery { + Arrays.toString(priority) + ", priorityNotIn=" + Arrays.toString(priorityNotIn) + + ", priorityWithin=" + + Arrays.toString(priorityWithin) + + ", priorityNotWithin=" + + Arrays.toString(priorityNotWithin) + ", stateIn=" + Arrays.toString(stateIn) + ", stateNotIn=" diff --git a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskQuerySqlProvider.java b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskQuerySqlProvider.java index 1a9a6898e..d750d83ae 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskQuerySqlProvider.java +++ b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskQuerySqlProvider.java @@ -506,6 +506,8 @@ public class TaskQuerySqlProvider { whereNotInInterval("plannedNotWithin", "t.PLANNED", sb); whereInInterval("receivedWithin", "t.RECEIVED", sb); whereNotInInterval("receivedNotWithin", "t.RECEIVED", sb); + whereInInterval("priorityWithin", "t.PRIORITY", sb); + whereNotInInterval("priorityNotWithin", "t.PRIORITY", sb); whereLike("ownerLongNameLike", "u.LONG_NAME", sb); whereNotLike("ownerLongNameNotLike", "u.LONG_NAME", sb); diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/task/rest/TaskQueryFilterParameter.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/task/rest/TaskQueryFilterParameter.java index 292131dd4..97ff7e9ad 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/task/rest/TaskQueryFilterParameter.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/task/rest/TaskQueryFilterParameter.java @@ -6,6 +6,7 @@ import java.time.Instant; import java.util.Arrays; import java.util.Optional; +import pro.taskana.common.api.IntInterval; import pro.taskana.common.api.KeyDomain; import pro.taskana.common.api.TimeInterval; import pro.taskana.common.api.exceptions.InvalidArgumentException; @@ -483,6 +484,30 @@ public class TaskQueryFilterParameter implements QueryParameter /** Filter by what the priority of the Task shouldn't be. This is an exact match. */ @JsonProperty("priority-not") private final int[] priorityNotIn; + + /** Filter by the range of values of the priority field of the Task. */ + @JsonProperty("priority-within") + private final Integer[] priorityWithin; + + /** Filter by priority starting from the given value (inclusive). */ + @JsonProperty("priority-from") + private final Integer priorityFrom; + + /** Filter by priority up to the given value (inclusive). */ + @JsonProperty("priority-until") + private final Integer priorityUntil; + + /** Filter by exclusing the range of values of the priority field of the Task. */ + @JsonProperty("priority-not-within") + private final Integer[] priorityNotWithin; + + /** Filter by excluding priority starting from the given value (inclusive). */ + @JsonProperty("priority-not-from") + private final Integer priorityNotFrom; + + /** Filter by excluding priority up to the given value (inclusive). */ + @JsonProperty("priority-not-until") + private final Integer priorityNotUntil; // endregion // region state /** Filter by the Task state. This is an exact match. */ @@ -1192,6 +1217,12 @@ public class TaskQueryFilterParameter implements QueryParameter "description-not-like", "priority", "priority-not", + "priority-within", + "priority-from", + "priority-until", + "priority-not-within", + "priority-not-from", + "priority-not-until", "state", "state-not", "classification-id", @@ -1342,6 +1373,12 @@ public class TaskQueryFilterParameter implements QueryParameter String[] descriptionNotLike, int[] priorityIn, int[] priorityNotIn, + Integer[] priorityWithin, + Integer priorityFrom, + Integer priorityUntil, + Integer[] priorityNotWithin, + Integer priorityNotFrom, + Integer priorityNotUntil, TaskState[] stateIn, TaskState[] stateNotIn, String[] classificationIdIn, @@ -1491,6 +1528,12 @@ public class TaskQueryFilterParameter implements QueryParameter this.descriptionNotLike = descriptionNotLike; this.priorityIn = priorityIn; this.priorityNotIn = priorityNotIn; + this.priorityWithin = priorityWithin; + this.priorityFrom = priorityFrom; + this.priorityUntil = priorityUntil; + this.priorityNotWithin = priorityNotWithin; + this.priorityNotFrom = priorityNotFrom; + this.priorityNotUntil = priorityNotUntil; this.stateIn = stateIn; this.stateNotIn = stateNotIn; this.classificationIdIn = classificationIdIn; @@ -1709,6 +1752,20 @@ public class TaskQueryFilterParameter implements QueryParameter Optional.ofNullable(priorityIn).ifPresent(query::priorityIn); Optional.ofNullable(priorityNotIn).ifPresent(query::priorityNotIn); + Optional.ofNullable(priorityWithin) + .map(this::extractIntIntervals) + .ifPresent(query::priorityWithin); + if (priorityFrom != null || priorityUntil != null) { + query.priorityWithin(new IntInterval(priorityFrom, priorityUntil)); + } + + Optional.ofNullable(priorityNotWithin) + .map(this::extractIntIntervals) + .ifPresent(query::priorityNotWithin); + if (priorityNotFrom != null || priorityNotUntil != null) { + query.priorityNotWithin(new IntInterval(priorityNotFrom, priorityNotUntil)); + } + Optional.ofNullable(stateIn).ifPresent(query::stateIn); Optional.ofNullable(stateNotIn).ifPresent(query::stateNotIn); @@ -2001,6 +2058,28 @@ public class TaskQueryFilterParameter implements QueryParameter + "'completed-not-in-from' and / or 'completed-not-in-until'"); } + if (priorityWithin != null && (priorityFrom != null || priorityUntil != null)) { + throw new InvalidArgumentException( + "It is prohibited to use the param 'priority-within' in combination with the params " + + "'priority-from' and / or 'priority-until'"); + } + + if (priorityNotWithin != null && (priorityNotFrom != null || priorityNotUntil != null)) { + throw new InvalidArgumentException( + "It is prohibited to use the param 'priority-not-within' in combination with the params " + + "'priority-not-from' and / or 'priority-not-until'"); + } + + if (priorityWithin != null && priorityWithin.length % 2 != 0) { + throw new InvalidArgumentException( + "provided length of the property 'priority-within' is not dividable by 2"); + } + + if (priorityNotWithin != null && priorityNotWithin.length % 2 != 0) { + throw new InvalidArgumentException( + "provided length of the property 'priority-not-within' is not dividable by 2"); + } + if (wildcardSearchFieldIn == null ^ wildcardSearchValue == null) { throw new InvalidArgumentException( "The params 'wildcard-search-field' and 'wildcard-search-value' must be used together"); diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/task/rest/TaskControllerIntTest.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/task/rest/TaskControllerIntTest.java index 6f9602408..760e0a594 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/task/rest/TaskControllerIntTest.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/task/rest/TaskControllerIntTest.java @@ -586,6 +586,182 @@ class TaskControllerIntTest { assertThat(response.getBody().getContent()).hasSize(22); } + @Test + void should_GetAllTasks_For_SpecifiedWorkbasketIdAndPriorityFromAndUntil() { + Integer priorityFrom = 2; + Integer priorityTo = 3; + String url = + restHelper.toUrl(RestEndpoints.URL_TASKS) + + String.format( + "?workbasket-id=WBI:100000000000000000000000000000000006" + + "&priority-from=%s&priority-until=%s", + priorityFrom, priorityTo); + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("user-1-1")); + + ResponseEntity 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(2); + } + + @Test + void should_GetAllTasks_For_SpecifiedWorkbasketIdAndMultiplePriorityWithinIntervals() { + Integer priorityFrom1 = 2; + Integer priorityFrom2 = 0; + String url = + restHelper.toUrl(RestEndpoints.URL_TASKS) + + String.format( + "?workbasket-id=WBI:100000000000000000000000000000000006" + + "&priority-within=%s&priority-within=&priority-within=%s&priority-within=", + priorityFrom1, priorityFrom2); + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("user-1-1")); + + ResponseEntity 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(3); + } + + @Test + void should_GetAllTasks_For_SpecifiedWorkbasketIdAndPriorityNotFromAndNotUntil() { + Integer priorityFrom = 2; + Integer priorityTo = 3; + String url = + restHelper.toUrl(RestEndpoints.URL_TASKS) + + String.format( + "?workbasket-id=WBI:100000000000000000000000000000000006" + + "&priority-not-from=%s&priority-not-until=%s", + priorityFrom, priorityTo); + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("user-1-1")); + + ResponseEntity 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(1); + } + + @Test + void should_GetAllTasks_For_SpecifiedWorkbasketIdAndMultiplePriorityNotWithinIntervals() { + Integer priorityFrom1 = 2; + Integer priorityFrom2 = 1; + String url = + restHelper.toUrl(RestEndpoints.URL_TASKS) + + String.format( + "?workbasket-id=WBI:100000000000000000000000000000000006" + + "&priority-not-within=%s&priority-not-within=" + + "&priority-not-within=%s&priority-not-within=", + priorityFrom1, priorityFrom2); + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("user-1-1")); + + ResponseEntity 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(1); + } + + @Test + void should_ThrowException_When_GettingTasksByWorkbasketIdWithPriorityNotWithinAndNotFrom() { + Integer priorityFrom1 = 2; + Integer priorityFrom2 = 1; + String url = + restHelper.toUrl(RestEndpoints.URL_TASKS) + + String.format( + "?workbasket-id=WBI:100000000000000000000000000000000006" + + "&priority-not-within=%s&priority-not-within=" + + "&priority-not-from=%s&priority-not-until=", + priorityFrom1, priorityFrom2); + + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("user-1-1")); + + ThrowingCallable httpCall = + () -> TEMPLATE.exchange(url, HttpMethod.GET, auth, TASK_SUMMARY_PAGE_MODEL_TYPE); + + assertThatThrownBy(httpCall) + .isInstanceOf(HttpStatusCodeException.class) + .extracting(HttpStatusCodeException.class::cast) + .extracting(HttpStatusCodeException::getStatusCode) + .isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + void should_ThrowException_When_GettingTasksByWorkbasketIdWithPriorityWithinAndPriorityFrom() { + Integer priorityFrom1 = 2; + Integer priorityFrom2 = 1; + String url = + restHelper.toUrl(RestEndpoints.URL_TASKS) + + String.format( + "?workbasket-id=WBI:100000000000000000000000000000000006" + + "&priority-within=%s&priority-within=&priority-from=%s&priority-until=", + priorityFrom1, priorityFrom2); + + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("user-1-1")); + + ThrowingCallable httpCall = + () -> TEMPLATE.exchange(url, HttpMethod.GET, auth, TASK_SUMMARY_PAGE_MODEL_TYPE); + + assertThatThrownBy(httpCall) + .isInstanceOf(HttpStatusCodeException.class) + .extracting(HttpStatusCodeException.class::cast) + .extracting(HttpStatusCodeException::getStatusCode) + .isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + void + should_ThrowException_When_GettingTasksByWorkbasketIdWithEvenNumberOfPriorityWithin() { + Integer priorityFrom1 = 2; + Integer priorityFrom2 = 1; + String url = + restHelper.toUrl(RestEndpoints.URL_TASKS) + + String.format( + "?workbasket-id=WBI:100000000000000000000000000000000006" + + "&priority-within=%s&priority-within=&priority-within=%s", + priorityFrom1, priorityFrom2); + + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("user-1-1")); + + ThrowingCallable httpCall = + () -> TEMPLATE.exchange(url, HttpMethod.GET, auth, TASK_SUMMARY_PAGE_MODEL_TYPE); + + assertThatThrownBy(httpCall) + .isInstanceOf(HttpStatusCodeException.class) + .extracting(HttpStatusCodeException.class::cast) + .extracting(HttpStatusCodeException::getStatusCode) + .isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + void + should_ThrowException_When_GettingTasksByWorkbasketIdWithEvenNumberOfPriorityNotWithin() { + Integer priorityFrom1 = 2; + Integer priorityFrom2 = 1; + String url = + restHelper.toUrl(RestEndpoints.URL_TASKS) + + String.format( + "?workbasket-id=WBI:100000000000000000000000000000000006" + + "&priority-not-within=%s&priority-not-within=&priority-not-within=%s", + priorityFrom1, priorityFrom2); + + HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("user-1-1")); + + ThrowingCallable httpCall = + () -> TEMPLATE.exchange(url, HttpMethod.GET, auth, TASK_SUMMARY_PAGE_MODEL_TYPE); + + assertThatThrownBy(httpCall) + .isInstanceOf(HttpStatusCodeException.class) + .extracting(HttpStatusCodeException.class::cast) + .extracting(HttpStatusCodeException::getStatusCode) + .isEqualTo(HttpStatus.BAD_REQUEST); + } + @Test void should_ReturnAllTasks_For_SpecifiedWorkbasketIdAndClassificationParentKeyIn() { String parentKey = "L11010"; @@ -593,7 +769,7 @@ class TaskControllerIntTest { restHelper.toUrl(RestEndpoints.URL_TASKS) + String.format( "?workbasket-id=WBI:100000000000000000000000000000000006" - + "&classification-parent-key=%s", + + "&classification-parent-key=%s", parentKey); HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("user-1-1")); ResponseEntity response = @@ -610,9 +786,9 @@ class TaskControllerIntTest { String url = restHelper.toUrl(RestEndpoints.URL_TASKS) + String.format( - "?workbasket-id=WBI:100000000000000000000000000000000006" - + "&classification-parent-key-not=%s", - parentKey); + "?workbasket-id=WBI:100000000000000000000000000000000006" + + "&classification-parent-key-not=%s", + parentKey); HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("user-1-1")); ResponseEntity response = TEMPLATE.exchange(url, HttpMethod.GET, auth, TASK_SUMMARY_PAGE_MODEL_TYPE); @@ -627,9 +803,7 @@ class TaskControllerIntTest { String parentKey = "L%"; String url = restHelper.toUrl(RestEndpoints.URL_TASKS) - + String.format( - "?classification-parent-key-like=%s", - parentKey); + + String.format("?classification-parent-key-like=%s", parentKey); HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("admin")); ResponseEntity response = TEMPLATE.exchange(url, HttpMethod.GET, auth, TASK_SUMMARY_PAGE_MODEL_TYPE); @@ -644,9 +818,7 @@ class TaskControllerIntTest { String parentKey = "L%"; String url = restHelper.toUrl(RestEndpoints.URL_TASKS) - + String.format( - "?classification-parent-key-not-like=%s", - parentKey); + + String.format("?classification-parent-key-not-like=%s", parentKey); HttpEntity auth = new HttpEntity<>(RestHelper.generateHeadersForUser("admin")); ResponseEntity response = TEMPLATE.exchange(url, HttpMethod.GET, auth, TASK_SUMMARY_PAGE_MODEL_TYPE);