Fixes #8596 Introduce mutually exclusive tags and glossary terms (#8597)

This commit is contained in:
Suresh Srinivas 2022-11-10 16:47:21 -08:00 committed by GitHub
parent d4b2621e9d
commit 86c3ae30f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 95 additions and 152 deletions

View File

@ -1,3 +1,6 @@
--
-- Upgrade changes for 0.13
--
CREATE TABLE IF NOT EXISTS web_analytic_event ( CREATE TABLE IF NOT EXISTS web_analytic_event (
id VARCHAR(36) GENERATED ALWAYS AS (json ->> '$.id') NOT NULL, id VARCHAR(36) GENERATED ALWAYS AS (json ->> '$.id') NOT NULL,
name VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.name') NOT NULL, name VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.name') NOT NULL,
@ -53,6 +56,21 @@ UPDATE pipeline_service_entity
SET json = JSON_REMOVE(json ,'$.connection.config.hostPort', '$.connection.config.numberOfStatus') SET json = JSON_REMOVE(json ,'$.connection.config.hostPort', '$.connection.config.numberOfStatus')
WHERE serviceType = 'Dagster'; WHERE serviceType = 'Dagster';
-- Remove categoryType
UPDATE tag_category
SET json = JSON_REMOVE(json ,'$.categoryType');
-- Set mutuallyExclusive flag
UPDATE tag_category
SET json = JSON_INSERT(json ,'$.mutuallyExclusive', 'false');
UPDATE tag_category
SET json = JSON_INSERT(json ,'$.mutuallyExclusive', 'true')
WHERE name in ('PersonalData', 'PII', 'Tier');
UPDATE tag
SET json = JSON_INSERT(json ,'$.mutuallyExclusive', 'false');
CREATE TABLE IF NOT EXISTS kpi_entity ( CREATE TABLE IF NOT EXISTS kpi_entity (
id VARCHAR(36) GENERATED ALWAYS AS (json ->> '$.id') STORED NOT NULL, id VARCHAR(36) GENERATED ALWAYS AS (json ->> '$.id') STORED NOT NULL,
name VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.name') NOT NULL, name VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.name') NOT NULL,

View File

@ -1,3 +1,6 @@
--
-- Upgrade changes for 0.13
--
CREATE TABLE IF NOT EXISTS web_analytic_event ( CREATE TABLE IF NOT EXISTS web_analytic_event (
id VARCHAR(36) GENERATED ALWAYS AS (json ->> 'id') STORED NOT NULL, id VARCHAR(36) GENERATED ALWAYS AS (json ->> 'id') STORED NOT NULL,
name VARCHAR(256) GENERATED ALWAYS AS (json ->> 'name') STORED NOT NULL, name VARCHAR(256) GENERATED ALWAYS AS (json ->> 'name') STORED NOT NULL,
@ -51,13 +54,28 @@ WHERE fullyQualifiedName in ('PersonalData.Personal', 'PersonalData.SpecialCateg
'Tier.Tier1', 'Tier.Tier2', 'Tier.Tier3', 'Tier.Tier4', 'Tier.Tier5'); 'Tier.Tier1', 'Tier.Tier2', 'Tier.Tier3', 'Tier.Tier4', 'Tier.Tier5');
UPDATE pipeline_service_entity UPDATE pipeline_service_entity
SET json = jsonb_set(json::jsonb,'{connection,config}',json::jsonb #>'{connection,config}' || jsonb_build_object('configSource',jsonb_build_object('hostPort',json #>'{connection,config,hostPort}')), true) SET json = JSONB_SET(json::jsonb,'{connection,config}',json::jsonb #>'{connection,config}' || jsonb_build_object('configSource',jsonb_build_object('hostPort',json #>'{connection,config,hostPort}')), true)
where servicetype = 'Dagster'; where servicetype = 'Dagster';
UPDATE pipeline_service_entity UPDATE pipeline_service_entity
SET json = json::jsonb #- '{connection,config,hostPort}' #- '{connection,config,numberOfStatus}' SET json = json::jsonb #- '{connection,config,hostPort}' #- '{connection,config,numberOfStatus}'
where servicetype = 'Dagster'; where servicetype = 'Dagster';
-- Remove categoryType
UPDATE tag_category
SET json = json::jsonb #- '{categoryType}';
-- set mutuallyExclusive flag
UPDATE tag_category
SET json = jsonb_set(json, '{mutuallyExclusive}', 'false'::jsonb, true);
UPDATE tag_category
SET json = jsonb_set(json, '{mutuallyExclusive}', 'true'::jsonb, true)
WHERE name in ('PersonalData', 'PII', 'Tier');
UPDATE tag
SET json = jsonb_set(json, '{mutuallyExclusive}', 'false'::jsonb, true);
CREATE TABLE IF NOT EXISTS kpi_entity ( CREATE TABLE IF NOT EXISTS kpi_entity (
id VARCHAR(36) GENERATED ALWAYS AS (json ->> 'id') STORED NOT NULL, id VARCHAR(36) GENERATED ALWAYS AS (json ->> 'id') STORED NOT NULL,
name VARCHAR(256) GENERATED ALWAYS AS (json ->> 'name') STORED NOT NULL, name VARCHAR(256) GENERATED ALWAYS AS (json ->> 'name') STORED NOT NULL,

View File

@ -194,7 +194,6 @@ class TableauSource(DashboardServiceSource):
category_name=CreateTagCategoryRequest( category_name=CreateTagCategoryRequest(
name=TABLEAU_TAG_CATEGORY, name=TABLEAU_TAG_CATEGORY,
description="Tags associates with tableau entities", description="Tags associates with tableau entities",
categoryType="Descriptive",
), ),
category_details=CreateTagRequest(name=tag, description="Tableau Tag"), category_details=CreateTagRequest(name=tag, description="Tableau Tag"),
) )

View File

@ -159,7 +159,6 @@ class BigquerySource(CommonDbSourceService):
category_name=CreateTagCategoryRequest( category_name=CreateTagCategoryRequest(
name=self.service_connection.tagCategoryName, name=self.service_connection.tagCategoryName,
description="", description="",
categoryType="Classification",
), ),
category_details=CreateTagRequest( category_details=CreateTagRequest(
name=tag.display_name, description="Bigquery Policy Tag" name=tag.display_name, description="Bigquery Policy Tag"

View File

@ -407,7 +407,6 @@ class DatabaseServiceSource(
category_name=CreateTagCategoryRequest( category_name=CreateTagCategoryRequest(
name="DBTTags", name="DBTTags",
description="", description="",
categoryType="Classification",
), ),
category_details=CreateTagRequest( category_details=CreateTagRequest(
name=tag_label.tagFQN.__root__.split(".")[1], name=tag_label.tagFQN.__root__.split(".")[1],

View File

@ -308,7 +308,6 @@ class SnowflakeSource(CommonDbSourceService):
category_name=CreateTagCategoryRequest( category_name=CreateTagCategoryRequest(
name=row[0], name=row[0],
description="SNOWFLAKE TAG NAME", description="SNOWFLAKE TAG NAME",
categoryType="Descriptive",
), ),
category_details=CreateTagRequest( category_details=CreateTagRequest(
name=row[1], description="SNOWFLAKE TAG VALUE" name=row[1], description="SNOWFLAKE TAG VALUE"

View File

@ -251,7 +251,6 @@ class AmundsenSource(Source[Entity]):
category_name=CreateTagCategoryRequest( category_name=CreateTagCategoryRequest(
name=AMUNDSEN_TAG_CATEGORY, name=AMUNDSEN_TAG_CATEGORY,
description="Tags associates with amundsen entities", description="Tags associates with amundsen entities",
categoryType="Descriptive",
), ),
category_details=CreateTagRequest( category_details=CreateTagRequest(
name=tag, description="Amundsen Table Tag" name=tag, description="Amundsen Table Tag"
@ -351,7 +350,6 @@ class AmundsenSource(Source[Entity]):
category_name=CreateTagCategoryRequest( category_name=CreateTagCategoryRequest(
name=AMUNDSEN_TAG_CATEGORY, name=AMUNDSEN_TAG_CATEGORY,
description="Tags associates with amundsen entities", description="Tags associates with amundsen entities",
categoryType="Descriptive",
), ),
category_details=CreateTagRequest( category_details=CreateTagRequest(
name=AMUNDSEN_TABLE_TAG, description="Amundsen Table Tag" name=AMUNDSEN_TABLE_TAG, description="Amundsen Table Tag"
@ -362,7 +360,6 @@ class AmundsenSource(Source[Entity]):
category_name=CreateTagCategoryRequest( category_name=CreateTagCategoryRequest(
name=AMUNDSEN_TAG_CATEGORY, name=AMUNDSEN_TAG_CATEGORY,
description="Tags associates with amundsen entities", description="Tags associates with amundsen entities",
categoryType="Descriptive",
), ),
category_details=CreateTagRequest( category_details=CreateTagRequest(
name=table["cluster"], description="Amundsen Cluster Tag" name=table["cluster"], description="Amundsen Cluster Tag"

View File

@ -172,7 +172,6 @@ class DagsterSource(PipelineServiceSource):
category_name=CreateTagCategoryRequest( category_name=CreateTagCategoryRequest(
name="DagsterTags", name="DagsterTags",
description="Tags associated with dagster", description="Tags associated with dagster",
categoryType="Descriptive",
), ),
category_details=CreateTagRequest( category_details=CreateTagRequest(
name=self.context.repository_name, description="Dagster Tag" name=self.context.repository_name, description="Dagster Tag"

View File

@ -42,7 +42,7 @@ class OMetaTagMixinPost(TestCase):
"""Test POST category Mixin method""" """Test POST category Mixin method"""
tag_category = CreateTagCategoryRequest( tag_category = CreateTagCategoryRequest(
categoryType="Descriptive", description="test tag", name=CATEGORY_NAME description="test tag", name=CATEGORY_NAME
) )
self.metadata.create_tag_category(tag_category) self.metadata.create_tag_category(tag_category)
@ -144,7 +144,7 @@ class OMetaTagMixinPut(TestCase):
rand_name = random.getrandbits(64) rand_name = random.getrandbits(64)
updated_tag_category = CreateTagCategoryRequest( updated_tag_category = CreateTagCategoryRequest(
categoryType="Descriptive", description="test tag", name=f"{rand_name}" description="test tag", name=f"{rand_name}"
) )
self.metadata.create_or_update_tag_category(CATEGORY_NAME, updated_tag_category) self.metadata.create_or_update_tag_category(CATEGORY_NAME, updated_tag_category)

View File

@ -12,7 +12,6 @@ slug: /main-concepts/metadata-standard/schemas/api/tags/createtagcategory
- **`name`**: Refer to *../../entity/tags/tagCategory.json#/definitions/tagName*. - **`name`**: Refer to *../../entity/tags/tagCategory.json#/definitions/tagName*.
- **`displayName`** *(string)*: Display Name that identifies this tag category. - **`displayName`** *(string)*: Display Name that identifies this tag category.
- **`description`**: Description of the tag category. Refer to *../../type/basic.json#/definitions/markdown*. - **`description`**: Description of the tag category. Refer to *../../type/basic.json#/definitions/markdown*.
- **`categoryType`**: Refer to *../../entity/tags/tagCategory.json#/definitions/tagCategoryType*.
Documentation file automatically generated at 2022-07-14 10:51:34.749986. Documentation file automatically generated at 2022-07-14 10:51:34.749986.

View File

@ -17,7 +17,6 @@ slug: /main-concepts/metadata-standard/schemas/entity/tags/tagcategory
- **`version`**: Metadata version of the entity. Refer to *../../type/entityHistory.json#/definitions/entityVersion*. - **`version`**: Metadata version of the entity. Refer to *../../type/entityHistory.json#/definitions/entityVersion*.
- **`updatedAt`**: Last update time corresponding to the new version of the entity in Unix epoch time milliseconds. Refer to *../../type/basic.json#/definitions/timestamp*. - **`updatedAt`**: Last update time corresponding to the new version of the entity in Unix epoch time milliseconds. Refer to *../../type/basic.json#/definitions/timestamp*.
- **`updatedBy`** *(string)*: User who made the update. - **`updatedBy`** *(string)*: User who made the update.
- **`categoryType`**: Refer to *#/definitions/tagCategoryType*.
- **`href`**: Link to the resource corresponding to the tag category. Refer to *../../type/basic.json#/definitions/href*. - **`href`**: Link to the resource corresponding to the tag category. Refer to *../../type/basic.json#/definitions/href*.
- **`usageCount`** *(integer)*: Count of how many times the tags from this tag category are used. - **`usageCount`** *(integer)*: Count of how many times the tags from this tag category are used.
- **`children`** *(array)*: Tags under this category. - **`children`** *(array)*: Tags under this category.
@ -27,7 +26,6 @@ slug: /main-concepts/metadata-standard/schemas/entity/tags/tagcategory
## Definitions ## Definitions
- **`tagName`** *(string)*: Name of the tag. - **`tagName`** *(string)*: Name of the tag.
- **`tagCategoryType`** *(string)*: Type of tag category. Must be one of: `['Descriptive', 'Classification']`.
- **`tag`**: Cannot contain additional properties. - **`tag`**: Cannot contain additional properties.
- **`id`**: Unique identifier of this entity instance. Refer to *../../type/basic.json#/definitions/uuid*. - **`id`**: Unique identifier of this entity instance. Refer to *../../type/basic.json#/definitions/uuid*.
- **`name`**: Name of the tag. Refer to *#/definitions/tagName*. - **`name`**: Name of the tag. Refer to *#/definitions/tagName*.

View File

@ -135,7 +135,8 @@ public class TagCategoryRepository extends EntityRepository<TagCategory> {
@Override @Override
public void entitySpecificUpdate() throws IOException { public void entitySpecificUpdate() throws IOException {
// TODO handle name change // TODO handle name change
recordChange("categoryType", original.getCategoryType(), updated.getCategoryType()); // TODO mutuallyExclusive from false to true?
recordChange("mutuallyExclusive", original.getMutuallyExclusive(), updated.getMutuallyExclusive());
updateName(original, updated); updateName(original, updated);
} }

View File

@ -144,6 +144,9 @@ public class TagRepository extends EntityRepository<Tag> {
@Override @Override
public void entitySpecificUpdate() throws IOException { public void entitySpecificUpdate() throws IOException {
// TODO mutuallyExclusive from false to true?
recordChange("mutuallyExclusive", original.getMutuallyExclusive(), updated.getMutuallyExclusive());
// TODO check the below
updateName(original, updated); updateName(original, updated);
} }

View File

@ -551,7 +551,7 @@ public class TagResource {
.withId(UUID.randomUUID()) .withId(UUID.randomUUID())
.withName(create.getName()) .withName(create.getName())
.withFullyQualifiedName(create.getName()) .withFullyQualifiedName(create.getName())
.withCategoryType(create.getCategoryType()) .withMutuallyExclusive(create.getMutuallyExclusive())
.withDescription(create.getDescription()) .withDescription(create.getDescription())
.withUpdatedBy(securityContext.getUserPrincipal().getName()) .withUpdatedBy(securityContext.getUserPrincipal().getName())
.withUpdatedAt(System.currentTimeMillis()); .withUpdatedAt(System.currentTimeMillis());
@ -564,6 +564,7 @@ public class TagResource {
.withFullyQualifiedName(FullyQualifiedName.add(parentFQN, create.getName())) .withFullyQualifiedName(FullyQualifiedName.add(parentFQN, create.getName()))
.withDescription(create.getDescription()) .withDescription(create.getDescription())
.withUpdatedBy(securityContext.getUserPrincipal().getName()) .withUpdatedBy(securityContext.getUserPrincipal().getName())
.withUpdatedAt(System.currentTimeMillis()); .withUpdatedAt(System.currentTimeMillis())
.withMutuallyExclusive(create.getMutuallyExclusive());
} }
} }

View File

@ -55,9 +55,6 @@
"href": { "href": {
"type": "text" "type": "text"
}, },
"categoryType": {
"type": "text"
},
"deleted": { "deleted": {
"type": "text" "type": "text"
}, },

View File

@ -1,8 +1,8 @@
{ {
"name": "PersonalData", "name": "PersonalData",
"categoryType": "Classification",
"description": "Tags related classifying **Personal data** as defined by **GDPR.**<br/><br/>_Note to Legal - This tag category is provided as a starting point. Please review and update the tags based on your company policy. Also, add a reference to your GDPR policy document in this description._", "description": "Tags related classifying **Personal data** as defined by **GDPR.**<br/><br/>_Note to Legal - This tag category is provided as a starting point. Please review and update the tags based on your company policy. Also, add a reference to your GDPR policy document in this description._",
"provider": "system", "provider": "system",
"mutuallyExclusive": "true",
"children": [ "children": [
{ {
"name": "Personal", "name": "Personal",

View File

@ -1,8 +1,8 @@
{ {
"name": "PII", "name": "PII",
"categoryType": "Classification",
"description": "Personally Identifiable Information information that, when used alone or with other relevant data, can identify an individual.<br/><br/>_Note to Legal - This tag category is provided as a starting point. Please review and update the tags based on your company policy. Also, add a reference to your PII policy document in this description._", "description": "Personally Identifiable Information information that, when used alone or with other relevant data, can identify an individual.<br/><br/>_Note to Legal - This tag category is provided as a starting point. Please review and update the tags based on your company policy. Also, add a reference to your PII policy document in this description._",
"provider": "system", "provider": "system",
"mutuallyExclusive": "true",
"children": [ "children": [
{ {
"name": "None", "name": "None",

View File

@ -1,8 +1,8 @@
{ {
"name": "Tier", "name": "Tier",
"categoryType": "Descriptive",
"description": "Tags related to tiering of the data. Tiers capture the business importance of data. When a data asset is tagged with `Tier` tag, all the upstream data assets used for producing it will also be labeled with the same tag. This will help upstream data asset owners to understand the critical purposes their data is being used.", "description": "Tags related to tiering of the data. Tiers capture the business importance of data. When a data asset is tagged with `Tier` tag, all the upstream data assets used for producing it will also be labeled with the same tag. This will help upstream data asset owners to understand the critical purposes their data is being used.",
"provider": "system", "provider": "system",
"mutuallyExclusive": "true",
"children": [ "children": [
{ {
"name": "Tier1", "name": "Tier1",

View File

@ -45,7 +45,6 @@ import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.openmetadata.schema.api.tags.CreateTag; import org.openmetadata.schema.api.tags.CreateTag;
import org.openmetadata.schema.api.tags.CreateTagCategory; import org.openmetadata.schema.api.tags.CreateTagCategory;
import org.openmetadata.schema.api.tags.CreateTagCategory.TagCategoryType;
import org.openmetadata.schema.entity.tags.Tag; import org.openmetadata.schema.entity.tags.Tag;
import org.openmetadata.schema.type.TagCategory; import org.openmetadata.schema.type.TagCategory;
import org.openmetadata.schema.type.TagLabel; import org.openmetadata.schema.type.TagLabel;
@ -80,11 +79,7 @@ public class TagResourceTest extends OpenMetadataApplicationTest {
EntityResourceTest.TIER1_TAG_LABEL = getTagLabel(FullyQualifiedName.add("Tier", "Tier1")); EntityResourceTest.TIER1_TAG_LABEL = getTagLabel(FullyQualifiedName.add("Tier", "Tier1"));
EntityResourceTest.TIER2_TAG_LABEL = getTagLabel(FullyQualifiedName.add("Tier", "Tier2")); EntityResourceTest.TIER2_TAG_LABEL = getTagLabel(FullyQualifiedName.add("Tier", "Tier2"));
CreateTagCategory create = CreateTagCategory create = new CreateTagCategory().withName("User").withDescription("description");
new CreateTagCategory()
.withName("User")
.withDescription("description")
.withCategoryType(TagCategoryType.Descriptive);
USER_TAG_CATEGORY = tagResourceTest.createAndCheckCategory(create, ADMIN_AUTH_HEADERS); USER_TAG_CATEGORY = tagResourceTest.createAndCheckCategory(create, ADMIN_AUTH_HEADERS);
List<String> associatedTags = new ArrayList<>(); List<String> associatedTags = new ArrayList<>();
@ -156,10 +151,7 @@ public class TagResourceTest extends OpenMetadataApplicationTest {
void post_alreadyExistingTagCategory_4xx() { void post_alreadyExistingTagCategory_4xx() {
// POST .../tags/{allReadyExistingCategory} returns 4xx // POST .../tags/{allReadyExistingCategory} returns 4xx
CreateTagCategory create = CreateTagCategory create =
new CreateTagCategory() new CreateTagCategory().withName(USER_TAG_CATEGORY.getName()).withDescription("description");
.withName(USER_TAG_CATEGORY.getName())
.withDescription("description")
.withCategoryType(TagCategoryType.Descriptive);
assertResponse(() -> createAndCheckCategory(create, ADMIN_AUTH_HEADERS), CONFLICT, "Entity already exists"); assertResponse(() -> createAndCheckCategory(create, ADMIN_AUTH_HEADERS), CONFLICT, "Entity already exists");
} }
@ -167,11 +159,7 @@ public class TagResourceTest extends OpenMetadataApplicationTest {
void post_delete_validTagCategory_as_admin_201(TestInfo test) throws HttpResponseException { void post_delete_validTagCategory_as_admin_201(TestInfo test) throws HttpResponseException {
// POST .../tags/{newCategory} returns 201 // POST .../tags/{newCategory} returns 201
String categoryName = test.getDisplayName().substring(0, 20); // Form a unique category name based on the test name String categoryName = test.getDisplayName().substring(0, 20); // Form a unique category name based on the test name
CreateTagCategory create = CreateTagCategory create = new CreateTagCategory().withName(categoryName).withDescription("description");
new CreateTagCategory()
.withName(categoryName)
.withDescription("description")
.withCategoryType(TagCategoryType.Descriptive);
TagCategory category = createAndCheckCategory(create, ADMIN_AUTH_HEADERS); TagCategory category = createAndCheckCategory(create, ADMIN_AUTH_HEADERS);
assertEquals(0, category.getChildren().size()); assertEquals(0, category.getChildren().size());
@ -198,11 +186,7 @@ public class TagResourceTest extends OpenMetadataApplicationTest {
void post_delete_validTags_as_admin_201(TestInfo test) throws HttpResponseException { void post_delete_validTags_as_admin_201(TestInfo test) throws HttpResponseException {
// Create tag category // Create tag category
String categoryName = test.getDisplayName().substring(0, 20); String categoryName = test.getDisplayName().substring(0, 20);
CreateTagCategory create = CreateTagCategory create = new CreateTagCategory().withName(categoryName).withDescription("description");
new CreateTagCategory()
.withName(categoryName)
.withDescription("description")
.withCategoryType(TagCategoryType.Descriptive);
TagCategory category = createAndCheckCategory(create, ADMIN_AUTH_HEADERS); TagCategory category = createAndCheckCategory(create, ADMIN_AUTH_HEADERS);
assertEquals(0, category.getChildren().size()); assertEquals(0, category.getChildren().size());
@ -272,21 +256,12 @@ public class TagResourceTest extends OpenMetadataApplicationTest {
String categoryName = test.getDisplayName().substring(0, 10); // Form a unique category name based on the test name String categoryName = test.getDisplayName().substring(0, 10); // Form a unique category name based on the test name
// Missing description // Missing description
CreateTagCategory create = CreateTagCategory create = new CreateTagCategory().withName(categoryName).withDescription(null);
new CreateTagCategory()
.withName(categoryName)
.withDescription(null)
.withCategoryType(TagCategoryType.Descriptive);
assertResponseContains( assertResponseContains(
() -> createAndCheckCategory(create, ADMIN_AUTH_HEADERS), BAD_REQUEST, "description must not be null"); () -> createAndCheckCategory(create, ADMIN_AUTH_HEADERS), BAD_REQUEST, "description must not be null");
// Missing category
create.withDescription("description").withCategoryType(null);
assertResponseContains(
() -> createAndCheckCategory(create, ADMIN_AUTH_HEADERS), BAD_REQUEST, "categoryType must not be null");
// Long name // Long name
create.withName(TestUtils.LONG_ENTITY_NAME).withCategoryType(TagCategoryType.Descriptive); create.withName(TestUtils.LONG_ENTITY_NAME);
assertResponseContains( assertResponseContains(
() -> createAndCheckCategory(create, ADMIN_AUTH_HEADERS), BAD_REQUEST, "name size must be between 2 and 64"); () -> createAndCheckCategory(create, ADMIN_AUTH_HEADERS), BAD_REQUEST, "name size must be between 2 and 64");
} }
@ -354,31 +329,11 @@ public class TagResourceTest extends OpenMetadataApplicationTest {
entityNotFound(Entity.TAG, FullyQualifiedName.build(USER_TAG_CATEGORY.getName(), nonExistent))); entityNotFound(Entity.TAG, FullyQualifiedName.build(USER_TAG_CATEGORY.getName(), nonExistent)));
} }
@Test
void put_tagCategory_200(TestInfo test) {
// Update an existing tag category
String newCategoryName = test.getDisplayName().substring(0, 10);
CreateTagCategory create =
new CreateTagCategory()
.withName(newCategoryName)
.withDescription("updatedDescription")
.withCategoryType(TagCategoryType.Descriptive);
updateCategory(USER_TAG_CATEGORY.getName(), create, ADMIN_AUTH_HEADERS);
// Revert tag category back
create.withName(USER_TAG_CATEGORY.getName()).withCategoryType(TagCategoryType.Classification);
updateCategory(newCategoryName, create, ADMIN_AUTH_HEADERS);
}
@Test @Test
void put_tagCategoryInvalidRequest_400(TestInfo test) { void put_tagCategoryInvalidRequest_400(TestInfo test) {
// Primary tag with missing description // Primary tag with missing description
String newCategoryName = test.getDisplayName().substring(0, 10); String newCategoryName = test.getDisplayName().substring(0, 10);
CreateTagCategory create = CreateTagCategory create = new CreateTagCategory().withName(newCategoryName).withDescription(null);
new CreateTagCategory()
.withName(newCategoryName)
.withDescription(null)
.withCategoryType(TagCategoryType.Descriptive);
assertResponseContains( assertResponseContains(
() -> updateCategory(USER_TAG_CATEGORY.getName(), create, ADMIN_AUTH_HEADERS), () -> updateCategory(USER_TAG_CATEGORY.getName(), create, ADMIN_AUTH_HEADERS),
BAD_REQUEST, BAD_REQUEST,
@ -448,12 +403,11 @@ public class TagResourceTest extends OpenMetadataApplicationTest {
String updatedBy = getPrincipalName(authHeaders); String updatedBy = getPrincipalName(authHeaders);
WebTarget target = getResource("tags"); WebTarget target = getResource("tags");
TagCategory tagCategory = TestUtils.post(target, create, TagCategory.class, authHeaders); TagCategory tagCategory = TestUtils.post(target, create, TagCategory.class, authHeaders);
TagCategory category = TagCategory category = validate(tagCategory, create.getName(), create.getDescription(), updatedBy);
validate(tagCategory, create.getCategoryType(), create.getName(), create.getDescription(), updatedBy);
assertEquals(0.1, category.getVersion()); assertEquals(0.1, category.getVersion());
TagCategory getCategory = getCategory(create.getName(), authHeaders); TagCategory getCategory = getCategory(create.getName(), authHeaders);
validate(getCategory, create.getCategoryType(), create.getName(), create.getDescription(), updatedBy); validate(getCategory, create.getName(), create.getDescription(), updatedBy);
return category; return category;
} }
@ -504,11 +458,11 @@ public class TagResourceTest extends OpenMetadataApplicationTest {
// Ensure PUT returns the updated tag category // Ensure PUT returns the updated tag category
TagCategory tagCategory = TestUtils.put(target, update, TagCategory.class, Status.OK, authHeaders); TagCategory tagCategory = TestUtils.put(target, update, TagCategory.class, Status.OK, authHeaders);
validate(tagCategory, update.getCategoryType(), update.getName(), update.getDescription(), updatedBy); validate(tagCategory, update.getName(), update.getDescription(), updatedBy);
// Ensure GET returns the updated tag category // Ensure GET returns the updated tag category
TagCategory getCategory = getCategory(update.getName(), authHeaders); TagCategory getCategory = getCategory(update.getName(), authHeaders);
validate(getCategory, update.getCategoryType(), update.getName(), update.getDescription(), updatedBy); validate(getCategory, update.getName(), update.getDescription(), updatedBy);
} }
private void updatePrimaryTag(String category, String primaryTag, CreateTag update, Map<String, String> authHeaders) private void updatePrimaryTag(String category, String primaryTag, CreateTag update, Map<String, String> authHeaders)
@ -587,14 +541,9 @@ public class TagResourceTest extends OpenMetadataApplicationTest {
} }
private TagCategory validate( private TagCategory validate(
TagCategory actual, TagCategory actual, String expectedName, String expectedDescription, String expectedUpdatedBy) {
TagCategoryType expectedCategoryType,
String expectedName,
String expectedDescription,
String expectedUpdatedBy) {
validate(actual); validate(actual);
assertEquals(expectedName, actual.getName()); assertEquals(expectedName, actual.getName());
assertEquals(expectedCategoryType, actual.getCategoryType());
assertEquals(expectedDescription, actual.getDescription()); assertEquals(expectedDescription, actual.getDescription());
assertEquals(expectedUpdatedBy, actual.getUpdatedBy()); assertEquals(expectedUpdatedBy, actual.getUpdatedBy());
return actual; return actual;

View File

@ -25,6 +25,11 @@
"items": { "items": {
"type": "string" "type": "string"
} }
},
"mutuallyExclusive" : {
"description" : "Tags under this category are mutually exclusive. When mutually exclusive is `true` the tags from this category are used to **classify** an entity. An entity can only be in one class - example, it can only be either `tier1` or `tier2` and not both. When mutually exclusive is `false`, the tags from this category are used to **categorize** an entity. An entity can be in multiple categories simultaneously - example a customer can be `newCustomer` and `atRisk` simultaneously. ** Note when a tag category is marked mutually exclusive, all the tag groups under it are also mutually exclusive.",
"type" : "boolean",
"default" : "false"
} }
}, },
"required": ["name", "description"], "required": ["name", "description"],

View File

@ -19,10 +19,12 @@
"description": "Description of the tag category", "description": "Description of the tag category",
"$ref": "../../type/basic.json#/definitions/markdown" "$ref": "../../type/basic.json#/definitions/markdown"
}, },
"categoryType": { "mutuallyExclusive" : {
"$ref": "../../entity/tags/tagCategory.json#/definitions/tagCategoryType" "description" : "Tags under this category are mutually exclusive. When mutually exclusive is `true` the tags from this category are used to **classify** an entity. An entity can only be in one class - example, it can only be either `tier1` or `tier2` and not both. When mutually exclusive is `false`, the tags from this category are used to **categorize** an entity. An entity can be in multiple categories simultaneously - example a customer can be `newCustomer` and `atRisk` simultaneously. ** Note when a tag category is marked mutually exclusive, all the tag groups under it are also mutually exclusive.",
"type" : "boolean",
"default" : "false"
} }
}, },
"required": ["name", "description", "categoryType"], "required": ["name", "description"],
"additionalProperties": false "additionalProperties": false
} }

View File

@ -18,7 +18,7 @@
"$ref": "../../type/basic.json#/definitions/uuid" "$ref": "../../type/basic.json#/definitions/uuid"
}, },
"name": { "name": {
"description": "Preferred name for the glossary term.", "description": "Name of the glossary",
"type": "string", "type": "string",
"$ref": "#/definitions/name" "$ref": "#/definitions/name"
}, },
@ -88,6 +88,11 @@
"disabled" : { "disabled" : {
"description": "System glossary can't be deleted. Use this flag to disable them.", "description": "System glossary can't be deleted. Use this flag to disable them.",
"type": "boolean" "type": "boolean"
},
"mutuallyExclusive" : {
"description" : "Glossary terms under 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. When Glossary is mutually exclusive, all the glossary term groups under it are also mutually exclusive.",
"type" : "boolean",
"default" : "false"
} }
}, },
"required": ["id", "name", "description"], "required": ["id", "name", "description"],

View File

@ -134,6 +134,11 @@
"disabled" : { "disabled" : {
"description": "System glossary can't be deleted. Use this flag to disable them.", "description": "System glossary can't be deleted. Use this flag to disable them.",
"type": "boolean" "type": "boolean"
},
"mutuallyExclusive" : {
"description" : "Glossary terms under this group 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. When a group is mutually exclusive, all the glossary term groups under it are also mutually exclusive.",
"type" : "boolean",
"default" : "false"
} }
}, },
"required": ["id", "name", "description", "glossary"], "required": ["id", "name", "description", "glossary"],

View File

@ -13,21 +13,6 @@
"minLength": 2, "minLength": 2,
"maxLength": 64 "maxLength": 64
}, },
"tagCategoryType": {
"description": "Type of tag category.",
"type": "string",
"enum": ["Descriptive", "Classification"],
"javaEnums": [
{
"name": "Descriptive",
"description": "Tag category used for describing an entity. Example - column is of of type User.Address."
},
{
"name": "Classification",
"description": "Tag category used for classifying an entity. Example - column is of of type PII.sensitive."
}
]
},
"tag": { "tag": {
"javaType": "org.openmetadata.schema.entity.tags.Tag", "javaType": "org.openmetadata.schema.entity.tags.Tag",
"javaInterfaces": ["org.openmetadata.schema.EntityInterface"], "javaInterfaces": ["org.openmetadata.schema.EntityInterface"],
@ -99,6 +84,11 @@
"disabled" : { "disabled" : {
"description": "System tags can't be deleted. Use this flag to disable them.", "description": "System tags can't be deleted. Use this flag to disable them.",
"type": "boolean" "type": "boolean"
},
"mutuallyExclusive" : {
"description" : "Children tags under this group are mutually exclusive. When mutually exclusive is `true` the tags from this group are used to **classify** an entity. An entity can only be in one class - example, it can only be either `tier1` or `tier2` and not both. When mutually exclusive is `false`, the tags from this group are used to **categorize** an entity. An entity can be in multiple categories simultaneously - example a customer can be `newCustomer` and `atRisk` simultaneously.",
"type" : "boolean",
"default" : "false"
} }
}, },
"required": ["name", "description"], "required": ["name", "description"],
@ -137,9 +127,6 @@
"description": "User who made the update.", "description": "User who made the update.",
"type": "string" "type": "string"
}, },
"categoryType": {
"$ref": "#/definitions/tagCategoryType"
},
"href": { "href": {
"description": "Link to the resource corresponding to the tag category.", "description": "Link to the resource corresponding to the tag category.",
"$ref": "../../type/basic.json#/definitions/href" "$ref": "../../type/basic.json#/definitions/href"
@ -170,8 +157,13 @@
"disabled" : { "disabled" : {
"description": "System tag categories can't be deleted. Use this flag to disable them.", "description": "System tag categories can't be deleted. Use this flag to disable them.",
"type": "boolean" "type": "boolean"
},
"mutuallyExclusive" : {
"description" : "Tags under this category are mutually exclusive. When mutually exclusive is `true` the tags from this category are used to **classify** an entity. An entity can only be in one class - example, it can only be either `tier1` or `tier2` and not both. When mutually exclusive is `false`, the tags from this category are used to **categorize** an entity. An entity can be in multiple categories simultaneously - example a customer can be `newCustomer` and `atRisk` simultaneously. ** Note when a tag category is marked mutually exclusive, all the tag groups under it are also mutually exclusive.",
"type" : "boolean",
"default" : "false"
} }
}, },
"required": ["name", "description", "categoryType"], "required": ["name", "description"],
"additionalProperties": false "additionalProperties": false
} }

View File

@ -131,7 +131,6 @@ const mockTagList = [
id: 'tagCatId1', id: 'tagCatId1',
name: 'TagCat1', name: 'TagCat1',
description: '', description: '',
categoryType: 'Classification',
children: [ children: [
{ {
id: 'tagId1', id: 'tagId1',
@ -147,7 +146,6 @@ const mockTagList = [
id: 'tagCatId2', id: 'tagCatId2',
name: 'TagCat2', name: 'TagCat2',
description: '', description: '',
categoryType: 'Classification',
children: [ children: [
{ {
id: 'tagId2', id: 'tagId2',

View File

@ -122,7 +122,6 @@ const mockTagList = [
id: 'tagCatId1', id: 'tagCatId1',
name: 'TagCat1', name: 'TagCat1',
description: '', description: '',
categoryType: 'Classification',
children: [ children: [
{ {
id: 'tagId1', id: 'tagId1',
@ -138,7 +137,6 @@ const mockTagList = [
id: 'tagCatId2', id: 'tagCatId2',
name: 'TagCat2', name: 'TagCat2',
description: '', description: '',
categoryType: 'Classification',
children: [ children: [
{ {
id: 'tagId2', id: 'tagId2',

View File

@ -23,7 +23,6 @@ const mockTierData = {
version: 0.1, version: 0.1,
updatedAt: 1665646906357, updatedAt: 1665646906357,
updatedBy: 'admin', updatedBy: 'admin',
categoryType: 'Descriptive',
href: 'http://localhost:8585/api/v1/tags/Tier', href: 'http://localhost:8585/api/v1/tags/Tier',
children: [ children: [
{ {

View File

@ -127,7 +127,6 @@ const mockTagList = [
id: 'tagCatId1', id: 'tagCatId1',
name: 'TagCat1', name: 'TagCat1',
description: '', description: '',
categoryType: 'Classification',
children: [ children: [
{ {
id: 'tagId1', id: 'tagId1',
@ -143,7 +142,6 @@ const mockTagList = [
id: 'tagCatId2', id: 'tagCatId2',
name: 'TagCat2', name: 'TagCat2',
description: '', description: '',
categoryType: 'Classification',
children: [ children: [
{ {
id: 'tagId2', id: 'tagId2',

View File

@ -5,7 +5,6 @@ import Form from './Form';
const mockFunction = jest.fn(); const mockFunction = jest.fn();
const mockInitialData = { const mockInitialData = {
categoryType: 'Descriptive',
description: '', description: '',
name: '', name: '',
}; };
@ -27,10 +26,8 @@ describe('Test TagsPage form component', () => {
} }
); );
const categoryType = await findByTestId(container, 'category-type');
const name = await findByTestId(container, 'name'); const name = await findByTestId(container, 'name');
expect(categoryType).toBeInTheDocument();
expect(name).toBeInTheDocument(); expect(name).toBeInTheDocument();
expect( expect(
await findByText(container, /MarkdownWithPreview component/i) await findByText(container, /MarkdownWithPreview component/i)

View File

@ -24,7 +24,6 @@ import { CreateTagCategory } from '../../generated/api/tags/createTagCategory';
import { errorMsg } from '../../utils/CommonUtils'; import { errorMsg } from '../../utils/CommonUtils';
type CustomTagCategory = { type CustomTagCategory = {
categoryType: string;
description: CreateTagCategory['description']; description: CreateTagCategory['description'];
name: CreateTagCategory['name']; name: CreateTagCategory['name'];
}; };
@ -42,7 +41,6 @@ const Form: React.FC<FormProp> = forwardRef(
const [data, setData] = useState<CustomTagCategory>({ const [data, setData] = useState<CustomTagCategory>({
name: initialData.name, name: initialData.name,
description: initialData.description, description: initialData.description,
categoryType: initialData.categoryType,
}); });
const isMounting = useRef<boolean>(true); const isMounting = useRef<boolean>(true);
@ -83,25 +81,6 @@ const Form: React.FC<FormProp> = forwardRef(
<div className="tw-w-full tw-flex "> <div className="tw-w-full tw-flex ">
<div className="tw-flex tw-w-full"> <div className="tw-flex tw-w-full">
<div className="tw-w-full"> <div className="tw-w-full">
{initialData.categoryType && (
<div className="tw-mb-4">
<label className="tw-form-label required-field">
Select Category Type
</label>
<select
required
className="tw-text-sm tw-appearance-none tw-border tw-border-main
tw-rounded tw-w-full tw-py-2 tw-px-3 tw-text-grey-body tw-leading-tight
focus:tw-outline-none focus:tw-border-focus hover:tw-border-hover tw-h-10 tw-bg-white"
data-testid="category-type"
name="categoryType"
value={data.categoryType}
onChange={onChangeHadler}>
<option value="Descriptive">Descriptive </option>
<option value="Classification">Classification</option>
</select>
</div>
)}
<div className="tw-mb-4"> <div className="tw-mb-4">
<label className="tw-form-label required-field">Name</label> <label className="tw-form-label required-field">Name</label>
<input <input

View File

@ -53,7 +53,6 @@ jest.mock('react-router-dom', () => ({
const mockTagsCategory = [ const mockTagsCategory = [
{ {
categoryType: 'Classification',
id: 'test', id: 'test',
children: [ children: [
{ {
@ -78,7 +77,6 @@ const mockTagsCategory = [
usageCount: 3, usageCount: 3,
}, },
{ {
categoryType: 'Classification',
id: 'test2', id: 'test2',
children: [], children: [],
description: 'description', description: 'description',
@ -96,7 +94,6 @@ const mockCategory = [
version: 0.1, version: 0.1,
updatedAt: 1649665563400, updatedAt: 1649665563400,
updatedBy: 'admin', updatedBy: 'admin',
categoryType: 'Classification',
href: 'http://localhost:8585/api/v1/tags/PersonalData', href: 'http://localhost:8585/api/v1/tags/PersonalData',
usageCount: 0, usageCount: 0,
children: [ children: [
@ -139,7 +136,6 @@ const mockCategory = [
version: 0.1, version: 0.1,
updatedAt: 1649665563410, updatedAt: 1649665563410,
updatedBy: 'admin', updatedBy: 'admin',
categoryType: 'Classification',
href: 'http://localhost:8585/api/v1/tags/PII', href: 'http://localhost:8585/api/v1/tags/PII',
usageCount: 0, usageCount: 0,
children: [ children: [

View File

@ -46,10 +46,7 @@ import {
import { TIER_CATEGORY } from '../../constants/constants'; import { TIER_CATEGORY } from '../../constants/constants';
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil'; import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
import { delimiterRegex } from '../../constants/regex.constants'; import { delimiterRegex } from '../../constants/regex.constants';
import { import { CreateTagCategory } from '../../generated/api/tags/createTagCategory';
CreateTagCategory,
TagCategoryType,
} from '../../generated/api/tags/createTagCategory';
import { Operation } from '../../generated/entity/policies/accessControl/rule'; import { Operation } from '../../generated/entity/policies/accessControl/rule';
import { TagCategory, TagClass } from '../../generated/entity/tags/tagCategory'; import { TagCategory, TagClass } from '../../generated/entity/tags/tagCategory';
import { EntityReference } from '../../generated/type/entityReference'; import { EntityReference } from '../../generated/type/entityReference';
@ -334,7 +331,6 @@ const TagsPage = () => {
const response = await updateTagCategory(currentCategory?.name ?? '', { const response = await updateTagCategory(currentCategory?.name ?? '', {
name: currentCategory?.name ?? '', name: currentCategory?.name ?? '',
description: updatedHTML, description: updatedHTML,
categoryType: currentCategory?.categoryType,
}); });
if (response) { if (response) {
await fetchCurrentCategory(currentCategory?.name as string, true); await fetchCurrentCategory(currentCategory?.name as string, true);
@ -722,7 +718,6 @@ const TagsPage = () => {
initialData={{ initialData={{
name: '', name: '',
description: '', description: '',
categoryType: TagCategoryType.Descriptive,
}} }}
isSaveButtonDisabled={!isEmpty(errorDataCategory)} isSaveButtonDisabled={!isEmpty(errorDataCategory)}
onCancel={() => setIsAddingCategory(false)} onCancel={() => setIsAddingCategory(false)}
@ -743,7 +738,6 @@ const TagsPage = () => {
initialData={{ initialData={{
name: '', name: '',
description: '', description: '',
categoryType: '',
}} }}
isSaveButtonDisabled={!isEmpty(errorDataTag)} isSaveButtonDisabled={!isEmpty(errorDataTag)}
onCancel={() => setIsAddingTag(false)} onCancel={() => setIsAddingTag(false)}

View File

@ -22,7 +22,6 @@ export type Tag = {
export type TagsCategory = { export type TagsCategory = {
name: string; name: string;
description: string; description: string;
categoryType?: string;
children?: Array<Tag>; children?: Array<Tag>;
href?: string; href?: string;
usageCount?: number; usageCount?: number;