Skip to content

Commit e7be03b

Browse files
committed
feat(platform): add support for via nodes
1 parent f3cc4e0 commit e7be03b

File tree

60 files changed

+2374
-277
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2374
-277
lines changed

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,8 @@ private void configureQueryResolvers(final RuntimeWiring.Builder builder) {
878878
"scrollAcrossEntities",
879879
new ScrollAcrossEntitiesResolver(this.entityClient, this.viewService))
880880
.dataFetcher(
881-
"searchAcrossLineage", new SearchAcrossLineageResolver(this.entityClient))
881+
"searchAcrossLineage",
882+
new SearchAcrossLineageResolver(this.entityClient, this.entityRegistry))
882883
.dataFetcher(
883884
"scrollAcrossLineage", new ScrollAcrossLineageResolver(this.entityClient))
884885
.dataFetcher(

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/BatchLoadUtils.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ public static CompletableFuture<List<Entity>> batchLoadEntitiesOfSameType(
2828
.filter(entity -> entities.get(0).getClass().isAssignableFrom(entity.objectClass()))
2929
.collect(Collectors.toList()));
3030

31-
final DataLoader loader = dataLoaderRegistry.getDataLoader(filteredEntity.name());
32-
List keyList = new ArrayList();
31+
final DataLoader<Object, Entity> loader =
32+
dataLoaderRegistry.getDataLoader(filteredEntity.name());
33+
List<Object> keyList = new ArrayList();
3334
for (Entity entity : entities) {
3435
keyList.add(filteredEntity.getKeyProvider().apply(entity));
3536
}

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchAcrossLineageResolver.java

+41-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
44
import static com.linkedin.datahub.graphql.resolvers.search.SearchUtils.*;
5+
import static com.linkedin.metadata.Constants.QUERY_ENTITY_NAME;
56

7+
import com.google.common.collect.ImmutableSet;
68
import com.linkedin.common.urn.Urn;
79
import com.linkedin.datahub.graphql.generated.EntityType;
810
import com.linkedin.datahub.graphql.generated.FacetFilterInput;
@@ -14,31 +16,63 @@
1416
import com.linkedin.datahub.graphql.types.entitytype.EntityTypeMapper;
1517
import com.linkedin.datahub.graphql.types.mappers.UrnSearchAcrossLineageResultsMapper;
1618
import com.linkedin.entity.client.EntityClient;
19+
import com.linkedin.metadata.models.EntitySpec;
20+
import com.linkedin.metadata.models.registry.EntityRegistry;
1721
import com.linkedin.metadata.query.SearchFlags;
1822
import com.linkedin.metadata.query.filter.Filter;
23+
import com.linkedin.metadata.search.LineageSearchResult;
1924
import com.linkedin.r2.RemoteInvocationException;
25+
import graphql.VisibleForTesting;
2026
import graphql.schema.DataFetcher;
2127
import graphql.schema.DataFetchingEnvironment;
2228
import java.net.URISyntaxException;
2329
import java.util.ArrayList;
2430
import java.util.List;
31+
import java.util.Set;
2532
import java.util.concurrent.CompletableFuture;
2633
import java.util.stream.Collectors;
2734
import javax.annotation.Nullable;
28-
import lombok.RequiredArgsConstructor;
2935
import lombok.extern.slf4j.Slf4j;
3036

3137
/** Resolver responsible for resolving 'searchAcrossEntities' field of the Query type */
3238
@Slf4j
33-
@RequiredArgsConstructor
3439
public class SearchAcrossLineageResolver
3540
implements DataFetcher<CompletableFuture<SearchAcrossLineageResults>> {
3641

3742
private static final int DEFAULT_START = 0;
3843
private static final int DEFAULT_COUNT = 10;
3944

45+
private static final Set<String> TRANSIENT_ENTITIES = ImmutableSet.of(QUERY_ENTITY_NAME);
46+
4047
private final EntityClient _entityClient;
4148

49+
private final EntityRegistry _entityRegistry;
50+
51+
@VisibleForTesting final Set<String> _allEntities;
52+
private final List<String> _allowedEntities;
53+
54+
public SearchAcrossLineageResolver(EntityClient entityClient, EntityRegistry entityRegistry) {
55+
this._entityClient = entityClient;
56+
this._entityRegistry = entityRegistry;
57+
this._allEntities =
58+
entityRegistry.getEntitySpecs().values().stream()
59+
.map(EntitySpec::getName)
60+
.collect(Collectors.toSet());
61+
62+
this._allowedEntities =
63+
this._allEntities.stream()
64+
.filter(e -> !TRANSIENT_ENTITIES.contains(e))
65+
.collect(Collectors.toList());
66+
}
67+
68+
private List<String> getEntityNamesFromInput(List<EntityType> inputTypes) {
69+
if (inputTypes != null && !inputTypes.isEmpty()) {
70+
return inputTypes.stream().map(EntityTypeMapper::getName).collect(Collectors.toList());
71+
} else {
72+
return this._allowedEntities;
73+
}
74+
}
75+
4276
@Override
4377
public CompletableFuture<SearchAcrossLineageResults> get(DataFetchingEnvironment environment)
4478
throws URISyntaxException {
@@ -50,12 +84,7 @@ public CompletableFuture<SearchAcrossLineageResults> get(DataFetchingEnvironment
5084

5185
final LineageDirection lineageDirection = input.getDirection();
5286

53-
List<EntityType> entityTypes =
54-
(input.getTypes() == null || input.getTypes().isEmpty())
55-
? SEARCHABLE_ENTITY_TYPES
56-
: input.getTypes();
57-
List<String> entityNames =
58-
entityTypes.stream().map(EntityTypeMapper::getName).collect(Collectors.toList());
87+
List<String> entityNames = getEntityNamesFromInput(input.getTypes());
5988

6089
// escape forward slash since it is a reserved character in Elasticsearch
6190
final String sanitizedQuery =
@@ -99,8 +128,7 @@ public CompletableFuture<SearchAcrossLineageResults> get(DataFetchingEnvironment
99128
} else {
100129
searchFlags = new SearchFlags().setFulltext(true).setSkipHighlighting(true);
101130
}
102-
103-
return UrnSearchAcrossLineageResultsMapper.map(
131+
LineageSearchResult salResults =
104132
_entityClient.searchAcrossLineage(
105133
urn,
106134
resolvedDirection,
@@ -114,7 +142,9 @@ public CompletableFuture<SearchAcrossLineageResults> get(DataFetchingEnvironment
114142
startTimeMillis,
115143
endTimeMillis,
116144
searchFlags,
117-
ResolverUtils.getAuthentication(environment)));
145+
getAuthentication(environment));
146+
147+
return UrnSearchAcrossLineageResultsMapper.map(salResults);
118148
} catch (RemoteInvocationException e) {
119149
log.error(
120150
"Failed to execute search across relationships: source urn {}, direction {}, entity types {}, query {}, filters: {}, start: {}, count: {}",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.linkedin.datahub.graphql.types.common.mappers;
2+
3+
import com.linkedin.datahub.graphql.generated.GroupingCriterion;
4+
import com.linkedin.datahub.graphql.types.mappers.ModelMapper;
5+
import javax.annotation.Nonnull;
6+
7+
public class GroupingCriterionInputMapper
8+
implements ModelMapper<GroupingCriterion, com.linkedin.metadata.query.GroupingCriterion> {
9+
10+
public static final GroupingCriterionInputMapper INSTANCE = new GroupingCriterionInputMapper();
11+
12+
public static com.linkedin.metadata.query.GroupingCriterion map(
13+
@Nonnull final GroupingCriterion groupingCriterion) {
14+
return INSTANCE.apply(groupingCriterion);
15+
}
16+
17+
@Override
18+
public com.linkedin.metadata.query.GroupingCriterion apply(GroupingCriterion input) {
19+
return new com.linkedin.metadata.query.GroupingCriterion()
20+
.setRawEntityType(input.getRawEntityType())
21+
.setGroupingEntityType(input.getGroupingEntityType());
22+
}
23+
}

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/common/mappers/SearchFlagsInputMapper.java

+12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import com.linkedin.datahub.graphql.generated.SearchFlags;
44
import com.linkedin.datahub.graphql.types.mappers.ModelMapper;
5+
import com.linkedin.metadata.query.GroupingCriterionArray;
6+
import com.linkedin.metadata.query.GroupingSpec;
7+
import java.util.stream.Collectors;
58
import javax.annotation.Nonnull;
69

710
/**
@@ -42,6 +45,15 @@ public com.linkedin.metadata.query.SearchFlags apply(@Nonnull final SearchFlags
4245
if (searchFlags.getGetSuggestions() != null) {
4346
result.setGetSuggestions(searchFlags.getGetSuggestions());
4447
}
48+
if (searchFlags.getGroupingSpec() != null) {
49+
result.setGroupingSpec(
50+
new GroupingSpec()
51+
.setGroupingCriteria(
52+
new GroupingCriterionArray(
53+
searchFlags.getGroupingSpec().getGroupingCriteria().stream()
54+
.map(GroupingCriterionInputMapper::map)
55+
.collect(Collectors.toList()))));
56+
}
4557
return result;
4658
}
4759
}

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/common/mappers/UrnToEntityMapper.java

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.linkedin.datahub.graphql.generated.MLPrimaryKey;
3131
import com.linkedin.datahub.graphql.generated.Notebook;
3232
import com.linkedin.datahub.graphql.generated.OwnershipTypeEntity;
33+
import com.linkedin.datahub.graphql.generated.QueryEntity;
3334
import com.linkedin.datahub.graphql.generated.Role;
3435
import com.linkedin.datahub.graphql.generated.SchemaFieldEntity;
3536
import com.linkedin.datahub.graphql.generated.StructuredPropertyEntity;
@@ -198,6 +199,11 @@ public Entity apply(Urn input) {
198199
((StructuredPropertyEntity) partialEntity).setUrn(input.toString());
199200
((StructuredPropertyEntity) partialEntity).setType(EntityType.STRUCTURED_PROPERTY);
200201
}
202+
if (input.getEntityType().equals(QUERY_ENTITY_NAME)) {
203+
partialEntity = new QueryEntity();
204+
((QueryEntity) partialEntity).setUrn(input.toString());
205+
((QueryEntity) partialEntity).setType(EntityType.QUERY);
206+
}
201207
return partialEntity;
202208
}
203209
}

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/mappers/UrnSearchAcrossLineageResultsMapper.java

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ private SearchAcrossLineageResult mapResult(LineageSearchEntity searchEntity) {
6262
.setMatchedFields(getMatchedFieldEntry(searchEntity.getMatchedFields()))
6363
.setPaths(searchEntity.getPaths().stream().map(this::mapPath).collect(Collectors.toList()))
6464
.setDegree(searchEntity.getDegree())
65+
.setDegrees(searchEntity.getDegrees().stream().collect(Collectors.toList()))
6566
.build();
6667
}
6768

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/query/QueryType.java

+3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import java.util.stream.Collectors;
2222
import javax.annotation.Nonnull;
2323
import lombok.RequiredArgsConstructor;
24+
import lombok.extern.slf4j.Slf4j;
2425

26+
@Slf4j
2527
@RequiredArgsConstructor
2628
public class QueryType
2729
implements com.linkedin.datahub.graphql.types.EntityType<QueryEntity, String> {
@@ -50,6 +52,7 @@ public List<DataFetcherResult<QueryEntity>> batchLoad(
5052
final List<Urn> viewUrns = urns.stream().map(UrnUtils::getUrn).collect(Collectors.toList());
5153

5254
try {
55+
log.debug("Fetching query entities: {}", viewUrns);
5356
final Map<Urn, EntityResponse> entities =
5457
_entityClient.batchGetV2(
5558
QUERY_ENTITY_NAME,

datahub-graphql-core/src/main/resources/entity.graphql

+5
Original file line numberDiff line numberDiff line change
@@ -10948,6 +10948,11 @@ enum QuerySource {
1094810948
The query was provided manually, e.g. from the UI.
1094910949
"""
1095010950
MANUAL
10951+
10952+
"""
10953+
The query was extracted by the system, e.g. from a dashboard.
10954+
"""
10955+
SYSTEM
1095110956
}
1095210957

1095310958
"""

datahub-graphql-core/src/main/resources/search.graphql

+38
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ input SearchFlags {
143143
Whether to request for search suggestions on the _entityName virtualized field
144144
"""
145145
getSuggestions: Boolean
146+
147+
"""
148+
Additional grouping specifications to apply to the search results
149+
"""
150+
groupingSpec: GroupingSpec
146151
}
147152

148153
"""
@@ -278,6 +283,7 @@ input ScrollAcrossEntitiesInput {
278283
searchFlags: SearchFlags
279284
}
280285

286+
281287
"""
282288
Input arguments for a search query over the results of a multi-hop graph query
283289
"""
@@ -669,6 +675,12 @@ type SearchAcrossLineageResult {
669675
Degree of relationship (number of hops to get to entity)
670676
"""
671677
degree: Int!
678+
679+
"""
680+
Degrees of relationship (for entities discoverable at multiple degrees)
681+
"""
682+
degrees: [Int!]
683+
672684
}
673685

674686
"""
@@ -1303,4 +1315,30 @@ input SortCriterion {
13031315
The order in which we will be sorting
13041316
"""
13051317
sortOrder: SortOrder!
1318+
}
1319+
1320+
"""
1321+
A grouping specification for search results
1322+
"""
1323+
input GroupingSpec {
1324+
1325+
groupingCriteria: [GroupingCriterion!]!
1326+
1327+
}
1328+
1329+
"""
1330+
A single grouping criterion for grouping search results
1331+
"""
1332+
input GroupingCriterion {
1333+
1334+
"""
1335+
The raw entity type that needs to be grouped
1336+
"""
1337+
rawEntityType: String!
1338+
1339+
"""
1340+
The grouping entity type
1341+
"""
1342+
groupingEntityType: String!
1343+
13061344
}

datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/SearchAcrossLineageResolverTest.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import com.linkedin.datahub.graphql.generated.SearchAcrossLineageResult;
1515
import com.linkedin.datahub.graphql.generated.SearchAcrossLineageResults;
1616
import com.linkedin.entity.client.EntityClient;
17+
import com.linkedin.metadata.models.registry.ConfigEntityRegistry;
18+
import com.linkedin.metadata.models.registry.EntityRegistry;
1719
import com.linkedin.metadata.query.SearchFlags;
1820
import com.linkedin.metadata.search.AggregationMetadataArray;
1921
import com.linkedin.metadata.search.LineageSearchEntity;
@@ -22,6 +24,7 @@
2224
import com.linkedin.metadata.search.MatchedFieldArray;
2325
import com.linkedin.metadata.search.SearchResultMetadata;
2426
import graphql.schema.DataFetchingEnvironment;
27+
import java.io.InputStream;
2528
import java.util.Collections;
2629
import java.util.List;
2730
import org.testng.annotations.BeforeMethod;
@@ -43,13 +46,28 @@ public class SearchAcrossLineageResolverTest {
4346
private Authentication _authentication;
4447
private SearchAcrossLineageResolver _resolver;
4548

49+
private EntityRegistry _entityRegistry;
50+
4651
@BeforeMethod
4752
public void setupTest() {
4853
_entityClient = mock(EntityClient.class);
4954
_dataFetchingEnvironment = mock(DataFetchingEnvironment.class);
5055
_authentication = mock(Authentication.class);
5156

52-
_resolver = new SearchAcrossLineageResolver(_entityClient);
57+
_entityRegistry = mock(EntityRegistry.class);
58+
_resolver = new SearchAcrossLineageResolver(_entityClient, _entityRegistry);
59+
}
60+
61+
@Test
62+
public void testAllEntitiesInitialization() {
63+
InputStream inputStream = ClassLoader.getSystemResourceAsStream("entity-registry.yml");
64+
EntityRegistry entityRegistry = new ConfigEntityRegistry(inputStream);
65+
SearchAcrossLineageResolver resolver =
66+
new SearchAcrossLineageResolver(_entityClient, entityRegistry);
67+
assertTrue(resolver._allEntities.contains("dataset"));
68+
assertTrue(resolver._allEntities.contains("dataFlow"));
69+
// Test for case sensitivity
70+
assertFalse(resolver._allEntities.contains("dataflow"));
5371
}
5472

5573
@Test

datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/SearchResolverTest.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ public void testDefaultSearchFlags() throws Exception {
4545
.setSkipAggregates(false)
4646
.setSkipHighlighting(true) // empty/wildcard
4747
.setMaxAggValues(20)
48-
.setSkipCache(false));
48+
.setSkipCache(false)
49+
.setConvertSchemaFieldsToDatasets(true));
4950
}
5051

5152
@Test
@@ -82,7 +83,8 @@ public void testOverrideSearchFlags() throws Exception {
8283
.setSkipAggregates(true)
8384
.setSkipHighlighting(true)
8485
.setMaxAggValues(10)
85-
.setSkipCache(true));
86+
.setSkipCache(true)
87+
.setConvertSchemaFieldsToDatasets(true));
8688
}
8789

8890
@Test
@@ -112,7 +114,8 @@ public void testNonWildCardSearchFlags() throws Exception {
112114
.setSkipAggregates(false)
113115
.setSkipHighlighting(false) // empty/wildcard
114116
.setMaxAggValues(20)
115-
.setSkipCache(false));
117+
.setSkipCache(false)
118+
.setConvertSchemaFieldsToDatasets(true));
116119
}
117120

118121
private EntityClient initMockSearchEntityClient() throws Exception {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.linkedin.datahub.upgrade.config;
2+
3+
import com.linkedin.datahub.upgrade.system.via.ReindexDataJobViaNodesCLL;
4+
import com.linkedin.metadata.entity.EntityService;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.context.annotation.Configuration;
7+
8+
@Configuration
9+
public class ReindexDataJobViaNodesCLLConfig {
10+
11+
@Bean
12+
public ReindexDataJobViaNodesCLL _reindexDataJobViaNodesCLL(EntityService<?> entityService) {
13+
return new ReindexDataJobViaNodesCLL(entityService);
14+
}
15+
}

0 commit comments

Comments
 (0)