Skip to content

Commit 90f694e

Browse files
Added support to query labels on entity schema (#101)
1 parent 143b977 commit 90f694e

File tree

19 files changed

+323
-32
lines changed

19 files changed

+323
-32
lines changed

hypertrace-graphql-entity-schema/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ dependencies {
2929
implementation("org.hypertrace.core.graphql:hypertrace-core-graphql-deserialization")
3030
implementation("org.hypertrace.core.graphql:hypertrace-core-graphql-rx-utils")
3131

32+
implementation(project(":hypertrace-graphql-labels-schema"))
33+
3234
testImplementation("org.junit.jupiter:junit-jupiter")
3335
testImplementation("org.mockito:mockito-core")
3436
testImplementation("org.mockito:mockito-junit-jupiter")

hypertrace-graphql-entity-schema/src/main/java/org/hypertrace/graphql/entity/dao/EntityDaoModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.hypertrace.gateway.service.v1.common.Value;
2929
import org.hypertrace.gateway.service.v1.entity.Entity;
3030
import org.hypertrace.graphql.entity.health.BaselineDao;
31+
import org.hypertrace.graphql.label.joiner.LabelJoinerBuilder;
3132
import org.hypertrace.graphql.metric.request.MetricAggregationRequest;
3233
import org.hypertrace.graphql.metric.request.MetricRequest;
3334
import org.hypertrace.graphql.metric.request.MetricSeriesRequest;
@@ -46,6 +47,7 @@ protected void configure() {
4647
requireBinding(GrpcChannelRegistry.class);
4748
requireBinding(GrpcContextBuilder.class);
4849
requireBinding(Key.get(Scheduler.class, BoundedIoScheduler.class));
50+
requireBinding(LabelJoinerBuilder.class);
4951

5052
requireBinding(
5153
Key.get(new TypeLiteral<Converter<Collection<AttributeRequest>, Set<Expression>>>() {}));

hypertrace-graphql-entity-schema/src/main/java/org/hypertrace/graphql/entity/dao/GatewayServiceEntityConverter.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.hypertrace.graphql.entity.schema.Entity;
2727
import org.hypertrace.graphql.entity.schema.EntityResultSet;
2828
import org.hypertrace.graphql.entity.schema.EntityType;
29+
import org.hypertrace.graphql.label.schema.LabelResultSet;
2930
import org.hypertrace.graphql.metric.request.MetricRequest;
3031
import org.hypertrace.graphql.metric.schema.MetricContainer;
3132

@@ -58,17 +59,23 @@ class GatewayServiceEntityConverter {
5859
}
5960

6061
Single<EntityResultSet> convert(
61-
EntityRequest request, EntitiesResponse response, BaselineEntitiesResponse baselineResponse) {
62+
EntityRequest request,
63+
EntitiesResponse response,
64+
BaselineEntitiesResponse baselineResponse,
65+
Map<org.hypertrace.gateway.service.v1.entity.Entity, LabelResultSet> labelResultSetMap) {
6266
return this.edgeLookupConverter
6367
.convert(request, response)
64-
.flatMap(edgeLookup -> this.convert(request, response, baselineResponse, edgeLookup));
68+
.flatMap(
69+
edgeLookup ->
70+
this.convert(request, response, baselineResponse, edgeLookup, labelResultSetMap));
6571
}
6672

6773
private Single<EntityResultSet> convert(
6874
EntityRequest request,
6975
EntitiesResponse response,
7076
BaselineEntitiesResponse baselineResponse,
71-
EdgeLookup edgeLookup) {
77+
EdgeLookup edgeLookup,
78+
Map<org.hypertrace.gateway.service.v1.entity.Entity, LabelResultSet> labelResultSetMap) {
7279
Map<String, BaselineEntity> baselineEntityMap = getBaselineEntityMap(baselineResponse);
7380
return Observable.fromIterable(response.getEntityList())
7481
.flatMapSingle(
@@ -78,7 +85,8 @@ private Single<EntityResultSet> convert(
7885
entity,
7986
getBaselineEntity(baselineEntityMap, entity.getId()),
8087
edgeLookup.getIncoming().row(entity),
81-
edgeLookup.getOutgoing().row(entity)))
88+
edgeLookup.getOutgoing().row(entity),
89+
labelResultSetMap.get(entity)))
8290
.toList()
8391
.map(
8492
entities ->
@@ -103,7 +111,8 @@ private Single<Entity> convertEntity(
103111
org.hypertrace.gateway.service.v1.entity.Entity platformEntity,
104112
BaselineEntity baselineEntity,
105113
Map<String, EdgeResultSet> incomingEdges,
106-
Map<String, EdgeResultSet> outgoingEdges) {
114+
Map<String, EdgeResultSet> outgoingEdges,
115+
LabelResultSet labelResultSet) {
107116
return zip(
108117
this.attributeMapConverter.convert(
109118
entityRequest.resultSetRequest().attributes(), platformEntity.getAttributeMap()),
@@ -118,7 +127,8 @@ private Single<Entity> convertEntity(
118127
attrMap,
119128
containerMap,
120129
incomingEdges,
121-
outgoingEdges));
130+
outgoingEdges,
131+
labelResultSet));
122132
}
123133

124134
@lombok.Value
@@ -130,6 +140,7 @@ private static class ConvertedEntity implements Entity {
130140
Map<String, MetricContainer> metricContainers;
131141
Map<String, EdgeResultSet> incomingEdges;
132142
Map<String, EdgeResultSet> outgoingEdges;
143+
LabelResultSet labels;
133144

134145
@Override
135146
public Object attribute(String key) {

hypertrace-graphql-entity-schema/src/main/java/org/hypertrace/graphql/entity/dao/GatewayServiceEntityDao.java

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,41 @@
55
import io.grpc.CallCredentials;
66
import io.reactivex.rxjava3.core.Scheduler;
77
import io.reactivex.rxjava3.core.Single;
8+
import java.util.Collections;
9+
import java.util.Map;
810
import javax.inject.Inject;
911
import javax.inject.Singleton;
12+
import lombok.extern.slf4j.Slf4j;
1013
import org.hypertrace.core.graphql.context.GraphQlRequestContext;
1114
import org.hypertrace.core.graphql.rx.BoundedIoScheduler;
1215
import org.hypertrace.core.graphql.spi.config.GraphQlServiceConfig;
1316
import org.hypertrace.core.graphql.utils.grpc.GrpcChannelRegistry;
1417
import org.hypertrace.core.graphql.utils.grpc.GrpcContextBuilder;
1518
import org.hypertrace.gateway.service.GatewayServiceGrpc;
1619
import org.hypertrace.gateway.service.GatewayServiceGrpc.GatewayServiceFutureStub;
20+
import org.hypertrace.gateway.service.v1.common.Value;
1721
import org.hypertrace.gateway.service.v1.entity.EntitiesRequest;
1822
import org.hypertrace.gateway.service.v1.entity.EntitiesResponse;
23+
import org.hypertrace.gateway.service.v1.entity.Entity;
1924
import org.hypertrace.graphql.entity.health.BaselineDao;
2025
import org.hypertrace.graphql.entity.request.EntityRequest;
2126
import org.hypertrace.graphql.entity.schema.EntityResultSet;
27+
import org.hypertrace.graphql.label.joiner.LabelJoiner;
28+
import org.hypertrace.graphql.label.joiner.LabelJoinerBuilder;
29+
import org.hypertrace.graphql.label.schema.LabelResultSet;
2230

31+
@Slf4j
2332
@Singleton
2433
class GatewayServiceEntityDao implements EntityDao {
34+
2535
private final GatewayServiceFutureStub gatewayServiceStub;
2636
private final GrpcContextBuilder grpcContextBuilder;
2737
private final GatewayServiceEntityRequestBuilder requestBuilder;
2838
private final GatewayServiceEntityConverter entityConverter;
2939
private final BaselineDao baselineDao;
3040
private final GraphQlServiceConfig serviceConfig;
3141
private final Scheduler boundedIoScheduler;
42+
private final LabelJoinerBuilder labelJoinerBuilder;
3243

3344
@Inject
3445
GatewayServiceEntityDao(
@@ -39,11 +50,13 @@ class GatewayServiceEntityDao implements EntityDao {
3950
GatewayServiceEntityRequestBuilder requestBuilder,
4051
GatewayServiceEntityConverter entityConverter,
4152
BaselineDao baselineDao,
53+
LabelJoinerBuilder labelJoinerBuilder,
4254
@BoundedIoScheduler Scheduler boundedIoScheduler) {
4355
this.grpcContextBuilder = grpcContextBuilder;
4456
this.requestBuilder = requestBuilder;
4557
this.entityConverter = entityConverter;
4658
this.baselineDao = baselineDao;
59+
this.labelJoinerBuilder = labelJoinerBuilder;
4760
this.serviceConfig = serviceConfig;
4861
this.boundedIoScheduler = boundedIoScheduler;
4962

@@ -56,27 +69,32 @@ class GatewayServiceEntityDao implements EntityDao {
5669

5770
@Override
5871
public Single<EntityResultSet> getEntities(EntityRequest request) {
72+
GraphQlRequestContext context = request.resultSetRequest().context();
5973
return this.requestBuilder
6074
.buildRequest(request)
6175
.subscribeOn(this.boundedIoScheduler)
62-
.flatMap(
63-
serverRequest ->
64-
this.makeRequest(request.resultSetRequest().context(), serverRequest)
65-
.flatMap(
66-
serverResponse ->
67-
baselineDao
68-
.getBaselines(
69-
request.resultSetRequest().context(),
70-
serverRequest,
71-
serverResponse,
72-
request)
73-
.flatMap(
74-
baselineResponse ->
75-
this.entityConverter.convert(
76-
request, serverResponse, baselineResponse))));
76+
.flatMap(serverRequest -> this.fetchAndMapEntities(context, request, serverRequest));
77+
}
78+
79+
private Single<EntityResultSet> fetchAndMapEntities(
80+
GraphQlRequestContext context, EntityRequest request, EntitiesRequest serverRequest) {
81+
return this.makeEntityRequest(context, serverRequest)
82+
.flatMap(serverResponse -> this.getEntityResultSet(request, serverRequest, serverResponse));
83+
}
84+
85+
private Single<EntityResultSet> getEntityResultSet(
86+
EntityRequest request, EntitiesRequest serverRequest, EntitiesResponse serverResponse) {
87+
GraphQlRequestContext context = request.resultSetRequest().context();
88+
return Single.zip(
89+
baselineDao.getBaselines(context, serverRequest, serverResponse, request),
90+
buildLabelResultSetMap(context, request, serverResponse),
91+
(baselineResponse, labelResultSetMap) ->
92+
this.entityConverter.convert(
93+
request, serverResponse, baselineResponse, labelResultSetMap))
94+
.flatMap(entityResultSet -> entityResultSet);
7795
}
7896

79-
private Single<EntitiesResponse> makeRequest(
97+
private Single<EntitiesResponse> makeEntityRequest(
8098
GraphQlRequestContext context, EntitiesRequest request) {
8199
return Single.fromFuture(
82100
this.grpcContextBuilder
@@ -88,4 +106,29 @@ private Single<EntitiesResponse> makeRequest(
88106
serviceConfig.getGatewayServiceTimeout().toMillis(), MILLISECONDS)
89107
.getEntities(request)));
90108
}
109+
110+
private Single<Map<Entity, LabelResultSet>> buildLabelResultSetMap(
111+
GraphQlRequestContext context, EntityRequest request, EntitiesResponse entitiesResponse) {
112+
return request
113+
.labelRequest()
114+
.map(labelRequest -> labelJoinerBuilder.build(context))
115+
.orElse(Single.just(LabelJoiner.NO_OP_JOINER))
116+
.flatMap(
117+
joiner ->
118+
joiner.joinLabels(
119+
entitiesResponse.getEntityList(), getEntityLabelsGetter(request)));
120+
}
121+
122+
private LabelJoiner.LabelIdGetter<Entity> getEntityLabelsGetter(EntityRequest request) {
123+
return entity -> {
124+
Value labelAttributeValue =
125+
entity.getAttributeOrDefault(
126+
request.labelRequest().get().labelIdArrayAttributeRequest().attribute().id(), null);
127+
if (labelAttributeValue == null) {
128+
log.warn("Unable to fetch labels attribute for entity with id {}", entity.getId());
129+
return Single.just(Collections.emptyList());
130+
}
131+
return Single.just(labelAttributeValue.getStringArrayList());
132+
};
133+
}
91134
}

hypertrace-graphql-entity-schema/src/main/java/org/hypertrace/graphql/entity/dao/GatewayServiceEntityRequestBuilder.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import io.reactivex.rxjava3.core.Single;
77
import java.util.Collection;
8+
import java.util.Collections;
89
import java.util.List;
910
import java.util.Set;
1011
import javax.inject.Inject;
@@ -67,19 +68,26 @@ Single<EntitiesRequest> buildRequest(EntityRequest entityRequest) {
6768
.collect(flatten(MetricRequest::seriesRequests))),
6869
this.interactionRequestBuilder.build(entityRequest.incomingEdgeRequests()),
6970
this.interactionRequestBuilder.build(entityRequest.outgoingEdgeRequests()),
71+
this.selectionConverter.convert(
72+
entityRequest
73+
.labelRequest()
74+
.map(request -> Set.of(request.labelIdArrayAttributeRequest()))
75+
.orElse(Collections.emptySet())),
7076
(selections,
7177
orderBys,
7278
filter,
7379
aggregations,
7480
series,
7581
incomingInteractions,
76-
outgoingInteractions) ->
82+
outgoingInteractions,
83+
labelSelections) ->
7784
EntitiesRequest.newBuilder()
7885
.setEntityType(entityRequest.entityType())
7986
.setStartTimeMillis(resultSetRequest.timeRange().startTime().toEpochMilli())
8087
.setEndTimeMillis(resultSetRequest.timeRange().endTime().toEpochMilli())
8188
.addAllSelection(selections)
8289
.addAllSelection(aggregations)
90+
.addAllSelection(labelSelections)
8391
.addAllTimeAggregation(series)
8492
.setIncomingInteractions(incomingInteractions)
8593
.setOutgoingInteractions(outgoingInteractions)

hypertrace-graphql-entity-schema/src/main/java/org/hypertrace/graphql/entity/joiner/DefaultEntityJoinerBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.hypertrace.graphql.entity.dao.EntityDao;
5252
import org.hypertrace.graphql.entity.request.EdgeSetGroupRequest;
5353
import org.hypertrace.graphql.entity.request.EntityRequest;
54+
import org.hypertrace.graphql.entity.request.LabelRequest;
5455
import org.hypertrace.graphql.entity.schema.Entity;
5556
import org.hypertrace.graphql.entity.schema.EntityJoinable;
5657
import org.hypertrace.graphql.entity.schema.EntityResultSet;
@@ -272,6 +273,7 @@ private static class DefaultEntityRequest implements EntityRequest {
272273
EdgeSetGroupRequest incomingEdgeRequests = new EmptyEdgeSetGroupRequest();
273274
EdgeSetGroupRequest outgoingEdgeRequests = new EmptyEdgeSetGroupRequest();
274275
boolean includeInactive = true; // When joining we want the entity regardless of time range
276+
Optional<LabelRequest> labelRequest = Optional.empty();
275277
}
276278

277279
@Value

hypertrace-graphql-entity-schema/src/main/java/org/hypertrace/graphql/entity/request/DefaultEntityRequestBuilder.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import javax.inject.Inject;
1515
import lombok.Value;
1616
import lombok.experimental.Accessors;
17+
import org.hypertrace.core.graphql.common.request.AttributeRequest;
18+
import org.hypertrace.core.graphql.common.request.AttributeRequestBuilder;
1719
import org.hypertrace.core.graphql.common.request.ResultSetRequest;
1820
import org.hypertrace.core.graphql.common.request.ResultSetRequestBuilder;
1921
import org.hypertrace.core.graphql.common.schema.arguments.TimeRangeArgument;
@@ -23,6 +25,7 @@
2325
import org.hypertrace.core.graphql.deserialization.ArgumentDeserializer;
2426
import org.hypertrace.core.graphql.utils.schema.GraphQlSelectionFinder;
2527
import org.hypertrace.core.graphql.utils.schema.SelectionQuery;
28+
import org.hypertrace.graphql.entity.schema.Entity;
2629
import org.hypertrace.graphql.entity.schema.EntityType;
2730
import org.hypertrace.graphql.entity.schema.argument.EntityScopeArgument;
2831
import org.hypertrace.graphql.entity.schema.argument.EntityTypeArgument;
@@ -33,24 +36,29 @@
3336

3437
class DefaultEntityRequestBuilder implements EntityRequestBuilder {
3538

39+
private static final String LABELS_KEY_NAME = "labels";
40+
3641
private final ResultSetRequestBuilder resultSetRequestBuilder;
3742
private final MetricRequestBuilder metricRequestBuilder;
3843
private final ArgumentDeserializer argumentDeserializer;
3944
private final GraphQlSelectionFinder selectionFinder;
4045
private final EdgeRequestBuilder edgeRequestBuilder;
46+
private final AttributeRequestBuilder attributeRequestBuilder;
4147

4248
@Inject
4349
DefaultEntityRequestBuilder(
4450
ResultSetRequestBuilder resultSetRequestBuilder,
4551
MetricRequestBuilder metricRequestBuilder,
4652
ArgumentDeserializer argumentDeserializer,
4753
GraphQlSelectionFinder selectionFinder,
48-
EdgeRequestBuilder edgeRequestBuilder) {
54+
EdgeRequestBuilder edgeRequestBuilder,
55+
AttributeRequestBuilder attributeRequestBuilder) {
4956
this.resultSetRequestBuilder = resultSetRequestBuilder;
5057
this.metricRequestBuilder = metricRequestBuilder;
5158
this.argumentDeserializer = argumentDeserializer;
5259
this.selectionFinder = selectionFinder;
5360
this.edgeRequestBuilder = edgeRequestBuilder;
61+
this.attributeRequestBuilder = attributeRequestBuilder;
5462
}
5563

5664
@Override
@@ -88,6 +96,17 @@ private Single<EntityRequest> build(
8896
.count()
8997
> 0;
9098

99+
boolean canFetchLabels =
100+
this.selectionFinder
101+
.findSelections(
102+
selectionSet,
103+
SelectionQuery.builder()
104+
.selectionPath(
105+
List.of(ResultSet.RESULT_SET_RESULTS_NAME, Entity.LABELS_KEY))
106+
.build())
107+
.count()
108+
> 0;
109+
91110
return zip(
92111
this.resultSetRequestBuilder.build(
93112
context, scope, arguments, selectionSet, AggregatableOrderArgument.class),
@@ -102,15 +121,23 @@ private Single<EntityRequest> build(
102121
this.timeRange(arguments),
103122
this.space(arguments),
104123
this.getOutgoingEdges(selectionSet)),
105-
(resultSetRequest, metricRequestList, incomingEdges, outgoingEdges) ->
124+
attributeRequestBuilder.buildForKey(context, scope, LABELS_KEY_NAME),
125+
(resultSetRequest,
126+
metricRequestList,
127+
incomingEdges,
128+
outgoingEdges,
129+
labelsAttributeRequest) ->
106130
new DefaultEntityRequest(
107131
scope,
108132
resultSetRequest,
109133
metricRequestList,
110134
incomingEdges,
111135
outgoingEdges,
112136
includeInactive,
113-
fetchTotal));
137+
fetchTotal,
138+
canFetchLabels
139+
? Optional.of(new DefaultLabelRequest(labelsAttributeRequest))
140+
: Optional.empty()));
114141
}
115142

116143
private Stream<SelectedField> getResultSets(DataFetchingFieldSelectionSet selectionSet) {
@@ -154,5 +181,12 @@ private static class DefaultEntityRequest implements EntityRequest {
154181
EdgeSetGroupRequest outgoingEdgeRequests;
155182
boolean includeInactive;
156183
boolean fetchTotal;
184+
Optional<LabelRequest> labelRequest;
185+
}
186+
187+
@Value
188+
@Accessors(fluent = true)
189+
private static class DefaultLabelRequest implements LabelRequest {
190+
AttributeRequest labelIdArrayAttributeRequest;
157191
}
158192
}

0 commit comments

Comments
 (0)