TSK-1836: add withoutAttachment as query parameter in REST and taskana-core

This commit is contained in:
ryzheboka 2022-04-04 14:39:17 +02:00 committed by Elena Mokeeva
parent 8049ac22ac
commit a23060ba64
6 changed files with 232 additions and 0 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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="

View File

@ -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;

View File

@ -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'");
}
}
}

View File

@ -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 =