12142.task2 - Add domain and data product properties to entities (#12255)

This commit is contained in:
Suresh Srinivas 2023-07-03 15:42:35 -07:00 committed by GitHub
parent a8396092d8
commit 97140e13fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 1150 additions and 60 deletions

View File

@ -9,3 +9,15 @@ CREATE TABLE IF NOT EXISTS domain_entity (
PRIMARY KEY (id),
UNIQUE (fqnHash)
);
-- create data product entity table
CREATE TABLE IF NOT EXISTS data_product_entity (
id VARCHAR(36) GENERATED ALWAYS AS (json ->> '$.id') STORED NOT NULL,
name VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.name') NOT NULL,
fqnHash VARCHAR(256) NOT NULL,
json JSON NOT NULL,
updatedAt BIGINT UNSIGNED GENERATED ALWAYS AS (json ->> '$.updatedAt') NOT NULL,
updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.updatedBy') NOT NULL,
PRIMARY KEY (id),
UNIQUE (fqnHash)
);

View File

@ -9,3 +9,15 @@ CREATE TABLE IF NOT EXISTS domain_entity (
PRIMARY KEY (id),
UNIQUE (fqnHash)
);
-- create data product entity table
CREATE TABLE IF NOT EXISTS data_product_entity (
id VARCHAR(36) GENERATED ALWAYS AS (json ->> 'id') STORED NOT NULL,
name VARCHAR(256) GENERATED ALWAYS AS (json ->> 'name') STORED NOT NULL,
fqnHash VARCHAR(256) NOT NULL,
json JSONB NOT NULL,
updatedAt BIGINT GENERATED ALWAYS AS ((json ->> 'updatedAt')::bigint) STORED NOT NULL,
updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> 'updatedBy') STORED NOT NULL,
PRIMARY KEY (id),
UNIQUE (fqnHash)
);

View File

@ -64,6 +64,8 @@ public final class Entity {
public static final String FIELD_DISPLAY_NAME = "displayName";
public static final String FIELD_EXTENSION = "extension";
public static final String FIELD_USAGE_SUMMARY = "usageSummary";
public static final String FIELD_DOMAIN = "domain";
public static final String FIELD_DATA_PRODUCTS = "dataProducts";
//
// Service entities
@ -132,6 +134,7 @@ public final class Entity {
// Domain related entities
//
public static final String DOMAIN = "domain";
public static final String DATA_PRODUCT = "dataProduct";
//
// Reserved names in OpenMetadata

View File

@ -81,6 +81,7 @@ import org.openmetadata.schema.entity.data.Query;
import org.openmetadata.schema.entity.data.Report;
import org.openmetadata.schema.entity.data.Table;
import org.openmetadata.schema.entity.data.Topic;
import org.openmetadata.schema.entity.domains.DataProduct;
import org.openmetadata.schema.entity.domains.Domain;
import org.openmetadata.schema.entity.events.EventSubscription;
import org.openmetadata.schema.entity.policies.Policy;
@ -198,6 +199,9 @@ public interface CollectionDAO {
@CreateSqlObject
DomainDAO domainDAO();
@CreateSqlObject
DataProductDAO dataProductDAO();
@CreateSqlObject
EventSubscriptionDAO eventSubscriptionDAO();
@ -1378,6 +1382,28 @@ public interface CollectionDAO {
}
}
interface DataProductDAO extends EntityDAO<DataProduct> {
@Override
default String getTableName() {
return "data_product_entity";
}
@Override
default Class<DataProduct> getEntityClass() {
return DataProduct.class;
}
@Override
default String getNameHashColumn() {
return "fqnHash";
}
@Override
default boolean supportsSoftDelete() {
return false;
}
}
interface EventSubscriptionDAO extends EntityDAO<EventSubscription> {
@Override
default String getTableName() {

View File

@ -0,0 +1,126 @@
/*
* Copyright 2021 Collate
* Licensed 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.service.jdbi3;
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.io.IOException;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.openmetadata.schema.entity.domains.DataProduct;
import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.schema.type.Relationship;
import org.openmetadata.service.Entity;
import org.openmetadata.service.jdbi3.CollectionDAO.EntityRelationshipRecord;
import org.openmetadata.service.resources.domains.DataProductResource;
import org.openmetadata.service.util.EntityUtil;
import org.openmetadata.service.util.EntityUtil.Fields;
import org.openmetadata.service.util.FullyQualifiedName;
@Slf4j
public class DataProductRepository extends EntityRepository<DataProduct> {
private static final String UPDATE_FIELDS = "domain,owner,experts"; // Domain field can't be updated
public DataProductRepository(CollectionDAO dao) {
super(
DataProductResource.COLLECTION_PATH,
Entity.DATA_PRODUCT,
DataProduct.class,
dao.dataProductDAO(),
dao,
UPDATE_FIELDS,
UPDATE_FIELDS);
}
@Override
public DataProduct setFields(DataProduct entity, Fields fields) throws IOException {
return entity.withExperts(fields.contains("experts") ? getExperts(entity) : null);
}
// TODO to to inheritance for experts
private List<EntityReference> getExperts(DataProduct entity) throws IOException {
List<EntityRelationshipRecord> ids = findTo(entity.getId(), Entity.DATA_PRODUCT, Relationship.EXPERT, Entity.USER);
return EntityUtil.populateEntityReferences(ids, Entity.USER);
}
@Override
public void prepare(DataProduct entity) throws IOException {
// Parent, Experts, Owner are already validated
}
@Override
public void storeEntity(DataProduct entity, boolean update) throws IOException {
EntityReference domain = entity.getDomain();
List<EntityReference> experts = entity.getExperts();
entity.withDomain(null).withExperts(null);
store(entity, update);
entity.withDomain(domain).withExperts(experts);
}
@Override
public void storeRelationships(DataProduct entity) {
addRelationship(
entity.getDomain().getId(), entity.getId(), Entity.DOMAIN, Entity.DATA_PRODUCT, Relationship.CONTAINS);
for (EntityReference expert : listOrEmpty(entity.getExperts())) {
addRelationship(entity.getId(), expert.getId(), Entity.DATA_PRODUCT, Entity.USER, Relationship.EXPERT);
}
}
@Override
public EntityUpdater getUpdater(DataProduct original, DataProduct updated, Operation operation) {
return new DataProductUpdater(original, updated, operation);
}
@Override
public void restorePatchAttributes(DataProduct original, DataProduct updated) {
updated.withDomain(original.getDomain()); // Domain can't be changed
}
@Override
public void setFullyQualifiedName(DataProduct entity) {
EntityReference domain = entity.getDomain();
entity.setFullyQualifiedName(FullyQualifiedName.add(domain.getFullyQualifiedName(), entity.getName()));
}
@Override
public String getFullyQualifiedNameHash(DataProduct entity) {
return FullyQualifiedName.buildHash(entity.getFullyQualifiedName());
}
public class DataProductUpdater extends EntityUpdater {
public DataProductUpdater(DataProduct original, DataProduct updated, Operation operation) {
super(original, updated, operation);
}
@Override
public void entitySpecificUpdate() throws IOException {
updateExperts();
}
private void updateExperts() throws JsonProcessingException {
List<EntityReference> origExperts = listOrEmpty(original.getExperts());
List<EntityReference> updatedExperts = listOrEmpty(updated.getExperts());
updateToRelationships(
"experts",
Entity.DATA_PRODUCT,
original.getId(),
Relationship.EXPERT,
Entity.USER,
origExperts,
updatedExperts,
false);
}
}
}

View File

@ -33,7 +33,7 @@ import org.openmetadata.service.util.FullyQualifiedName;
@Slf4j
public class DomainRepository extends EntityRepository<Domain> {
private static final String UPDATE_FIELDS = "owner,experts";
private static final String UPDATE_FIELDS = "parent,children,owner,experts";
public DomainRepository(CollectionDAO dao) {
super(
@ -50,8 +50,7 @@ public class DomainRepository extends EntityRepository<Domain> {
public Domain setFields(Domain entity, Fields fields) throws IOException {
entity.withParent(fields.contains("parent") ? getParent(entity) : null);
entity.withChildren(fields.contains("children") ? getChildren(entity) : null);
entity.withExperts(fields.contains("experts") ? getExperts(entity) : null);
return entity.withOwner(fields.contains("owner") ? getOwner(entity) : null);
return entity.withExperts(fields.contains("experts") ? getExperts(entity) : null);
}
private EntityReference getParent(Domain entity) throws IOException {

View File

@ -19,9 +19,13 @@ import static org.openmetadata.schema.type.Include.ALL;
import static org.openmetadata.schema.type.Include.DELETED;
import static org.openmetadata.schema.type.Include.NON_DELETED;
import static org.openmetadata.service.Entity.ADMIN_USER_NAME;
import static org.openmetadata.service.Entity.DATA_PRODUCT;
import static org.openmetadata.service.Entity.DOMAIN;
import static org.openmetadata.service.Entity.FIELD_DATA_PRODUCTS;
import static org.openmetadata.service.Entity.FIELD_DELETED;
import static org.openmetadata.service.Entity.FIELD_DESCRIPTION;
import static org.openmetadata.service.Entity.FIELD_DISPLAY_NAME;
import static org.openmetadata.service.Entity.FIELD_DOMAIN;
import static org.openmetadata.service.Entity.FIELD_EXTENSION;
import static org.openmetadata.service.Entity.FIELD_FOLLOWERS;
import static org.openmetadata.service.Entity.FIELD_OWNER;
@ -163,6 +167,8 @@ public abstract class EntityRepository<T extends EntityInterface> {
@Getter protected final boolean supportsOwner;
protected final boolean supportsFollower;
protected final boolean supportsVotes;
protected final boolean supportsDomain;
protected final boolean supportsDataProducts;
/** Fields that can be updated during PATCH operation */
@Getter private final Fields patchFields;
@ -192,6 +198,8 @@ public abstract class EntityRepository<T extends EntityInterface> {
this.supportsSoftDelete = allowedFields.contains(FIELD_DELETED);
this.supportsFollower = allowedFields.contains(FIELD_FOLLOWERS);
this.supportsVotes = allowedFields.contains(FIELD_VOTES);
this.supportsDomain = allowedFields.contains(FIELD_DOMAIN);
this.supportsDataProducts = allowedFields.contains(FIELD_DATA_PRODUCTS);
}
/**
@ -547,11 +555,14 @@ public abstract class EntityRepository<T extends EntityInterface> {
prepare(entity);
setFullyQualifiedName(entity);
validateExtension(entity);
// Domain is already validated
}
public void storeRelationshipsInternal(T entity) throws IOException {
storeOwner(entity, entity.getOwner());
applyTags(entity);
storeDomain(entity, entity.getDomain());
storeDataProducts(entity, entity.getDataProducts());
storeRelationships(entity);
}
@ -559,6 +570,8 @@ public abstract class EntityRepository<T extends EntityInterface> {
entity.setOwner(fields.contains(FIELD_OWNER) ? getOwner(entity) : null);
entity.setTags(fields.contains(FIELD_TAGS) ? getTags(entity.getFullyQualifiedName()) : null);
entity.setExtension(fields.contains(FIELD_EXTENSION) ? getExtension(entity) : null);
entity.setDomain(fields.contains(FIELD_DOMAIN) ? getDomain(entity) : null);
entity.setDataProducts(fields.contains(FIELD_DATA_PRODUCTS) ? getDataProducts(entity) : null);
setFields(entity, fields);
setInheritedFields(entity);
return entity;
@ -901,6 +914,10 @@ public abstract class EntityRepository<T extends EntityInterface> {
entity.setOwner(null);
List<TagLabel> tags = entity.getTags();
entity.setTags(null);
EntityReference domain = entity.getDomain();
entity.setDomain(null);
List<EntityReference> dataProducts = entity.getDataProducts();
entity.setDataProducts(null);
if (update) {
dao.update(entity.getId(), getFullyQualifiedNameHash(entity), JsonUtils.pojoToJson(entity));
@ -913,6 +930,8 @@ public abstract class EntityRepository<T extends EntityInterface> {
// Restore the relationships
entity.setOwner(owner);
entity.setTags(tags);
entity.setDomain(domain);
entity.setDataProducts(dataProducts);
}
protected void storeTimeSeries(
@ -990,7 +1009,7 @@ public abstract class EntityRepository<T extends EntityInterface> {
daoCollection.entityExtensionTimeSeriesDao().deleteBeforeTimestamp(fqnHash, extension, timestamp);
}
public void validateExtension(T entity) {
private void validateExtension(T entity) {
if (entity.getExtension() == null) {
return;
}
@ -1357,6 +1376,18 @@ public abstract class EntityRepository<T extends EntityInterface> {
return !supportsOwner ? null : getFromEntityRef(entity.getId(), Relationship.OWNS, null, false);
}
public EntityReference getDomain(T entity) throws IOException {
return getFromEntityRef(entity.getId(), Relationship.HAS, DOMAIN, false);
}
private List<EntityReference> getDataProducts(T entity) throws IOException {
if (!supportsDataProducts || entity == null) {
return null;
}
List<EntityRelationshipRecord> ids = findFrom(entity.getId(), entityType, Relationship.HAS, DATA_PRODUCT);
return EntityUtil.populateEntityReferences(ids, entityType);
}
public EntityReference getOwner(EntityReference ref) throws IOException {
return !supportsOwner ? null : Entity.getEntityReferenceById(ref.getType(), ref.getId(), ALL);
}
@ -1372,15 +1403,39 @@ public abstract class EntityRepository<T extends EntityInterface> {
protected void storeOwner(T entity, EntityReference owner) {
if (supportsOwner && owner != null) {
// Add relationship owner --- owns ---> ownedEntity
LOG.info("Adding owner {}:{} for entity {}:{}", owner.getType(), owner.getId(), entityType, entity.getId());
LOG.info(
"Adding owner {}:{} for entity {}:{}",
owner.getType(),
owner.getFullyQualifiedName(),
entityType,
entity.getId());
addRelationship(owner.getId(), entity.getId(), owner.getType(), entityType, Relationship.OWNS);
}
}
protected void storeDomain(T entity, EntityReference domain) {
if (supportsDomain && domain != null) {
// Add relationship domain --- has ---> entity
LOG.info("Adding domain {} for entity {}:{}", domain.getFullyQualifiedName(), entityType, entity.getId());
addRelationship(domain.getId(), entity.getId(), Entity.DOMAIN, entityType, Relationship.HAS);
}
}
protected void storeDataProducts(T entity, List<EntityReference> dataProducts) {
if (supportsDataProducts && !nullOrEmpty(dataProducts)) {
for (EntityReference dataProduct : dataProducts) {
// Add relationship dataProduct --- has ---> entity
LOG.info(
"Adding dataProduct {} for entity {}:{}", dataProduct.getFullyQualifiedName(), entityType, entity.getId());
addRelationship(dataProduct.getId(), entity.getId(), Entity.DATA_PRODUCT, entityType, Relationship.HAS);
}
}
}
/** Remove owner relationship for a given entity */
private void removeOwner(T entity, EntityReference owner) {
if (EntityUtil.getId(owner) != null) {
LOG.info("Removing owner {}:{} for entity {}", owner.getType(), owner.getId(), entity.getId());
LOG.info("Removing owner {}:{} for entity {}", owner.getType(), owner.getFullyQualifiedName(), entity.getId());
deleteRelationship(owner.getId(), owner.getType(), entity.getId(), entityType, Relationship.OWNS);
}
}
@ -1457,6 +1512,13 @@ public abstract class EntityRepository<T extends EntityInterface> {
return Entity.getEntityReferenceById(owner.getType(), owner.getId(), ALL);
}
public EntityReference validateDomain(String domainFqn) throws IOException {
if (!supportsDomain || domainFqn == null) {
return null;
}
return Entity.getEntityReferenceByName(Entity.DOMAIN, domainFqn, NON_DELETED);
}
/** Override this method to support downloading CSV functionality */
public String exportToCsv(String name, String user) throws IOException {
throw new IllegalArgumentException(csvNotSupported(entityType));
@ -1531,6 +1593,8 @@ public abstract class EntityRepository<T extends EntityInterface> {
updateOwner();
updateExtension();
updateTags(updated.getFullyQualifiedName(), FIELD_TAGS, original.getTags(), updated.getTags());
updateDomain();
updateDataProducts();
entitySpecificUpdate();
}
@ -1664,6 +1728,47 @@ public abstract class EntityRepository<T extends EntityInterface> {
storeExtension(updated);
}
private void updateDomain() throws JsonProcessingException {
if (original.getDomain() == updated.getDomain()) {
return;
}
EntityReference origDomain = original.getDomain();
EntityReference updatedDomain = updated.getDomain();
if ((operation.isPatch() || updatedDomain != null)
&& recordChange(FIELD_DOMAIN, origDomain, updatedDomain, true, entityReferenceMatch)) {
if (origDomain != null) {
LOG.info(
"Removing domain {} for entity {}", origDomain.getFullyQualifiedName(), original.getFullyQualifiedName());
deleteRelationship(origDomain.getId(), Entity.DOMAIN, original.getId(), entityType, Relationship.HAS);
}
if (updatedDomain != null) {
// Add relationship owner --- owns ---> ownedEntity
LOG.info(
"Adding domain {} for entity {}",
updatedDomain.getFullyQualifiedName(),
original.getFullyQualifiedName());
addRelationship(updatedDomain.getId(), original.getId(), Entity.DOMAIN, entityType, Relationship.HAS);
}
}
}
private void updateDataProducts() throws JsonProcessingException {
if (!supportsDataProducts) {
return;
}
List<EntityReference> origDataProducts = listOrEmpty(original.getDataProducts());
List<EntityReference> updatedDataProducts = listOrEmpty(updated.getDataProducts());
updateFromRelationships(
"dataProducts",
DATA_PRODUCT,
origDataProducts,
updatedDataProducts,
Relationship.HAS,
entityType,
original.getId());
}
public final boolean updateVersion(Double oldVersion) {
Double newVersion = oldVersion;
if (majorVersionChange) {

View File

@ -217,7 +217,6 @@ public abstract class EntityResource<T extends EntityInterface, K extends Entity
OperationContext operationContext = new OperationContext(entityType, CREATE);
authorizer.authorize(securityContext, operationContext, getResourceContext());
entity = addHref(uriInfo, repository.create(uriInfo, entity));
LOG.info("Created {}:{}", Entity.getEntityTypeFromObject(entity), entity.getId());
return Response.created(entity.getHref()).entity(entity).build();
}
@ -288,11 +287,14 @@ public abstract class EntityResource<T extends EntityInterface, K extends Entity
public T copy(T entity, CreateEntity request, String updatedBy) throws IOException {
EntityReference owner = repository.validateOwner(request.getOwner());
EntityReference domain = repository.validateDomain(request.getDomain());
entity.setId(UUID.randomUUID());
entity.setName(request.getName());
entity.setDisplayName(request.getDisplayName());
entity.setDescription(request.getDescription());
entity.setOwner(owner);
entity.setDomain(domain);
entity.setDataProducts(getEntityReferences(Entity.DATA_PRODUCT, request.getDataProducts()));
entity.setExtension(request.getExtension());
entity.setUpdatedBy(updatedBy);
entity.setUpdatedAt(System.currentTimeMillis());

View File

@ -73,7 +73,7 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "charts")
public class ChartResource extends EntityResource<Chart, ChartRepository> {
public static final String COLLECTION_PATH = "v1/charts/";
static final String FIELDS = "owner,followers,tags";
static final String FIELDS = "owner,followers,tags,domain,dataProducts";
@Override
public Chart addHref(UriInfo uriInfo, Chart chart) {

View File

@ -73,7 +73,8 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "dashboards")
public class DashboardResource extends EntityResource<Dashboard, DashboardRepository> {
public static final String COLLECTION_PATH = "v1/dashboards/";
protected static final String FIELDS = "owner,charts,followers,tags,usageSummary,extension,dataModels";
protected static final String FIELDS =
"owner,charts,followers,tags,usageSummary,extension,dataModels," + "domain,dataProducts";
@Override
public Dashboard addHref(UriInfo uriInfo, Dashboard dashboard) {

View File

@ -71,7 +71,7 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "databases")
public class DatabaseResource extends EntityResource<Database, DatabaseRepository> {
public static final String COLLECTION_PATH = "v1/databases/";
static final String FIELDS = "owner,databaseSchemas,usageSummary,location,tags,extension";
static final String FIELDS = "owner,databaseSchemas,usageSummary,location,tags,extension,domain";
@Override
public Database addHref(UriInfo uriInfo, Database db) {

View File

@ -71,7 +71,7 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "databaseSchemas")
public class DatabaseSchemaResource extends EntityResource<DatabaseSchema, DatabaseSchemaRepository> {
public static final String COLLECTION_PATH = "v1/databaseSchemas/";
static final String FIELDS = "owner,tables,usageSummary,tags,extension";
static final String FIELDS = "owner,tables,usageSummary,tags,extension,domain";
@Override
public DatabaseSchema addHref(UriInfo uriInfo, DatabaseSchema schema) {

View File

@ -84,6 +84,9 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "tables")
public class TableResource extends EntityResource<Table, TableRepository> {
public static final String COLLECTION_PATH = "v1/tables/";
static final String FIELDS =
"tableConstraints,tablePartition,usageSummary,owner,customMetrics,"
+ "tags,followers,joins,viewDefinition,dataModel,extension,testSuite,domain,dataProducts";
@Override
public Table addHref(UriInfo uriInfo, Table table) {
@ -136,10 +139,6 @@ public class TableResource extends EntityResource<Table, TableRepository> {
/* Required for serde */
}
static final String FIELDS =
"tableConstraints,tablePartition,usageSummary,owner,customMetrics,"
+ "tags,followers,joins,viewDefinition,dataModel,extension,testSuite";
@GET
@Operation(
operationId = "listTables",

View File

@ -70,7 +70,7 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "datamodels")
public class DashboardDataModelResource extends EntityResource<DashboardDataModel, DashboardDataModelRepository> {
public static final String COLLECTION_PATH = "/v1/dashboard/datamodels";
protected static final String FIELDS = "owner,tags,followers";
protected static final String FIELDS = "owner,tags,followers,domain";
@Override
public DashboardDataModel addHref(UriInfo uriInfo, DashboardDataModel dashboardDataModel) {

View File

@ -0,0 +1,341 @@
/*
* Copyright 2021 Collate
* Licensed 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.service.resources.domains;
import io.swagger.v3.oas.annotations.ExternalDocumentation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.json.JsonPatch;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.PATCH;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import lombok.extern.slf4j.Slf4j;
import org.openmetadata.schema.api.domains.CreateDataProduct;
import org.openmetadata.schema.entity.domains.DataProduct;
import org.openmetadata.schema.type.EntityHistory;
import org.openmetadata.schema.utils.EntityInterfaceUtil;
import org.openmetadata.service.Entity;
import org.openmetadata.service.jdbi3.CollectionDAO;
import org.openmetadata.service.jdbi3.DataProductRepository;
import org.openmetadata.service.jdbi3.ListFilter;
import org.openmetadata.service.resources.Collection;
import org.openmetadata.service.resources.EntityResource;
import org.openmetadata.service.security.Authorizer;
import org.openmetadata.service.util.EntityUtil;
import org.openmetadata.service.util.RestUtil;
import org.openmetadata.service.util.ResultList;
@Slf4j
@Path("/v1/dataProducts")
@Tag(
name = "Domains",
description =
"A `Data Product` or `Data as a Product` is a logical unit that contains all components to process and store "
+ "domain data for analytical or data-intensive use cases made available to data consumers.")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Collection(name = "dataProducts", order = 4) // initialize after user resource
public class DataProductResource extends EntityResource<DataProduct, DataProductRepository> {
public static final String COLLECTION_PATH = "/v1/dataProducts/";
static final String FIELDS = "domain,owner,experts";
public DataProductResource(CollectionDAO dao, Authorizer authorizer) {
super(DataProduct.class, new DataProductRepository(dao), authorizer);
}
@Override
public DataProduct addHref(UriInfo uriInfo, DataProduct dataProduct) {
dataProduct.withHref(RestUtil.getHref(uriInfo, COLLECTION_PATH, dataProduct.getId()));
Entity.withHref(uriInfo, dataProduct.getExperts());
Entity.withHref(uriInfo, dataProduct.getOwner());
Entity.withHref(uriInfo, dataProduct.getDomain());
return dataProduct;
}
public static class DataProductList extends ResultList<DataProduct> {
@SuppressWarnings("unused")
public DataProductList() {
/* Required for serde */
}
}
@GET
@Operation(
operationId = "listDataProducts",
summary = "List dataProducts",
description = "Get a list of DataProducts.",
responses = {
@ApiResponse(
responseCode = "200",
description = "List of DataProducts",
content =
@Content(mediaType = "application/json", schema = @Schema(implementation = DataProductList.class)))
})
public ResultList<DataProduct> list(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@Parameter(
description = "Fields requested in the returned resource",
schema = @Schema(type = "string", example = FIELDS))
@QueryParam("fields")
String fieldsParam,
@DefaultValue("10") @Min(0) @Max(1000000) @QueryParam("limit") int limitParam,
@Parameter(description = "Returns list of DataProduct before this cursor", schema = @Schema(type = "string"))
@QueryParam("before")
String before,
@Parameter(description = "Returns list of DataProduct after this cursor", schema = @Schema(type = "string"))
@QueryParam("after")
String after)
throws IOException {
return listInternal(uriInfo, securityContext, fieldsParam, new ListFilter(null), limitParam, before, after);
}
@GET
@Path("/{id}")
@Operation(
operationId = "getDataProductByID",
summary = "Get a dataProduct by Id",
description = "Get a dataProduct by `Id`.",
responses = {
@ApiResponse(
responseCode = "200",
description = "The dataProduct",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = DataProduct.class))),
@ApiResponse(responseCode = "404", description = "DataProduct for instance {id} is not found")
})
public DataProduct get(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@Parameter(
description = "Fields requested in the returned resource",
schema = @Schema(type = "string", example = FIELDS))
@QueryParam("fields")
String fieldsParam,
@Parameter(description = "Id of the dataProduct", schema = @Schema(type = "UUID")) @PathParam("id") UUID id)
throws IOException {
return getInternal(uriInfo, securityContext, id, fieldsParam, null);
}
@GET
@Path("/name/{name}")
@Operation(
operationId = "getDataProductByFQN",
summary = "Get a dataProduct by name",
description = "Get a dataProduct by `name`.",
responses = {
@ApiResponse(
responseCode = "200",
description = "dataProduct",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = DataProduct.class))),
@ApiResponse(responseCode = "404", description = "DataProduct for instance {name} is not found")
})
public DataProduct getByName(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@Parameter(description = "Name of the dataProduct", schema = @Schema(type = "string")) @PathParam("name")
String name,
@Parameter(
description = "Fields requested in the returned resource",
schema = @Schema(type = "string", example = FIELDS))
@QueryParam("fields")
String fieldsParam)
throws IOException {
return getByNameInternal(uriInfo, securityContext, name, fieldsParam, null);
}
@GET
@Path("/{id}/versions")
@Operation(
operationId = "listAllDataProductVersion",
summary = "List dataProduct versions",
description = "Get a list of all the versions of a dataProduct identified by `Id`",
responses = {
@ApiResponse(
responseCode = "200",
description = "List of dataProduct versions",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = EntityHistory.class)))
})
public EntityHistory listVersions(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@Parameter(description = "Id of the dataProduct", schema = @Schema(type = "UUID")) @PathParam("id") UUID id)
throws IOException {
return super.listVersionsInternal(securityContext, id);
}
@GET
@Path("/{id}/versions/{version}")
@Operation(
operationId = "listSpecificDataProductVersion",
summary = "Get a version of the dataProduct",
description = "Get a version of the dataProduct by given `Id`",
responses = {
@ApiResponse(
responseCode = "200",
description = "dataProduct",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = DataProduct.class))),
@ApiResponse(
responseCode = "404",
description = "DataProduct for instance {id} and version {version} is " + "not found")
})
public DataProduct getVersion(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@Parameter(description = "Id of the dataProduct", schema = @Schema(type = "UUID")) @PathParam("id") UUID id,
@Parameter(
description = "DataProduct version number in the form `major`.`minor`",
schema = @Schema(type = "string", example = "0.1 or 1.1"))
@PathParam("version")
String version)
throws IOException {
return super.getVersionInternal(securityContext, id, version);
}
@POST
@Operation(
operationId = "createDataProduct",
summary = "Create a dataProduct",
description = "Create a new dataProduct.",
responses = {
@ApiResponse(
responseCode = "200",
description = "The dataProduct ",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = DataProduct.class))),
@ApiResponse(responseCode = "400", description = "Bad request")
})
public Response create(
@Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid CreateDataProduct create)
throws IOException {
DataProduct dataProduct = getDataProduct(create, securityContext.getUserPrincipal().getName());
return create(uriInfo, securityContext, dataProduct);
}
@PUT
@Operation(
operationId = "createOrUpdateDataProduct",
summary = "Create or update a dataProduct",
description =
"Create a dataProduct. if it does not exist. If a dataProduct already exists, update the " + "dataProduct.",
responses = {
@ApiResponse(
responseCode = "200",
description = "The dataProduct",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = DataProduct.class))),
@ApiResponse(responseCode = "400", description = "Bad request")
})
public Response createOrUpdate(
@Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid CreateDataProduct create)
throws IOException {
DataProduct dataProduct = getDataProduct(create, securityContext.getUserPrincipal().getName());
return createOrUpdate(uriInfo, securityContext, dataProduct);
}
@PATCH
@Path("/{id}")
@Operation(
operationId = "patchDataProduct",
summary = "Update a dataProduct",
description = "Update an existing dataProduct using JsonPatch.",
externalDocs = @ExternalDocumentation(description = "JsonPatch RFC", url = "https://tools.ietf.org/html/rfc6902"))
@Consumes(MediaType.APPLICATION_JSON_PATCH_JSON)
public Response patch(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@Parameter(description = "Id of the dataProduct", schema = @Schema(type = "UUID")) @PathParam("id") UUID id,
@RequestBody(
description = "JsonPatch with array of operations",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON_PATCH_JSON,
examples = {
@ExampleObject("[" + "{op:remove, path:/a}," + "{op:add, path: /b, value: val}" + "]")
}))
JsonPatch patch)
throws IOException {
return patchInternal(uriInfo, securityContext, id, patch);
}
@DELETE
@Path("/{id}")
@Operation(
operationId = "deleteDataProduct",
summary = "Delete a dataProduct by Id",
description = "Delete a dataProduct by `Id`.",
responses = {
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "DataProduct for instance {id} is not found")
})
public Response delete(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@Parameter(description = "Id of the dataProduct", schema = @Schema(type = "UUID")) @PathParam("id") UUID id)
throws IOException {
return delete(uriInfo, securityContext, id, true, true);
}
@DELETE
@Path("/name/{name}")
@Operation(
operationId = "deleteDataProductByFQN",
summary = "Delete a dataProduct by name",
description = "Delete a dataProduct by `name`.",
responses = {
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "DataProduct for instance {name} is not found")
})
public Response delete(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@Parameter(description = "Name of the dataProduct", schema = @Schema(type = "string")) @PathParam("name")
String name)
throws IOException {
return deleteByName(uriInfo, securityContext, name, true, true);
}
private DataProduct getDataProduct(CreateDataProduct create, String user) throws IOException {
List<String> experts =
create.getExperts() == null
? create.getExperts()
: create.getExperts().stream().map(EntityInterfaceUtil::quoteName).collect(Collectors.toList());
return copy(new DataProduct(), create, user)
.withFullyQualifiedName(create.getName())
.withExperts(EntityUtil.populateEntityReferences(getEntityReferences(Entity.USER, experts)));
}
}

View File

@ -50,6 +50,7 @@ import lombok.extern.slf4j.Slf4j;
import org.openmetadata.schema.api.domains.CreateDomain;
import org.openmetadata.schema.entity.domains.Domain;
import org.openmetadata.schema.type.EntityHistory;
import org.openmetadata.schema.type.Include;
import org.openmetadata.schema.utils.EntityInterfaceUtil;
import org.openmetadata.service.Entity;
import org.openmetadata.service.jdbi3.CollectionDAO;
@ -332,7 +333,8 @@ public class DomainResource extends EntityResource<Domain, DomainRepository> {
return copy(new Domain(), create, user)
.withDomainType(create.getDomainType())
.withFullyQualifiedName(create.getName())
.withParent(getEntityReference(Entity.DOMAIN, create.getParent()))
.withParent(
Entity.getEntityReference(getEntityReference(Entity.DOMAIN, create.getParent()), Include.NON_DELETED))
.withExperts(EntityUtil.populateEntityReferences(getEntityReferences(Entity.USER, experts)));
}
}

View File

@ -339,7 +339,6 @@ public class TestCaseResource extends EntityResource<TestCase, TestCaseRepositor
authorizer.authorize(securityContext, operationContext, resourceContext);
repository.isTestSuiteExecutable(create.getTestSuite());
test = addHref(uriInfo, repository.create(uriInfo, test));
LOG.info("Created {}:{}", Entity.getEntityTypeFromObject(test), test.getId());
return Response.created(test.getHref()).entity(test).build();
}

View File

@ -72,7 +72,7 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "glossaries", order = 6) // Initialize before GlossaryTerm and after Classification and Tags
public class GlossaryResource extends EntityResource<Glossary, GlossaryRepository> {
public static final String COLLECTION_PATH = "v1/glossaries/";
static final String FIELDS = "owner,tags,reviewers,usageCount,termCount";
static final String FIELDS = "owner,tags,reviewers,usageCount,termCount,domain";
@Override
public Glossary addHref(UriInfo uriInfo, Glossary glossary) {

View File

@ -76,7 +76,7 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "glossaryTerms", order = 7) // Initialized after Glossary, Classification, and Tags
public class GlossaryTermResource extends EntityResource<GlossaryTerm, GlossaryTermRepository> {
public static final String COLLECTION_PATH = "v1/glossaryTerms/";
static final String FIELDS = "children,relatedTerms,reviewers,owner,tags,usageCount";
static final String FIELDS = "children,relatedTerms,reviewers,owner,tags,usageCount,domain";
@Override
public GlossaryTerm addHref(UriInfo uriInfo, GlossaryTerm term) {

View File

@ -63,7 +63,7 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "metrics")
public class MetricsResource extends EntityResource<Metrics, MetricsRepository> {
public static final String COLLECTION_PATH = "/v1/metrics/";
static final String FIELDS = "owner,usageSummary";
static final String FIELDS = "owner,usageSummary,domain";
public MetricsResource(CollectionDAO dao, Authorizer authorizer) {
super(Metrics.class, new MetricsRepository(dao), authorizer);

View File

@ -73,7 +73,7 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "mlmodels")
public class MlModelResource extends EntityResource<MlModel, MlModelRepository> {
public static final String COLLECTION_PATH = "v1/mlmodels/";
static final String FIELDS = "owner,dashboard,followers,tags,usageSummary,extension";
static final String FIELDS = "owner,dashboard,followers,tags,usageSummary,extension,domain";
@Override
public MlModel addHref(UriInfo uriInfo, MlModel mlmodel) {

View File

@ -78,7 +78,7 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "pipelines")
public class PipelineResource extends EntityResource<Pipeline, PipelineRepository> {
public static final String COLLECTION_PATH = "v1/pipelines/";
static final String FIELDS = "owner,tasks,pipelineStatus,followers,tags,extension,scheduleInterval";
static final String FIELDS = "owner,tasks,pipelineStatus,followers,tags,extension,scheduleInterval,domain";
@Override
public Pipeline addHref(UriInfo uriInfo, Pipeline pipeline) {

View File

@ -13,8 +13,6 @@
package org.openmetadata.service.resources.services.dashboard;
import static org.openmetadata.service.Entity.FIELD_OWNER;
import io.swagger.v3.oas.annotations.ExternalDocumentation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -79,7 +77,7 @@ import org.openmetadata.service.util.ResultList;
public class DashboardServiceResource
extends ServiceEntityResource<DashboardService, DashboardServiceRepository, DashboardConnection> {
public static final String COLLECTION_PATH = "v1/services/dashboardServices";
static final String FIELDS = FIELD_OWNER;
static final String FIELDS = "owner,domain";
@Override
public DashboardService addHref(UriInfo uriInfo, DashboardService service) {

View File

@ -83,7 +83,7 @@ import org.openmetadata.service.util.ResultList;
public class DatabaseServiceResource
extends ServiceEntityResource<DatabaseService, DatabaseServiceRepository, DatabaseConnection> {
public static final String COLLECTION_PATH = "v1/services/databaseServices/";
static final String FIELDS = "pipelines,owner,tags";
static final String FIELDS = "pipelines,owner,tags,domain";
@Override
public DatabaseService addHref(UriInfo uriInfo, DatabaseService service) {

View File

@ -13,8 +13,6 @@
package org.openmetadata.service.resources.services.messaging;
import static org.openmetadata.service.Entity.FIELD_OWNER;
import io.swagger.v3.oas.annotations.ExternalDocumentation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -79,7 +77,7 @@ import org.openmetadata.service.util.ResultList;
public class MessagingServiceResource
extends ServiceEntityResource<MessagingService, MessagingServiceRepository, MessagingConnection> {
public static final String COLLECTION_PATH = "v1/services/messagingServices/";
public static final String FIELDS = FIELD_OWNER;
public static final String FIELDS = "owner,domain";
@Override
public MessagingService addHref(UriInfo uriInfo, MessagingService service) {

View File

@ -79,7 +79,7 @@ import org.openmetadata.service.util.ResultList;
public class MlModelServiceResource
extends ServiceEntityResource<MlModelService, MlModelServiceRepository, MlModelConnection> {
public static final String COLLECTION_PATH = "v1/services/mlmodelServices/";
public static final String FIELDS = "pipelines,owner,tags";
public static final String FIELDS = "pipelines,owner,tags,domain";
@Override
public MlModelService addHref(UriInfo uriInfo, MlModelService service) {

View File

@ -77,7 +77,7 @@ import org.openmetadata.service.util.ResultList;
public class PipelineServiceResource
extends ServiceEntityResource<PipelineService, PipelineServiceRepository, PipelineConnection> {
public static final String COLLECTION_PATH = "v1/services/pipelineServices/";
static final String FIELDS = "pipelines,owner";
static final String FIELDS = "pipelines,owner,domain";
@Override
public PipelineService addHref(UriInfo uriInfo, PipelineService service) {

View File

@ -69,7 +69,7 @@ import org.openmetadata.service.util.ResultList;
public class StorageServiceResource
extends ServiceEntityResource<StorageService, StorageServiceRepository, StorageConnection> {
public static final String COLLECTION_PATH = "v1/services/storageServices/";
static final String FIELDS = "pipelines,owner,tags";
static final String FIELDS = "pipelines,owner,tags,domain";
@Override
public StorageService addHref(UriInfo uriInfo, StorageService service) {

View File

@ -61,7 +61,7 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "containers")
public class ContainerResource extends EntityResource<Container, ContainerRepository> {
public static final String COLLECTION_PATH = "v1/containers/";
static final String FIELDS = "parent,children,dataModel,owner,tags,followers,extension";
static final String FIELDS = "parent,children,dataModel,owner,tags,followers,extension,domain";
@Override
public Container addHref(UriInfo uriInfo, Container container) {

View File

@ -85,6 +85,8 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "teams", order = 2) // Load after roles, and policy resources
public class TeamResource extends EntityResource<Team, TeamRepository> {
public static final String COLLECTION_PATH = "/v1/teams/";
static final String FIELDS =
"owner,profile,users,owns,defaultRoles,parents,children,policies,userCount,childrenCount,domain";
@Override
public Team addHref(UriInfo uriInfo, Team team) {
@ -122,9 +124,6 @@ public class TeamResource extends EntityResource<Team, TeamRepository> {
/* Required for serde */
}
static final String FIELDS =
"owner,profile,users,owns,defaultRoles,parents,children,policies,userCount,childrenCount";
@GET
@Path("/hierarchy")
@Valid

View File

@ -159,7 +159,7 @@ public class UserResource extends EntityResource<User, UserRepository> {
private boolean isEmailServiceEnabled;
private AuthenticationConfiguration authenticationConfiguration;
private final AuthenticatorHandler authHandler;
static final String FIELDS = "profile,roles,teams,follows,owns";
static final String FIELDS = "profile,roles,teams,follows,owns,domain";
@Override
public User addHref(UriInfo uriInfo, User user) {

View File

@ -77,7 +77,7 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "topics")
public class TopicResource extends EntityResource<Topic, TopicRepository> {
public static final String COLLECTION_PATH = "v1/topics/";
static final String FIELDS = "owner,followers,tags,extension";
static final String FIELDS = "owner,followers,tags,extension,domain,dataProducts";
@Override
public Topic addHref(UriInfo uriInfo, Topic topic) {

View File

@ -141,6 +141,8 @@ import org.openmetadata.schema.entity.data.DatabaseSchema;
import org.openmetadata.schema.entity.data.Glossary;
import org.openmetadata.schema.entity.data.GlossaryTerm;
import org.openmetadata.schema.entity.data.Table;
import org.openmetadata.schema.entity.domains.DataProduct;
import org.openmetadata.schema.entity.domains.Domain;
import org.openmetadata.schema.entity.policies.Policy;
import org.openmetadata.schema.entity.policies.accessControl.Rule;
import org.openmetadata.schema.entity.services.connections.TestConnectionResult;
@ -172,6 +174,8 @@ import org.openmetadata.service.elasticsearch.ElasticSearchIndexDefinition;
import org.openmetadata.service.exception.CatalogExceptionMessage;
import org.openmetadata.service.resources.bots.BotResourceTest;
import org.openmetadata.service.resources.databases.TableResourceTest;
import org.openmetadata.service.resources.domains.DataProductResourceTest;
import org.openmetadata.service.resources.domains.DomainResourceTest;
import org.openmetadata.service.resources.dqtests.TestCaseResourceTest;
import org.openmetadata.service.resources.dqtests.TestDefinitionResourceTest;
import org.openmetadata.service.resources.dqtests.TestSuiteResourceTest;
@ -328,6 +332,11 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
public static final String C4 = "\"c.4\"";
public static List<Column> COLUMNS;
public static Domain DOMAIN;
public static Domain SUB_DOMAIN;
public static DataProduct DOMAIN_DATA_PRODUCT;
public static DataProduct SUB_DOMAIN_DATA_PRODUCT;
public static final TestConnectionResult TEST_CONNECTION_RESULT =
new TestConnectionResult()
.withStatus(TestConnectionResultStatus.SUCCESSFUL)
@ -405,6 +414,8 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
new KpiResourceTest().setupKpi();
new BotResourceTest().setupBots();
new QueryResourceTest().setupQuery(test);
new DomainResourceTest().setupDomains(test);
new DataProductResourceTest().setupDataProducts(test);
runWebhookTests = new Random().nextBoolean();
if (runWebhookTests) {
@ -577,7 +588,7 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
int totalRecords = allEntities.getData().size();
printEntities(allEntities);
// List entity with "limit" set from 1 to maxTables size with random jumps (to reduce the test time)
// List entity with "limit" set from 1 to maxEntities size with random jumps (to reduce the test time)
// Each time compare the returned list with allTables list to make sure right results are returned
for (int limit = 1; limit < maxEntities; limit += random.nextInt(5) + 1) {
String after = null;
@ -588,7 +599,13 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
ResultList<T> backwardPage;
boolean foundDeleted = false;
do { // For each limit (or page size) - forward scroll till the end
LOG.debug("Limit {} forward scrollCount {} afterCursor {}", limit, pageCount, after);
LOG.debug(
"Limit {} forward pageCount {} indexInAllTables {} totalRecords {} afterCursor {}",
limit,
pageCount,
indexInAllTables,
totalRecords,
after);
forwardPage = listEntities(queryParams, limit, null, after, ADMIN_AUTH_HEADERS);
foundDeleted = forwardPage.getData().stream().anyMatch(matchDeleted) || foundDeleted;
after = forwardPage.getPaging().getAfter();
@ -620,7 +637,13 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
indexInAllTables = totalRecords - limit - forwardPage.getData().size();
foundDeleted = false;
do {
LOG.debug("Limit {} backward scrollCount {} beforeCursor {}", limit, pageCount, before);
LOG.debug(
"Limit {} backward pageCount {} indexInAllTables {} totalRecords {} afterCursor {}",
limit,
pageCount,
indexInAllTables,
totalRecords,
after);
forwardPage = listEntities(queryParams, limit, before, null, ADMIN_AUTH_HEADERS);
foundDeleted = forwardPage.getData().stream().anyMatch(matchDeleted) || foundDeleted;
printEntities(forwardPage);

View File

@ -25,7 +25,6 @@ public class WorkflowResourceTest extends EntityResourceTest<Workflow, CreateWor
public WorkflowResourceTest() {
super(Entity.WORKFLOW, Workflow.class, WorkflowList.class, "automations/workflows", WorkflowResource.FIELDS);
supportsEmptyDescription = true;
}
@Override

View File

@ -0,0 +1,99 @@
package org.openmetadata.service.resources.domains;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.openmetadata.common.utils.CommonUtil.listOf;
import static org.openmetadata.service.util.TestUtils.ADMIN_AUTH_HEADERS;
import static org.openmetadata.service.util.TestUtils.assertEntityReferenceNames;
import static org.openmetadata.service.util.TestUtils.assertListNull;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.http.client.HttpResponseException;
import org.junit.jupiter.api.TestInfo;
import org.openmetadata.schema.api.domains.CreateDataProduct;
import org.openmetadata.schema.entity.domains.DataProduct;
import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.service.Entity;
import org.openmetadata.service.resources.EntityResourceTest;
import org.openmetadata.service.resources.domains.DataProductResource.DataProductList;
import org.openmetadata.service.util.JsonUtils;
public class DataProductResourceTest extends EntityResourceTest<DataProduct, CreateDataProduct> {
public DataProductResourceTest() {
super(Entity.DATA_PRODUCT, DataProduct.class, DataProductList.class, "dataProducts", DataProductResource.FIELDS);
supportsFieldsQueryParam = false; // TODO
supportsEmptyDescription = false;
}
public void setupDataProducts(TestInfo test) throws HttpResponseException {
DOMAIN_DATA_PRODUCT = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
SUB_DOMAIN_DATA_PRODUCT =
createEntity(
createRequest(getEntityName(test, 1)).withDomain(SUB_DOMAIN.getFullyQualifiedName()), ADMIN_AUTH_HEADERS);
}
@Override
public CreateDataProduct createRequest(String name) {
return new CreateDataProduct()
.withName(name)
.withDescription(name)
.withDomain(DOMAIN.getFullyQualifiedName())
.withExperts(listOf(USER1.getFullyQualifiedName()));
}
@Override
public void validateCreatedEntity(
DataProduct createdEntity, CreateDataProduct request, Map<String, String> authHeaders) {
// Entity specific validation
assertEquals(request.getDomain(), createdEntity.getDomain().getFullyQualifiedName());
assertEntityReferenceNames(request.getExperts(), createdEntity.getExperts());
}
@Override
public void compareEntities(DataProduct expected, DataProduct updated, Map<String, String> authHeaders) {
// Entity specific validation
assertReference(expected.getDomain(), updated.getDomain());
assertEntityReferences(expected.getExperts(), updated.getExperts());
}
@Override
public DataProduct validateGetWithDifferentFields(DataProduct dataProduct, boolean byName)
throws HttpResponseException {
DataProduct getDataProduct =
byName
? getEntityByName(dataProduct.getFullyQualifiedName(), null, ADMIN_AUTH_HEADERS)
: getEntity(dataProduct.getId(), null, ADMIN_AUTH_HEADERS);
assertListNull(getDataProduct.getOwner(), getDataProduct.getExperts());
String fields = "owner,domain,experts";
getDataProduct =
byName
? getEntityByName(getDataProduct.getFullyQualifiedName(), fields, ADMIN_AUTH_HEADERS)
: getEntity(getDataProduct.getId(), fields, ADMIN_AUTH_HEADERS);
// Fields requested are received
assertReference(dataProduct.getDomain(), getDataProduct.getDomain());
assertEntityReferences(dataProduct.getExperts(), getDataProduct.getExperts());
// Checks for other owner, tags, and followers is done in the base class
return getDataProduct;
}
@Override
public void assertFieldChange(String fieldName, Object expected, Object actual) throws IOException {
if (expected == actual) {
return;
}
if (fieldName.startsWith("domain")) {
EntityReference expectedRef = (EntityReference) expected;
EntityReference actualRef = JsonUtils.readValue(actual.toString(), EntityReference.class);
assertEquals(expectedRef.getId(), actualRef.getId());
} else if (fieldName.startsWith("experts")) {
@SuppressWarnings("unchecked")
List<EntityReference> expectedRefs = (List<EntityReference>) expected;
List<EntityReference> actualRefs = JsonUtils.readObjects(actual.toString(), EntityReference.class);
assertEntityReferences(expectedRefs, actualRefs);
} else {
assertCommonFieldChange(fieldName, expected, actual);
}
}
}

View File

@ -11,6 +11,7 @@ import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.http.client.HttpResponseException;
import org.junit.jupiter.api.TestInfo;
import org.openmetadata.schema.api.domains.CreateDomain;
import org.openmetadata.schema.api.domains.CreateDomain.DomainType;
import org.openmetadata.schema.entity.domains.Domain;
@ -24,6 +25,13 @@ public class DomainResourceTest extends EntityResourceTest<Domain, CreateDomain>
public DomainResourceTest() {
super(Entity.DOMAIN, Domain.class, DomainList.class, "domains", DomainResource.FIELDS);
supportsFieldsQueryParam = false; // TODO
supportsEmptyDescription = false;
}
public void setupDomains(TestInfo test) throws IOException {
DOMAIN = createEntity(createRequest(test), ADMIN_AUTH_HEADERS);
SUB_DOMAIN =
createEntity(createRequest("sub-domain").withParent(DOMAIN.getFullyQualifiedName()), ADMIN_AUTH_HEADERS);
}
@Override
@ -31,6 +39,7 @@ public class DomainResourceTest extends EntityResourceTest<Domain, CreateDomain>
return new CreateDomain()
.withName(name)
.withDomainType(DomainType.AGGREGATE)
.withDescription("name")
.withExperts(listOf(USER1.getFullyQualifiedName()));
}

View File

@ -59,7 +59,6 @@ public class EventSubscriptionResourceTest extends EntityResourceTest<EventSubsc
supportedNameCharacters = supportedNameCharacters.replace(" ", ""); // Space not supported
supportsSoftDelete = false;
supportsFieldsQueryParam = false;
supportsEmptyDescription = true;
}
@Test

View File

@ -91,12 +91,11 @@ public class GlossaryResourceTest extends EntityResourceTest<Glossary, CreateGlo
}
public void setupGlossaries() throws IOException {
GlossaryResourceTest glossaryResourceTest = new GlossaryResourceTest();
CreateGlossary createGlossary = glossaryResourceTest.createRequest("g1", "", "", null);
GLOSSARY1 = glossaryResourceTest.createEntity(createGlossary, ADMIN_AUTH_HEADERS);
CreateGlossary createGlossary = createRequest("g1", "", "", null);
GLOSSARY1 = createEntity(createGlossary, ADMIN_AUTH_HEADERS);
createGlossary = glossaryResourceTest.createRequest("g2", "", "", null);
GLOSSARY2 = glossaryResourceTest.createEntity(createGlossary, ADMIN_AUTH_HEADERS);
createGlossary = createRequest("g2", "", "", null);
GLOSSARY2 = createEntity(createGlossary, ADMIN_AUTH_HEADERS);
GlossaryTermResourceTest glossaryTermResourceTest = new GlossaryTermResourceTest();
CreateGlossaryTerm createGlossaryTerm =
@ -532,11 +531,4 @@ public class GlossaryResourceTest extends EntityResourceTest<Glossary, CreateGlo
assertTagPrefixAbsent(table.getColumns().get(0).getTags(), previousTermFqn);
}
}
private static String quoteName(String name) {
if (name != null && !name.contains("\"")) {
return name.contains(".") ? "\\\"" + name + "\\\"" : name;
}
return name;
}
}

View File

@ -13,8 +13,10 @@
package org.openmetadata.schema;
import java.util.List;
import org.openmetadata.schema.type.EntityReference;
@SuppressWarnings("unchecked")
public interface CreateEntity {
String getName();
@ -30,6 +32,14 @@ public interface CreateEntity {
return null;
}
default String getDomain() {
return null;
}
default List<String> getDataProducts() {
return null;
}
<K extends CreateEntity> K withName(String name);
<K extends CreateEntity> K withDisplayName(String displayName);
@ -43,4 +53,8 @@ public interface CreateEntity {
default <K extends CreateEntity> K withExtension(Object extension) {
return (K) this;
}
default <K extends CreateEntity> K withDomain(String domain) {
return (K) this;
}
}

View File

@ -28,6 +28,7 @@ import org.openmetadata.schema.type.Votes;
import org.openmetadata.schema.utils.EntityInterfaceUtil;
/** Interface to be implemented by all entities to provide a way to access all the common fields. */
@SuppressWarnings("unused")
public interface EntityInterface {
// Lower case entity name to canonical entity name map
Map<String, String> CANONICAL_ENTITY_NAME_MAP = new HashMap<>();
@ -81,6 +82,14 @@ public interface EntityInterface {
return null;
}
default EntityReference getDomain() {
return null;
}
default List<EntityReference> getDataProducts() {
return null;
}
void setId(UUID id);
void setDescription(String description);
@ -115,6 +124,14 @@ public interface EntityInterface {
/* no-op implementation to be overridden */
}
default void setDomain(EntityReference entityReference) {
/* no-op implementation to be overridden */
}
default void setDataProducts(List<EntityReference> dataProducts) {
/* no-op implementation to be overridden */
}
<T extends EntityInterface> T withHref(URI href);
@JsonIgnore

View File

@ -41,6 +41,17 @@
"service": {
"description": "Link to the chart service where this chart is hosted in",
"$ref": "../../type/basic.json#/definitions/fullyQualifiedEntityName"
},
"domain" : {
"description": "Fully qualified name of the domain the Chart belongs to.",
"$ref" : "../../type/basic.json#/definitions/fullyQualifiedEntityName"
},
"dataProducts" : {
"description": "List of fully qualified names of data products this entity is part of.",
"type": "array",
"items" : {
"$ref" : "../../type/basic.json#/definitions/fullyQualifiedEntityName"
}
}
},
"required": ["name", "service"],

View File

@ -71,6 +71,10 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"domain" : {
"description": "Fully qualified name of the domain the Container belongs to.",
"type": "string"
}
},
"required": ["name", "service"],

View File

@ -65,6 +65,17 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"domain" : {
"description": "Fully qualified name of the domain the Dashboard belongs to.",
"$ref" : "../../type/basic.json#/definitions/fullyQualifiedEntityName"
},
"dataProducts" : {
"description": "List of fully qualified names of data products this entity is part of.",
"type": "array",
"items" : {
"$ref" : "../../type/basic.json#/definitions/fullyQualifiedEntityName"
}
}
},
"required": ["name", "service"],

View File

@ -58,6 +58,10 @@
"project": {
"description": "Name of the project / workspace / collection in which the dataModel is contained",
"type": "string"
},
"domain" : {
"description": "Fully qualified name of the domain the Dashboard Data Model belongs to.",
"type": "string"
}
},
"required": ["name", "service", "dataModelType", "columns"],

View File

@ -48,6 +48,10 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"domain" : {
"description": "Fully qualified name of the domain the Database belongs to.",
"type": "string"
}
},
"required": ["name", "service"],

View File

@ -44,6 +44,10 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"domain" : {
"description": "Fully qualified name of the domain the Database Schema belongs to.",
"type": "string"
}
},
"required": [

View File

@ -46,6 +46,10 @@
"description" : "Glossary terms that are direct children in this glossary are mutually exclusive. When mutually exclusive is `true` only one term can be used to label an entity. When mutually exclusive is `false`, multiple terms from this group can be used to label an entity.",
"type" : "boolean",
"default" : "false"
},
"domain" : {
"description": "Fully qualified name of the domain the Glossary belongs to.",
"type": "string"
}
},
"required": ["name", "description"],

View File

@ -75,6 +75,10 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"domain" : {
"description": "Fully qualified name of the domain the MLModel belongs to.",
"type": "string"
}
},
"required": ["name", "algorithm", "service"],

View File

@ -68,6 +68,10 @@
"description": "Scheduler Interval for the pipeline in cron format.",
"type": "string",
"default": null
},
"domain" : {
"description": "Fully qualified name of the domain the Pipeline belongs to.",
"type": "string"
}
},
"required": ["name", "service"],

View File

@ -77,6 +77,17 @@
"sourceUrl": {
"description": "Source URL of table.",
"$ref": "../../type/basic.json#/definitions/sourceUrl"
},
"domain" : {
"description": "Fully qualified name of the domain the Table belongs to.",
"type": "string"
},
"dataProducts" : {
"description": "List of fully qualified names of data products this entity is part of.",
"type": "array",
"items" : {
"$ref" : "../../type/basic.json#/definitions/fullyQualifiedEntityName"
}
}
},
"required": ["name", "columns", "databaseSchema"],

View File

@ -79,6 +79,18 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"domain" : {
"description": "Fully qualified name of the domain the Topic belongs to.",
"type": "string",
"$ref" : "../../type/basic.json#/definitions/fullyQualifiedEntityName"
},
"dataProducts" : {
"description": "List of fully qualified names of data products this entity is part of.",
"type": "array",
"items" : {
"$ref" : "../../type/basic.json#/definitions/fullyQualifiedEntityName"
}
}
},
"required": ["name", "service", "partitions"],

View File

@ -0,0 +1,47 @@
{
"$id": "https://open-metadata.org/schema/entity/domains/createDataProduct.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "createDataProduct",
"description": "Create DataProduct API request",
"type": "object",
"javaType": "org.openmetadata.schema.api.domains.CreateDataProduct",
"javaInterfaces": ["org.openmetadata.schema.CreateEntity"],
"properties": {
"name": {
"description": "A unique name of the DataProduct",
"$ref": "../../type/basic.json#/definitions/entityName"
},
"fullyQualifiedName": {
"description": "FullyQualifiedName of the Domain.",
"$ref": "../../type/basic.json#/definitions/fullyQualifiedEntityName"
},
"displayName": {
"description": "Name used for display purposes. Example 'Customer Churn', 'Sentiment Analysis', etc.",
"type": "string"
},
"description": {
"description": "Description of the DataProduct.",
"$ref": "../../type/basic.json#/definitions/markdown"
},
"owner": {
"description": "Owner of this DataProduct.",
"$ref": "../../type/entityReference.json",
"default": null
},
"domain": {
"description": "Fully qualified name of the Domain the DataProduct belongs to.",
"$ref" : "../../type/basic.json#/definitions/fullyQualifiedEntityName",
"default": null
},
"experts": {
"description": "List of of user/login names of users who are experts in this DataProduct.",
"type" : "array",
"items": {
"type" : "string"
},
"default": null
}
},
"required": ["id", "name", "description", "domain","href"],
"additionalProperties": false
}

View File

@ -45,6 +45,6 @@
"default": null
}
},
"required": ["id", "name", "domainType","href"],
"required": ["id", "name", "description", "domainType","href"],
"additionalProperties": false
}

View File

@ -36,6 +36,10 @@
"owner": {
"description": "Owner of this dashboard service.",
"$ref": "../../type/entityReference.json"
},
"domain" : {
"description": "Fully qualified name of the domain the Dashboard Service belongs to.",
"type": "string"
}
},
"required": ["name", "serviceType", "connection"],

View File

@ -37,6 +37,10 @@
"owner": {
"description": "Owner of this database service.",
"$ref": "../../type/entityReference.json"
},
"domain" : {
"description": "Fully qualified name of the domain the Database Service belongs to.",
"type": "string"
}
},
"required": ["name", "serviceType", "connection"],

View File

@ -37,6 +37,10 @@
"owner": {
"description": "Owner of this messaging service.",
"$ref": "../../type/entityReference.json"
},
"domain" : {
"description": "Fully qualified name of the domain the Messaging Service belongs to.",
"type": "string"
}
},
"required": ["name", "serviceType", "connection"],

View File

@ -37,6 +37,10 @@
"owner": {
"description": "Owner of this mlModel service.",
"$ref": "../../type/entityReference.json"
},
"domain" : {
"description": "Fully qualified name of the domain the MLModel Service belongs to.",
"type": "string"
}
},
"required": ["name", "serviceType", "connection"],

View File

@ -42,6 +42,10 @@
"description": "Scheduler Interval for the pipeline in cron format.",
"type": "string",
"default": null
},
"domain" : {
"description": "Fully qualified name of the domain the Pipeline Service belongs to.",
"type": "string"
}
},
"required": ["name", "serviceType", "connection"],

View File

@ -37,6 +37,10 @@
"owner": {
"description": "Owner of this object store service.",
"$ref": "../../type/entityReference.json"
},
"domain" : {
"description": "Fully qualified name of the domain the Storage Service belongs to.",
"type": "string"
}
},
"required": ["name", "serviceType", "connection"],

View File

@ -80,6 +80,10 @@
"$ref": "../../type/basic.json#/definitions/uuid"
},
"default": null
},
"domain" : {
"description": "Fully qualified name of the domain the Team belongs to.",
"type": "string"
}
},
"required": ["name", "teamType"],

View File

@ -138,6 +138,14 @@
"description": "When `true` indicates the entity has been soft deleted.",
"type": "boolean",
"default": false
},
"domain" : {
"description": "Domain the Chart belongs to. The Chart inherits domain from the dashboard service it belongs to.",
"$ref": "../../type/entityReference.json"
},
"dataProducts" : {
"description": "List of of data products this entity is part of.",
"$ref" : "../../type/entityReferenceList.json#/definitions/entityReferenceList"
}
},
"required": ["id", "name", "service"],

View File

@ -167,6 +167,10 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"domain" : {
"description": "Domain the Container belongs to. When not set, the Container inherits the domain from the storage service it belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": [

View File

@ -126,6 +126,14 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"domain" : {
"description": "Domain the Dashboard belongs to. When not set, the Dashboard inherits the domain from the dashboard service it belongs to.",
"$ref": "../../type/entityReference.json"
},
"dataProducts" : {
"description": "List of of data products this entity is part of.",
"$ref" : "../../type/entityReferenceList.json#/definitions/entityReferenceList"
}
},
"required": ["id", "name", "service"],

View File

@ -137,6 +137,10 @@
"project": {
"description": "Name of the project / workspace / collection in which the dataModel is contained",
"type": "string"
},
"domain" : {
"description": "Domain the Dashboard Data Model belongs to. When not set, the Dashboard model inherits the domain from the dashboard service it belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": [

View File

@ -107,6 +107,10 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"domain" : {
"description": "Domain the Database belongs to. When not set, the Database inherits the domain from the database service it belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["name", "service"],

View File

@ -102,6 +102,10 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"domain" : {
"description": "Domain the Database Schema belongs to. When not set, the Schema inherits the domain from the database it belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["name", "database", "service"],

View File

@ -91,6 +91,10 @@
"description" : "Glossary terms that are direct children in this glossary are mutually exclusive. When mutually exclusive is `true` only one term can be used to label an entity. When mutually exclusive is `false`, multiple terms from this group can be used to label an entity.",
"type" : "boolean",
"default" : "false"
},
"domain" : {
"description": "Domain the Glossary belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["id", "name", "description"],

View File

@ -139,6 +139,10 @@
"description" : "Glossary terms that are children of this term are mutually exclusive. When mutually exclusive is `true` only one term can be used to label an entity from this group. When mutually exclusive is `false`, multiple terms from this group can be used to label an entity.",
"type" : "boolean",
"default" : "false"
},
"domain" : {
"description": "Domain the Glossary Term belongs to. When not set, the Glossary TErm inherits the domain from the Glossary it belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["id", "name", "description", "glossary"],

View File

@ -72,6 +72,10 @@
"description": "When `true` indicates the entity has been soft deleted.",
"type": "boolean",
"default": false
},
"domain" : {
"description": "Domain the Metrics belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["id", "name", "service"],

View File

@ -264,7 +264,12 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"domain" : {
"description": "Domain the MLModel belongs to. When not set, the MLModel inherits the domain from the ML Model Service it belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["id", "name", "algorithm", "service"],
"additionalProperties": false

View File

@ -253,7 +253,12 @@
"description": "Scheduler Interval for the pipeline in cron format.",
"type": "string",
"default": null
},
"domain" : {
"description": "Domain the Pipeline belongs to. When not set, the pipeline inherits the domain from the Pipeline service it belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["id", "name", "service"],
"additionalProperties": false

View File

@ -1006,6 +1006,14 @@
"sourceUrl": {
"description": "Source URL of table.",
"$ref": "../../type/basic.json#/definitions/sourceUrl"
},
"domain" : {
"description": "Domain the table belongs to. When not set, the table inherits the domain from the database schema it belongs to.",
"$ref": "../../type/entityReference.json"
},
"dataProducts" : {
"description": "List of of data products this entity is part of.",
"$ref" : "../../type/entityReferenceList.json#/definitions/entityReferenceList"
}
},
"required": [

View File

@ -152,6 +152,14 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"domain" : {
"description": "Domain the Topic belongs to. When not set, the Topic inherits the domain from the messaging service it belongs to.",
"$ref": "../../type/entityReference.json"
},
"dataProducts" : {
"description": "List of of data products this entity is part of.",
"$ref" : "../../type/entityReferenceList.json#/definitions/entityReferenceList"
}
},
"required": ["id", "name", "partitions", "service"],

View File

@ -0,0 +1,66 @@
{
"$id": "https://open-metadata.org/schema/entity/domains/dataProduct.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DataProduct",
"description": "A `Data Product` or `Data as a Product` is a logical unit that contains all components to process and store domain data for analytical or data-intensive use cases made available to data consumers.",
"type": "object",
"javaType": "org.openmetadata.schema.entity.domains.DataProduct",
"javaInterfaces": ["org.openmetadata.schema.EntityInterface"],
"properties": {
"id": {
"description": "Unique ID of the Data Product",
"$ref": "../../type/basic.json#/definitions/uuid"
},
"name": {
"description": "A unique name of the Data Product",
"$ref": "../../type/basic.json#/definitions/entityName"
},
"fullyQualifiedName": {
"description": "FullyQualifiedName is `domain.dataProductName` or `sub-domain.dataProductName`.",
"$ref": "../../type/basic.json#/definitions/fullyQualifiedEntityName"
},
"displayName": {
"description": "Name used for display purposes. Example 'Marketing', 'Payments', etc.",
"type": "string"
},
"description": {
"description": "Description of the Data Product.",
"$ref": "../../type/basic.json#/definitions/markdown"
},
"version": {
"description": "Metadata version of the entity.",
"$ref": "../../type/entityHistory.json#/definitions/entityVersion"
},
"updatedAt": {
"description": "Last update time corresponding to the new version of the entity in Unix epoch time milliseconds.",
"$ref": "../../type/basic.json#/definitions/timestamp"
},
"updatedBy": {
"description": "User who made the update.",
"type": "string"
},
"href": {
"description": "Link to the resource corresponding to this entity.",
"$ref": "../../type/basic.json#/definitions/href"
},
"owner": {
"description": "Owner of this Data Product.",
"$ref": "../../type/entityReference.json"
},
"experts": {
"description": "List of of users who are experts for this Data Product.",
"$ref": "../../type/entityReferenceList.json#/definitions/entityReferenceList",
"default" : null
},
"domain": {
"description": "Domain or sub-domain to which this Data Product belongs to.",
"$ref": "../../type/entityReference.json"
},
"changeDescription": {
"description": "Change that lead to this version of the entity.",
"$ref": "../../type/entityHistory.json#/definitions/changeDescription"
}
},
"required": ["id", "name", "description", "domain", "href"],
"additionalProperties": false
}

View File

@ -80,6 +80,6 @@
"$ref": "../../type/entityHistory.json#/definitions/changeDescription"
}
},
"required": ["id", "name", "domainType, ","href"],
"required": ["id", "name", "description", "domainType, ","href"],
"additionalProperties": false
}

View File

@ -178,6 +178,10 @@
"description": "When `true` indicates the entity has been soft deleted.",
"type": "boolean",
"default": false
},
"domain" : {
"description": "Domain the Dashboard service belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["id", "name", "serviceType"],

View File

@ -332,6 +332,10 @@
"description": "When `true` indicates the entity has been soft deleted.",
"type": "boolean",
"default": false
},
"domain" : {
"description": "Domain the Database service belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["id", "name", "serviceType"],

View File

@ -139,6 +139,10 @@
"description": "When `true` indicates the entity has been soft deleted.",
"type": "boolean",
"default": false
},
"domain" : {
"description": "Domain the Messaging service belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["id", "name", "serviceType"],

View File

@ -131,6 +131,10 @@
"description": "When `true` indicates the entity has been soft deleted.",
"type": "boolean",
"default": false
},
"domain" : {
"description": "Domain the MLModel service belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["id", "name", "serviceType"],

View File

@ -180,6 +180,10 @@
"description": "When `true` indicates the entity has been soft deleted.",
"type": "boolean",
"default": false
},
"domain" : {
"description": "Domain the Pipeline service belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": [

View File

@ -121,6 +121,10 @@
"description": "When `true` indicates the entity has been soft deleted.",
"type": "boolean",
"default": false
},
"domain" : {
"description": "Domain the Storage service belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["id", "name", "serviceType"],

View File

@ -123,6 +123,10 @@
"policies": {
"description": "Policies that is attached to this team.",
"$ref": "../../type/entityReferenceList.json#/definitions/entityReferenceList"
},
"domain" : {
"description": "Domain the Team belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["id", "name", "href"],

View File

@ -132,6 +132,10 @@
"isEmailVerified": {
"description": "If the User has verified the mail",
"type": "boolean"
},
"domain" : {
"description": "Domain the User belongs to. This is inherited by the team the user belongs to.",
"$ref": "../../type/entityReference.json"
}
},
"additionalProperties": false,