TSK-1836: add withoutAttachment as query parameter in REST and taskana-core
This commit is contained in:
parent
8049ac22ac
commit
a23060ba64
|
@ -1,6 +1,7 @@
|
|||
package acceptance.task.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static pro.taskana.task.api.CallbackState.CALLBACK_PROCESSING_REQUIRED;
|
||||
import static pro.taskana.testapi.DefaultTestEntities.defaultTestClassification;
|
||||
import static pro.taskana.testapi.DefaultTestEntities.defaultTestObjectReference;
|
||||
|
@ -10,19 +11,26 @@ import java.security.PrivilegedActionException;
|
|||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
import org.junit.jupiter.api.function.ThrowingConsumer;
|
||||
|
||||
import pro.taskana.classification.api.ClassificationService;
|
||||
import pro.taskana.classification.api.models.ClassificationSummary;
|
||||
import pro.taskana.common.api.KeyDomain;
|
||||
import pro.taskana.common.api.TimeInterval;
|
||||
import pro.taskana.common.api.security.CurrentUserContext;
|
||||
import pro.taskana.common.internal.util.Pair;
|
||||
import pro.taskana.task.api.CallbackState;
|
||||
import pro.taskana.task.api.TaskCustomField;
|
||||
import pro.taskana.task.api.TaskQuery;
|
||||
import pro.taskana.task.api.TaskService;
|
||||
import pro.taskana.task.api.TaskState;
|
||||
import pro.taskana.task.api.WildcardSearchField;
|
||||
|
@ -2569,6 +2577,132 @@ class TaskQueryImplAccTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
class WithoutAttachment {
|
||||
WorkbasketSummary wb;
|
||||
TaskSummary taskSummary1;
|
||||
TaskSummary taskSummary2;
|
||||
TaskSummary taskSummary3;
|
||||
TaskSummary taskSummary4;
|
||||
|
||||
@WithAccessId(user = "user-1-1")
|
||||
@BeforeAll
|
||||
void setup() throws Exception {
|
||||
wb = createWorkbasketWithPermission();
|
||||
Attachment attachment = createAttachment().build();
|
||||
taskSummary1 =
|
||||
taskInWorkbasket(wb).attachments(attachment).buildAndStoreAsSummary(taskService);
|
||||
taskSummary2 =
|
||||
taskInWorkbasket(wb)
|
||||
.attachments(attachment.copy(), attachment.copy())
|
||||
.buildAndStoreAsSummary(taskService);
|
||||
taskSummary3 = taskInWorkbasket(wb).buildAndStoreAsSummary(taskService);
|
||||
taskSummary4 = taskInWorkbasket(wb).buildAndStoreAsSummary(taskService);
|
||||
}
|
||||
|
||||
@WithAccessId(user = "user-1-1")
|
||||
@Test
|
||||
void should_ReturnTasksThatDontHaveAttachment() {
|
||||
List<TaskSummary> list =
|
||||
taskService.createTaskQuery().workbasketIdIn(wb.getId()).withoutAttachment().list();
|
||||
|
||||
assertThat(list).containsExactlyInAnyOrder(taskSummary3, taskSummary4);
|
||||
}
|
||||
|
||||
@WithAccessId(user = "user-1-1")
|
||||
@TestFactory
|
||||
Stream<DynamicTest>
|
||||
should_ThrowException_When_UsingWithoutAttachmentWithAnyOtherAttachmentFilter() {
|
||||
List<Pair<String, Function<TaskQuery, TaskQuery>>> testCases =
|
||||
List.of(
|
||||
Pair.of("AttachmentChannelIn", query -> query.attachmentChannelIn("not important")),
|
||||
Pair.of(
|
||||
"AttachmentChannelLike", query -> query.attachmentChannelLike("not important")),
|
||||
Pair.of(
|
||||
"AttachmentChannelNotIn",
|
||||
query -> query.attachmentChannelNotIn("not important")),
|
||||
Pair.of(
|
||||
"AttachmentChannelNotLike",
|
||||
query -> query.attachmentChannelNotLike("not important")),
|
||||
Pair.of(
|
||||
"AttachmentClassificationIdIn",
|
||||
query -> query.attachmentClassificationIdIn("not important")),
|
||||
Pair.of(
|
||||
"AttachmentClassificationIdNotIn",
|
||||
query -> query.attachmentClassificationIdNotIn("not important")),
|
||||
Pair.of(
|
||||
"AttachmentClassificationKeyIn",
|
||||
query -> query.attachmentClassificationKeyIn("not important")),
|
||||
Pair.of(
|
||||
"AttachmentClassificationKeyIn",
|
||||
query -> query.attachmentClassificationKeyIn("not important")),
|
||||
Pair.of(
|
||||
"AttachmentClassificationKeyNotIn",
|
||||
query -> query.attachmentClassificationKeyNotIn("not important")),
|
||||
Pair.of(
|
||||
"AttachmentClassificationKeyNotLike",
|
||||
query -> query.attachmentClassificationKeyNotLike("not important")),
|
||||
Pair.of(
|
||||
"AttachmentClassificationNameIn",
|
||||
query -> query.attachmentClassificationNameIn("not important")),
|
||||
Pair.of(
|
||||
"AttachmentClassificationNameLike",
|
||||
query -> query.attachmentClassificationNameLike("not important")),
|
||||
Pair.of(
|
||||
"AttachmentClassificationNameNotIn",
|
||||
query -> query.attachmentClassificationNameNotIn("not important")),
|
||||
Pair.of(
|
||||
"AttachmentClassificationNameNotLike",
|
||||
query -> query.attachmentClassificationNameNotLike("not important")),
|
||||
Pair.of(
|
||||
"AttachmentReceivedWithin",
|
||||
query ->
|
||||
query.attachmentReceivedWithin(
|
||||
new TimeInterval(Instant.now(), Instant.now()))),
|
||||
Pair.of(
|
||||
"AttachmentReceivedNotWithin",
|
||||
query ->
|
||||
query.attachmentNotReceivedWithin(
|
||||
new TimeInterval(Instant.now(), Instant.now()))),
|
||||
Pair.of(
|
||||
"AttachmentReferenceValueIn",
|
||||
query -> query.attachmentReferenceValueIn("not important")),
|
||||
Pair.of(
|
||||
"AttachmentReferenceValueLike",
|
||||
query -> query.attachmentReferenceValueLike("not important")),
|
||||
Pair.of(
|
||||
"AttachmentReferenceValueNotIn",
|
||||
query -> query.attachmentReferenceValueNotIn("not important")),
|
||||
Pair.of(
|
||||
"AttachmentReferenceValueNotLike",
|
||||
query -> query.attachmentReferenceValueNotLike("not important")),
|
||||
Pair.of(
|
||||
"AttachmentReferenceValueNotLike, AttachmentClassificationKeyNotLike",
|
||||
query ->
|
||||
query
|
||||
.attachmentReferenceValueNotLike("not important")
|
||||
.attachmentClassificationKeyNotLike("not important")));
|
||||
|
||||
ThrowingConsumer<Pair<String, Function<TaskQuery, TaskQuery>>> test =
|
||||
p -> {
|
||||
Function<TaskQuery, TaskQuery> addAttachmentFilter = p.getRight();
|
||||
|
||||
TaskQuery query =
|
||||
taskService.createTaskQuery().workbasketIdIn(wb.getId()).withoutAttachment();
|
||||
query = addAttachmentFilter.apply(query);
|
||||
|
||||
assertThatThrownBy(query::list)
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining(
|
||||
"The param \"withoutAttachment\" can only be used "
|
||||
+ "without adding attributes of attachment as filter parameter");
|
||||
};
|
||||
|
||||
return DynamicTest.stream(testCases.iterator(), Pair::getLeft, test);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
class QueryingObjectReferenceCombinations {
|
||||
|
|
|
@ -4,6 +4,7 @@ import pro.taskana.common.api.BaseQuery;
|
|||
import pro.taskana.common.api.KeyDomain;
|
||||
import pro.taskana.common.api.TimeInterval;
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.task.api.models.Attachment;
|
||||
import pro.taskana.task.api.models.ObjectReference;
|
||||
import pro.taskana.task.api.models.Task;
|
||||
import pro.taskana.task.api.models.TaskSummary;
|
||||
|
@ -1458,6 +1459,17 @@ public interface TaskQuery extends BaseQuery<TaskSummary, TaskQueryColumnName> {
|
|||
*/
|
||||
TaskQuery orderByAttachmentReceived(SortDirection sortDirection);
|
||||
|
||||
// endregion
|
||||
// region withoutAttachment
|
||||
|
||||
/**
|
||||
* This method adds the condition that only {@linkplain Task Tasks} that don't have any
|
||||
* {@linkplain Attachment Attachments} will be included in the {@linkplain TaskQuery}.
|
||||
*
|
||||
* @return the {@linkplain TaskQuery}
|
||||
*/
|
||||
TaskQuery withoutAttachment();
|
||||
|
||||
// endregion
|
||||
// region secondaryObjectReference
|
||||
|
||||
|
|
|
@ -256,6 +256,9 @@ public class TaskQueryImpl implements TaskQuery {
|
|||
private TimeInterval[] attachmentReceivedWithin;
|
||||
private TimeInterval[] attachmentReceivedNotWithin;
|
||||
// endregion
|
||||
// region withoutAttachment
|
||||
private boolean withoutAttachment;
|
||||
// endregion
|
||||
// region secondaryObjectReferences
|
||||
private ObjectReference[] secondaryObjectReferences;
|
||||
// endregion
|
||||
|
@ -391,6 +394,7 @@ public class TaskQueryImpl implements TaskQuery {
|
|||
this.taskService = (TaskServiceImpl) taskanaEngine.getEngine().getTaskService();
|
||||
this.orderBy = new ArrayList<>();
|
||||
this.filterByAccessIdIn = true;
|
||||
this.withoutAttachment = false;
|
||||
this.joinWithUserInfo = taskanaEngine.getEngine().getConfiguration().getAddAdditionalUserInfo();
|
||||
}
|
||||
|
||||
|
@ -1393,6 +1397,16 @@ public class TaskQueryImpl implements TaskQuery {
|
|||
: addOrderCriteria("a.RECEIVED", sortDirection);
|
||||
}
|
||||
|
||||
// endregion
|
||||
// region withoutAttachment
|
||||
|
||||
@Override
|
||||
public TaskQuery withoutAttachment() {
|
||||
this.joinWithAttachments = true;
|
||||
this.withoutAttachment = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
public String[] getOwnerLongNameIn() {
|
||||
|
@ -2082,6 +2096,31 @@ public class TaskQueryImpl implements TaskQuery {
|
|||
"The params \"wildcardSearchFieldIn\" and \"wildcardSearchValueLike\""
|
||||
+ " must be used together!");
|
||||
}
|
||||
if (withoutAttachment
|
||||
&& (attachmentChannelIn != null
|
||||
|| attachmentChannelLike != null
|
||||
|| attachmentChannelNotIn != null
|
||||
|| attachmentChannelNotLike != null
|
||||
|| attachmentClassificationIdIn != null
|
||||
|| attachmentClassificationIdNotIn != null
|
||||
|| attachmentClassificationKeyIn != null
|
||||
|| attachmentClassificationKeyLike != null
|
||||
|| attachmentClassificationKeyNotIn != null
|
||||
|| attachmentClassificationKeyNotLike != null
|
||||
|| attachmentClassificationNameIn != null
|
||||
|| attachmentClassificationNameLike != null
|
||||
|| attachmentClassificationNameNotIn != null
|
||||
|| attachmentClassificationNameNotLike != null
|
||||
|| attachmentReceivedWithin != null
|
||||
|| attachmentReceivedNotWithin != null
|
||||
|| attachmentReferenceIn != null
|
||||
|| attachmentReferenceLike != null
|
||||
|| attachmentReferenceNotIn != null
|
||||
|| attachmentReferenceNotLike != null)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The param \"withoutAttachment\" can only be used "
|
||||
+ "without adding attributes of attachment as filter parameter");
|
||||
}
|
||||
}
|
||||
|
||||
private String getDatabaseId() {
|
||||
|
@ -2453,6 +2492,8 @@ public class TaskQueryImpl implements TaskQuery {
|
|||
+ Arrays.toString(attachmentReceivedWithin)
|
||||
+ ", attachmentReceivedNotWithin="
|
||||
+ Arrays.toString(attachmentReceivedNotWithin)
|
||||
+ ", withoutAttachment="
|
||||
+ withoutAttachment
|
||||
+ ", secondaryObjectReferences="
|
||||
+ Arrays.toString(secondaryObjectReferences)
|
||||
+ ", sorCompanyIn="
|
||||
|
|
|
@ -429,6 +429,7 @@ public class TaskQuerySqlProvider {
|
|||
+ "LIKE #{wildcardSearchValueLike}"
|
||||
+ "</foreach>)"
|
||||
+ "</if> ");
|
||||
sb.append("<if test='withoutAttachment'> AND a.ID IS NULL</if> ");
|
||||
sb.append(commonTaskObjectReferenceWhereStatement());
|
||||
sb.append(commonTaskSecondaryObjectReferencesWhereStatement());
|
||||
return sb;
|
||||
|
|
|
@ -1092,6 +1092,14 @@ public class TaskQueryFilterParameter implements QueryParameter<TaskQuery, Void>
|
|||
@JsonProperty("attachment-received-not")
|
||||
private final Instant[] attachmentReceivedNotWithin;
|
||||
// endregion
|
||||
// region withoutAttachment
|
||||
/**
|
||||
* In order to filter Tasks that don't have any Attachments, set 'without-attachment' to 'true'.
|
||||
* Any other value for 'without-attachment' is invalid.
|
||||
*/
|
||||
@JsonProperty("without-attachment")
|
||||
private final Boolean withoutAttachment;
|
||||
// endregion
|
||||
// region customAttributes
|
||||
/** Filter by the value of the field custom1 of the Task. This is an exact match. */
|
||||
@JsonProperty("custom-1")
|
||||
|
@ -1645,6 +1653,7 @@ public class TaskQueryFilterParameter implements QueryParameter<TaskQuery, Void>
|
|||
"attachment-reference-not-like",
|
||||
"attachment-received",
|
||||
"attachment-received-not",
|
||||
"without-attachment",
|
||||
"custom-1",
|
||||
"custom-1-not",
|
||||
"custom-1-like",
|
||||
|
@ -1854,6 +1863,7 @@ public class TaskQueryFilterParameter implements QueryParameter<TaskQuery, Void>
|
|||
String[] attachmentReferenceNotLike,
|
||||
Instant[] attachmentReceivedWithin,
|
||||
Instant[] attachmentReceivedNotWithin,
|
||||
Boolean withoutAttachment,
|
||||
String[] custom1In,
|
||||
String[] custom1NotIn,
|
||||
String[] custom1Like,
|
||||
|
@ -2062,6 +2072,7 @@ public class TaskQueryFilterParameter implements QueryParameter<TaskQuery, Void>
|
|||
this.attachmentReferenceNotLike = attachmentReferenceNotLike;
|
||||
this.attachmentReceivedWithin = attachmentReceivedWithin;
|
||||
this.attachmentReceivedNotWithin = attachmentReceivedNotWithin;
|
||||
this.withoutAttachment = withoutAttachment;
|
||||
this.custom1In = custom1In;
|
||||
this.custom1NotIn = custom1NotIn;
|
||||
this.custom1Like = custom1Like;
|
||||
|
@ -2460,6 +2471,10 @@ public class TaskQueryFilterParameter implements QueryParameter<TaskQuery, Void>
|
|||
.map(this::extractTimeIntervals)
|
||||
.ifPresent(query::attachmentNotReceivedWithin);
|
||||
|
||||
if (Boolean.TRUE.equals(withoutAttachment)) {
|
||||
query.withoutAttachment();
|
||||
}
|
||||
|
||||
Stream.of(
|
||||
Pair.of(CUSTOM_1, of(custom1In, custom1NotIn, custom1Like, custom1NotLike)),
|
||||
Pair.of(CUSTOM_2, of(custom2In, custom2NotIn, custom2Like, custom2NotLike)),
|
||||
|
@ -2660,5 +2675,10 @@ public class TaskQueryFilterParameter implements QueryParameter<TaskQuery, Void>
|
|||
throw new InvalidArgumentException(
|
||||
"provided length of the property 'attachment-not-received' is not dividable by 2");
|
||||
}
|
||||
|
||||
if (withoutAttachment != null && !withoutAttachment) {
|
||||
throw new InvalidArgumentException(
|
||||
"provided value of the property 'without-attachment' must be 'true'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -639,6 +639,30 @@ class TaskControllerIntTest {
|
|||
assertThat(repModel.getAttachments()).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnFilteredTasks_When_GettingTaskWithoutAttachments() {
|
||||
String url = restHelper.toUrl(RestEndpoints.URL_TASKS) + "?without-attachment=true";
|
||||
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(83);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_ThrowException_When_WithoutAttachmentsIsSetToFalse() {
|
||||
String url = restHelper.toUrl(RestEndpoints.URL_TASKS) + "?without-attachment=false";
|
||||
HttpEntity<Object> auth = new HttpEntity<>(RestHelper.generateHeadersForUser("admin"));
|
||||
|
||||
assertThatThrownBy(
|
||||
() -> TEMPLATE.exchange(url, HttpMethod.GET, auth, TASK_SUMMARY_PAGE_MODEL_TYPE))
|
||||
.isInstanceOf(HttpStatusCodeException.class)
|
||||
.hasMessageContaining("provided value of the property 'without-attachment' must be 'true'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_NotGetEmptyObjectReferencesList_When_GettingTaskWithObjectReferences() {
|
||||
String url =
|
||||
|
|
Loading…
Reference in New Issue