Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds option to manage URL-based icons for custom assets #1218

Merged
merged 3 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,66 +27,135 @@ public class CustomAssetTest extends AtlanLiveTest {

public static final AtlanConnectorType CONNECTOR_TYPE = AtlanConnectorType.CUSTOM;
public static final String CONNECTION_NAME = PREFIX;
private static final String ENTITY_NAME = PREFIX + "-entity";
private static final String PARENT_NAME = PREFIX + "-parent";
private static final String CHILD_NAME1 = PREFIX + "-child1";
private static final String CHILD_NAME2 = PREFIX + "-child2";

private static Connection connection = null;
private static CustomEntity entity = null;
private static CustomEntity parent = null;
private static CustomEntity child1 = null;
private static CustomEntity child2 = null;

@Test(groups = {"custom.create.connection"})
void createConnection() throws AtlanException, InterruptedException {
connection = ConnectionTest.createConnection(client, CONNECTION_NAME, CONNECTOR_TYPE);
}

@Test(
groups = {"custom.create.entity"},
groups = {"custom.create.parent"},
dependsOnGroups = {"custom.create.connection"})
void createEntity() throws AtlanException {
CustomEntity toCreate =
CustomEntity.creator(ENTITY_NAME, connection.getQualifiedName()).build();
void createParent() throws AtlanException {
CustomEntity toCreate = CustomEntity.creator(PARENT_NAME, connection.getQualifiedName())
.iconUrl("http://assets.atlan.com/assets/ph-bowl-food-light.svg")
.subType("Fruit Salad")
.build();
AssetMutationResponse response = toCreate.save(client);
Asset one = validateSingleCreate(response);
assertTrue(one instanceof CustomEntity);
entity = (CustomEntity) one;
assertNotNull(entity.getGuid());
assertNotNull(entity.getQualifiedName());
assertEquals(entity.getName(), ENTITY_NAME);
assertEquals(entity.getConnectorType(), CONNECTOR_TYPE);
assertEquals(entity.getConnectionQualifiedName(), connection.getQualifiedName());
parent = (CustomEntity) one;
assertNotNull(parent.getGuid());
assertNotNull(parent.getQualifiedName());
assertEquals(parent.getName(), PARENT_NAME);
assertEquals(parent.getConnectorType(), CONNECTOR_TYPE);
assertEquals(parent.getConnectionQualifiedName(), connection.getQualifiedName());
}

@Test(
groups = {"custom.update.entity"},
dependsOnGroups = {"custom.create.entity"})
void updateEntity() throws AtlanException {
groups = {"custom.create.children"},
dependsOnGroups = {"custom.create.parent"})
void createChildren() throws AtlanException {
CustomEntity one = CustomEntity.creator(CHILD_NAME1, connection.getQualifiedName())
.iconUrl("http://assets.atlan.com/assets/ph-apple-logo-light.svg")
.subType("Apple")
.customParentEntity(CustomEntity.refByGuid(parent.getGuid()))
.build();
CustomEntity two = CustomEntity.creator(CHILD_NAME2, connection.getQualifiedName())
.iconUrl("http://assets.atlan.com/assets/ph-orange-slice-light.svg")
.subType("Orange")
.customParentEntity(CustomEntity.refByQualifiedName(parent.getQualifiedName()))
.customRelatedToEntity(CustomEntity.refByGuid(one.getGuid()))
.build();
AssetMutationResponse response = client.assets.save(List.of(one, two), false);
assertNotNull(response);
assertNotNull(response.getUpdatedAssets());
assertEquals(response.getUpdatedAssets().size(), 1);
assertEquals(response.getUpdatedAssets().get(0).getGuid(), parent.getGuid());
assertNotNull(response.getCreatedAssets());
assertEquals(response.getCreatedAssets().size(), 2);
for (Asset asset : response.getCreatedAssets()) {
assertTrue(asset instanceof CustomEntity);
assertNotNull(asset.getGuid());
assertNotNull(asset.getQualifiedName());
assertEquals(asset.getConnectorType(), CONNECTOR_TYPE);
assertEquals(asset.getConnectionQualifiedName(), connection.getQualifiedName());
if (asset.getName().equals(CHILD_NAME1)) {
child1 = (CustomEntity) asset;
} else if (asset.getName().equals(CHILD_NAME2)) {
child2 = (CustomEntity) asset;
} else {
throw new IllegalStateException("Created entity did not match either we attempted to create.");
}
}
}

@Test(
groups = {"custom.update.parent"},
dependsOnGroups = {"custom.create.*"})
void updateParent() throws AtlanException {
CustomEntity updated = CustomEntity.updateCertificate(
client, entity.getQualifiedName(), CERTIFICATE_STATUS, CERTIFICATE_MESSAGE);
client, parent.getQualifiedName(), CERTIFICATE_STATUS, CERTIFICATE_MESSAGE);
assertNotNull(updated);
assertEquals(updated.getCertificateStatus(), CERTIFICATE_STATUS);
assertEquals(updated.getCertificateStatusMessage(), CERTIFICATE_MESSAGE);
updated = CustomEntity.updateAnnouncement(
client, entity.getQualifiedName(), ANNOUNCEMENT_TYPE, ANNOUNCEMENT_TITLE, ANNOUNCEMENT_MESSAGE);
client, parent.getQualifiedName(), ANNOUNCEMENT_TYPE, ANNOUNCEMENT_TITLE, ANNOUNCEMENT_MESSAGE);
assertNotNull(updated);
assertEquals(updated.getAnnouncementType(), ANNOUNCEMENT_TYPE);
assertEquals(updated.getAnnouncementTitle(), ANNOUNCEMENT_TITLE);
assertEquals(updated.getAnnouncementMessage(), ANNOUNCEMENT_MESSAGE);
}

@Test(
groups = {"custom.read.entity"},
dependsOnGroups = {"custom.create.*", "custom.update.entity"})
void retrieveEntity() throws AtlanException {
CustomEntity c = CustomEntity.get(client, entity.getGuid(), true);
groups = {"custom.read.parent"},
dependsOnGroups = {"custom.create.*", "custom.update.parent"})
void retrieveParent() throws AtlanException {
CustomEntity c = CustomEntity.get(client, parent.getGuid(), true);
assertNotNull(c);
assertTrue(c.isComplete());
assertEquals(c.getGuid(), entity.getGuid());
assertEquals(c.getQualifiedName(), entity.getQualifiedName());
assertEquals(c.getName(), ENTITY_NAME);
assertEquals(c.getGuid(), parent.getGuid());
assertEquals(c.getQualifiedName(), parent.getQualifiedName());
assertEquals(c.getName(), PARENT_NAME);
assertEquals(c.getCertificateStatus(), CERTIFICATE_STATUS);
assertEquals(c.getCustomChildEntities().size(), 2);
assertEquals(c.getSubType(), "Fruit Salad");
assertEquals(c.getIconUrl(), "http://assets.atlan.com/assets/ph-bowl-food-light.svg");
}

@Test(
groups = {"custom.read.child1"},
dependsOnGroups = {"custom.create.*", "custom.update.parent"})
void retrieveChild1() throws AtlanException {
CustomEntity c = CustomEntity.get(client, child1.getGuid(), true);
assertNotNull(c);
assertTrue(c.isComplete());
assertEquals(c.getGuid(), child1.getGuid());
assertEquals(c.getQualifiedName(), child1.getQualifiedName());
assertEquals(c.getName(), CHILD_NAME1);
assertTrue(
c.getCustomChildEntities() == null || c.getCustomChildEntities().isEmpty());
assertNotNull(c.getCustomParentEntity());
assertEquals(c.getCustomParentEntity().getGuid(), parent.getGuid());
assertFalse(c.getCustomRelatedFromEntities() == null
|| c.getCustomRelatedFromEntities().isEmpty());
assertEquals(c.getCustomRelatedFromEntities().size(), 1);
assertEquals(c.getCustomRelatedFromEntities().first().getGuid(), child2.getGuid());
assertEquals(c.getSubType(), "Apple");
assertEquals(c.getIconUrl(), "http://assets.atlan.com/assets/ph-apple-logo-light.svg");
}

@Test(
groups = {"custom.search.assets"},
dependsOnGroups = {"custom.read.entity"})
dependsOnGroups = {"custom.read.*"})
void searchAssets() throws AtlanException, InterruptedException {
IndexSearchRequest index = client.assets
.select()
Expand All @@ -95,11 +164,12 @@ void searchAssets() throws AtlanException, InterruptedException {
.pageSize(10)
.aggregate("type", IReferenceable.TYPE_NAME.bucketBy())
.sort(Asset.CREATE_TIME.order(SortOrder.Asc))
.sort(Asset.NAME.order(SortOrder.Asc))
.includeOnResults(Asset.NAME)
.includeOnResults(Asset.CONNECTION_QUALIFIED_NAME)
.toRequest();

IndexSearchResponse response = retrySearchUntil(index, 9L);
IndexSearchResponse response = retrySearchUntil(index, 3L);

assertNotNull(response.getAggregations());
assertEquals(response.getAggregations().size(), 1);
Expand All @@ -108,7 +178,7 @@ void searchAssets() throws AtlanException, InterruptedException {
((AggregationBucketResult) response.getAggregations().get("type"))
.getBuckets()
.size(),
3);
1);

assertEquals(response.getApproximateCount().longValue(), 3L);
List<Asset> entities = response.getAssets();
Expand All @@ -119,62 +189,78 @@ void searchAssets() throws AtlanException, InterruptedException {
assertTrue(one instanceof CustomEntity);
assertFalse(one.isComplete());
CustomEntity d = (CustomEntity) one;
assertEquals(d.getQualifiedName(), entity.getQualifiedName());
assertEquals(d.getName(), entity.getName());
assertEquals(d.getQualifiedName(), parent.getQualifiedName());
assertEquals(d.getName(), parent.getName());
assertEquals(d.getConnectionQualifiedName(), connection.getQualifiedName());

one = entities.get(1);
assertTrue(one instanceof CustomEntity);
assertFalse(one.isComplete());
d = (CustomEntity) one;
assertEquals(d.getQualifiedName(), child1.getQualifiedName());
assertEquals(d.getName(), child1.getName());
assertEquals(d.getConnectionQualifiedName(), connection.getQualifiedName());

one = entities.get(2);
assertTrue(one instanceof CustomEntity);
assertFalse(one.isComplete());
d = (CustomEntity) one;
assertEquals(d.getQualifiedName(), child2.getQualifiedName());
assertEquals(d.getName(), child2.getName());
assertEquals(d.getConnectionQualifiedName(), connection.getQualifiedName());
}

@Test(
groups = {"custom.delete.entity"},
groups = {"custom.delete.child2"},
dependsOnGroups = {"custom.update.*", "custom.search.*"})
void deleteEntity() throws AtlanException {
AssetMutationResponse response = Asset.delete(client, entity.getGuid()).block();
void deleteChild2() throws AtlanException {
AssetMutationResponse response = Asset.delete(client, child2.getGuid()).block();
assertNotNull(response);
assertTrue(response.getCreatedAssets().isEmpty());
assertTrue(response.getUpdatedAssets().isEmpty());
assertEquals(response.getDeletedAssets().size(), 1);
Asset one = response.getDeletedAssets().get(0);
assertTrue(one instanceof CustomEntity);
CustomEntity s = (CustomEntity) one;
assertEquals(s.getGuid(), entity.getGuid());
assertEquals(s.getQualifiedName(), entity.getQualifiedName());
assertEquals(s.getGuid(), child2.getGuid());
assertEquals(s.getQualifiedName(), child2.getQualifiedName());
assertEquals(s.getDeleteHandler(), "SOFT");
assertEquals(s.getStatus(), AtlanStatus.DELETED);
}

@Test(
groups = {"custom.delete.entity.read"},
dependsOnGroups = {"custom.delete.entity"})
groups = {"custom.delete.child2.read"},
dependsOnGroups = {"custom.delete.child2"})
void readDeletedEntity() throws AtlanException {
validateDeletedAsset(entity, log);
validateDeletedAsset(child2, log);
}

@Test(
groups = {"custom.delete.entity.restore"},
dependsOnGroups = {"custom.delete.entity.read"})
groups = {"custom.delete.child2.restore"},
dependsOnGroups = {"custom.delete.child2.read"})
void restoreEntity() throws AtlanException {
assertTrue(CustomEntity.restore(client, entity.getQualifiedName()));
CustomEntity restored = CustomEntity.get(client, entity.getQualifiedName());
assertTrue(CustomEntity.restore(client, child2.getQualifiedName()));
CustomEntity restored = CustomEntity.get(client, child2.getQualifiedName());
assertFalse(restored.isComplete());
assertEquals(restored.getGuid(), entity.getGuid());
assertEquals(restored.getQualifiedName(), entity.getQualifiedName());
assertEquals(restored.getGuid(), child2.getGuid());
assertEquals(restored.getQualifiedName(), child2.getQualifiedName());
assertEquals(restored.getStatus(), AtlanStatus.ACTIVE);
}

@Test(
groups = {"custom.purge.entity"},
dependsOnGroups = {"custom.delete.entity.restore"})
groups = {"custom.purge.child2"},
dependsOnGroups = {"custom.delete.child2.restore"})
void purgeEntity() throws AtlanException {
AssetMutationResponse response = Asset.purge(client, entity.getGuid()).block();
AssetMutationResponse response = Asset.purge(client, child2.getGuid()).block();
assertNotNull(response);
assertTrue(response.getCreatedAssets().isEmpty());
assertTrue(response.getUpdatedAssets().isEmpty());
assertEquals(response.getDeletedAssets().size(), 1);
Asset one = response.getDeletedAssets().get(0);
assertTrue(one instanceof CustomEntity);
CustomEntity s = (CustomEntity) one;
assertEquals(s.getGuid(), entity.getGuid());
assertEquals(s.getQualifiedName(), entity.getQualifiedName());
assertEquals(s.getGuid(), child2.getGuid());
assertEquals(s.getQualifiedName(), child2.getQualifiedName());
assertEquals(s.getDeleteHandler(), "PURGE");
assertEquals(s.getStatus(), AtlanStatus.DELETED);
}
Expand All @@ -186,7 +272,7 @@ void purgeEntity() throws AtlanException {
"custom.read.*",
"custom.search.*",
"custom.update.*",
"custom.purge.entity"
"custom.purge.child2"
},
alwaysRun = true)
void purgeConnection() throws AtlanException, InterruptedException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ void searchAssets() throws AtlanException, InterruptedException {
.includeOnResults(Asset.CONNECTION_QUALIFIED_NAME)
.toRequest();

IndexSearchResponse response = retrySearchUntil(index, 9L);
IndexSearchResponse response = retrySearchUntil(index, 2L);

assertNotNull(response.getAggregations());
assertEquals(response.getAggregations().size(), 1);
Expand All @@ -143,12 +143,12 @@ void searchAssets() throws AtlanException, InterruptedException {
((AggregationBucketResult) response.getAggregations().get("type"))
.getBuckets()
.size(),
9);
2);

assertEquals(response.getApproximateCount().longValue(), 9L);
assertEquals(response.getApproximateCount().longValue(), 2L);
List<Asset> entities = response.getAssets();
assertNotNull(entities);
assertEquals(entities.size(), 9);
assertEquals(entities.size(), 2);

Asset one = entities.get(0);
assertTrue(one instanceof DataverseEntity);
Expand Down
3 changes: 3 additions & 0 deletions sdk/src/main/java/com/atlan/model/assets/Asset.java
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,9 @@ public abstract class Asset extends Reference implements IAsset, IReferenceable
@Singular
SortedSet<String> viewerUsers;

/** URL of an icon to use for this asset. (Only applies to CustomEntity and Fivetran Catalog assets, currently.) */
transient String iconUrl;

/** Internal tracking of fields that should be serialized with null values. */
@JsonIgnore
@Singular
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,10 +361,10 @@ public static boolean restore(AtlanClient client, String qualifiedName) throws A
/**
* Builds the minimal object necessary to create a DataverseAttribute.
*
* @param name of the DataverseAttribute
* @param entity in which DataverseAttribute should be created, which must have at least
* @param name of the attribute
* @param entity in which the attribute should be created, which must have at least
* a qualifiedName
* @return the minimal request necessary to create the DataverseAttribute, as a builder
* @return the minimal request necessary to create the attribute, as a builder
* @throws InvalidRequestException if the entity provided is without a qualifiedName
*/
public static DataverseAttribute.DataverseAttributeBuilder<?, ?> creator(String name, DataverseEntity entity)
Expand All @@ -381,7 +381,7 @@ public static boolean restore(AtlanClient client, String qualifiedName) throws A
/**
* Builds the minimal object necessary to create a DataverseAttribute.
*
* @param name of the DataverseAttribute
* @param name of the attribute
* @param entityQualifiedName unique name of the entity in which this attribute exists
* @return the minimal request necessary to create the attribute, as a builder
*/
Expand All @@ -394,7 +394,7 @@ public static boolean restore(AtlanClient client, String qualifiedName) throws A
/**
* Builds the minimal object necessary to create a DataverseAttribute.
*
* @param name of the DataverseAttribute
* @param name of the attribute
* @param connectionQualifiedName unique name of the connection in which to create the attribute
* @param entityQualifiedName unique name of the entity in which to create the attribute
* @return the minimal request necessary to create the attribute, as a builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,11 @@ public static boolean restore(AtlanClient client, String qualifiedName) throws A
}

/**
* Builds the minimal object necessary to create a DataverseEntity.
* Builds the minimal object necessary to create a Dataverse entity.
*
* @param name of the DataverseEntity
* @param connectionQualifiedName unique name of the connection through which the DataverseEntity is accessible
* @return the minimal object necessary to create the DataverseEntity, as a builder
* @param name of the Dataverse entity
* @param connectionQualifiedName unique name of the connection through which the entity is accessible
* @return the minimal object necessary to create the Dataverse entity, as a builder
*/
public static DataverseEntity.DataverseEntityBuilder<?, ?> creator(String name, String connectionQualifiedName) {
return DataverseEntity._internal()
Expand Down
3 changes: 3 additions & 0 deletions sdk/src/main/java/com/atlan/model/assets/ICatalog.java
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,9 @@ public static ICatalog getLineageReference(String typeName, String qualifiedName
case PowerBIDataflow.TYPE_NAME:
ref = PowerBIDataflow.refByQualifiedName(qualifiedName);
break;
case PowerBIDataflowEntityColumn.TYPE_NAME:
ref = PowerBIDataflowEntityColumn.refByQualifiedName(qualifiedName);
break;
case PowerBIDataset.TYPE_NAME:
ref = PowerBIDataset.refByQualifiedName(qualifiedName);
break;
Expand Down
Loading
Loading