From 1da37ba9b207dc92bab968480bb8641bebbb2f2e Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Wed, 6 Sep 2023 19:38:53 +0530 Subject: [PATCH 1/6] fix: add topology-edge-filter-support --- .../topology/application-flow.dashboard.ts | 3 + .../topology/topology-data-source.model.ts | 67 +++++++++++++++---- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/projects/observability/src/pages/apis/topology/application-flow.dashboard.ts b/projects/observability/src/pages/apis/topology/application-flow.dashboard.ts index 7887375ea..a5add5d0e 100644 --- a/projects/observability/src/pages/apis/topology/application-flow.dashboard.ts +++ b/projects/observability/src/pages/apis/topology/application-flow.dashboard.ts @@ -9,6 +9,7 @@ import { defaultSecondaryNodeMetricCategories } from '../../../shared/dashboard/widgets/topology/metric/node-metric-category'; import { MetricAggregationType } from '../../../shared/graphql/model/metrics/metric-aggregation'; +import { TopologyEdgeFilterMetadata } from '../../../shared/dashboard/data/graphql/topology/topology-data-source.model'; export const getTopologyJson = (options?: TopologyJsonOptions): ModelJson => ({ type: 'topology-widget', @@ -18,6 +19,7 @@ export const getTopologyJson = (options?: TopologyJsonOptions): ModelJson => ({ type: 'topology-data-source', entity: 'SERVICE', 'downstream-entities': ['SERVICE', 'BACKEND'], + 'edge-filter-metadata': options?.edgeFilterMetadata, 'edge-metrics': { type: 'topology-metrics', primary: { @@ -211,4 +213,5 @@ export const applicationFlowDefaultJson: DashboardDefaultConfiguration = { export interface TopologyJsonOptions { showBrush?: boolean; + edgeFilterMetadata?: TopologyEdgeFilterMetadata; } diff --git a/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts b/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts index 15065fb74..f06592990 100644 --- a/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts +++ b/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts @@ -5,7 +5,8 @@ import { Model, ModelModelPropertyTypeInstance, ModelProperty, - ModelPropertyType + ModelPropertyType, + PLAIN_OBJECT_PROPERTY } from '@hypertrace/hyperdash'; import { uniq } from 'lodash-es'; import { Observable } from 'rxjs'; @@ -22,6 +23,7 @@ import { } from '../../../../graphql/request/handlers/entities/query/topology/entity-topology-graphql-query-handler.service'; import { GraphQlDataSourceModel } from '../graphql-data-source.model'; import { TopologyMetricsData, TopologyMetricsModel } from './metrics/topology-metrics.model'; +import { GraphQlFieldFilter } from '../../../../../public-api'; @Model({ type: 'topology-data-source' @@ -79,6 +81,14 @@ export class TopologyDataSourceModel extends GraphQlDataSourceModel( - filters => ({ + return this.query(filters => { + const filtersAsFieldFilters = filters as GraphQlFieldFilter[]; + + const edgeFilterEntityType = this.edgeFilterMetadata?.entityType; + const edgeFilterFields = this.edgeFilterMetadata?.fields ?? []; + const edgeFilters: GraphQlFieldFilter[] = filtersAsFieldFilters.filter(f => + edgeFilterFields.includes(typeof f.keyOrExpression === 'string' ? f.keyOrExpression : f.keyOrExpression.key) + ); + const nodeFilters = filtersAsFieldFilters.filter( + f => + !edgeFilterFields.includes(typeof f.keyOrExpression === 'string' ? f.keyOrExpression : f.keyOrExpression.key) + ); + + return { requestType: ENTITY_TOPOLOGY_GQL_REQUEST, rootNodeType: this.entityType, rootNodeLimit: 100, rootNodeSpecification: rootEntitySpec, - rootNodeFilters: filters, + rootNodeFilters: nodeFilters, edgeSpecification: edgeSpec, - upstreamNodeSpecifications: this.buildUpstreamSpecifications(), - downstreamNodeSpecifications: this.buildDownstreamSpecifications(), + edgeFilters: edgeFilters, + upstreamNodeSpecifications: this.buildUpstreamSpecifications( + edgeFilters.length > 0 ? edgeFilterEntityType : undefined + ), + downstreamNodeSpecifications: this.buildDownstreamSpecifications( + edgeFilters.length > 0 ? edgeFilterEntityType : undefined + ), timeRange: this.getTimeRangeOrThrow() - }), - this.requestOptions - ).pipe( + }; + }, this.requestOptions).pipe( map(nodes => ({ nodes: nodes, nodeSpecification: rootEntitySpec, @@ -119,14 +145,24 @@ export class TopologyDataSourceModel extends GraphQlDataSourceModel { + private buildDownstreamSpecifications( + edgeFilterEntityType?: string + ): Map { return new Map( - this.defaultedEntityTypeArray(this.downstreamEntityTypes).map(type => [type, this.buildEntitySpec()]) + this.defaultedEntityTypeArray(this.downstreamEntityTypes) + .filter(entityType => (edgeFilterEntityType === undefined ? true : entityType === edgeFilterEntityType)) + .map(type => [type, this.buildEntitySpec()]) ); } - private buildUpstreamSpecifications(): Map { - return new Map(this.defaultedEntityTypeArray(this.upstreamEntityTypes).map(type => [type, this.buildEntitySpec()])); + private buildUpstreamSpecifications( + edgeFilterEntityType?: string + ): Map { + return new Map( + this.defaultedEntityTypeArray(this.upstreamEntityTypes) + .filter(entityType => (edgeFilterEntityType === undefined ? true : entityType === edgeFilterEntityType)) + .map(type => [type, this.buildEntitySpec()]) + ); } private buildEntitySpec(): TopologyNodeSpecification { @@ -160,3 +196,8 @@ export interface TopologyData { nodeMetrics: TopologyMetricsData; edgeMetrics: TopologyMetricsData; } + +export interface TopologyEdgeFilterMetadata { + entityType: string; + fields: string[]; +} From 66844f479166ce8cb458b093775c1b175fc62e1f Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Mon, 11 Sep 2023 13:56:09 +0530 Subject: [PATCH 2/6] fix: code cleanup --- .../topology/application-flow.dashboard.ts | 6 +- .../topology/topology-data-source.model.ts | 77 ++++++++++++------- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/projects/observability/src/pages/apis/topology/application-flow.dashboard.ts b/projects/observability/src/pages/apis/topology/application-flow.dashboard.ts index a5add5d0e..73081961e 100644 --- a/projects/observability/src/pages/apis/topology/application-flow.dashboard.ts +++ b/projects/observability/src/pages/apis/topology/application-flow.dashboard.ts @@ -9,7 +9,7 @@ import { defaultSecondaryNodeMetricCategories } from '../../../shared/dashboard/widgets/topology/metric/node-metric-category'; import { MetricAggregationType } from '../../../shared/graphql/model/metrics/metric-aggregation'; -import { TopologyEdgeFilterMetadata } from '../../../shared/dashboard/data/graphql/topology/topology-data-source.model'; +import { TopologyEdgeFilterConfig } from '../../../shared/dashboard/data/graphql/topology/topology-data-source.model'; export const getTopologyJson = (options?: TopologyJsonOptions): ModelJson => ({ type: 'topology-widget', @@ -19,7 +19,7 @@ export const getTopologyJson = (options?: TopologyJsonOptions): ModelJson => ({ type: 'topology-data-source', entity: 'SERVICE', 'downstream-entities': ['SERVICE', 'BACKEND'], - 'edge-filter-metadata': options?.edgeFilterMetadata, + 'edge-filter-config': options?.edgeFilterConfig, 'edge-metrics': { type: 'topology-metrics', primary: { @@ -213,5 +213,5 @@ export const applicationFlowDefaultJson: DashboardDefaultConfiguration = { export interface TopologyJsonOptions { showBrush?: boolean; - edgeFilterMetadata?: TopologyEdgeFilterMetadata; + edgeFilterConfig?: TopologyEdgeFilterConfig; } diff --git a/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts b/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts index f06592990..4953e9f9f 100644 --- a/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts +++ b/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts @@ -23,7 +23,7 @@ import { } from '../../../../graphql/request/handlers/entities/query/topology/entity-topology-graphql-query-handler.service'; import { GraphQlDataSourceModel } from '../graphql-data-source.model'; import { TopologyMetricsData, TopologyMetricsModel } from './metrics/topology-metrics.model'; -import { GraphQlFieldFilter } from '../../../../../public-api'; +import { AttributeExpression, GraphQlFieldFilter } from '../../../../../public-api'; @Model({ type: 'topology-data-source' @@ -82,12 +82,12 @@ export class TopologyDataSourceModel extends GraphQlDataSourceModel(filters => { - const filtersAsFieldFilters = filters as GraphQlFieldFilter[]; - - const edgeFilterEntityType = this.edgeFilterMetadata?.entityType; - const edgeFilterFields = this.edgeFilterMetadata?.fields ?? []; - const edgeFilters: GraphQlFieldFilter[] = filtersAsFieldFilters.filter(f => - edgeFilterFields.includes(typeof f.keyOrExpression === 'string' ? f.keyOrExpression : f.keyOrExpression.key) - ); - const nodeFilters = filtersAsFieldFilters.filter( - f => - !edgeFilterFields.includes(typeof f.keyOrExpression === 'string' ? f.keyOrExpression : f.keyOrExpression.key) - ); + const topologyFilters = this.getTopologyFilters(filters as GraphQlFieldFilter[]); + const edgeFilterEntityType = this.edgeFilterConfig?.entityType; + const requiredEdgeEntityTypes = + topologyFilters.forEdge.length > 0 && edgeFilterEntityType !== undefined ? [edgeFilterEntityType] : undefined; return { requestType: ENTITY_TOPOLOGY_GQL_REQUEST, rootNodeType: this.entityType, rootNodeLimit: 100, rootNodeSpecification: rootEntitySpec, - rootNodeFilters: nodeFilters, + rootNodeFilters: topologyFilters.forNode, edgeSpecification: edgeSpec, - edgeFilters: edgeFilters, - upstreamNodeSpecifications: this.buildUpstreamSpecifications( - edgeFilters.length > 0 ? edgeFilterEntityType : undefined - ), - downstreamNodeSpecifications: this.buildDownstreamSpecifications( - edgeFilters.length > 0 ? edgeFilterEntityType : undefined - ), + edgeFilters: topologyFilters.forEdge, + upstreamNodeSpecifications: this.buildUpstreamSpecifications(requiredEdgeEntityTypes), + downstreamNodeSpecifications: this.buildDownstreamSpecifications(requiredEdgeEntityTypes), timeRange: this.getTimeRangeOrThrow() }; }, this.requestOptions).pipe( @@ -145,22 +134,51 @@ export class TopologyDataSourceModel extends GraphQlDataSourceModel { + if (edgeFilterFields.includes(this.getFieldFromExpression(fieldFilter.keyOrExpression))) { + edgeFilters.push(fieldFilter); + } else { + nodeFilters.push(fieldFilter); + } + }); + + return { + forNode: nodeFilters, + forEdge: edgeFilters + }; + } + + private getFieldFromExpression(keyOrExpression: string | AttributeExpression): string { + return typeof keyOrExpression === 'string' ? keyOrExpression : keyOrExpression.key; + } + + /** + * @param requiredEntityTypes If given, the function will return all the specs only for given required types + */ private buildDownstreamSpecifications( - edgeFilterEntityType?: string + requiredEntityTypes?: string[] ): Map { return new Map( this.defaultedEntityTypeArray(this.downstreamEntityTypes) - .filter(entityType => (edgeFilterEntityType === undefined ? true : entityType === edgeFilterEntityType)) + .filter(entityType => (requiredEntityTypes === undefined ? true : requiredEntityTypes.includes(entityType))) .map(type => [type, this.buildEntitySpec()]) ); } + /** + * @param requiredEntityTypes If given, the function will return all the specs only for given required types + */ private buildUpstreamSpecifications( - edgeFilterEntityType?: string + requiredEntityTypes?: string[] ): Map { return new Map( this.defaultedEntityTypeArray(this.upstreamEntityTypes) - .filter(entityType => (edgeFilterEntityType === undefined ? true : entityType === edgeFilterEntityType)) + .filter(entityType => (requiredEntityTypes === undefined ? true : requiredEntityTypes.includes(entityType))) .map(type => [type, this.buildEntitySpec()]) ); } @@ -197,7 +215,12 @@ export interface TopologyData { edgeMetrics: TopologyMetricsData; } -export interface TopologyEdgeFilterMetadata { +export interface TopologyEdgeFilterConfig { entityType: string; fields: string[]; } + +interface TopologyFilters { + forNode: GraphQlFieldFilter[]; + forEdge: GraphQlFieldFilter[]; +} From 18207cc946fbf0ce71553eb108bcd5376a9f74cb Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Wed, 13 Sep 2023 18:34:56 +0530 Subject: [PATCH 3/6] fix: tests --- .../topology-data-source.model.test.ts | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.test.ts b/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.test.ts index a8a213c47..674b5c3fd 100644 --- a/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.test.ts +++ b/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.test.ts @@ -13,13 +13,21 @@ import { TopologyMetricCategoryModel } from './metrics/topology-metric-category. import { TopologyMetricWithCategoryModel } from './metrics/topology-metric-with-category.model'; import { TopologyMetricsModel } from './metrics/topology-metrics.model'; import { TopologyDataSourceModel } from './topology-data-source.model'; +import { GraphQlFieldFilter } from '../../../../graphql/model/schema/filter/field/graphql-field-filter'; +import { GraphQlOperatorType } from '../../../../graphql/model/schema/filter/graphql-filter'; describe('topology data source model', () => { const testTimeRange = { startTime: new Date(1568907645141), endTime: new Date(1568911245141) }; let model!: TopologyDataSourceModel; + let totalQueries: number = 0; let lastEmittedQuery: unknown; let lastEmittedQueryRequestOption: GraphQlRequestOptions | undefined; + const filters = [ + new GraphQlFieldFilter('service_id', GraphQlOperatorType.Equals, 'test-id'), + new GraphQlFieldFilter('backend_id', GraphQlOperatorType.Equals, 'test-backend-id') + ]; + const createCategoryModel = ( name: string, minValue: number, @@ -81,17 +89,24 @@ describe('topology data source model', () => { model.entityType = ObservabilityEntityType.Service; model.nodeMetricsModel = createTopologyMetricsModel('numCalls', MetricAggregationType.Average); model.edgeMetricsModel = createTopologyMetricsModel('duration', MetricAggregationType.Average); + model.edgeFilterConfig = { entityType: ObservabilityEntityType.Backend, fields: ['backend_id'] }; model.api = mockApi as ModelApi; model.query$.subscribe(query => { - lastEmittedQuery = query.buildRequest([]); + if (totalQueries === 0) { + // Without filters + lastEmittedQuery = query.buildRequest([]); + } else { + // With filters + lastEmittedQuery = query.buildRequest(filters); + } lastEmittedQueryRequestOption = query.requestOptions; + totalQueries += 1; }); model.getData(); }); - test('builds expected request', () => { - model.getData(); + test('builds expected request without filters', () => { expect(lastEmittedQuery).toEqual({ requestType: ENTITY_TOPOLOGY_GQL_REQUEST, rootNodeType: ObservabilityEntityType.Service, @@ -102,6 +117,7 @@ describe('topology data source model', () => { ] }, rootNodeFilters: [], + edgeFilters: [], rootNodeLimit: 100, timeRange: new GraphQlTimeRange(testTimeRange.startTime, testTimeRange.endTime), downstreamNodeSpecifications: new Map([ @@ -147,4 +163,38 @@ describe('topology data source model', () => { isolated: true }); }); + + test('builds expected request with filters', () => { + expect(lastEmittedQuery).toEqual({ + requestType: ENTITY_TOPOLOGY_GQL_REQUEST, + rootNodeType: ObservabilityEntityType.Service, + rootNodeSpecification: { + titleSpecification: expect.objectContaining({ name: 'name' }), + metricSpecifications: [ + expect.objectContaining({ metric: 'numCalls', aggregation: MetricAggregationType.Average }) + ] + }, + rootNodeFilters: [filters[0]], + edgeFilters: [filters[1]], + rootNodeLimit: 100, + timeRange: new GraphQlTimeRange(testTimeRange.startTime, testTimeRange.endTime), + downstreamNodeSpecifications: new Map([ + [ + ObservabilityEntityType.Backend, + { + titleSpecification: expect.objectContaining({ name: 'name' }), + metricSpecifications: [ + expect.objectContaining({ metric: 'numCalls', aggregation: MetricAggregationType.Average }) + ] + } + ] + ]), + upstreamNodeSpecifications: new Map(), + edgeSpecification: { + metricSpecifications: [ + expect.objectContaining({ metric: 'duration', aggregation: MetricAggregationType.Average }) + ] + } + }); + }); }); From d9bf05f21a9e792dd69c7bf4da80414143cf0a78 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Thu, 14 Sep 2023 13:39:31 +0530 Subject: [PATCH 4/6] fix: addressing review comments --- .../topology/topology-data-source.model.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts b/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts index 4953e9f9f..8f07939fa 100644 --- a/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts +++ b/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts @@ -23,7 +23,8 @@ import { } from '../../../../graphql/request/handlers/entities/query/topology/entity-topology-graphql-query-handler.service'; import { GraphQlDataSourceModel } from '../graphql-data-source.model'; import { TopologyMetricsData, TopologyMetricsModel } from './metrics/topology-metrics.model'; -import { AttributeExpression, GraphQlFieldFilter } from '../../../../../public-api'; +import { GraphQlFieldFilter } from '../../../../graphql/model/schema/filter/field/graphql-field-filter'; +import { AttributeExpression } from '../../../../graphql/model/attribute/attribute-expression'; @Model({ type: 'topology-data-source' @@ -104,16 +105,16 @@ export class TopologyDataSourceModel extends GraphQlDataSourceModel 0 && edgeFilterEntityType !== undefined ? [edgeFilterEntityType] : undefined; + topologyFilters.edges.length > 0 && edgeFilterEntityType !== undefined ? [edgeFilterEntityType] : undefined; return { requestType: ENTITY_TOPOLOGY_GQL_REQUEST, rootNodeType: this.entityType, rootNodeLimit: 100, rootNodeSpecification: rootEntitySpec, - rootNodeFilters: topologyFilters.forNode, + rootNodeFilters: topologyFilters.nodes, edgeSpecification: edgeSpec, - edgeFilters: topologyFilters.forEdge, + edgeFilters: topologyFilters.edges, upstreamNodeSpecifications: this.buildUpstreamSpecifications(requiredEdgeEntityTypes), downstreamNodeSpecifications: this.buildDownstreamSpecifications(requiredEdgeEntityTypes), timeRange: this.getTimeRangeOrThrow() @@ -148,8 +149,8 @@ export class TopologyDataSourceModel extends GraphQlDataSourceModel Date: Fri, 15 Sep 2023 18:41:41 +0530 Subject: [PATCH 5/6] fix: check for field filters --- .../topology/topology-data-source.model.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts b/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts index 8f07939fa..613f8d206 100644 --- a/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts +++ b/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts @@ -25,6 +25,7 @@ import { GraphQlDataSourceModel } from '../graphql-data-source.model'; import { TopologyMetricsData, TopologyMetricsModel } from './metrics/topology-metrics.model'; import { GraphQlFieldFilter } from '../../../../graphql/model/schema/filter/field/graphql-field-filter'; import { AttributeExpression } from '../../../../graphql/model/attribute/attribute-expression'; +import { GraphQlFilter } from '../../../../../public-api'; @Model({ type: 'topology-data-source' @@ -102,7 +103,7 @@ export class TopologyDataSourceModel extends GraphQlDataSourceModel(filters => { - const topologyFilters = this.getTopologyFilters(filters as GraphQlFieldFilter[]); + const topologyFilters = this.getTopologyFilters(filters); const edgeFilterEntityType = this.edgeFilterConfig?.entityType; const requiredEdgeEntityTypes = topologyFilters.edges.length > 0 && edgeFilterEntityType !== undefined ? [edgeFilterEntityType] : undefined; @@ -135,16 +136,23 @@ export class TopologyDataSourceModel extends GraphQlDataSourceModel { - if (edgeFilterFields.includes(this.getFieldFromExpression(fieldFilter.keyOrExpression))) { - edgeFilters.push(fieldFilter); + const edgeFilters: GraphQlFilter[] = []; + const nodeFilters: GraphQlFilter[] = []; + + const isFieldFilter = (gqlFilter: GraphQlFilter): gqlFilter is GraphQlFieldFilter => + 'keyOrExpression' in gqlFilter && 'operator' in gqlFilter && 'value' in gqlFilter; + + filters.forEach(gqlFilter => { + // Edge filter only supported for `GraphQlFieldFilter` for now + if ( + isFieldFilter(gqlFilter) && + edgeFilterFields.includes(this.getFieldFromExpression(gqlFilter.keyOrExpression)) + ) { + edgeFilters.push(gqlFilter); } else { - nodeFilters.push(fieldFilter); + nodeFilters.push(gqlFilter); } }); @@ -222,6 +230,6 @@ export interface TopologyEdgeFilterConfig { } interface TopologyFilters { - nodes: GraphQlFieldFilter[]; - edges: GraphQlFieldFilter[]; + nodes: GraphQlFilter[]; + edges: GraphQlFilter[]; } From 78060f35e758b8d3ccf484153b2b1c2313438393 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Fri, 15 Sep 2023 18:43:14 +0530 Subject: [PATCH 6/6] fix: update import --- .../data/graphql/topology/topology-data-source.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts b/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts index 613f8d206..69c77f7aa 100644 --- a/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts +++ b/projects/observability/src/shared/dashboard/data/graphql/topology/topology-data-source.model.ts @@ -25,7 +25,7 @@ import { GraphQlDataSourceModel } from '../graphql-data-source.model'; import { TopologyMetricsData, TopologyMetricsModel } from './metrics/topology-metrics.model'; import { GraphQlFieldFilter } from '../../../../graphql/model/schema/filter/field/graphql-field-filter'; import { AttributeExpression } from '../../../../graphql/model/attribute/attribute-expression'; -import { GraphQlFilter } from '../../../../../public-api'; +import { GraphQlFilter } from '../../../../graphql/model/schema/filter/graphql-filter'; @Model({ type: 'topology-data-source'