Skip to content

Commit

Permalink
feat(containers) Get and display all parent containers in header and …
Browse files Browse the repository at this point in the history
…search (datahub-project#4910)

Co-authored-by: Chris Collins <chriscollins@Chriss-MBP.lan>
  • Loading branch information
2 people authored and justinas-marozas committed May 17, 2022
1 parent ef449a0 commit 9401286
Show file tree
Hide file tree
Showing 29 changed files with 829 additions and 205 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import com.linkedin.datahub.graphql.resolvers.browse.BrowsePathsResolver;
import com.linkedin.datahub.graphql.resolvers.browse.BrowseResolver;
import com.linkedin.datahub.graphql.resolvers.config.AppConfigResolver;
import com.linkedin.datahub.graphql.resolvers.container.ParentContainersResolver;
import com.linkedin.datahub.graphql.resolvers.container.ContainerEntitiesResolver;
import com.linkedin.datahub.graphql.resolvers.dataset.DatasetHealthResolver;
import com.linkedin.datahub.graphql.resolvers.deprecation.UpdateDeprecationResolver;
Expand Down Expand Up @@ -482,6 +483,7 @@ private void configureContainerResolvers(final RuntimeWiring.Builder builder) {
return container.getContainer() != null ? container.getContainer().getUrn() : null;
})
)
.dataFetcher("parentContainers", new ParentContainersResolver(entityClient))
.dataFetcher("dataPlatformInstance",
new LoadableTypeResolver<>(dataPlatformInstanceType,
(env) -> {
Expand Down Expand Up @@ -724,7 +726,8 @@ private void configureDatasetResolvers(final RuntimeWiring.Builder builder) {
this.entityClient,
"dataset",
"subTypes"))
.dataFetcher("runs", new EntityRunsResolver(entityClient)))
.dataFetcher("runs", new EntityRunsResolver(entityClient))
.dataFetcher("parentContainers", new ParentContainersResolver(entityClient)))
.type("Owner", typeWiring -> typeWiring
.dataFetcher("owner", new OwnerTypeResolver<>(ownerTypes,
(env) -> ((Owner) env.getSource()).getOwner()))
Expand Down Expand Up @@ -854,6 +857,7 @@ private void configureDashboardResolvers(final RuntimeWiring.Builder builder) {
return dashboard.getContainer() != null ? dashboard.getContainer().getUrn() : null;
})
)
.dataFetcher("parentContainers", new ParentContainersResolver(entityClient))
);
builder.type("DashboardInfo", typeWiring -> typeWiring
.dataFetcher("charts", new LoadableTypeBatchResolver<>(chartType,
Expand Down Expand Up @@ -893,6 +897,7 @@ private void configureChartResolvers(final RuntimeWiring.Builder builder) {
return chart.getContainer() != null ? chart.getContainer().getUrn() : null;
})
)
.dataFetcher("parentContainers", new ParentContainersResolver(entityClient))
);
builder.type("ChartInfo", typeWiring -> typeWiring
.dataFetcher("inputs", new LoadableTypeBatchResolver<>(datasetType,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.linkedin.datahub.graphql.resolvers.container;

import com.linkedin.common.urn.Urn;
import com.linkedin.data.DataMap;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.DataHubGraphQLException;
import com.linkedin.datahub.graphql.generated.Container;
import com.linkedin.datahub.graphql.generated.Entity;
import com.linkedin.datahub.graphql.generated.ParentContainersResult;
import com.linkedin.datahub.graphql.types.container.mappers.ContainerMapper;
import com.linkedin.entity.EntityResponse;
import com.linkedin.entity.client.EntityClient;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import static com.linkedin.metadata.Constants.CONTAINER_ASPECT_NAME;

public class ParentContainersResolver implements DataFetcher<CompletableFuture<ParentContainersResult>> {

private final EntityClient _entityClient;

public ParentContainersResolver(final EntityClient entityClient) {
_entityClient = entityClient;
}

private void aggregateParentContainers(List<Container> containers, String urn, QueryContext context) {
try {
Urn entityUrn = new Urn(urn);
EntityResponse entityResponse = _entityClient.getV2(
entityUrn.getEntityType(),
entityUrn,
Collections.singleton(CONTAINER_ASPECT_NAME),
context.getAuthentication()
);

if (entityResponse != null && entityResponse.getAspects().containsKey(CONTAINER_ASPECT_NAME)) {
DataMap dataMap = entityResponse.getAspects().get(CONTAINER_ASPECT_NAME).getValue().data();
com.linkedin.container.Container container = new com.linkedin.container.Container(dataMap);
Urn containerUrn = container.getContainer();
EntityResponse response = _entityClient.getV2(containerUrn.getEntityType(), containerUrn, null, context.getAuthentication());
if (response != null) {
Container mappedContainer = ContainerMapper.map(response);
containers.add(mappedContainer);
aggregateParentContainers(containers, mappedContainer.getUrn(), context);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public CompletableFuture<ParentContainersResult> get(DataFetchingEnvironment environment) {

final QueryContext context = environment.getContext();
final String urn = ((Entity) environment.getSource()).getUrn();
final List<Container> containers = new ArrayList<>();

return CompletableFuture.supplyAsync(() -> {
try {
aggregateParentContainers(containers, urn, context);
final ParentContainersResult result = new ParentContainersResult();
result.setCount(containers.size());
result.setContainers(containers);
return result;
} catch (DataHubGraphQLException e) {
throw new RuntimeException("Failed to load all containers", e);
}
});
}
}
40 changes: 40 additions & 0 deletions datahub-graphql-core/src/main/resources/entity.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,11 @@ type Dataset implements EntityWithRelationships & Entity {
"""
container: Container

"""
Recursively get the lineage of containers for this entity
"""
parentContainers: ParentContainersResult

"""
Unique guid for dataset
No longer to be used as the Dataset display name. Use properties.name instead
Expand Down Expand Up @@ -878,6 +883,21 @@ type Dataset implements EntityWithRelationships & Entity {
runs(start: Int, count: Int, direction: RelationshipDirection!): DataProcessInstanceResult
}

"""
All of the parent containers for a given entity
"""
type ParentContainersResult {
"""
The number of containers bubbling up for this entity
"""
count: Int!

"""
A list of parent containers in order from direct parent, to parent's parent etc. If there are no containers, return an emty list
"""
containers: [Container!]!
}

"""
A Dataset entity, which encompasses Relational Tables, Document store collections, streaming topics, and other sets of data having an independent lifecycle
"""
Expand All @@ -902,6 +922,11 @@ type VersionedDataset implements Entity {
"""
container: Container

"""
Recursively get the lineage of containers for this entity
"""
parentContainers: ParentContainersResult

"""
Unique guid for dataset
No longer to be used as the Dataset display name. Use properties.name instead
Expand Down Expand Up @@ -1515,6 +1540,11 @@ type Container implements Entity {
"""
container: Container

"""
Recursively get the lineage of containers for this entity
"""
parentContainers: ParentContainersResult

"""
Read-only properties that originate in the source data platform
"""
Expand Down Expand Up @@ -3720,6 +3750,11 @@ type Dashboard implements EntityWithRelationships & Entity {
"""
container: Container

"""
Recursively get the lineage of containers for this entity
"""
parentContainers: ParentContainersResult

"""
The dashboard tool name
Note that this will soon be deprecated in favor of a standardized notion of Data Platform
Expand Down Expand Up @@ -3973,6 +4008,11 @@ type Chart implements EntityWithRelationships & Entity {
"""
container: Container

"""
Recursively get the lineage of containers for this entity
"""
parentContainers: ParentContainersResult

"""
The chart tool name
Note that this field will soon be deprecated in favor a unified notion of Data Platform
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.linkedin.datahub.graphql.resolvers.container;

import com.datahub.authentication.Authentication;
import com.linkedin.common.urn.Urn;
import com.linkedin.container.Container;
import com.linkedin.container.ContainerProperties;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.generated.Dataset;
import com.linkedin.datahub.graphql.generated.EntityType;
import com.linkedin.datahub.graphql.generated.ParentContainersResult;
import com.linkedin.entity.Aspect;
import com.linkedin.entity.EntityResponse;
import com.linkedin.entity.EnvelopedAspect;
import com.linkedin.entity.EnvelopedAspectMap;
import com.linkedin.entity.client.EntityClient;
import graphql.schema.DataFetchingEnvironment;
import org.mockito.Mockito;
import org.testng.annotations.Test;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static com.linkedin.metadata.Constants.CONTAINER_ASPECT_NAME;
import static com.linkedin.metadata.Constants.CONTAINER_ENTITY_NAME;
import static com.linkedin.metadata.Constants.CONTAINER_PROPERTIES_ASPECT_NAME;

import static org.testng.Assert.*;

public class ParentContainersResolverTest {
@Test
public void testGetSuccess() throws Exception {
EntityClient mockClient = Mockito.mock(EntityClient.class);
QueryContext mockContext = Mockito.mock(QueryContext.class);
Mockito.when(mockContext.getAuthentication()).thenReturn(Mockito.mock(Authentication.class));
DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class);
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);

Urn datasetUrn = Urn.createFromString("urn:li:dataset:(test,test,test)");
Dataset datasetEntity = new Dataset();
datasetEntity.setUrn(datasetUrn.toString());
datasetEntity.setType(EntityType.DATASET);
Mockito.when(mockEnv.getSource()).thenReturn(datasetEntity);

final Container parentContainer1 = new Container().setContainer(Urn.createFromString("urn:li:container:test-container"));
final Container parentContainer2 = new Container().setContainer(Urn.createFromString("urn:li:container:test-container2"));

Map<String, EnvelopedAspect> datasetAspects = new HashMap<>();
datasetAspects.put(CONTAINER_ASPECT_NAME, new EnvelopedAspect().setValue(new Aspect(parentContainer1.data())));

Map<String, EnvelopedAspect> parentContainer1Aspects = new HashMap<>();
parentContainer1Aspects.put(CONTAINER_PROPERTIES_ASPECT_NAME, new EnvelopedAspect().setValue(new Aspect(
new ContainerProperties().setName("test_schema").data()
)));
parentContainer1Aspects.put(CONTAINER_ASPECT_NAME, new EnvelopedAspect().setValue(new Aspect(
parentContainer2.data()
)));

Map<String, EnvelopedAspect> parentContainer2Aspects = new HashMap<>();
parentContainer2Aspects.put(CONTAINER_PROPERTIES_ASPECT_NAME, new EnvelopedAspect().setValue(new Aspect(
new ContainerProperties().setName("test_database").data()
)));

Mockito.when(mockClient.getV2(
Mockito.eq(datasetUrn.getEntityType()),
Mockito.eq(datasetUrn),
Mockito.eq(Collections.singleton(CONTAINER_ASPECT_NAME)),
Mockito.any(Authentication.class)
)).thenReturn(new EntityResponse().setAspects(new EnvelopedAspectMap(datasetAspects)));

Mockito.when(mockClient.getV2(
Mockito.eq(parentContainer1.getContainer().getEntityType()),
Mockito.eq(parentContainer1.getContainer()),
Mockito.eq(null),
Mockito.any(Authentication.class)
)).thenReturn(new EntityResponse()
.setEntityName(CONTAINER_ENTITY_NAME)
.setUrn(parentContainer1.getContainer())
.setAspects(new EnvelopedAspectMap(parentContainer1Aspects)));

Mockito.when(mockClient.getV2(
Mockito.eq(parentContainer1.getContainer().getEntityType()),
Mockito.eq(parentContainer1.getContainer()),
Mockito.eq(Collections.singleton(CONTAINER_ASPECT_NAME)),
Mockito.any(Authentication.class)
)).thenReturn(new EntityResponse().setAspects(new EnvelopedAspectMap(parentContainer1Aspects)));

Mockito.when(mockClient.getV2(
Mockito.eq(parentContainer2.getContainer().getEntityType()),
Mockito.eq(parentContainer2.getContainer()),
Mockito.eq(null),
Mockito.any(Authentication.class)
)).thenReturn(new EntityResponse()
.setEntityName(CONTAINER_ENTITY_NAME)
.setUrn(parentContainer2.getContainer())
.setAspects(new EnvelopedAspectMap(parentContainer2Aspects)));

Mockito.when(mockClient.getV2(
Mockito.eq(parentContainer2.getContainer().getEntityType()),
Mockito.eq(parentContainer2.getContainer()),
Mockito.eq(Collections.singleton(CONTAINER_ASPECT_NAME)),
Mockito.any(Authentication.class)
)).thenReturn(new EntityResponse().setAspects(new EnvelopedAspectMap(parentContainer2Aspects)));

ParentContainersResolver resolver = new ParentContainersResolver(mockClient);
ParentContainersResult result = resolver.get(mockEnv).get();

Mockito.verify(mockClient, Mockito.times(5)).getV2(
Mockito.any(),
Mockito.any(),
Mockito.any(),
Mockito.any()
);
assertEquals(result.getCount(), 2);
assertEquals(result.getContainers().get(0).getUrn(), parentContainer1.getContainer().toString());
assertEquals(result.getContainers().get(1).getUrn(), parentContainer2.getContainer().toString());
}
}
28 changes: 28 additions & 0 deletions datahub-web-react/src/Mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
ScenarioType,
RecommendationRenderType,
RelationshipDirection,
Container,
} from './types.generated';
import { GetTagDocument } from './graphql/tag.generated';
import { GetMlModelDocument } from './graphql/mlModel.generated';
Expand Down Expand Up @@ -318,6 +319,10 @@ export const dataset3 = {
customProperties: [{ key: 'propertyAKey', value: 'propertyAValue' }],
externalUrl: 'https://data.hub',
},
parentContainers: {
count: 0,
containers: [],
},
editableProperties: null,
created: {
time: 0,
Expand Down Expand Up @@ -758,6 +763,29 @@ export const dataset7WithSelfReferentialLineage = {
],
},
};

export const container1 = {
urn: 'urn:li:container:DATABASE',
type: EntityType.Container,
platform: dataPlatform,
properties: {
name: 'database1',
__typename: 'ContainerProperties',
},
__typename: 'Container',
} as Container;

export const container2 = {
urn: 'urn:li:container:SCHEMA',
type: EntityType.Container,
platform: dataPlatform,
properties: {
name: 'schema1',
__typename: 'ContainerProperties',
},
__typename: 'Container',
} as Container;

const glossaryTerm1 = {
urn: 'urn:li:glossaryTerm:1',
type: EntityType.GlossaryTerm,
Expand Down
1 change: 1 addition & 0 deletions datahub-web-react/src/app/entity/chart/ChartEntity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export class ChartEntity implements Entity<Chart> {
glossaryTerms={data?.glossaryTerms}
logoUrl={data?.platform?.properties?.logoUrl}
domain={data.domain}
parentContainers={data.parentContainers}
/>
);
};
Expand Down
Loading

0 comments on commit 9401286

Please sign in to comment.