Add Chart and Dashboard Service entities

This commit is contained in:
Suresh Srinivas 2021-08-26 00:20:18 -07:00
parent 0e4a3b26f9
commit 3244960083
7 changed files with 779 additions and 40 deletions

View File

@ -17,13 +17,11 @@
package org.openmetadata.catalog.jdbi3; package org.openmetadata.catalog.jdbi3;
import org.openmetadata.catalog.entity.data.Chart; import org.openmetadata.catalog.entity.data.Chart;
import org.openmetadata.catalog.entity.data.Database; import org.openmetadata.catalog.entity.services.DashboardService;
import org.openmetadata.catalog.entity.data.Table;
import org.openmetadata.catalog.exception.CatalogExceptionMessage; import org.openmetadata.catalog.exception.CatalogExceptionMessage;
import org.openmetadata.catalog.exception.EntityNotFoundException; import org.openmetadata.catalog.exception.EntityNotFoundException;
import org.openmetadata.catalog.jdbi3.TeamRepository.TeamDAO; import org.openmetadata.catalog.jdbi3.TeamRepository.TeamDAO;
import org.openmetadata.catalog.jdbi3.UserRepository.UserDAO; import org.openmetadata.catalog.jdbi3.UserRepository.UserDAO;
import org.openmetadata.catalog.resources.charts.ChartResource;
import org.openmetadata.catalog.resources.dashboards.DashboardResource; import org.openmetadata.catalog.resources.dashboards.DashboardResource;
import org.openmetadata.catalog.Entity; import org.openmetadata.catalog.Entity;
import org.openmetadata.catalog.entity.data.Dashboard; import org.openmetadata.catalog.entity.data.Dashboard;
@ -59,6 +57,9 @@ public abstract class DashboardRepository {
private static final Fields DASHBOARD_PATCH_FIELDS = new Fields(DashboardResource.FIELD_LIST, private static final Fields DASHBOARD_PATCH_FIELDS = new Fields(DashboardResource.FIELD_LIST,
"owner,service,tags,charts"); "owner,service,tags,charts");
public static String getFQN(EntityReference service, Dashboard dashboard) {
return (service.getName() + "." + dashboard.getName());
}
@CreateSqlObject @CreateSqlObject
abstract DashboardDAO dashboardDAO(); abstract DashboardDAO dashboardDAO();
@ -111,6 +112,12 @@ public abstract class DashboardRepository {
return dashboards; return dashboards;
} }
@Transaction
public Dashboard getByName(String fqn, Fields fields) throws IOException {
Dashboard dashboard = EntityUtil.validate(fqn, dashboardDAO().findByFQN(fqn), Dashboard.class);
return setFields(dashboard, fields);
}
@Transaction @Transaction
public Dashboard create(Dashboard dashboard, EntityReference service, EntityReference owner) throws IOException { public Dashboard create(Dashboard dashboard, EntityReference service, EntityReference owner) throws IOException {
getService(service); // Validate service getService(service); // Validate service
@ -120,7 +127,8 @@ public abstract class DashboardRepository {
@Transaction @Transaction
public PutResponse<Dashboard> createOrUpdate(Dashboard updatedDashboard, EntityReference service, public PutResponse<Dashboard> createOrUpdate(Dashboard updatedDashboard, EntityReference service,
EntityReference newOwner) throws IOException { EntityReference newOwner) throws IOException {
String fqn = service.getName() + "." + updatedDashboard.getName(); getService(service); // Validate service
String fqn = getFQN(service, updatedDashboard);
Dashboard storedDashboard = JsonUtils.readValue(dashboardDAO().findByFQN(fqn), Dashboard.class); Dashboard storedDashboard = JsonUtils.readValue(dashboardDAO().findByFQN(fqn), Dashboard.class);
if (storedDashboard == null) { if (storedDashboard == null) {
return new PutResponse<>(Status.CREATED, createInternal(updatedDashboard, service, newOwner)); return new PutResponse<>(Status.CREATED, createInternal(updatedDashboard, service, newOwner));
@ -134,11 +142,12 @@ public abstract class DashboardRepository {
// Update owner relationship // Update owner relationship
setFields(storedDashboard, DASHBOARD_UPDATE_FIELDS); // First get the ownership information setFields(storedDashboard, DASHBOARD_UPDATE_FIELDS); // First get the ownership information
updateRelationships(storedDashboard, updatedDashboard); updateOwner(storedDashboard, storedDashboard.getOwner(), newOwner);
// Service can't be changed in update since service name is part of FQN and // Service can't be changed in update since service name is part of FQN and
// change to a different service will result in a different FQN and creation of a new database under the new service // change to a different service will result in a different FQN and creation of a new database under the new service
storedDashboard.setService(service); storedDashboard.setService(service);
applyTags(updatedDashboard);
return new PutResponse<>(Response.Status.OK, storedDashboard); return new PutResponse<>(Response.Status.OK, storedDashboard);
} }
@ -216,16 +225,24 @@ public abstract class DashboardRepository {
return dashboard; return dashboard;
} }
private EntityReference getService(Dashboard dashboard) { private EntityReference getService(Dashboard dashboard) throws IOException {
return dashboard == null ? null : getService(EntityUtil.getService(relationshipDAO(), dashboard.getId())); return dashboard == null ? null : getService(EntityUtil.getService(relationshipDAO(), dashboard.getId()));
} }
private EntityReference getService(EntityReference service) { private EntityReference getService(EntityReference service) throws IOException {
// TODO What are the dashboard services? String id = service.getId().toString();
if (service.getType().equalsIgnoreCase(Entity.DASHBOARD_SERVICE)) {
DashboardService serviceInstance = EntityUtil.validate(id, dashboardServiceDAO().findById(id),
DashboardService.class);
service.setDescription(serviceInstance.getDescription());
service.setName(serviceInstance.getName());
} else {
throw new IllegalArgumentException(String.format("Invalid service type %s for the chart", service.getType()));
}
return service; return service;
} }
public void setService(Dashboard dashboard, EntityReference service) { public void setService(Dashboard dashboard, EntityReference service) throws IOException {
if (service != null && dashboard != null) { if (service != null && dashboard != null) {
getService(service); // Populate service details getService(service); // Populate service details
relationshipDAO().insert(service.getId().toString(), dashboard.getId().toString(), service.getType(), relationshipDAO().insert(service.getId().toString(), dashboard.getId().toString(), service.getType(),
@ -303,9 +320,11 @@ public abstract class DashboardRepository {
private void addRelationships(Dashboard dashboard) throws IOException { private void addRelationships(Dashboard dashboard) throws IOException {
// Add relationship from dashboard to chart // Add relationship from dashboard to chart
String dashboardId = dashboard.getId().toString(); String dashboardId = dashboard.getId().toString();
for (EntityReference chart: dashboard.getCharts()) { if (dashboard.getCharts() != null) {
relationshipDAO().insert(dashboardId, chart.getId().toString(), Entity.DASHBOARD, Entity.CHART, for (EntityReference chart : dashboard.getCharts()) {
Relationship.CONTAINS.ordinal()); relationshipDAO().insert(dashboardId, chart.getId().toString(), Entity.DASHBOARD, Entity.CHART,
Relationship.CONTAINS.ordinal());
}
} }
// Add owner relationship // Add owner relationship
EntityUtil.setOwner(relationshipDAO(), dashboard.getId(), Entity.DASHBOARD, dashboard.getOwner()); EntityUtil.setOwner(relationshipDAO(), dashboard.getId(), Entity.DASHBOARD, dashboard.getOwner());
@ -314,14 +333,6 @@ public abstract class DashboardRepository {
applyTags(dashboard); applyTags(dashboard);
} }
private void updateRelationships(Dashboard origDashboard, Dashboard updatedDashboard) throws IOException {
// Add owner relationship
origDashboard.setOwner(getOwner(origDashboard));
EntityUtil.updateOwner(relationshipDAO(), origDashboard.getOwner(), updatedDashboard.getOwner(),
origDashboard.getId(), Entity.TABLE);
applyTags(updatedDashboard);
}
private Dashboard validateDashboard(String id) throws IOException { private Dashboard validateDashboard(String id) throws IOException {
return EntityUtil.validate(id, dashboardDAO().findById(id), Dashboard.class); return EntityUtil.validate(id, dashboardDAO().findById(id), Dashboard.class);
} }
@ -351,7 +362,7 @@ public abstract class DashboardRepository {
List<String> listBefore(@Bind("fqnPrefix") String fqnPrefix, @Bind("limit") int limit, List<String> listBefore(@Bind("fqnPrefix") String fqnPrefix, @Bind("limit") int limit,
@Bind("before") String before); @Bind("before") String before);
@SqlQuery("SELECT json FROM chart_entity WHERE " + @SqlQuery("SELECT json FROM dashboard_entity WHERE " +
"(fullyQualifiedName LIKE CONCAT(:fqnPrefix, '.%') OR :fqnPrefix IS NULL) AND " + "(fullyQualifiedName LIKE CONCAT(:fqnPrefix, '.%') OR :fqnPrefix IS NULL) AND " +
"fullyQualifiedName > :after " + "fullyQualifiedName > :after " +
"ORDER BY fullyQualifiedName " + "ORDER BY fullyQualifiedName " +

View File

@ -92,7 +92,7 @@ public abstract class UsageRepository {
@Transaction @Transaction
public EntityUsage getByName(String entityType, String fqn, String date, int days) throws IOException { public EntityUsage getByName(String entityType, String fqn, String date, int days) throws IOException {
EntityReference ref = EntityUtil.getEntityReferenceByName(entityType, fqn, tableDAO(), databaseDAO(), EntityReference ref = EntityUtil.getEntityReferenceByName(entityType, fqn, tableDAO(), databaseDAO(),
metricsDAO(), reportDAO(), topicDAO(), chartDAO()); metricsDAO(), reportDAO(), topicDAO(), chartDAO(), dashboardDAO());
List<UsageDetails> usageDetails = usageDAO().getUsageById(ref.getId().toString(), date, days - 1); List<UsageDetails> usageDetails = usageDAO().getUsageById(ref.getId().toString(), date, days - 1);
return new EntityUsage().withUsage(usageDetails).withEntity(ref); return new EntityUsage().withUsage(usageDetails).withEntity(ref);
} }
@ -108,7 +108,7 @@ public abstract class UsageRepository {
@Transaction @Transaction
public void createByName(String entityType, String fullyQualifiedName, DailyCount usage) throws IOException { public void createByName(String entityType, String fullyQualifiedName, DailyCount usage) throws IOException {
EntityReference ref = EntityUtil.getEntityReferenceByName(entityType, fullyQualifiedName, tableDAO(), EntityReference ref = EntityUtil.getEntityReferenceByName(entityType, fullyQualifiedName, tableDAO(),
databaseDAO(), metricsDAO(), reportDAO(), topicDAO(), chartDAO()); databaseDAO(), metricsDAO(), reportDAO(), topicDAO(), chartDAO(), dashboardDAO());
addUsage(entityType, ref.getId().toString(), usage); addUsage(entityType, ref.getId().toString(), usage);
LOG.info("Usage successfully posted by name"); LOG.info("Usage successfully posted by name");
} }

View File

@ -98,6 +98,7 @@ public class ChartResource {
EntityUtil.addHref(uriInfo, chart.getOwner()); EntityUtil.addHref(uriInfo, chart.getOwner());
EntityUtil.addHref(uriInfo, chart.getService()); EntityUtil.addHref(uriInfo, chart.getService());
EntityUtil.addHref(uriInfo, chart.getFollowers()); EntityUtil.addHref(uriInfo, chart.getFollowers());
return chart; return chart;
} }

View File

@ -26,6 +26,7 @@ import org.openmetadata.catalog.entity.data.Dashboard;
import org.openmetadata.catalog.jdbi3.DashboardRepository; import org.openmetadata.catalog.jdbi3.DashboardRepository;
import org.openmetadata.catalog.resources.Collection; import org.openmetadata.catalog.resources.Collection;
import org.openmetadata.catalog.security.SecurityUtil; import org.openmetadata.catalog.security.SecurityUtil;
import org.openmetadata.catalog.type.EntityReference;
import org.openmetadata.catalog.util.EntityUtil; import org.openmetadata.catalog.util.EntityUtil;
import org.openmetadata.catalog.util.EntityUtil.Fields; import org.openmetadata.catalog.util.EntityUtil.Fields;
import org.openmetadata.catalog.util.RestUtil; import org.openmetadata.catalog.util.RestUtil;
@ -77,20 +78,30 @@ import java.util.UUID;
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Collection(name = "dashboards", repositoryClass = "org.openmetadata.catalog.jdbi3.DashboardRepository") @Collection(name = "dashboards", repositoryClass = "org.openmetadata.catalog.jdbi3.DashboardRepository")
public class DashboardResource { public class DashboardResource {
public static final String COLLECTION_PATH = "/v1/dashboards/"; public static final String DASHBOARD_COLLECTION_PATH = "v1/dashboards/";
private final List<String> attributes = RestUtil.getAttributes(Dashboard.class); private final List<String> attributes = RestUtil.getAttributes(Dashboard.class);
private final List<String> relationships = RestUtil.getAttributes(Dashboard.class); private final List<String> relationships = RestUtil.getAttributes(Dashboard.class);
private final DashboardRepository dao; private final DashboardRepository dao;
private final CatalogAuthorizer authorizer; private final CatalogAuthorizer authorizer;
private static List<Dashboard> addHref(UriInfo uriInfo, List<Dashboard> dashboards) { public static void addHref(UriInfo uriInfo, EntityReference ref) {
ref.withHref(RestUtil.getHref(uriInfo, DASHBOARD_COLLECTION_PATH, ref.getId()));
}
public static List<Dashboard> addHref(UriInfo uriInfo, List<Dashboard> dashboards) {
Optional.ofNullable(dashboards).orElse(Collections.emptyList()).forEach(i -> addHref(uriInfo, i)); Optional.ofNullable(dashboards).orElse(Collections.emptyList()).forEach(i -> addHref(uriInfo, i));
return dashboards; return dashboards;
} }
private static Dashboard addHref(UriInfo uriInfo, Dashboard dashboard) { public static Dashboard addHref(UriInfo uriInfo, Dashboard dashboard) {
dashboard.setHref(RestUtil.getHref(uriInfo, COLLECTION_PATH, dashboard.getId())); dashboard.setHref(RestUtil.getHref(uriInfo, DASHBOARD_COLLECTION_PATH, dashboard.getId()));
EntityUtil.addHref(uriInfo, dashboard.getOwner());
EntityUtil.addHref(uriInfo, dashboard.getService());
if (dashboard.getCharts() != null) {
EntityUtil.addHref(uriInfo, dashboard.getCharts());
}
EntityUtil.addHref(uriInfo, dashboard.getFollowers());
return dashboard; return dashboard;
} }
@ -113,7 +124,7 @@ public class DashboardResource {
} }
} }
static final String FIELDS = "owner,service,followers,tags,usageSummary"; static final String FIELDS = "owner,service,charts,followers,tags,usageSummary";
public static final List<String> FIELD_LIST = Arrays.asList(FIELDS.replaceAll(" ", "") public static final List<String> FIELD_LIST = Arrays.asList(FIELDS.replaceAll(" ", "")
.split(",")); .split(","));
@ -196,6 +207,27 @@ public class DashboardResource {
return addHref(uriInfo, dao.get(id, fields)); return addHref(uriInfo, dao.get(id, fields));
} }
@GET
@Path("/name/{fqn}")
@Operation(summary = "Get a dashboard by name", tags = "dashboards",
description = "Get a dashboard by fully qualified name.",
responses = {
@ApiResponse(responseCode = "200", description = "The dashboard",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Chart.class))),
@ApiResponse(responseCode = "404", description = "Dashboard for instance {id} is not found")
})
public Dashboard getByName(@Context UriInfo uriInfo, @PathParam("fqn") String fqn,
@Context SecurityContext securityContext,
@Parameter(description = "Fields requested in the returned resource",
schema = @Schema(type = "string", example = FIELDS))
@QueryParam("fields") String fieldsParam) throws IOException {
Fields fields = new Fields(FIELD_LIST, fieldsParam);
Dashboard dashboard = dao.getByName(fqn, fields);
return addHref(uriInfo, dashboard);
}
@POST @POST
@Operation(summary = "Create a dashboard", tags = "dashboards", @Operation(summary = "Create a dashboard", tags = "dashboards",
description = "Create a new dashboard.", description = "Create a new dashboard.",
@ -209,9 +241,10 @@ public class DashboardResource {
@Valid CreateDashboard create) throws IOException { @Valid CreateDashboard create) throws IOException {
SecurityUtil.checkAdminOrBotRole(authorizer, securityContext); SecurityUtil.checkAdminOrBotRole(authorizer, securityContext);
Dashboard dashboard = new Dashboard().withId(UUID.randomUUID()).withName(create.getName()) Dashboard dashboard = new Dashboard().withId(UUID.randomUUID()).withName(create.getName())
.withService(create.getService()).withCharts(create.getCharts()) .withDescription(create.getDescription()).withService(create.getService()).withCharts(create.getCharts())
.withDashboardUrl(create.getDashboardUrl()).withTags(create.getTags()); .withDashboardUrl(create.getDashboardUrl()).withTags(create.getTags())
addHref(uriInfo, dao.create(dashboard, dashboard.getService(), dashboard.getOwner())); .withOwner(create.getOwner());
dashboard = addHref(uriInfo, dao.create(dashboard, dashboard.getService(), dashboard.getOwner()));
return Response.created(dashboard.getHref()).entity(dashboard).build(); return Response.created(dashboard.getHref()).entity(dashboard).build();
} }
@ -249,15 +282,17 @@ public class DashboardResource {
schema = @Schema(implementation = Dashboard.class))), schema = @Schema(implementation = Dashboard.class))),
@ApiResponse(responseCode = "400", description = "Bad request") @ApiResponse(responseCode = "400", description = "Bad request")
}) })
public Response createOrUpdate(@Context UriInfo uriInfo, @Context SecurityContext securityContext, public Response createOrUpdate(@Context UriInfo uriInfo,
@Valid Dashboard create) throws IOException { @Context SecurityContext securityContext,
@Valid CreateDashboard create) throws IOException {
Dashboard dashboard = new Dashboard().withId(UUID.randomUUID()).withName(create.getName()) Dashboard dashboard = new Dashboard().withId(UUID.randomUUID()).withName(create.getName())
.withService(create.getService()).withCharts(create.getCharts()) .withDescription(create.getDescription()).withService(create.getService()).withCharts(create.getCharts())
.withDashboardUrl(create.getDashboardUrl()).withTags(create.getTags()); .withDashboardUrl(create.getDashboardUrl()).withTags(create.getTags())
addHref(uriInfo, dao.create(dashboard, dashboard.getService(), dashboard.getOwner())); .withOwner(create.getOwner());
PutResponse<Dashboard> response = dao.createOrUpdate(dashboard, dashboard.getService(), dashboard.getOwner()); PutResponse<Dashboard> response = dao.createOrUpdate(dashboard, dashboard.getService(), dashboard.getOwner());
addHref(uriInfo, response.getEntity()); dashboard = addHref(uriInfo, response.getEntity());
return Response.status(response.getStatus()).entity(response.getEntity()).build(); return Response.status(response.getStatus()).entity(dashboard).build();
} }
@PUT @PUT

View File

@ -45,6 +45,7 @@ import org.openmetadata.catalog.jdbi3.TopicRepository.TopicDAO;
import org.openmetadata.catalog.jdbi3.UsageRepository.UsageDAO; import org.openmetadata.catalog.jdbi3.UsageRepository.UsageDAO;
import org.openmetadata.catalog.jdbi3.UserRepository.UserDAO; import org.openmetadata.catalog.jdbi3.UserRepository.UserDAO;
import org.openmetadata.catalog.resources.charts.ChartResource; import org.openmetadata.catalog.resources.charts.ChartResource;
import org.openmetadata.catalog.resources.dashboards.DashboardResource;
import org.openmetadata.catalog.resources.databases.DatabaseResource; import org.openmetadata.catalog.resources.databases.DatabaseResource;
import org.openmetadata.catalog.resources.databases.TableResource; import org.openmetadata.catalog.resources.databases.TableResource;
import org.openmetadata.catalog.resources.feeds.MessageParser.EntityLink; import org.openmetadata.catalog.resources.feeds.MessageParser.EntityLink;
@ -135,6 +136,9 @@ public final class EntityUtil {
case Entity.CHART: case Entity.CHART:
ChartResource.addHref(uriInfo, ref); ChartResource.addHref(uriInfo, ref);
break; break;
case Entity.DASHBOARD:
DashboardResource.addHref(uriInfo, ref);
break;
case Entity.MESSAGING_SERVICE: case Entity.MESSAGING_SERVICE:
MessagingServiceResource.addHref(uriInfo, ref); MessagingServiceResource.addHref(uriInfo, ref);
break; break;
@ -273,7 +277,8 @@ public final class EntityUtil {
public static EntityReference getEntityReferenceByName(String entity, String fqn, TableDAO tableDAO, public static EntityReference getEntityReferenceByName(String entity, String fqn, TableDAO tableDAO,
DatabaseDAO databaseDAO, MetricsDAO metricsDAO, DatabaseDAO databaseDAO, MetricsDAO metricsDAO,
ReportDAO reportDAO, TopicDAO topicDAO, ChartDAO chartDAO) ReportDAO reportDAO, TopicDAO topicDAO, ChartDAO chartDAO,
DashboardDAO dashboardDAO)
throws IOException { throws IOException {
if (entity.equalsIgnoreCase(Entity.TABLE)) { if (entity.equalsIgnoreCase(Entity.TABLE)) {
Table instance = EntityUtil.validate(fqn, tableDAO.findByFQN(fqn), Table.class); Table instance = EntityUtil.validate(fqn, tableDAO.findByFQN(fqn), Table.class);
@ -299,6 +304,10 @@ public final class EntityUtil {
Chart instance = EntityUtil.validate(fqn, chartDAO.findByFQN(fqn), Chart.class); Chart instance = EntityUtil.validate(fqn, chartDAO.findByFQN(fqn), Chart.class);
return new EntityReference().withId(instance.getId()).withName(instance.getName()).withType(Entity.CHART) return new EntityReference().withId(instance.getId()).withName(instance.getName()).withType(Entity.CHART)
.withDescription(instance.getDescription()); .withDescription(instance.getDescription());
} else if (entity.equalsIgnoreCase(Entity.DASHBOARD)) {
Dashboard instance = EntityUtil.validate(fqn, dashboardDAO.findByFQN(fqn), Dashboard.class);
return new EntityReference().withId(instance.getId()).withName(instance.getName()).withType(Entity.DASHBOARD)
.withDescription(instance.getDescription());
} }
throw EntityNotFoundException.byMessage(CatalogExceptionMessage.entityNotFound(entity, fqn)); throw EntityNotFoundException.byMessage(CatalogExceptionMessage.entityNotFound(entity, fqn));
} }
@ -331,6 +340,9 @@ public final class EntityUtil {
} else if (clazz.toString().toLowerCase().endsWith(Entity.CHART.toLowerCase())) { } else if (clazz.toString().toLowerCase().endsWith(Entity.CHART.toLowerCase())) {
Chart instance = (Chart) entity; Chart instance = (Chart) entity;
return getEntityReference(instance); return getEntityReference(instance);
} else if (clazz.toString().toLowerCase().endsWith(Entity.DASHBOARD.toLowerCase())) {
Dashboard instance = (Dashboard) entity;
return getEntityReference(instance);
} else if (clazz.toString().toLowerCase().endsWith(Entity.MESSAGING_SERVICE.toLowerCase())) { } else if (clazz.toString().toLowerCase().endsWith(Entity.MESSAGING_SERVICE.toLowerCase())) {
MessagingService instance = (MessagingService) entity; MessagingService instance = (MessagingService) entity;
return getEntityReference(instance); return getEntityReference(instance);

View File

@ -0,0 +1,668 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openmetadata.catalog.resources.dashboards;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.http.client.HttpResponseException;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.openmetadata.catalog.CatalogApplicationTest;
import org.openmetadata.catalog.Entity;
import org.openmetadata.catalog.api.data.CreateDashboard;
import org.openmetadata.catalog.api.services.CreateDashboardService;
import org.openmetadata.catalog.api.services.CreateDashboardService.DashboardServiceType;
import org.openmetadata.catalog.entity.data.Dashboard;
import org.openmetadata.catalog.entity.services.DashboardService;
import org.openmetadata.catalog.entity.teams.Team;
import org.openmetadata.catalog.entity.teams.User;
import org.openmetadata.catalog.exception.CatalogExceptionMessage;
import org.openmetadata.catalog.resources.dashboards.DashboardResource.DashboardList;
import org.openmetadata.catalog.resources.services.DashboardServiceResourceTest;
import org.openmetadata.catalog.resources.teams.TeamResourceTest;
import org.openmetadata.catalog.resources.teams.UserResourceTest;
import org.openmetadata.catalog.type.EntityReference;
import org.openmetadata.catalog.util.EntityUtil;
import org.openmetadata.catalog.util.JsonUtils;
import org.openmetadata.catalog.util.TestUtils;
import org.openmetadata.common.utils.JsonSchemaUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.json.JsonPatch;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response.Status;
import java.util.Map;
import java.util.UUID;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.CONFLICT;
import static javax.ws.rs.core.Response.Status.CREATED;
import static javax.ws.rs.core.Response.Status.FORBIDDEN;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static javax.ws.rs.core.Response.Status.OK;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.openmetadata.catalog.exception.CatalogExceptionMessage.entityNotFound;
import static org.openmetadata.catalog.exception.CatalogExceptionMessage.readOnlyAttribute;
import static org.openmetadata.catalog.util.TestUtils.adminAuthHeaders;
import static org.openmetadata.catalog.util.TestUtils.assertEntityPagination;
import static org.openmetadata.catalog.util.TestUtils.assertResponse;
import static org.openmetadata.catalog.util.TestUtils.authHeaders;
public class DashboardResourceTest extends CatalogApplicationTest {
private static final Logger LOG = LoggerFactory.getLogger(DashboardResourceTest.class);
public static User USER1;
public static EntityReference USER_OWNER1;
public static Team TEAM1;
public static EntityReference TEAM_OWNER1;
public static EntityReference SUPERSET_REFERENCE;
public static EntityReference LOOKER_REFERENCE;
@BeforeAll
public static void setup(TestInfo test) throws HttpResponseException {
USER1 = UserResourceTest.createUser(UserResourceTest.create(test), authHeaders("test@open-metadata.org"));
USER_OWNER1 = new EntityReference().withId(USER1.getId()).withType("user");
TEAM1 = TeamResourceTest.createTeam(TeamResourceTest.create(test), adminAuthHeaders());
TEAM_OWNER1 = new EntityReference().withId(TEAM1.getId()).withType("team");
CreateDashboardService createService = new CreateDashboardService().withName("superset")
.withServiceType(DashboardServiceType.Superset).withDashboardUrl(TestUtils.DASHBOARD_URL);
DashboardService service = DashboardServiceResourceTest.createService(createService, adminAuthHeaders());
SUPERSET_REFERENCE = EntityUtil.getEntityReference(service);
createService.withName("looker").withServiceType(DashboardServiceType.Looker);
service = DashboardServiceResourceTest.createService(createService, adminAuthHeaders());
LOOKER_REFERENCE = EntityUtil.getEntityReference(service);
}
@Test
public void post_dashboardWithLongName_400_badRequest(TestInfo test) {
// Create dashboard with mandatory name field empty
CreateDashboard create = create(test).withName(TestUtils.LONG_ENTITY_NAME);
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
createDashboard(create, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, "[name size must be between 1 and 64]");
}
@Test
public void post_DashboardWithoutName_400_badRequest(TestInfo test) {
// Create Dashboard with mandatory name field empty
CreateDashboard create = create(test).withName("");
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
createDashboard(create, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, "[name size must be between 1 and 64]");
}
@Test
public void post_DashboardAlreadyExists_409_conflict(TestInfo test) throws HttpResponseException {
CreateDashboard create = create(test);
createDashboard(create, adminAuthHeaders());
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
createDashboard(create, adminAuthHeaders()));
assertResponse(exception, CONFLICT, CatalogExceptionMessage.ENTITY_ALREADY_EXISTS);
}
@Test
public void post_validDashboards_as_admin_200_OK(TestInfo test) throws HttpResponseException {
// Create team with different optional fields
CreateDashboard create = create(test);
createAndCheckDashboard(create, adminAuthHeaders());
create.withName(getDashboardName(test, 1)).withDescription("description");
createAndCheckDashboard(create, adminAuthHeaders());
}
@Test
public void post_DashboardWithUserOwner_200_ok(TestInfo test) throws HttpResponseException {
createAndCheckDashboard(create(test).withOwner(USER_OWNER1), adminAuthHeaders());
}
@Test
public void post_DashboardWithTeamOwner_200_ok(TestInfo test) throws HttpResponseException {
createAndCheckDashboard(create(test).withOwner(TEAM_OWNER1), adminAuthHeaders());
}
@Test
public void post_Dashboard_as_non_admin_401(TestInfo test) {
CreateDashboard create = create(test);
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
createDashboard(create, authHeaders("test@open-metadata.org")));
assertResponse(exception, FORBIDDEN, "Principal: CatalogPrincipal{name='test'} is not admin");
}
@Test
public void post_DashboardWithoutRequiredService_4xx(TestInfo test) {
CreateDashboard create = create(test).withService(null);
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
createDashboard(create, adminAuthHeaders()));
TestUtils.assertResponseContains(exception, BAD_REQUEST, "service must not be null");
}
@Test
public void post_DashboardWithInvalidOwnerType_4xx(TestInfo test) {
EntityReference owner = new EntityReference().withId(TEAM1.getId()); /* No owner type is set */
CreateDashboard create = create(test).withOwner(owner);
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
createDashboard(create, adminAuthHeaders()));
TestUtils.assertResponseContains(exception, BAD_REQUEST, "type must not be null");
}
@Test
public void post_DashboardWithNonExistentOwner_4xx(TestInfo test) {
EntityReference owner = new EntityReference().withId(TestUtils.NON_EXISTENT_ENTITY).withType("user");
CreateDashboard create = create(test).withOwner(owner);
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
createDashboard(create, adminAuthHeaders()));
assertResponse(exception, NOT_FOUND, entityNotFound("User", TestUtils.NON_EXISTENT_ENTITY));
}
@Test
public void post_DashboardWithDifferentService_200_ok(TestInfo test) throws HttpResponseException {
EntityReference[] differentServices = {SUPERSET_REFERENCE, LOOKER_REFERENCE};
// Create Dashboard for each service and test APIs
for (EntityReference service : differentServices) {
createAndCheckDashboard(create(test).withService(service), adminAuthHeaders());
// List Dashboards by filtering on service name and ensure right Dashboards are returned in the response
DashboardList list = listDashboards("service", service.getName(), adminAuthHeaders());
for (Dashboard db : list.getData()) {
assertEquals(service.getName(), db.getService().getName());
}
}
}
@Test
public void get_DashboardListWithInvalidLimitOffset_4xx() {
// Limit must be >= 1 and <= 1000,000
HttpResponseException exception = assertThrows(HttpResponseException.class, ()
-> listDashboards(null, null, -1, null, null, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, "[query param limit must be greater than or equal to 1]");
exception = assertThrows(HttpResponseException.class, ()
-> listDashboards(null, null, 0, null, null, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, "[query param limit must be greater than or equal to 1]");
exception = assertThrows(HttpResponseException.class, ()
-> listDashboards(null, null, 1000001, null, null, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, "[query param limit must be less than or equal to 1000000]");
}
@Test
public void get_DashboardListWithInvalidPaginationCursors_4xx() {
// Passing both before and after cursors is invalid
HttpResponseException exception = assertThrows(HttpResponseException.class, ()
-> listDashboards(null, null, 1, "", "", adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, "Only one of before or after query parameter allowed");
}
@Test
public void get_DashboardListWithValidLimitOffset_4xx(TestInfo test) throws HttpResponseException {
// Create a large number of Dashboards
int maxDashboards = 40;
for (int i = 0; i < maxDashboards; i++) {
createDashboard(create(test, i), adminAuthHeaders());
}
// List all Dashboards
DashboardList allDashboards = listDashboards(null, null, 1000000, null,
null, adminAuthHeaders());
int totalRecords = allDashboards.getData().size();
printDashboards(allDashboards);
// List limit number Dashboards at a time at various offsets and ensure right results are returned
for (int limit = 1; limit < maxDashboards; limit++) {
String after = null;
String before;
int pageCount = 0;
int indexInAllDashboards = 0;
DashboardList forwardPage;
DashboardList backwardPage;
do { // For each limit (or page size) - forward scroll till the end
LOG.info("Limit {} forward scrollCount {} afterCursor {}", limit, pageCount, after);
forwardPage = listDashboards(null, null, limit, null, after, adminAuthHeaders());
printDashboards(forwardPage);
after = forwardPage.getPaging().getAfter();
before = forwardPage.getPaging().getBefore();
assertEntityPagination(allDashboards.getData(), forwardPage, limit, indexInAllDashboards);
if (pageCount == 0) { // CASE 0 - First page is being returned. There is no before cursor
assertNull(before);
} else {
// Make sure scrolling back based on before cursor returns the correct result
backwardPage = listDashboards(null, null, limit, before, null, adminAuthHeaders());
assertEntityPagination(allDashboards.getData(), backwardPage, limit, (indexInAllDashboards - limit));
}
indexInAllDashboards += forwardPage.getData().size();
pageCount++;
} while (after != null);
// We have now reached the last page - test backward scroll till the beginning
pageCount = 0;
indexInAllDashboards = totalRecords - limit - forwardPage.getData().size();
do {
LOG.info("Limit {} backward scrollCount {} beforeCursor {}", limit, pageCount, before);
forwardPage = listDashboards(null, null, limit, before, null, adminAuthHeaders());
printDashboards(forwardPage);
before = forwardPage.getPaging().getBefore();
assertEntityPagination(allDashboards.getData(), forwardPage, limit, indexInAllDashboards);
pageCount++;
indexInAllDashboards -= forwardPage.getData().size();
} while (before != null);
}
}
private void printDashboards(DashboardList list) {
list.getData().forEach(Dashboard -> LOG.info("DB {}", Dashboard.getFullyQualifiedName()));
LOG.info("before {} after {} ", list.getPaging().getBefore(), list.getPaging().getAfter());
}
@Test
public void put_DashboardUpdateWithNoChange_200(TestInfo test) throws HttpResponseException {
// Create a Dashboard with POST
CreateDashboard request = create(test).withService(SUPERSET_REFERENCE).withOwner(USER_OWNER1);
createAndCheckDashboard(request, adminAuthHeaders());
// Update Dashboard two times successfully with PUT requests
updateAndCheckDashboard(request, OK, adminAuthHeaders());
updateAndCheckDashboard(request, OK, adminAuthHeaders());
}
@Test
public void put_DashboardCreate_200(TestInfo test) throws HttpResponseException {
// Create a new Dashboard with put
CreateDashboard request = create(test).withService(SUPERSET_REFERENCE).withOwner(USER_OWNER1);
updateAndCheckDashboard(request.withName(test.getDisplayName()).withDescription(null), CREATED, adminAuthHeaders());
}
@Test
public void put_DashboardCreate_as_owner_200(TestInfo test) throws HttpResponseException {
// Create a new Dashboard with put
CreateDashboard request = create(test).withService(SUPERSET_REFERENCE).withOwner(USER_OWNER1);
// Add Owner as admin
createAndCheckDashboard(request, adminAuthHeaders());
//Update the table as Owner
updateAndCheckDashboard(request.withName(test.getDisplayName()).withDescription(null),
CREATED, authHeaders(USER1.getEmail()));
}
@Test
public void put_DashboardNullDescriptionUpdate_200(TestInfo test) throws HttpResponseException {
CreateDashboard request = create(test).withService(SUPERSET_REFERENCE).withDescription(null);
createAndCheckDashboard(request, adminAuthHeaders());
// Update null description with a new description
Dashboard db = updateAndCheckDashboard(request.withDescription("newDescription"), OK, adminAuthHeaders());
assertEquals("newDescription", db.getDescription());
}
@Test
public void put_DashboardEmptyDescriptionUpdate_200(TestInfo test) throws HttpResponseException {
// Create table with empty description
CreateDashboard request = create(test).withService(SUPERSET_REFERENCE).withDescription("");
createAndCheckDashboard(request, adminAuthHeaders());
// Update empty description with a new description
Dashboard db = updateAndCheckDashboard(request.withDescription("newDescription"), OK, adminAuthHeaders());
assertEquals("newDescription", db.getDescription());
}
@Test
public void put_DashboardNonEmptyDescriptionUpdate_200(TestInfo test) throws HttpResponseException {
CreateDashboard request = create(test).withService(SUPERSET_REFERENCE).withDescription("description");
createAndCheckDashboard(request, adminAuthHeaders());
// Updating description is ignored when backend already has description
Dashboard db = updateDashboard(request.withDescription("newDescription"), OK, adminAuthHeaders());
assertEquals("description", db.getDescription());
}
@Test
public void put_DashboardUpdateOwner_200(TestInfo test) throws HttpResponseException {
CreateDashboard request = create(test).withService(SUPERSET_REFERENCE).withDescription("");
createAndCheckDashboard(request, adminAuthHeaders());
// Change ownership from USER_OWNER1 to TEAM_OWNER1
updateAndCheckDashboard(request.withOwner(TEAM_OWNER1), OK, adminAuthHeaders());
// Remove ownership
Dashboard db = updateAndCheckDashboard(request.withOwner(null), OK, adminAuthHeaders());
assertNull(db.getOwner());
}
@Test
public void get_nonExistentDashboard_404_notFound() {
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
getDashboard(TestUtils.NON_EXISTENT_ENTITY, adminAuthHeaders()));
assertResponse(exception, NOT_FOUND,
entityNotFound(Entity.DASHBOARD, TestUtils.NON_EXISTENT_ENTITY));
}
@Test
public void get_DashboardWithDifferentFields_200_OK(TestInfo test) throws HttpResponseException {
CreateDashboard create = create(test).withDescription("description").withOwner(USER_OWNER1)
.withService(SUPERSET_REFERENCE);
Dashboard Dashboard = createAndCheckDashboard(create, adminAuthHeaders());
validateGetWithDifferentFields(Dashboard, false);
}
@Test
public void get_DashboardByNameWithDifferentFields_200_OK(TestInfo test) throws HttpResponseException {
CreateDashboard create = create(test).withDescription("description").withOwner(USER_OWNER1)
.withService(SUPERSET_REFERENCE);
Dashboard Dashboard = createAndCheckDashboard(create, adminAuthHeaders());
validateGetWithDifferentFields(Dashboard, true);
}
@Test
public void patch_DashboardAttributes_200_ok(TestInfo test) throws HttpResponseException, JsonProcessingException {
// Create Dashboard without description, owner
Dashboard Dashboard = createDashboard(create(test), adminAuthHeaders());
assertNull(Dashboard.getDescription());
assertNull(Dashboard.getOwner());
assertNotNull(Dashboard.getService());
Dashboard = getDashboard(Dashboard.getId(), "service,owner,usageSummary", adminAuthHeaders());
Dashboard.getService().setHref(null); // href is readonly and not patchable
// Add description, owner when previously they were null
Dashboard = patchDashboardAttributesAndCheck(Dashboard, "description", TEAM_OWNER1, adminAuthHeaders());
Dashboard.setOwner(TEAM_OWNER1); // Get rid of href and name returned in the response for owner
Dashboard.setService(SUPERSET_REFERENCE); // Get rid of href and name returned in the response for service
// Replace description, tier, owner
Dashboard = patchDashboardAttributesAndCheck(Dashboard, "description1", USER_OWNER1, adminAuthHeaders());
Dashboard.setOwner(USER_OWNER1); // Get rid of href and name returned in the response for owner
Dashboard.setService(SUPERSET_REFERENCE); // Get rid of href and name returned in the response for service
// Remove description, tier, owner
patchDashboardAttributesAndCheck(Dashboard, null, null, adminAuthHeaders());
}
@Test
public void patch_DashboardIDChange_400(TestInfo test) throws HttpResponseException, JsonProcessingException {
// Ensure Dashboard ID can't be changed using patch
Dashboard Dashboard = createDashboard(create(test), adminAuthHeaders());
UUID DashboardId = Dashboard.getId();
String DashboardJson = JsonUtils.pojoToJson(Dashboard);
Dashboard.setId(UUID.randomUUID());
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
patchDashboard(DashboardId, DashboardJson, Dashboard, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, readOnlyAttribute(Entity.DASHBOARD, "id"));
// ID can't be deleted
Dashboard.setId(null);
exception = assertThrows(HttpResponseException.class, () ->
patchDashboard(DashboardId, DashboardJson, Dashboard, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, readOnlyAttribute(Entity.DASHBOARD, "id"));
}
@Test
public void patch_DashboardNameChange_400(TestInfo test) throws HttpResponseException, JsonProcessingException {
// Ensure Dashboard name can't be changed using patch
Dashboard Dashboard = createDashboard(create(test), adminAuthHeaders());
String DashboardJson = JsonUtils.pojoToJson(Dashboard);
Dashboard.setName("newName");
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
patchDashboard(DashboardJson, Dashboard, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, readOnlyAttribute(Entity.DASHBOARD, "name"));
// Name can't be removed
Dashboard.setName(null);
exception = assertThrows(HttpResponseException.class, () ->
patchDashboard(DashboardJson, Dashboard, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, readOnlyAttribute(Entity.DASHBOARD, "name"));
}
@Test
public void patch_DashboardRemoveService_400(TestInfo test) throws HttpResponseException, JsonProcessingException {
// Ensure service corresponding to Dashboard can't be changed by patch operation
Dashboard Dashboard = createDashboard(create(test), adminAuthHeaders());
Dashboard.getService().setHref(null); // Remove href from returned response as it is read-only field
String DashboardJson = JsonUtils.pojoToJson(Dashboard);
Dashboard.setService(LOOKER_REFERENCE);
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
patchDashboard(DashboardJson, Dashboard, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, readOnlyAttribute(Entity.DASHBOARD, "service"));
// Service relationship can't be removed
Dashboard.setService(null);
exception = assertThrows(HttpResponseException.class, () ->
patchDashboard(DashboardJson, Dashboard, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, readOnlyAttribute(Entity.DASHBOARD, "service"));
}
// TODO listing tables test:1
// TODO Change service?
@Test
public void delete_emptyDashboard_200_ok(TestInfo test) throws HttpResponseException {
Dashboard Dashboard = createDashboard(create(test), adminAuthHeaders());
deleteDashboard(Dashboard.getId(), adminAuthHeaders());
}
@Test
public void delete_nonEmptyDashboard_4xx() {
// TODO
}
@Test
public void delete_nonExistentDashboard_404() {
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
deleteDashboard(TestUtils.NON_EXISTENT_ENTITY, adminAuthHeaders()));
assertResponse(exception, NOT_FOUND, entityNotFound(Entity.DASHBOARD, TestUtils.NON_EXISTENT_ENTITY));
}
public static Dashboard createAndCheckDashboard(CreateDashboard create,
Map<String, String> authHeaders) throws HttpResponseException {
Dashboard Dashboard = createDashboard(create, authHeaders);
validateDashboard(Dashboard, create.getDescription(), create.getOwner(), create.getService());
return getAndValidate(Dashboard.getId(), create, authHeaders);
}
public static Dashboard updateAndCheckDashboard(CreateDashboard create,
Status status,
Map<String, String> authHeaders) throws HttpResponseException {
Dashboard updatedDashboard = updateDashboard(create, status, authHeaders);
validateDashboard(updatedDashboard, create.getDescription(), create.getOwner(), create.getService());
// GET the newly updated Dashboard and validate
return getAndValidate(updatedDashboard.getId(), create, authHeaders);
}
// Make sure in GET operations the returned Dashboard has all the required information passed during creation
public static Dashboard getAndValidate(UUID DashboardId,
CreateDashboard create,
Map<String, String> authHeaders) throws HttpResponseException {
// GET the newly created Dashboard by ID and validate
Dashboard Dashboard = getDashboard(DashboardId, "service,owner", authHeaders);
validateDashboard(Dashboard, create.getDescription(), create.getOwner(), create.getService());
// GET the newly created Dashboard by name and validate
String fqn = Dashboard.getFullyQualifiedName();
Dashboard = getDashboardByName(fqn, "service,owner", authHeaders);
return validateDashboard(Dashboard, create.getDescription(), create.getOwner(), create.getService());
}
public static Dashboard updateDashboard(CreateDashboard create,
Status status,
Map<String, String> authHeaders) throws HttpResponseException {
return TestUtils.put(getResource("dashboards"),
create, Dashboard.class, status, authHeaders);
}
public static Dashboard createDashboard(CreateDashboard create,
Map<String, String> authHeaders) throws HttpResponseException {
return TestUtils.post(getResource("dashboards"), create, Dashboard.class, authHeaders);
}
/** Validate returned fields GET .../dashboards/{id}?fields="..." or GET .../dashboards/name/{fqn}?fields="..." */
private void validateGetWithDifferentFields(Dashboard Dashboard, boolean byName) throws HttpResponseException {
// .../Dashboards?fields=owner
String fields = "owner";
Dashboard = byName ? getDashboardByName(Dashboard.getFullyQualifiedName(), fields, adminAuthHeaders()) :
getDashboard(Dashboard.getId(), fields, adminAuthHeaders());
assertNotNull(Dashboard.getOwner());
assertNull(Dashboard.getService());
assertNull(Dashboard.getCharts());
// .../Dashboards?fields=owner,service
fields = "owner,service";
Dashboard = byName ? getDashboardByName(Dashboard.getFullyQualifiedName(), fields, adminAuthHeaders()) :
getDashboard(Dashboard.getId(), fields, adminAuthHeaders());
assertNotNull(Dashboard.getOwner());
assertNotNull(Dashboard.getService());
assertNull(Dashboard.getCharts());
// .../Dashboards?fields=owner,service,tables
fields = "owner,service,charts,usageSummary";
Dashboard = byName ? getDashboardByName(Dashboard.getFullyQualifiedName(), fields, adminAuthHeaders()) :
getDashboard(Dashboard.getId(), fields, adminAuthHeaders());
assertNotNull(Dashboard.getOwner());
assertNotNull(Dashboard.getService());
assertNotNull(Dashboard.getCharts());
TestUtils.validateEntityReference(Dashboard.getCharts());
assertNotNull(Dashboard.getUsageSummary());
}
private static Dashboard validateDashboard(Dashboard dashboard, String expectedDescription,
EntityReference expectedOwner, EntityReference expectedService) {
assertNotNull(dashboard.getId());
assertNotNull(dashboard.getHref());
assertEquals(expectedDescription, dashboard.getDescription());
// Validate owner
if (expectedOwner != null) {
TestUtils.validateEntityReference(dashboard.getOwner());
assertEquals(expectedOwner.getId(), dashboard.getOwner().getId());
assertEquals(expectedOwner.getType(), dashboard.getOwner().getType());
assertNotNull(dashboard.getOwner().getHref());
}
// Validate service
if (expectedService != null) {
TestUtils.validateEntityReference(dashboard.getService());
assertEquals(expectedService.getId(), dashboard.getService().getId());
assertEquals(expectedService.getType(), dashboard.getService().getType());
}
return dashboard;
}
private Dashboard patchDashboardAttributesAndCheck(Dashboard dashboard, String newDescription,
EntityReference newOwner, Map<String, String> authHeaders)
throws JsonProcessingException, HttpResponseException {
String DashboardJson = JsonUtils.pojoToJson(dashboard);
// Update the table attributes
dashboard.setDescription(newDescription);
dashboard.setOwner(newOwner);
// Validate information returned in patch response has the updates
Dashboard updatedDashboard = patchDashboard(DashboardJson, dashboard, authHeaders);
validateDashboard(updatedDashboard, dashboard.getDescription(), newOwner, null);
// GET the table and Validate information returned
Dashboard getDashboard = getDashboard(dashboard.getId(), "service,owner", authHeaders);
validateDashboard(getDashboard, dashboard.getDescription(), newOwner, null);
return updatedDashboard;
}
private Dashboard patchDashboard(UUID dashboardId, String originalJson, Dashboard updatedDashboard,
Map<String, String> authHeaders)
throws JsonProcessingException, HttpResponseException {
String updateDashboardJson = JsonUtils.pojoToJson(updatedDashboard);
JsonPatch patch = JsonSchemaUtil.getJsonPatch(originalJson, updateDashboardJson);
return TestUtils.patch(getResource("dashboards/" + dashboardId), patch, Dashboard.class, authHeaders);
}
private Dashboard patchDashboard(String originalJson,
Dashboard updatedDashboard,
Map<String, String> authHeaders)
throws JsonProcessingException, HttpResponseException {
return patchDashboard(updatedDashboard.getId(), originalJson, updatedDashboard, authHeaders);
}
public static void getDashboard(UUID id, Map<String, String> authHeaders) throws HttpResponseException {
getDashboard(id, null, authHeaders);
}
public static Dashboard getDashboard(UUID id, String fields, Map<String, String> authHeaders)
throws HttpResponseException {
WebTarget target = getResource("dashboards/" + id);
target = fields != null ? target.queryParam("fields", fields): target;
return TestUtils.get(target, Dashboard.class, authHeaders);
}
public static Dashboard getDashboardByName(String fqn, String fields, Map<String, String> authHeaders)
throws HttpResponseException {
WebTarget target = getResource("dashboards/name/" + fqn);
target = fields != null ? target.queryParam("fields", fields): target;
return TestUtils.get(target, Dashboard.class, authHeaders);
}
public static DashboardList listDashboards(String fields, String serviceParam, Map<String, String> authHeaders)
throws HttpResponseException {
return listDashboards(fields, serviceParam, null, null, null, authHeaders);
}
public static DashboardList listDashboards(String fields, String serviceParam, Integer limitParam,
String before, String after, Map<String, String> authHeaders)
throws HttpResponseException {
WebTarget target = getResource("dashboards");
target = fields != null ? target.queryParam("fields", fields): target;
target = serviceParam != null ? target.queryParam("service", serviceParam): target;
target = limitParam != null ? target.queryParam("limit", limitParam): target;
target = before != null ? target.queryParam("before", before) : target;
target = after != null ? target.queryParam("after", after) : target;
return TestUtils.get(target, DashboardList.class, authHeaders);
}
private void deleteDashboard(UUID id, Map<String, String> authHeaders) throws HttpResponseException {
TestUtils.delete(getResource("dashboards/" + id), authHeaders);
// Ensure deleted Dashboard does not exist
HttpResponseException exception = assertThrows(HttpResponseException.class, () -> getDashboard(id, authHeaders));
assertResponse(exception, NOT_FOUND, entityNotFound(Entity.DASHBOARD, id));
}
public static String getDashboardName(TestInfo test) {
return String.format("dash_%s", test.getDisplayName());
}
public static String getDashboardName(TestInfo test, int index) {
return String.format("dash%d_%s", index, test.getDisplayName());
}
public static CreateDashboard create(TestInfo test) {
return new CreateDashboard().withName(getDashboardName(test)).withService(SUPERSET_REFERENCE);
}
public static CreateDashboard create(TestInfo test, int index) {
return new CreateDashboard().withName(getDashboardName(test, index)).withService(SUPERSET_REFERENCE);
}
}

View File

@ -49,6 +49,8 @@ public final class TestUtils {
public static final String LONG_ENTITY_NAME = "012345678901234567890123456789012345678901234567890123456789012345"; public static final String LONG_ENTITY_NAME = "012345678901234567890123456789012345678901234567890123456789012345";
public static final UUID NON_EXISTENT_ENTITY = UUID.randomUUID(); public static final UUID NON_EXISTENT_ENTITY = UUID.randomUUID();
public static JdbcInfo JDBC_INFO; public static JdbcInfo JDBC_INFO;
public static URI DASHBOARD_URL;
static { static {
try { try {
JDBC_INFO = new JdbcInfo().withConnectionUrl(new URI("jdbc:service://")).withDriverClass("driverClass"); JDBC_INFO = new JdbcInfo().withConnectionUrl(new URI("jdbc:service://")).withDriverClass("driverClass");
@ -58,6 +60,15 @@ public final class TestUtils {
} }
} }
static {
try {
DASHBOARD_URL = new URI("http://localhost:8088");
} catch (URISyntaxException e) {
DASHBOARD_URL = null;
e.printStackTrace();
}
}
private TestUtils() { private TestUtils() {
} }
@ -169,7 +180,8 @@ public final class TestUtils {
assertNotNull(ref.getName()); assertNotNull(ref.getName());
assertNotNull(ref.getType()); assertNotNull(ref.getType());
// Ensure data entities use fully qualified name // Ensure data entities use fully qualified name
if (List.of("table", "database", "metrics", "dashboard", "pipeline", "report", "topic").contains(ref.getName())) { if (List.of("table", "database", "metrics", "dashboard", "pipeline", "report", "topic", "chart")
.contains(ref.getName())) {
ref.getName().contains("."); // FullyQualifiedName has "." as separator ref.getName().contains("."); // FullyQualifiedName has "." as separator
} }
} }