Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import lombok.experimental.Accessors;
import org.hypertrace.core.graphql.common.request.AttributeRequest;
import org.hypertrace.core.graphql.common.schema.attributes.arguments.AttributeExpression;
import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterArgument;
import org.hypertrace.core.graphql.common.utils.BiConverter;
import org.hypertrace.core.graphql.common.utils.TriConverter;
import org.hypertrace.gateway.service.v1.baseline.BaselineEntitiesResponse;
Expand All @@ -33,6 +34,7 @@
import org.hypertrace.graphql.metric.schema.MetricContainer;

class GatewayServiceEntityConverter {

private final BiConverter<
Collection<AttributeRequest>, Map<String, Value>, Map<AttributeExpression, Object>>
attributeMapConverter;
Expand Down Expand Up @@ -143,6 +145,7 @@ private Single<Entity> convertEntity(
@lombok.Value
@Accessors(fluent = true)
private static class ConvertedEntity implements Entity {

String id;
String type;
Map<AttributeExpression, Object> attributeValues;
Expand All @@ -161,13 +164,15 @@ public MetricContainer metric(AttributeExpression attributeExpression) {
}

@Override
public EdgeResultSet incomingEdges(EntityType neighborType, String neighborScope) {
public EdgeResultSet incomingEdges(
EntityType neighborType, String neighborScope, List<FilterArgument> filterBy) {
return this.incomingEdges.getOrDefault(
this.resolveEntityScope(neighborType, neighborScope), EMPTY_EDGE_RESULT_SET);
}

@Override
public EdgeResultSet outgoingEdges(EntityType neighborType, String neighborScope) {
public EdgeResultSet outgoingEdges(
EntityType neighborType, String neighborScope, List<FilterArgument> filterBy) {

return this.outgoingEdges.getOrDefault(
this.resolveEntityScope(neighborType, neighborScope), EMPTY_EDGE_RESULT_SET);
Expand All @@ -188,6 +193,7 @@ private String resolveEntityScope(
@lombok.Value
@Accessors(fluent = true)
private static class ConvertedEntityResultSet implements EntityResultSet {

List<Entity> results;
long total;
long count;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import lombok.Value;
import lombok.experimental.Accessors;
Expand All @@ -25,6 +26,7 @@
import org.hypertrace.graphql.metric.request.MetricAggregationRequest;

class GatewayServiceEntityInteractionRequestBuilder {

private static final Integer DEFAULT_INTERACTION_LIMIT = 1000;
private final Converter<Collection<AttributeRequest>, Set<Expression>> selectionConverter;
private final Converter<Collection<MetricAggregationRequest>, Set<Expression>>
Expand All @@ -48,7 +50,7 @@ Single<InteractionsRequest> build(EdgeSetGroupRequest edgeSetRequestGroup) {

return zip(
this.collectSelectionsAndAggregations(edgeSetRequestGroup),
this.buildEntityTypeFilter(edgeSetRequestGroup),
this.buildEntityInteractionFilter(edgeSetRequestGroup),
(selections, filter) ->
InteractionsRequest.newBuilder()
.addAllSelection(selections)
Expand All @@ -66,8 +68,8 @@ private Single<Set<Expression>> collectSelectionsAndAggregations(EdgeSetGroupReq
.collect(Collectors.toUnmodifiableSet());
}

private Single<Filter> buildEntityTypeFilter(EdgeSetGroupRequest request) {
return Observable.fromIterable(request.entityTypes())
private Single<Filter> buildEntityInteractionFilter(EdgeSetGroupRequest request) {
return Observable.fromIterable(request.entityTypes()) // add entity types filter
.collect(Collectors.toUnmodifiableSet())
.map(
entityTypes ->
Expand All @@ -76,7 +78,13 @@ private Single<Filter> buildEntityTypeFilter(EdgeSetGroupRequest request) {
new EntityNeighborTypeFilter(
request.neighborTypeAttribute().attributeExpressionAssociation().value(),
entityTypes)))
.flatMap(filterAssociation -> this.filterConverter.convert(Set.of(filterAssociation)));
.flatMap(
filterAssociation ->
this.filterConverter.convert(
Stream.concat(
request.filterArguments().stream(),
Stream.of(filterAssociation)) // add all other filters
.collect(Collectors.toSet())));
}

@Value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ private static class EmptyEdgeSetGroupRequest implements EdgeSetGroupRequest {
Set<String> entityTypes = Collections.emptySet();
Collection<AttributeRequest> attributeRequests = Collections.emptyList();
Collection<MetricAggregationRequest> metricAggregationRequests = Collections.emptyList();
Collection<AttributeAssociation<FilterArgument>> filterArguments = Collections.emptyList();
AttributeRequest neighborIdAttribute = null;
AttributeRequest neighborTypeAttribute = null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import graphql.schema.SelectedField;
import io.reactivex.rxjava3.core.Single;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
Expand All @@ -16,11 +17,14 @@
import javax.inject.Provider;
import lombok.Value;
import lombok.experimental.Accessors;
import org.hypertrace.core.graphql.common.request.AttributeAssociation;
import org.hypertrace.core.graphql.common.request.AttributeRequest;
import org.hypertrace.core.graphql.common.request.AttributeRequestBuilder;
import org.hypertrace.core.graphql.common.request.FilterRequestBuilder;
import org.hypertrace.core.graphql.common.schema.arguments.TimeRangeArgument;
import org.hypertrace.core.graphql.common.schema.attributes.arguments.AttributeExpression;
import org.hypertrace.core.graphql.common.schema.results.ResultSet;
import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterArgument;
import org.hypertrace.core.graphql.context.GraphQlRequestContext;
import org.hypertrace.core.graphql.deserialization.ArgumentDeserializer;
import org.hypertrace.core.graphql.utils.schema.GraphQlSelectionFinder;
Expand All @@ -40,6 +44,7 @@ class EdgeRequestBuilder {
private final ArgumentDeserializer argumentDeserializer;
private final GraphQlSelectionFinder selectionFinder;
private final MetricAggregationRequestBuilder metricAggregationRequestBuilder;
private final FilterRequestBuilder filterRequestBuilder;
private final AttributeRequestBuilder attributeRequestBuilder;
// Use provider to avoid cycle
private final Provider<NeighborEntitiesRequestBuilder> neighborEntitiesRequestBuilderProvider;
Expand All @@ -49,40 +54,44 @@ class EdgeRequestBuilder {
ArgumentDeserializer argumentDeserializer,
GraphQlSelectionFinder selectionFinder,
MetricAggregationRequestBuilder metricAggregationRequestBuilder,
FilterRequestBuilder filterRequestBuilder,
AttributeRequestBuilder attributeRequestBuilder,
Provider<NeighborEntitiesRequestBuilder> neighborEntitiesRequestBuilderProvider) {
this.argumentDeserializer = argumentDeserializer;
this.selectionFinder = selectionFinder;
this.metricAggregationRequestBuilder = metricAggregationRequestBuilder;
this.attributeRequestBuilder = attributeRequestBuilder;
this.neighborEntitiesRequestBuilderProvider = neighborEntitiesRequestBuilderProvider;
this.filterRequestBuilder = filterRequestBuilder;
}

Single<EdgeSetGroupRequest> buildIncomingEdgeRequest(
GraphQlRequestContext context,
TimeRangeArgument timeRange,
Optional<String> space,
Stream<SelectedField> edgeSetFields) {
return this.buildEdgeRequest(
context, timeRange, space, this.getEdgesByType(edgeSetFields), EdgeType.INCOMING);
return this.buildEdgeRequest(context, timeRange, space, edgeSetFields, EdgeType.INCOMING);
}

Single<EdgeSetGroupRequest> buildOutgoingEdgeRequest(
GraphQlRequestContext context,
TimeRangeArgument timeRange,
Optional<String> space,
Stream<SelectedField> edgeSetFields) {
return this.buildEdgeRequest(
context, timeRange, space, this.getEdgesByType(edgeSetFields), EdgeType.OUTGOING);
return this.buildEdgeRequest(context, timeRange, space, edgeSetFields, EdgeType.OUTGOING);
}

private Single<EdgeSetGroupRequest> buildEdgeRequest(
GraphQlRequestContext context,
TimeRangeArgument timeRange,
Optional<String> space,
Map<String, Set<SelectedField>> edgesByType,
Stream<SelectedField> edgeSetFields,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks to be off. There are potentially multiple edge requests that each can have different fields and filters - hence the previous map of type to fields. We're splitting them up later here, but we're not doing the same thing for filters.

EdgeType edgeType) {

Set<SelectedField> edgeFields = edgeSetFields.collect(Collectors.toSet());
List<FilterArgument> filterArguments = this.getFilters(edgeFields);

Map<String, Set<SelectedField>> edgesByType = this.getEdgesByType(edgeFields.stream());
Set<SelectedField> allEdges =
edgesByType.values().stream()
.flatMap(Collection::stream)
Expand All @@ -94,7 +103,9 @@ private Single<EdgeSetGroupRequest> buildEdgeRequest(
this.getNeighborTypeAttribute(context, edgeType),
this.metricAggregationRequestBuilder.build(
context, HypertraceAttributeScopeString.INTERACTION, allEdges.stream()),
(attributeRequests, neighborIdRequest, neighborTypeRequest, metricRequests) ->
this.filterRequestBuilder.build(
context, HypertraceAttributeScopeString.INTERACTION, filterArguments),
(attributeRequests, neighborIdRequest, neighborTypeRequest, metricRequests, filters) ->
new DefaultEdgeSetGroupRequest(
edgesByType.keySet(),
attributeRequests,
Expand All @@ -110,7 +121,8 @@ private Single<EdgeSetGroupRequest> buildEdgeRequest(
timeRange,
space,
neighborIds,
edgesByType.get(entityType))));
edgesByType.get(entityType)),
filters));
}

private Map<String, Set<SelectedField>> getEdgesByType(Stream<SelectedField> edgeSetStream) {
Expand Down Expand Up @@ -165,6 +177,17 @@ private Single<AttributeRequest> getNeighborIdAttribute(
}
}

private List<FilterArgument> getFilters(Set<SelectedField> selectedFields) {
return selectedFields.stream()
.map(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

referred to in other comment, but this is combining the filter arguments from each independent edge request (each field here is one edge request like incomingEdges(type: X)).

Copy link
Member Author

@aman-bansal aman-bansal Nov 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This I have already discussed with @skjindal93 . With this PR we are adding two limitations in the case of using edge filters,

  1. If using edge filters, we can only have one entityType incoming/outgoing edge
  2. In the case of different filters for each selected field. They will be merged into one AND filter.

we are introducing this to solve the deliverable first. Immediately after this we will make the changes to remove above two limitations. Currently, no one is using multiple entity types interactions, so it won't break anything. also old behaviour is still intact.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason we didnt solve this is because it will need big change in gateway service where we will have to make interactions request as an array of request rather than one single filter which it is right now. https://github.com/hypertrace/gateway-service/blob/main/gateway-service-api/src/main/proto/org/hypertrace/gateway/service/v1/entities.proto#L41 this is the filter i am referring to

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I agree here, but we can discuss further.

Currently, no one is using multiple entity types interactions, so it won't break anything

Confused here - the main application flow uses multiple entity types? It shows services with downstream services and downstream backends.

The reason we didnt solve this is because it will need big change in gateway service where we will have to make interactions request as an array of request rather than one single filter which it is right now.

We already handle multiple entity types through a filter, is this different? You'd need to just change how you build the filter to OR across selection types and then AND for each selection type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confused here - the main application flow uses multiple entity types? It shows services with downstream services and downstream backends.

It won't break the current workflows. Those api will work. These limitations only come into the picture when filters are being used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to leave an api in this broken state though - so if this is going to impact how we implement things, we should be considering that now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We were just planning to do it in iterations. Have a basic application flow working with limitations (not breaking the existing application flow), and then refactor it next to make it perfect. (because, refactoring would take considerable amount of time otherwise, causing delays for Third Party APIs)

We are definitely planning to pick up the refactor as the next task

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't require a big refactor though - that's the part that's confusing me. The refactor would just be in the new code that's being written in these 2 PRs, right? I can definitely be mistaken, but aren't we talking a handful of lines (since we already have the infrastructure to separate out the requests then merge them for the existing code)?

If we choose not to however, can we at least have this error out if we receive an unsupported request? My bigger concern is that certain calls will just result in success but have inaccurate results (because filters get merged that shouldn't be).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already handle multiple entity types through a filter, is this different? You'd need to just change how you build the filter to OR across selection types and then AND for each selection type.

This won't work actually if I am understanding it correctly. Consider this for an example, let's say we query for two edges like this

incomingEdge (scope: SERVICE, filter: {dataType in "dataTypeIds1"})
incomingEdge (scope: API, filter: {dataType in "dataTypeIds2"} }

Now the interaction request to the gateway service will be like this

filter AND [
{ fromEntityType in SERVICE, API}
{ dataType in dataTypeIds1 } OR { dataType in dataTypeIds2 }
]

Now this will return wrong results for both service and api. Since each filter corresponding to the edge is specific to that interaction we cannot do it in a single query. We will need to change the API contract and make gateway service to run each edge query separately. For now, we have made the code error out in case of above-mentioned limitations

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now this will return wrong results for both service and api. Since each filter corresponding to the edge is specific to that interaction we cannot do it in a single query

I was actually suggesting:

filter OR [
{{ fromEntityType = SERVICE} AND { dataType in dataTypeIds1 }}
{{ fromEntityType = API} AND { dataType in dataTypeIds2 }}
]

Would that be problematic?

selectedField ->
this.argumentDeserializer.deserializeObjectList(
selectedField.getArguments(), FilterArgument.class))
.flatMap(arguments -> Stream.of(arguments.orElse(new ArrayList<>())))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}

private Single<AttributeRequest> getNeighborTypeAttribute(
GraphQlRequestContext context, EdgeType edgeType) {
switch (edgeType) {
Expand All @@ -191,12 +214,14 @@ private enum EdgeType {
@Value
@Accessors(fluent = true)
private static class DefaultEdgeSetGroupRequest implements EdgeSetGroupRequest {

Set<String> entityTypes;
Collection<AttributeRequest> attributeRequests;
Collection<MetricAggregationRequest> metricAggregationRequests;
AttributeRequest neighborIdAttribute;
AttributeRequest neighborTypeAttribute;
BiFunction<String, Collection<String>, Single<EntityRequest>> neighborRequestBuilder;
Collection<AttributeAssociation<FilterArgument>> filterArguments;

@Override
public Single<EntityRequest> buildNeighborRequest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import io.reactivex.rxjava3.core.Single;
import java.util.Collection;
import java.util.Set;
import org.hypertrace.core.graphql.common.request.AttributeAssociation;
import org.hypertrace.core.graphql.common.request.AttributeRequest;
import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterArgument;
import org.hypertrace.graphql.metric.request.MetricAggregationRequest;

public interface EdgeSetGroupRequest {
Expand All @@ -20,4 +22,6 @@ public interface EdgeSetGroupRequest {
AttributeRequest neighborTypeAttribute();

Single<EntityRequest> buildNeighborRequest(String entityType, Collection<String> neighborIds);

Collection<AttributeAssociation<FilterArgument>> filterArguments();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import graphql.annotations.annotationTypes.GraphQLField;
import graphql.annotations.annotationTypes.GraphQLName;
import graphql.annotations.annotationTypes.GraphQLNonNull;
import java.util.List;
import org.hypertrace.core.graphql.common.schema.attributes.AttributeQueryable;
import org.hypertrace.core.graphql.common.schema.id.Identifiable;
import org.hypertrace.core.graphql.common.schema.results.arguments.filter.FilterArgument;
import org.hypertrace.core.graphql.common.schema.type.Typed;
import org.hypertrace.graphql.entity.schema.argument.NeighborEntityScopeArgument;
import org.hypertrace.graphql.entity.schema.argument.NeighborEntityTypeArgument;
Expand All @@ -29,14 +31,16 @@ public interface Entity extends AttributeQueryable, MetricQueryable, Identifiabl
@GraphQLName(ENTITY_INCOMING_EDGES_KEY)
EdgeResultSet incomingEdges(
@GraphQLName(NeighborEntityTypeArgument.ARGUMENT_NAME) EntityType neighborType,
@GraphQLName(NeighborEntityScopeArgument.ARGUMENT_NAME) String neighborScope);
@GraphQLName(NeighborEntityScopeArgument.ARGUMENT_NAME) String neighborScope,
@GraphQLName(FilterArgument.ARGUMENT_NAME) List<FilterArgument> filterBy);

@GraphQLField
@GraphQLNonNull
@GraphQLName(ENTITY_OUTGOING_EDGES_KEY)
EdgeResultSet outgoingEdges(
@GraphQLName(NeighborEntityTypeArgument.ARGUMENT_NAME) EntityType neighborType,
@GraphQLName(NeighborEntityScopeArgument.ARGUMENT_NAME) String neighborScope);
@GraphQLName(NeighborEntityScopeArgument.ARGUMENT_NAME) String neighborScope,
@GraphQLName(FilterArgument.ARGUMENT_NAME) List<FilterArgument> filterBy);

@GraphQLField
@GraphQLNonNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,12 +342,14 @@ public Object attribute(AttributeExpression attributeExpression) {
}

@Override
public EdgeResultSet incomingEdges(EntityType neighborType, String neighborScope) {
public EdgeResultSet incomingEdges(
EntityType neighborType, String neighborScope, List<FilterArgument> filterBy) {
return null;
}

@Override
public EdgeResultSet outgoingEdges(EntityType neighborType, String neighborScope) {
public EdgeResultSet outgoingEdges(
EntityType neighborType, String neighborScope, List<FilterArgument> filterBy) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ private static class EmptyEdgeSetGroupRequest implements EdgeSetGroupRequest {
Set<String> entityTypes = Collections.emptySet();
Collection<AttributeRequest> attributeRequests = Collections.emptyList();
Collection<MetricAggregationRequest> metricAggregationRequests = Collections.emptyList();
Collection<AttributeAssociation<FilterArgument>> filterArguments = Collections.emptyList();
AttributeRequest neighborIdAttribute = null;
AttributeRequest neighborTypeAttribute = null;

Expand Down