diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/DashboardRepository.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/DashboardRepository.java index 576e9bd6f01..c49cf38e1f6 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/DashboardRepository.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/DashboardRepository.java @@ -17,13 +17,11 @@ package org.openmetadata.catalog.jdbi3; import org.openmetadata.catalog.entity.data.Chart; -import org.openmetadata.catalog.entity.data.Database; -import org.openmetadata.catalog.entity.data.Table; +import org.openmetadata.catalog.entity.services.DashboardService; import org.openmetadata.catalog.exception.CatalogExceptionMessage; import org.openmetadata.catalog.exception.EntityNotFoundException; import org.openmetadata.catalog.jdbi3.TeamRepository.TeamDAO; 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.Entity; 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, "owner,service,tags,charts"); + public static String getFQN(EntityReference service, Dashboard dashboard) { + return (service.getName() + "." + dashboard.getName()); + } @CreateSqlObject abstract DashboardDAO dashboardDAO(); @@ -111,6 +112,12 @@ public abstract class DashboardRepository { 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 public Dashboard create(Dashboard dashboard, EntityReference service, EntityReference owner) throws IOException { getService(service); // Validate service @@ -120,7 +127,8 @@ public abstract class DashboardRepository { @Transaction public PutResponse createOrUpdate(Dashboard updatedDashboard, EntityReference service, 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); if (storedDashboard == null) { return new PutResponse<>(Status.CREATED, createInternal(updatedDashboard, service, newOwner)); @@ -134,11 +142,12 @@ public abstract class DashboardRepository { // Update owner relationship 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 // change to a different service will result in a different FQN and creation of a new database under the new service storedDashboard.setService(service); + applyTags(updatedDashboard); return new PutResponse<>(Response.Status.OK, storedDashboard); } @@ -216,16 +225,24 @@ public abstract class DashboardRepository { return dashboard; } - private EntityReference getService(Dashboard dashboard) { + private EntityReference getService(Dashboard dashboard) throws IOException { return dashboard == null ? null : getService(EntityUtil.getService(relationshipDAO(), dashboard.getId())); } - private EntityReference getService(EntityReference service) { - // TODO What are the dashboard services? + private EntityReference getService(EntityReference service) throws IOException { + 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; } - public void setService(Dashboard dashboard, EntityReference service) { + public void setService(Dashboard dashboard, EntityReference service) throws IOException { if (service != null && dashboard != null) { getService(service); // Populate service details 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 { // Add relationship from dashboard to chart String dashboardId = dashboard.getId().toString(); - for (EntityReference chart: dashboard.getCharts()) { - relationshipDAO().insert(dashboardId, chart.getId().toString(), Entity.DASHBOARD, Entity.CHART, - Relationship.CONTAINS.ordinal()); + if (dashboard.getCharts() != null) { + for (EntityReference chart : dashboard.getCharts()) { + relationshipDAO().insert(dashboardId, chart.getId().toString(), Entity.DASHBOARD, Entity.CHART, + Relationship.CONTAINS.ordinal()); + } } // Add owner relationship EntityUtil.setOwner(relationshipDAO(), dashboard.getId(), Entity.DASHBOARD, dashboard.getOwner()); @@ -314,14 +333,6 @@ public abstract class DashboardRepository { 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 { return EntityUtil.validate(id, dashboardDAO().findById(id), Dashboard.class); } @@ -351,7 +362,7 @@ public abstract class DashboardRepository { List listBefore(@Bind("fqnPrefix") String fqnPrefix, @Bind("limit") int limit, @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 > :after " + "ORDER BY fullyQualifiedName " + diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/UsageRepository.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/UsageRepository.java index 456c3b5a3e8..36061b58638 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/UsageRepository.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/UsageRepository.java @@ -92,7 +92,7 @@ public abstract class UsageRepository { @Transaction public EntityUsage getByName(String entityType, String fqn, String date, int days) throws IOException { EntityReference ref = EntityUtil.getEntityReferenceByName(entityType, fqn, tableDAO(), databaseDAO(), - metricsDAO(), reportDAO(), topicDAO(), chartDAO()); + metricsDAO(), reportDAO(), topicDAO(), chartDAO(), dashboardDAO()); List usageDetails = usageDAO().getUsageById(ref.getId().toString(), date, days - 1); return new EntityUsage().withUsage(usageDetails).withEntity(ref); } @@ -108,7 +108,7 @@ public abstract class UsageRepository { @Transaction public void createByName(String entityType, String fullyQualifiedName, DailyCount usage) throws IOException { EntityReference ref = EntityUtil.getEntityReferenceByName(entityType, fullyQualifiedName, tableDAO(), - databaseDAO(), metricsDAO(), reportDAO(), topicDAO(), chartDAO()); + databaseDAO(), metricsDAO(), reportDAO(), topicDAO(), chartDAO(), dashboardDAO()); addUsage(entityType, ref.getId().toString(), usage); LOG.info("Usage successfully posted by name"); } diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/charts/ChartResource.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/charts/ChartResource.java index 98c01a33038..3c715d2a31b 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/charts/ChartResource.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/charts/ChartResource.java @@ -98,6 +98,7 @@ public class ChartResource { EntityUtil.addHref(uriInfo, chart.getOwner()); EntityUtil.addHref(uriInfo, chart.getService()); EntityUtil.addHref(uriInfo, chart.getFollowers()); + return chart; } diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/dashboards/DashboardResource.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/dashboards/DashboardResource.java index 953206ecac3..85202b9fe66 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/dashboards/DashboardResource.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/dashboards/DashboardResource.java @@ -26,6 +26,7 @@ import org.openmetadata.catalog.entity.data.Dashboard; import org.openmetadata.catalog.jdbi3.DashboardRepository; import org.openmetadata.catalog.resources.Collection; import org.openmetadata.catalog.security.SecurityUtil; +import org.openmetadata.catalog.type.EntityReference; import org.openmetadata.catalog.util.EntityUtil; import org.openmetadata.catalog.util.EntityUtil.Fields; import org.openmetadata.catalog.util.RestUtil; @@ -77,20 +78,30 @@ import java.util.UUID; @Consumes(MediaType.APPLICATION_JSON) @Collection(name = "dashboards", repositoryClass = "org.openmetadata.catalog.jdbi3.DashboardRepository") public class DashboardResource { - public static final String COLLECTION_PATH = "/v1/dashboards/"; + public static final String DASHBOARD_COLLECTION_PATH = "v1/dashboards/"; private final List attributes = RestUtil.getAttributes(Dashboard.class); private final List relationships = RestUtil.getAttributes(Dashboard.class); private final DashboardRepository dao; private final CatalogAuthorizer authorizer; - private static List addHref(UriInfo uriInfo, List dashboards) { + public static void addHref(UriInfo uriInfo, EntityReference ref) { + ref.withHref(RestUtil.getHref(uriInfo, DASHBOARD_COLLECTION_PATH, ref.getId())); + } + + public static List addHref(UriInfo uriInfo, List dashboards) { Optional.ofNullable(dashboards).orElse(Collections.emptyList()).forEach(i -> addHref(uriInfo, i)); return dashboards; } - private static Dashboard addHref(UriInfo uriInfo, Dashboard dashboard) { - dashboard.setHref(RestUtil.getHref(uriInfo, COLLECTION_PATH, dashboard.getId())); + public static Dashboard addHref(UriInfo uriInfo, Dashboard dashboard) { + 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; } @@ -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 FIELD_LIST = Arrays.asList(FIELDS.replaceAll(" ", "") .split(",")); @@ -196,6 +207,27 @@ public class DashboardResource { 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 @Operation(summary = "Create a dashboard", tags = "dashboards", description = "Create a new dashboard.", @@ -209,9 +241,10 @@ public class DashboardResource { @Valid CreateDashboard create) throws IOException { SecurityUtil.checkAdminOrBotRole(authorizer, securityContext); Dashboard dashboard = new Dashboard().withId(UUID.randomUUID()).withName(create.getName()) - .withService(create.getService()).withCharts(create.getCharts()) - .withDashboardUrl(create.getDashboardUrl()).withTags(create.getTags()); - addHref(uriInfo, dao.create(dashboard, dashboard.getService(), dashboard.getOwner())); + .withDescription(create.getDescription()).withService(create.getService()).withCharts(create.getCharts()) + .withDashboardUrl(create.getDashboardUrl()).withTags(create.getTags()) + .withOwner(create.getOwner()); + dashboard = addHref(uriInfo, dao.create(dashboard, dashboard.getService(), dashboard.getOwner())); return Response.created(dashboard.getHref()).entity(dashboard).build(); } @@ -249,15 +282,17 @@ public class DashboardResource { schema = @Schema(implementation = Dashboard.class))), @ApiResponse(responseCode = "400", description = "Bad request") }) - public Response createOrUpdate(@Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Valid Dashboard create) throws IOException { + public Response createOrUpdate(@Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Valid CreateDashboard create) throws IOException { Dashboard dashboard = new Dashboard().withId(UUID.randomUUID()).withName(create.getName()) - .withService(create.getService()).withCharts(create.getCharts()) - .withDashboardUrl(create.getDashboardUrl()).withTags(create.getTags()); - addHref(uriInfo, dao.create(dashboard, dashboard.getService(), dashboard.getOwner())); + .withDescription(create.getDescription()).withService(create.getService()).withCharts(create.getCharts()) + .withDashboardUrl(create.getDashboardUrl()).withTags(create.getTags()) + .withOwner(create.getOwner()); + PutResponse response = dao.createOrUpdate(dashboard, dashboard.getService(), dashboard.getOwner()); - addHref(uriInfo, response.getEntity()); - return Response.status(response.getStatus()).entity(response.getEntity()).build(); + dashboard = addHref(uriInfo, response.getEntity()); + return Response.status(response.getStatus()).entity(dashboard).build(); } @PUT diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/util/EntityUtil.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/util/EntityUtil.java index 251203a2204..e4df758e8bc 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/util/EntityUtil.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/util/EntityUtil.java @@ -45,6 +45,7 @@ import org.openmetadata.catalog.jdbi3.TopicRepository.TopicDAO; import org.openmetadata.catalog.jdbi3.UsageRepository.UsageDAO; 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.databases.DatabaseResource; import org.openmetadata.catalog.resources.databases.TableResource; import org.openmetadata.catalog.resources.feeds.MessageParser.EntityLink; @@ -135,6 +136,9 @@ public final class EntityUtil { case Entity.CHART: ChartResource.addHref(uriInfo, ref); break; + case Entity.DASHBOARD: + DashboardResource.addHref(uriInfo, ref); + break; case Entity.MESSAGING_SERVICE: MessagingServiceResource.addHref(uriInfo, ref); break; @@ -273,7 +277,8 @@ public final class EntityUtil { public static EntityReference getEntityReferenceByName(String entity, String fqn, TableDAO tableDAO, DatabaseDAO databaseDAO, MetricsDAO metricsDAO, - ReportDAO reportDAO, TopicDAO topicDAO, ChartDAO chartDAO) + ReportDAO reportDAO, TopicDAO topicDAO, ChartDAO chartDAO, + DashboardDAO dashboardDAO) throws IOException { if (entity.equalsIgnoreCase(Entity.TABLE)) { 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); return new EntityReference().withId(instance.getId()).withName(instance.getName()).withType(Entity.CHART) .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)); } @@ -331,6 +340,9 @@ public final class EntityUtil { } else if (clazz.toString().toLowerCase().endsWith(Entity.CHART.toLowerCase())) { Chart instance = (Chart) entity; 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())) { MessagingService instance = (MessagingService) entity; return getEntityReference(instance); diff --git a/catalog-rest-service/src/test/java/org/openmetadata/catalog/resources/dashboards/DashboardResourceTest.java b/catalog-rest-service/src/test/java/org/openmetadata/catalog/resources/dashboards/DashboardResourceTest.java new file mode 100644 index 00000000000..0fd4f43fade --- /dev/null +++ b/catalog-rest-service/src/test/java/org/openmetadata/catalog/resources/dashboards/DashboardResourceTest.java @@ -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 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 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 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 authHeaders) throws HttpResponseException { + return TestUtils.put(getResource("dashboards"), + create, Dashboard.class, status, authHeaders); + } + + public static Dashboard createDashboard(CreateDashboard create, + Map 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 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 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 authHeaders) + throws JsonProcessingException, HttpResponseException { + return patchDashboard(updatedDashboard.getId(), originalJson, updatedDashboard, authHeaders); + } + + public static void getDashboard(UUID id, Map authHeaders) throws HttpResponseException { + getDashboard(id, null, authHeaders); + } + + public static Dashboard getDashboard(UUID id, String fields, Map 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 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 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 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 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); + } +} diff --git a/catalog-rest-service/src/test/java/org/openmetadata/catalog/util/TestUtils.java b/catalog-rest-service/src/test/java/org/openmetadata/catalog/util/TestUtils.java index 5a749b560a8..4e554ce88cb 100644 --- a/catalog-rest-service/src/test/java/org/openmetadata/catalog/util/TestUtils.java +++ b/catalog-rest-service/src/test/java/org/openmetadata/catalog/util/TestUtils.java @@ -49,6 +49,8 @@ public final class TestUtils { public static final String LONG_ENTITY_NAME = "012345678901234567890123456789012345678901234567890123456789012345"; public static final UUID NON_EXISTENT_ENTITY = UUID.randomUUID(); public static JdbcInfo JDBC_INFO; + public static URI DASHBOARD_URL; + static { try { 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() { } @@ -169,7 +180,8 @@ public final class TestUtils { assertNotNull(ref.getName()); assertNotNull(ref.getType()); // 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 } }