Fixes #11305 Delete tasks corresponding to an entity when the entity is deleted (#12740)

This commit is contained in:
Suresh Srinivas 2023-08-03 18:27:01 -07:00 committed by GitHub
parent 45370c773f
commit c5e7a63fbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 222 additions and 155 deletions

View File

@ -13,6 +13,7 @@
package org.openmetadata.service;
import static org.openmetadata.common.utils.CommonUtil.listOf;
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@ -30,7 +31,9 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.ws.rs.core.UriInfo;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.openmetadata.schema.EntityInterface;
import org.openmetadata.schema.entity.services.ServiceType;
@ -41,6 +44,7 @@ import org.openmetadata.schema.type.TagLabel;
import org.openmetadata.service.exception.CatalogExceptionMessage;
import org.openmetadata.service.exception.EntityNotFoundException;
import org.openmetadata.service.jdbi3.EntityRepository;
import org.openmetadata.service.jdbi3.FeedRepository;
import org.openmetadata.service.resources.feeds.MessageParser.EntityLink;
import org.openmetadata.service.util.EntityUtil.Fields;
@ -52,6 +56,8 @@ public final class Entity {
// Canonical entity name to corresponding EntityRepository map
private static final Map<String, EntityRepository<? extends EntityInterface>> ENTITY_REPOSITORY_MAP = new HashMap<>();
@Getter @Setter private static FeedRepository feedRepository;
// List of all the entities
private static final List<String> ENTITY_LIST = new ArrayList<>();
@ -89,15 +95,13 @@ public final class Entity {
public static final String DATABASE_SCHEMA = "databaseSchema";
public static final String METRICS = "metrics";
public static final String DASHBOARD = "dashboard";
public static final String DASHBOARD_DATA_MODEL = "dashboardDataModel";
public static final String PIPELINE = "pipeline";
public static final String CHART = "chart";
public static final String REPORT = "report";
public static final String TOPIC = "topic";
public static final String MLMODEL = "mlmodel";
public static final String CONTAINER = "container";
public static final String BOT = "bot";
public static final String EVENT_SUBSCRIPTION = "eventsubscription";
public static final String THREAD = "THREAD";
public static final String QUERY = "query";
public static final String GLOSSARY = "glossary";
@ -107,15 +111,12 @@ public final class Entity {
public static final String TYPE = "type";
public static final String TEST_DEFINITION = "testDefinition";
public static final String TEST_CONNECTION_DEFINITION = "testConnectionDefinition";
public static final String WORKFLOW = "workflow";
public static final String TEST_SUITE = "testSuite";
public static final String KPI = "kpi";
public static final String TEST_CASE = "testCase";
public static final String WEB_ANALYTIC_EVENT = "webAnalyticEvent";
public static final String DATA_INSIGHT_CHART = "dataInsightChart";
public static final String DASHBOARD_DATA_MODEL = "dashboardDataModel";
//
// Policy entity
//
@ -128,6 +129,7 @@ public final class Entity {
public static final String ROLE = "role";
public static final String USER = "user";
public static final String TEAM = "team";
public static final String BOT = "bot";
//
// Operation related entities
@ -140,6 +142,12 @@ public final class Entity {
public static final String DOMAIN = "domain";
public static final String DATA_PRODUCT = "dataProduct";
//
// Other entities
public static final String EVENT_SUBSCRIPTION = "eventsubscription";
public static final String THREAD = "THREAD";
public static final String WORKFLOW = "workflow";
//
// Reserved names in OpenMetadata
//
@ -357,6 +365,29 @@ public final class Entity {
return new HashSet<>(Arrays.asList(propertyOrder.value()));
}
/** Returns true if the entity supports activity feeds, announcement, and tasks */
public static boolean supportsFeed(String entityType) {
return listOf(
TABLE,
DATABASE,
DATABASE_SCHEMA,
METRICS,
DASHBOARD,
DASHBOARD_DATA_MODEL,
PIPELINE,
CHART,
REPORT,
TOPIC,
MLMODEL,
CONTAINER,
QUERY,
GLOSSARY,
GLOSSARY_TERM,
TAG,
CLASSIFICATION)
.contains(entityType);
}
/** Class for getting validated entity list from a queryParam with list of entities. */
public static class EntityList {
private EntityList() {}

View File

@ -42,6 +42,7 @@ public class CatalogGenericExceptionMapper implements ExceptionMapper<Throwable>
@Override
public Response toResponse(Throwable ex) {
LOG.debug(ex.getMessage());
ex.printStackTrace();
if (ex instanceof ProcessingException
|| ex instanceof IllegalArgumentException
|| ex instanceof javax.ws.rs.BadRequestException) {

View File

@ -850,6 +850,9 @@ public abstract class EntityRepository<T extends EntityInterface> {
// Delete the extension data storing custom properties
removeExtension(entityInterface);
// Delete all the threads that are about this entity
Entity.getFeedRepository().deleteByAbout(entityInterface.getId());
// Finally, delete the entity
dao.delete(id);
}

View File

@ -92,6 +92,15 @@ import org.openmetadata.service.util.RestUtil.DeleteResponse;
import org.openmetadata.service.util.RestUtil.PatchResponse;
import org.openmetadata.service.util.ResultList;
/*
* Feed relationships:
* - 'user' --- createdBy ---> 'thread' in entity_relationship
* - 'user' --- repliedTo ---> 'thread' in entity_relationship
* - 'user' --- mentionedIn ---> 'thread' in entity_relationship
* - 'user' --- reactedTo ---> 'thread' in entity_relationship
* - 'thread' --- addressedTo ---> 'user' in field_relationship
* - 'thread' --- isAbout ---> 'entity' in entity_relationship
*/
@Slf4j
public class FeedRepository {
private final CollectionDAO dao;
@ -369,19 +378,32 @@ public class FeedRepository {
@Transaction
public DeleteResponse<Thread> deleteThread(Thread thread, String deletedByUser) {
String id = thread.getId().toString();
deleteThreadInternal(thread.getId().toString());
LOG.info("{} deleted thread with id {}", deletedByUser, thread.getId());
return new DeleteResponse<>(thread, RestUtil.ENTITY_DELETED);
}
public void deleteThreadInternal(String id) {
// Delete all the relationships to other entities
dao.relationshipDAO().deleteAll(id, Entity.THREAD);
// Delete all the field relationships to other entities
dao.fieldRelationshipDAO().deleteAllByPrefix(id);
// Finally, delete the entity
// Finally, delete the thread
dao.feedDAO().delete(id);
}
LOG.info("{} deleted thread with id {}", deletedByUser, thread.getId());
return new DeleteResponse<>(thread, RestUtil.ENTITY_DELETED);
@Transaction
public void deleteByAbout(UUID entityId) {
List<String> threadIds = listOrEmpty(dao.feedDAO().findByEntityId(entityId.toString()));
for (String threadId : threadIds) {
try {
deleteThreadInternal(threadId);
} catch (Exception ex) {
// Continue deletion
}
}
}
@Transaction

View File

@ -100,6 +100,7 @@ public class FeedResource {
public FeedResource(CollectionDAO dao, Authorizer authorizer) {
Objects.requireNonNull(dao, "FeedRepository must not be null");
this.dao = new FeedRepository(dao);
Entity.setFeedRepository(this.dao);
this.authorizer = authorizer;
}
@ -145,7 +146,7 @@ public class FeedResource {
@QueryParam("after")
String after,
@Parameter(
description = "Filter threads by entity link",
description = "Filter threads by entity link of entity about which this thread is created",
schema = @Schema(type = "string", example = "<E#/{entityType}/{entityFQN}/{fieldName}>"))
@QueryParam("entityLink")
String entityLink,
@ -354,8 +355,7 @@ public class FeedResource {
@Parameter(description = "Filter threads by whether it is active or resolved", schema = @Schema(type = "boolean"))
@DefaultValue("false")
@QueryParam("isResolved")
Boolean isResolved)
throws IOException {
Boolean isResolved) {
FeedFilter filter = FeedFilter.builder().threadType(threadType).taskStatus(taskStatus).resolved(isResolved).build();
return dao.getThreadsCount(filter, entityLink);
}

View File

@ -31,12 +31,12 @@ public final class MessageParser {
private static final String ENTITY_LINK_SEPARATOR = "::";
// Pattern to match the following markdown entity links:
// <#E::{entityType}::{entityFQN}> -- <#E::table::bigquery_gcp.shopify.raw_product_catalog>
// <#E::{entityType}::{entityFQN}::{fieldName}> -- <#E::table::bigquery_gcp.shopify.raw_product_catalog::description>
// <#E::{entityType}::{entityFQN}> -- <#E::table::bigquery_gcp.shopify.product>
// <#E::{entityType}::{entityFQN}::{fieldName}> -- <#E::table::bigquery_gcp.shopify.product::description>
// <#E::{entityType}::{entityFQN}::{fieldName}::{arrayFieldName}>
// -- <#E::table::bigquery_gcp.shopify.raw_product_catalog::columns::comment>
// -- <#E::table::bigquery_gcp.shopify.product::columns::product_id>
// <#E::{entityType}::{entityFQN}::{fieldName}::{arrayFieldName}::{arrayFieldValue}>
// -- <#E::table::bigquery_gcp.shopify.raw_product_catalog::columns::comment::description>
// -- <#E::table::bigquery_gcp.shopify.product::columns::product_id::description>
private static final Pattern ENTITY_LINK_PATTERN =
Pattern.compile(
"<#E"
@ -77,6 +77,10 @@ public final class MessageParser {
ENTITY_ARRAY_FIELD
}
public EntityLink(String entityType, String entityFqn) {
this(entityType, entityFqn, null, null, null);
}
public EntityLink(
String entityType, String entityFqn, String fieldName, String arrayFieldName, String arrayFieldValue) {
if (entityType == null || entityFqn == null) {
@ -92,18 +96,30 @@ public final class MessageParser {
if (arrayFieldName == null) {
throw new IllegalArgumentException(INVALID_ENTITY_LINK);
}
// Entity link example: <#E::table::bigquery_gcp.shopify.product::columns::product_id::description>
// FullyQualifiedFieldType: table.columns.member
// FullyQualifiedFieldValue: bigQuery_gcp.shopify.product.product_id.description
this.linkType = LinkType.ENTITY_ARRAY_FIELD;
this.fullyQualifiedFieldType = String.format("%s.%s.member", entityType, fieldName);
this.fullyQualifiedFieldValue = String.format("%s.%s.%s", entityFqn, arrayFieldName, arrayFieldValue);
} else if (arrayFieldName != null) {
// Entity link example: <#E::table::bigquery_gcp.shopify.product::columns::product_id>
// FullyQualifiedFieldType: table.columns.member
// FullyQualifiedFieldValue: bigQuery_gcp.shopify.product.product_id
this.linkType = LinkType.ENTITY_ARRAY_FIELD;
this.fullyQualifiedFieldType = String.format("%s.%s.member", entityType, fieldName);
this.fullyQualifiedFieldValue = String.format("%s.%s", entityFqn, arrayFieldName);
} else if (fieldName != null) {
this.fullyQualifiedFieldType = String.format("%s.%s", entityType, fieldName);
// Entity link example: <#E::table::bigquery_gcp.shopify.product::description>
// FullyQualifiedFieldType: table.description
// FullyQualifiedFieldValue: bigQuery_gcp.shopify.product.description
this.linkType = LinkType.ENTITY_REGULAR_FIELD;
this.fullyQualifiedFieldType = String.format("%s.%s", entityType, fieldName);
this.fullyQualifiedFieldValue = String.format("%s.%s", entityFqn, fieldName);
} else {
// Entity link example: <#E::table::bigquery_gcp.shopify.product>
// FullyQualifiedFieldType: table
// FullyQualifiedFieldValue: bigQuery_gcp.shopify.product
this.linkType = LinkType.ENTITY;
this.fullyQualifiedFieldType = entityType;
this.fullyQualifiedFieldValue = entityFqn;
@ -111,13 +127,8 @@ public final class MessageParser {
}
public String getLinkString() {
StringBuilder builder = new StringBuilder();
builder
.append("<#E")
.append(ENTITY_LINK_SEPARATOR)
.append(entityType)
.append(ENTITY_LINK_SEPARATOR)
.append(entityFQN);
StringBuilder builder = new StringBuilder("<#E");
builder.append(ENTITY_LINK_SEPARATOR).append(entityType).append(ENTITY_LINK_SEPARATOR).append(entityFQN);
if (linkType == LinkType.ENTITY_REGULAR_FIELD || linkType == LinkType.ENTITY_ARRAY_FIELD) {
builder.append(ENTITY_LINK_SEPARATOR).append(fieldName);
}

View File

@ -26,11 +26,13 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.openmetadata.common.utils.CommonUtil.listOf;
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.csv.EntityCsvTest.assertSummary;
import static org.openmetadata.schema.type.MetadataOperation.EDIT_ALL;
import static org.openmetadata.schema.type.MetadataOperation.EDIT_TESTS;
import static org.openmetadata.schema.type.TaskType.RequestDescription;
import static org.openmetadata.service.Entity.ADMIN_USER_NAME;
import static org.openmetadata.service.Entity.FIELD_DELETED;
import static org.openmetadata.service.Entity.FIELD_EXTENSION;
@ -129,6 +131,7 @@ import org.openmetadata.schema.CreateEntity;
import org.openmetadata.schema.EntityInterface;
import org.openmetadata.schema.api.data.RestoreEntity;
import org.openmetadata.schema.api.data.TermReference;
import org.openmetadata.schema.api.feed.CreateThread;
import org.openmetadata.schema.api.teams.CreateTeam;
import org.openmetadata.schema.api.teams.CreateTeam.TeamType;
import org.openmetadata.schema.api.tests.CreateTestSuite;
@ -144,6 +147,7 @@ import org.openmetadata.schema.entity.data.GlossaryTerm;
import org.openmetadata.schema.entity.data.Table;
import org.openmetadata.schema.entity.domains.DataProduct;
import org.openmetadata.schema.entity.domains.Domain;
import org.openmetadata.schema.entity.feed.Thread;
import org.openmetadata.schema.entity.policies.Policy;
import org.openmetadata.schema.entity.policies.accessControl.Rule;
import org.openmetadata.schema.entity.services.connections.TestConnectionResult;
@ -156,6 +160,7 @@ import org.openmetadata.schema.entity.type.Category;
import org.openmetadata.schema.entity.type.CustomProperty;
import org.openmetadata.schema.tests.TestDefinition;
import org.openmetadata.schema.tests.TestSuite;
import org.openmetadata.schema.type.AnnouncementDetails;
import org.openmetadata.schema.type.ChangeDescription;
import org.openmetadata.schema.type.ChangeEvent;
import org.openmetadata.schema.type.Column;
@ -182,6 +187,7 @@ import org.openmetadata.service.resources.dqtests.TestDefinitionResourceTest;
import org.openmetadata.service.resources.dqtests.TestSuiteResourceTest;
import org.openmetadata.service.resources.events.EventResource.EventList;
import org.openmetadata.service.resources.events.EventSubscriptionResourceTest;
import org.openmetadata.service.resources.feeds.FeedResourceTest;
import org.openmetadata.service.resources.glossary.GlossaryResourceTest;
import org.openmetadata.service.resources.kpi.KpiResourceTest;
import org.openmetadata.service.resources.metadata.TypeResourceTest;
@ -465,14 +471,14 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
public abstract K createRequest(String name);
// Get container entity used in createRequest that has CONTAINS relationship to the entity created with this
// request has . For table, it is database. For database, it is databaseService. See Relationship.CONTAINS for
// request has. For table, it is database. For database, it is databaseService. See Relationship.CONTAINS for
// details.
public EntityReference getContainer() {
return null;
}
// Get container entity based on create request that has CONTAINS relationship to the entity created with this
// request has . For table, it is database. For database, it is databaseService. See Relationship.CONTAINS for
// request has. For table, it is database. For database, it is databaseService. See Relationship.CONTAINS for
// details.
public EntityReference getContainer(T e) {
return null;
@ -1837,6 +1843,48 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
return response;
}
@Test
@Execution(ExecutionMode.CONCURRENT)
void test_cleanupConversations(TestInfo test) throws HttpResponseException {
if (!Entity.supportsFeed(entityType)) {
return;
}
K request = createRequest(getEntityName(test), "", "", null);
T entity = createEntity(request, ADMIN_AUTH_HEADERS);
// Add a conversation thread for the entity
FeedResourceTest feedTest = new FeedResourceTest();
String about = String.format("<#E::%s::%s>", entityType, entity.getFullyQualifiedName());
CreateThread createThread = new CreateThread().withFrom(USER1.getName()).withMessage("message").withAbout(about);
Thread thread = feedTest.createAndCheck(createThread, ADMIN_AUTH_HEADERS);
// Add task thread for the entity from user1 to user2
Thread taskThread =
feedTest.createTaskThread(
USER1.getName(),
about,
USER2.getEntityReference(),
"old",
"new",
RequestDescription,
authHeaders(USER1.getName()));
// Add announcement thread for the entity from user1 to user2
AnnouncementDetails announcementDetails = feedTest.getAnnouncementDetails("Announcement", 10, 11);
Thread announcementThread =
feedTest.createAnnouncement(
USER1.getName(), about, "message", announcementDetails, authHeaders(USER1.getName()));
// When the entity is deleted, all the threads also should be deleted
deleteEntity(entity.getId(), true, true, ADMIN_AUTH_HEADERS);
for (UUID id : listOf(thread.getId(), taskThread.getId(), announcementThread.getId())) {
assertResponseContains(
() -> feedTest.getThread(id, ADMIN_AUTH_HEADERS),
NOT_FOUND,
CatalogExceptionMessage.entityNotFound("Thread", id));
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Common entity functionality for tests
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -2710,7 +2758,7 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
return entity;
}
public T assertDomainInheritanceOverride(T entity, K updateRequest, EntityReference newDomain)
public void assertDomainInheritanceOverride(T entity, K updateRequest, EntityReference newDomain)
throws JsonProcessingException, HttpResponseException {
// When an entity has domain set, it does not inherit domain from the parent
String json = JsonUtils.pojoToJson(entity);
@ -2721,6 +2769,5 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
assertReference(newDomain, entity.getDomain()); // Domain remains the same
entity = getEntity(entity.getId(), "domain", ADMIN_AUTH_HEADERS);
assertReference(newDomain, entity.getDomain()); // Domain remains the same
return entity;
}
}

View File

@ -394,50 +394,23 @@ public class FeedResourceTest extends OpenMetadataApplicationTest {
// create two announcements with start time in the future
LocalDateTime now = LocalDateTime.now();
AnnouncementDetails announcementDetails =
new AnnouncementDetails()
.withDescription("First announcement")
.withStartTime(now.plusDays(10L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(11L).toEpochSecond(ZoneOffset.UTC));
CreateThread create =
create()
.withMessage("Announcement One")
.withType(ThreadType.Announcement)
.withAnnouncementDetails(announcementDetails);
createAndCheck(create, USER_AUTH_HEADERS);
String about = String.format("<#E::%s::%s>", Entity.TABLE, TABLE.getFullyQualifiedName());
announcementDetails
.withStartTime(now.plusDays(12L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(13L).toEpochSecond(ZoneOffset.UTC));
create =
create()
.withMessage("Announcement Two")
.withType(ThreadType.Announcement)
.withAnnouncementDetails(announcementDetails);
createAndCheck(create, USER_AUTH_HEADERS);
// Create announcement 1
AnnouncementDetails announcementDetails = getAnnouncementDetails("First announcement", 10, 11);
createAnnouncement(USER.getName(), about, "Announcement One", announcementDetails, USER_AUTH_HEADERS);
// create one expired announcement
announcementDetails
.withStartTime(now.minusDays(30L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.minusDays(20L).toEpochSecond(ZoneOffset.UTC));
create =
create()
.withMessage("Announcement Three")
.withType(ThreadType.Announcement)
.withAnnouncementDetails(announcementDetails);
createAndCheck(create, USER_AUTH_HEADERS);
// Create announcement 2
announcementDetails = getAnnouncementDetails("Second announcement", 12, 13);
createAnnouncement(USER.getName(), about, "Announcement Two", announcementDetails, USER_AUTH_HEADERS);
// create an expired announcement
announcementDetails = getAnnouncementDetails("Expired", -30, -20);
createAnnouncement(USER.getName(), about, "Announcement Three", announcementDetails, USER_AUTH_HEADERS);
// create one active announcement
announcementDetails
.withDescription("Active Announcement")
.withStartTime(now.minusDays(1L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(1L).toEpochSecond(ZoneOffset.UTC));
create =
create()
.withMessage("Announcement Four")
.withType(ThreadType.Announcement)
.withAnnouncementDetails(announcementDetails);
createAndCheck(create, USER_AUTH_HEADERS);
announcementDetails = getAnnouncementDetails("Active", -1, 1);
createAnnouncement(USER.getName(), about, "Announcement Four", announcementDetails, USER_AUTH_HEADERS);
ThreadList announcements = listAnnouncements(null, null, null, ADMIN_AUTH_HEADERS);
int announcementCount = announcements.getPaging().getTotal();
@ -445,7 +418,7 @@ public class FeedResourceTest extends OpenMetadataApplicationTest {
assertEquals(totalAnnouncementCount + 4, announcementCount);
assertEquals(totalAnnouncementCount + 4, announcements.getData().size());
announcements = listAnnouncements(create.getAbout(), null, null, ADMIN_AUTH_HEADERS);
announcements = listAnnouncements(about, null, null, ADMIN_AUTH_HEADERS);
assertEquals(announcementCount, announcements.getPaging().getTotal());
assertEquals(announcementCount, announcements.getData().size());
@ -454,9 +427,9 @@ public class FeedResourceTest extends OpenMetadataApplicationTest {
assertEquals(1, activeAnnouncementCount);
assertEquals(1, announcements.getData().size());
assertEquals("Active Announcement", announcements.getData().get(0).getAnnouncement().getDescription());
assertEquals("Active", announcements.getData().get(0).getAnnouncement().getDescription());
announcements = listAnnouncements(create.getAbout(), null, true, ADMIN_AUTH_HEADERS);
announcements = listAnnouncements(about, null, true, ADMIN_AUTH_HEADERS);
assertEquals(activeAnnouncementCount, announcements.getPaging().getTotal());
assertEquals(activeAnnouncementCount, announcements.getData().size());
@ -465,7 +438,7 @@ public class FeedResourceTest extends OpenMetadataApplicationTest {
assertEquals(totalAnnouncementCount + 3, announcements.getPaging().getTotal());
assertEquals(totalAnnouncementCount + 3, announcements.getData().size());
announcements = listAnnouncements(create.getAbout(), null, false, ADMIN_AUTH_HEADERS);
announcements = listAnnouncements(about, null, false, ADMIN_AUTH_HEADERS);
assertEquals(totalAnnouncementCount + 3, announcements.getPaging().getTotal());
assertEquals(totalAnnouncementCount + 3, announcements.getData().size());
}
@ -474,58 +447,31 @@ public class FeedResourceTest extends OpenMetadataApplicationTest {
void post_invalidAnnouncement_400() throws IOException {
// create two announcements with same start time in the future
LocalDateTime now = LocalDateTime.now();
AnnouncementDetails announcementDetails =
new AnnouncementDetails()
.withDescription("First announcement")
.withStartTime(now.plusDays(3L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(5L).toEpochSecond(ZoneOffset.UTC));
CreateThread create =
create()
.withMessage("Announcement One")
.withType(ThreadType.Announcement)
.withAnnouncementDetails(announcementDetails);
createAndCheck(create, USER_AUTH_HEADERS);
CreateThread create2 =
create()
.withMessage("Announcement Two")
.withType(ThreadType.Announcement)
.withAnnouncementDetails(announcementDetails);
String about = String.format("<#E::%s::%s>", Entity.TABLE, TABLE.getFullyQualifiedName());
AnnouncementDetails announcementDetails = getAnnouncementDetails("1", 3, 5);
createAnnouncement(USER.getName(), about, "Announcement One", announcementDetails, USER_AUTH_HEADERS);
// create announcement with same start and end time
assertResponse(() -> createThread(create2, USER_AUTH_HEADERS), BAD_REQUEST, ANNOUNCEMENT_OVERLAP);
assertResponse(
() -> createAnnouncement(USER.getName(), about, "Announcement Two", announcementDetails, USER_AUTH_HEADERS),
BAD_REQUEST,
ANNOUNCEMENT_OVERLAP);
// create announcement with start time > end time
announcementDetails
.withStartTime(now.plusDays(3L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(2L).toEpochSecond(ZoneOffset.UTC));
CreateThread create3 = create2.withAnnouncementDetails(announcementDetails);
assertResponse(() -> createThread(create3, USER_AUTH_HEADERS), BAD_REQUEST, ANNOUNCEMENT_INVALID_START_TIME);
assertResponse(
() ->
createAnnouncement(
USER.getName(), about, "Announcement Three", getAnnouncementDetails("2", 3, 2), USER_AUTH_HEADERS),
BAD_REQUEST,
ANNOUNCEMENT_INVALID_START_TIME);
// create announcement with overlaps
announcementDetails
.withStartTime(now.plusDays(2L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(6L).toEpochSecond(ZoneOffset.UTC));
CreateThread create4 = create2.withAnnouncementDetails(announcementDetails);
assertResponse(() -> createThread(create4, USER_AUTH_HEADERS), BAD_REQUEST, ANNOUNCEMENT_OVERLAP);
announcementDetails
.withStartTime(now.plusDays(3L).plusHours(2L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(4L).toEpochSecond(ZoneOffset.UTC));
CreateThread create5 = create2.withAnnouncementDetails(announcementDetails);
assertResponse(() -> createThread(create5, USER_AUTH_HEADERS), BAD_REQUEST, ANNOUNCEMENT_OVERLAP);
announcementDetails
.withStartTime(now.plusDays(2L).plusHours(12L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(4L).toEpochSecond(ZoneOffset.UTC));
CreateThread create6 = create2.withAnnouncementDetails(announcementDetails);
assertResponse(() -> createThread(create6, USER_AUTH_HEADERS), BAD_REQUEST, ANNOUNCEMENT_OVERLAP);
announcementDetails
.withStartTime(now.plusDays(4L).plusHours(12L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(6L).toEpochSecond(ZoneOffset.UTC));
CreateThread create7 = create2.withAnnouncementDetails(announcementDetails);
assertResponse(() -> createThread(create7, USER_AUTH_HEADERS), BAD_REQUEST, ANNOUNCEMENT_OVERLAP);
// create announcement with overlap
assertResponse(
() ->
createAnnouncement(
USER.getName(), about, "Announcement Four", getAnnouncementDetails("3", 2, 6), USER_AUTH_HEADERS),
BAD_REQUEST,
ANNOUNCEMENT_OVERLAP);
}
@Test
@ -856,18 +802,10 @@ public class FeedResourceTest extends OpenMetadataApplicationTest {
@Test
void patch_announcement_200() throws IOException {
LocalDateTime now = LocalDateTime.now();
AnnouncementDetails announcementDetails =
new AnnouncementDetails()
.withDescription("First announcement")
.withStartTime(now.plusDays(5L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(6L).toEpochSecond(ZoneOffset.UTC));
CreateThread create =
create()
.withMessage("Announcement One")
.withType(ThreadType.Announcement)
.withAnnouncementDetails(announcementDetails);
Thread thread = createAndCheck(create, USER_AUTH_HEADERS);
AnnouncementDetails announcementDetails = getAnnouncementDetails("First announcement", 5, 6);
String about = String.format("<#E::%s::%s>", Entity.TABLE, TABLE.getFullyQualifiedName());
Thread thread =
createAnnouncement(USER.getName(), about, "Announcement One", announcementDetails, USER_AUTH_HEADERS);
String originalJson = JsonUtils.pojoToJson(thread);
long startTs = now.plusDays(6L).toEpochSecond(ZoneOffset.UTC);
@ -900,27 +838,14 @@ public class FeedResourceTest extends OpenMetadataApplicationTest {
@Test
void patch_invalidAnnouncement_400() throws IOException {
LocalDateTime now = LocalDateTime.now();
AnnouncementDetails announcementDetails =
new AnnouncementDetails()
.withDescription("First announcement")
.withStartTime(now.plusDays(53L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(55L).toEpochSecond(ZoneOffset.UTC));
CreateThread create =
create()
.withMessage("Announcement One")
.withType(ThreadType.Announcement)
.withAnnouncementDetails(announcementDetails);
Thread thread1 = createAndCheck(create, USER_AUTH_HEADERS);
String about = String.format("<#E::%s::%s>", Entity.TABLE, TABLE.getFullyQualifiedName());
AnnouncementDetails announcementDetails = getAnnouncementDetails("First announcement", 53, 55);
Thread thread1 =
createAnnouncement(USER.getName(), about, "Announcement One", announcementDetails, USER_AUTH_HEADERS);
announcementDetails
.withStartTime(now.plusDays(57L).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(59L).toEpochSecond(ZoneOffset.UTC));
CreateThread create2 =
create()
.withMessage("Announcement Two")
.withType(ThreadType.Announcement)
.withAnnouncementDetails(announcementDetails);
Thread thread2 = createAndCheck(create2, USER_AUTH_HEADERS);
announcementDetails = getAnnouncementDetails("Second announcement", 57, 59);
Thread thread2 =
createAnnouncement(USER.getName(), about, "Announcement Two", announcementDetails, USER_AUTH_HEADERS);
String originalJson = JsonUtils.pojoToJson(thread2);
@ -1573,7 +1498,7 @@ public class FeedResourceTest extends OpenMetadataApplicationTest {
return true;
}
private Thread createTaskThread(
public Thread createTaskThread(
String fromUser,
String about,
EntityReference assignee,
@ -1598,6 +1523,23 @@ public class FeedResourceTest extends OpenMetadataApplicationTest {
return createAndCheck(create, authHeaders);
}
public Thread createAnnouncement(
String fromUser,
String about,
String message,
AnnouncementDetails announcementDetails,
Map<String, String> authHeaders)
throws HttpResponseException {
CreateThread create =
new CreateThread()
.withFrom(fromUser)
.withMessage(message)
.withAbout(about)
.withType(ThreadType.Announcement)
.withAnnouncementDetails(announcementDetails);
return createAndCheck(create, authHeaders);
}
public void validateTaskList(
UUID expectedAssignee,
String expectedSuggestion,
@ -1623,4 +1565,12 @@ public class FeedResourceTest extends OpenMetadataApplicationTest {
assertEquals(expected.getSuggestion(), actual.getSuggestion());
assertEquals(expected.getStatus(), actual.getStatus());
}
public AnnouncementDetails getAnnouncementDetails(String description, long start, long end) {
LocalDateTime now = LocalDateTime.now();
return new AnnouncementDetails()
.withDescription(description)
.withStartTime(now.plusDays(start).toEpochSecond(ZoneOffset.UTC))
.withEndTime(now.plusDays(end).toEpochSecond(ZoneOffset.UTC));
}
}

View File

@ -89,8 +89,10 @@ server:
# Logging settings.
logging:
level: OFF
appenders: []
level: INFO
appenders:
- type: console
logFormat: "%level %logger{5} - %msg%n"
database:
# the name of the JDBC driver, h2 is used for testing