From 796acd371b0ae5165d98ff8470e97f6feff0cc9e Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Fri, 2 Jul 2021 20:37:08 +0530 Subject: [PATCH 1/2] feat: custom metrics support for topology-widget --- .../topology/application-flow.component.ts | 173 +++++++++++------- .../topology/topology-data-source.model.ts | 46 ++--- .../curved/entity-edge-curve-renderer.scss | 62 +------ .../entity-edge-curve-renderer.service.ts | 138 +++++++++----- .../topology/metric/edge-metric-category.ts | 129 +++++++++++++ .../topology/metric/metric-category.ts | 84 +++------ .../widgets/topology/metric/metric.ts | 23 +++ .../topology/metric/node-metric-category.ts | 131 +++++++++++++ .../api-node-box-renderer.service.ts | 2 +- .../backend-node-box-renderer.service.ts | 2 +- .../node/box/entity-node-box-renderer.scss | 50 ----- .../box/entity-node-box-renderer.service.ts | 89 ++++++--- .../service-node-box-renderer.service.ts | 2 +- ...gy-data-source-model-properties.service.ts | 28 +++ .../topology-widget-renderer.component.ts | 18 +- 15 files changed, 639 insertions(+), 338 deletions(-) create mode 100644 projects/observability/src/shared/dashboard/widgets/topology/metric/edge-metric-category.ts create mode 100644 projects/observability/src/shared/dashboard/widgets/topology/metric/metric.ts create mode 100644 projects/observability/src/shared/dashboard/widgets/topology/metric/node-metric-category.ts create mode 100644 projects/observability/src/shared/dashboard/widgets/topology/topology-data-source-model-properties.service.ts diff --git a/projects/observability/src/pages/apis/topology/application-flow.component.ts b/projects/observability/src/pages/apis/topology/application-flow.component.ts index d0344d1d6..3d66e059f 100644 --- a/projects/observability/src/pages/apis/topology/application-flow.component.ts +++ b/projects/observability/src/pages/apis/topology/application-flow.component.ts @@ -1,6 +1,8 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { Color } from '@hypertrace/common'; import { MetricAggregationType } from '@hypertrace/distributed-tracing'; import { ModelJson } from '@hypertrace/hyperdash'; +import { SecondaryNodeMetricCategoryValueType } from '../../../shared/dashboard/widgets/topology/metric/node-metric-category'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -26,78 +28,113 @@ export class ApplicationFlowComponent { type: 'topology-data-source', entity: 'SERVICE', 'downstream-entities': ['SERVICE', 'BACKEND'], - 'node-metrics': [ - { - type: 'percentile-latency-metric-aggregation', - 'display-name': 'P99 Latency' + 'node-metrics': { + primary: { + specification: { + type: 'percentile-latency-metric-aggregation', + 'display-name': 'P99 Latency' + } }, - { - type: 'metric-aggregation', - metric: 'duration', - aggregation: MetricAggregationType.P50, - 'display-name': 'P50 Latency' + secondary: { + specification: { + type: 'error-percentage-metric-aggregation', + aggregation: MetricAggregationType.Average, + 'display-name': 'Error %' + }, + categories: [ + { + value: SecondaryNodeMetricCategoryValueType.GreaterThanOrEqualTo5, + color: Color.Orange3, + secondaryColor: Color.Orange5, + focusedColor: Color.Orange3, + categoryClass: 'greater-than-or-equal-to-5-secondary-category' + } + ] }, - { - type: 'error-percentage-metric-aggregation', - aggregation: MetricAggregationType.Average, - 'display-name': 'Error %' + others: [ + { + specification: { + type: 'metric-aggregation', + metric: 'duration', + aggregation: MetricAggregationType.P50, + 'display-name': 'P50 Latency' + } + }, + { + specification: { + type: 'metric-aggregation', + metric: 'errorCount', + aggregation: MetricAggregationType.Sum, + 'display-name': 'Error Count' + } + }, + { + specification: { + type: 'metric-aggregation', + metric: 'numCalls', + aggregation: MetricAggregationType.AvgrateSecond, + 'display-name': 'Call Rate/sec' + } + }, + { + specification: { + type: 'metric-aggregation', + metric: 'numCalls', + aggregation: MetricAggregationType.Sum, + 'display-name': 'Call Count' + } + } + ] + }, + 'edge-metrics': { + primary: { + specification: { + type: 'percentile-latency-metric-aggregation', + 'display-name': 'P99 Latency' + } }, - - { - type: 'metric-aggregation', - metric: 'errorCount', - aggregation: MetricAggregationType.Sum, - 'display-name': 'Error Count' - }, - { - type: 'metric-aggregation', - metric: 'numCalls', - aggregation: MetricAggregationType.AvgrateSecond, - 'display-name': 'Call Rate/sec' - }, - { - type: 'metric-aggregation', - metric: 'numCalls', - aggregation: MetricAggregationType.Sum, - 'display-name': 'Call Count' - } - ], - 'edge-metrics': [ - { - type: 'percentile-latency-metric-aggregation', - 'display-name': 'P99 Latency' - }, - { - type: 'metric-aggregation', - metric: 'duration', - aggregation: MetricAggregationType.P50, - 'display-name': 'P50 Latency' - }, - { - type: 'error-percentage-metric-aggregation', - aggregation: MetricAggregationType.Average, - 'display-name': 'Error %' - }, - - { - type: 'metric-aggregation', - metric: 'errorCount', - aggregation: MetricAggregationType.Sum, - 'display-name': 'Error Count' - }, - { - type: 'metric-aggregation', - metric: 'numCalls', - aggregation: MetricAggregationType.AvgrateSecond, - 'display-name': 'Call Rate/sec' + secondary: { + specification: { + type: 'error-percentage-metric-aggregation', + aggregation: MetricAggregationType.Average, + 'display-name': 'Error %' + } }, - { - type: 'metric-aggregation', - metric: 'numCalls', - aggregation: MetricAggregationType.Sum, - 'display-name': 'Call Count' - } - ] + others: [ + { + specification: { + type: 'metric-aggregation', + metric: 'duration', + aggregation: MetricAggregationType.P50, + 'display-name': 'P50 Latency' + } + }, + { + specification: { + type: 'metric-aggregation', + metric: 'errorCount', + aggregation: MetricAggregationType.Sum, + 'display-name': 'Error Count' + } + }, + { + specification: { + type: 'metric-aggregation', + metric: 'numCalls', + aggregation: MetricAggregationType.AvgrateSecond, + 'display-name': 'Call Rate/sec' + } + }, + { + specification: { + type: 'metric-aggregation', + metric: 'numCalls', + aggregation: MetricAggregationType.Sum, + 'display-name': 'Call Count' + } + } + ] + } } } ] 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 2cf33f001..3e8cd3a73 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 @@ -1,7 +1,8 @@ +import { Dictionary } from '@hypertrace/common'; import { ArrayPropertyTypeInstance, EnumPropertyTypeInstance, ENUM_TYPE } from '@hypertrace/dashboards'; import { GraphQlDataSourceModel, SpecificationBuilder } from '@hypertrace/distributed-tracing'; import { GraphQlRequestCacheability, GraphQlRequestOptions } from '@hypertrace/graphql-client'; -import { ARRAY_PROPERTY, Model, ModelProperty, ModelPropertyType } from '@hypertrace/hyperdash'; +import { ARRAY_PROPERTY, Model, ModelProperty, PLAIN_OBJECT_PROPERTY } from '@hypertrace/hyperdash'; import { uniq } from 'lodash-es'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -14,7 +15,7 @@ import { TopologyEdgeSpecification, TopologyNodeSpecification } from '../../../../graphql/request/handlers/entities/query/topology/entity-topology-graphql-query-handler.service'; -import { EntityMetricAggregationDataSourceModel } from '../entity/aggregation/entity-metric-aggregation-data-source.model'; +import { MetricData } from '../../../widgets/topology/metric/metric'; @Model({ type: 'topology-data-source' @@ -59,29 +60,15 @@ export class TopologyDataSourceModel extends GraphQlDataSourceModel { const rootEntitySpec = this.buildEntitySpec(); const edgeSpec = { - metricSpecifications: this.edgeMetricSpecifications + metricSpecifications: this.getAllMetricSpecifications(this.edgeMetrics) }; return this.query( @@ -116,7 +103,11 @@ export class TopologyDataSourceModel extends GraphQlDataSourceModel _.specification) : []) + ]; + } } export interface TopologyData { @@ -151,4 +150,5 @@ export interface TopologyData { nodeTypes: ObservabilityEntityType[]; nodeSpecification: TopologyNodeSpecification; edgeSpecification: TopologyEdgeSpecification; + modelProperties: Dictionary; } diff --git a/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.scss b/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.scss index cb316fac5..855f71e51 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.scss +++ b/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.scss @@ -18,7 +18,7 @@ stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; - stroke: currentColor; + stroke: $gray-3; fill: none; @include chart-small-regular; @@ -34,7 +34,6 @@ .edge-path { stroke-dasharray: 2, 2; stroke-width: 1px; - stroke: $gray-3; } &.background { @@ -50,7 +49,6 @@ &.focused { .entity-edge-metric-bubble { @include show; - fill: currentColor; } .entity-edge-metric-value { @@ -65,35 +63,6 @@ .edge-path { stroke-dasharray: none; stroke-width: 1.5px; - stroke: currentColor; - } - - &.less-than-20-category { - color: $blue-gray-1; - } - - &.from-20-to-100-category { - color: $blue-gray-2; - } - - &.from-100-to-500-category { - color: $blue-gray-3; - } - - &.from-500-to-1000-category { - color: $blue-gray-4; - } - - &.greater-than-or-equal-to-1000-category { - color: $blue-gray-5; - } - - &.not-specified-category { - color: lightgray; - } - - &.greater-than-or-equal-to-5-error-category { - color: $red-5; } } } @@ -101,35 +70,6 @@ .entity-edge-arrow { stroke-linecap: round; stroke-linejoin: round; - fill: currentColor; - - &.less-than-20-category { - color: $blue-gray-1; - } - - &.from-20-to-100-category { - color: $blue-gray-2; - } - - &.from-100-to-500-category { - color: $blue-gray-3; - } - - &.from-500-to-1000-category { - color: $blue-gray-4; - } - - &.greater-than-or-equal-to-1000-category { - color: $blue-gray-5; - } - - &.not-specified-category { - color: lightgray; - } - - &.greater-than-or-equal-to-5-error-category { - color: $red-5; - } } } } diff --git a/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts b/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts index a60671e7f..ca43794aa 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts +++ b/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts @@ -1,11 +1,8 @@ import { Injectable, Renderer2 } from '@angular/core'; -import { DomElementMeasurerService, NumericFormatter, selector } from '@hypertrace/common'; +import { Color, DomElementMeasurerService, NumericFormatter, selector } from '@hypertrace/common'; +import { MetricAggregation } from '@hypertrace/distributed-tracing'; import { select, Selection } from 'd3-selection'; import { linkHorizontal } from 'd3-shape'; -import { - ErrorPercentageMetricAggregation, - ErrorPercentageMetricValueCategory -} from '../../../../..//graphql/model/schema/specifications/error-percentage-aggregation-specification'; import { TopologyEdgePositionInformation, TopologyEdgeRenderDelegate @@ -19,19 +16,19 @@ import { import { D3UtilService } from '../../../../../components/utils/d3/d3-util.service'; import { SvgUtilService } from '../../../../../components/utils/svg/svg-util.service'; import { MetricAggregationSpecification } from '../../../../../graphql/model/schema/specifications/metric-aggregation-specification'; -import { PercentileLatencyMetricAggregation } from '../../../../../graphql/model/schema/specifications/percentile-latency-aggregation-specification'; import { EntityEdge } from '../../../../../graphql/request/handlers/entities/query/topology/entity-topology-graphql-query-handler.service'; import { - allErrorPercentageMetricCategories, - allLatencyMetricCategories, - getErrorPercentageCategoryClass, - getErrorPercentageMetric, - getLatencyCategoryClass, - getLatencyMetric -} from '../../metric/metric-category'; + EdgeMetricCategory, + getAllCategories, + getPrimaryEdgeMetricCategory, + getSecondaryEdgeMetricCategory, + SecondaryEdgeMetricCategoryValueType +} from '../../metric/edge-metric-category'; +import { getTopologyMetric } from '../../metric/metric-category'; +import { TopologyDataSourceModelPropertiesService } from '../../topology-data-source-model-properties.service'; import { VisibilityUpdater } from '../../visibility-updater'; -@Injectable({ providedIn: 'root' }) +@Injectable() export class EntityEdgeCurveRendererService implements TopologyEdgeRenderDelegate { private readonly edgeClass: string = 'entity-edge'; private readonly edgeLineClass: string = 'entity-edge-line'; @@ -41,10 +38,13 @@ export class EntityEdgeCurveRendererService implements TopologyEdgeRenderDelegat private readonly numberFormatter: NumericFormatter = new NumericFormatter(); private readonly visibilityUpdater: VisibilityUpdater = new VisibilityUpdater(); + private allEdgeCategories: EdgeMetricCategory[] = []; + public constructor( private readonly domElementMeasurerService: DomElementMeasurerService, private readonly svgUtils: SvgUtilService, - private readonly d3Utils: D3UtilService + private readonly d3Utils: D3UtilService, + private readonly topologyDataSourceModelPropertiesService: TopologyDataSourceModelPropertiesService ) {} public matches(edge: TopologyEdge & Partial): edge is EntityEdge { @@ -58,6 +58,10 @@ export class EntityEdgeCurveRendererService implements TopologyEdgeRenderDelegat state: TopologyEdgeState, domRenderer: Renderer2 ): void { + const primaryMetric = this.topologyDataSourceModelPropertiesService.getPrimaryEdgeMetric(); + const secondaryMetric = this.topologyDataSourceModelPropertiesService.geSecondaryEdgeMetric(); + this.allEdgeCategories = getAllCategories(primaryMetric?.categories, secondaryMetric?.categories); + this.defineArrowMarkersIfNeeded(element, domRenderer); this.d3Utils .select(element, domRenderer) @@ -90,13 +94,27 @@ export class EntityEdgeCurveRendererService implements TopologyEdgeRenderDelegat domRenderer: Renderer2 ): void { const selection = this.d3Utils.select(element, domRenderer); - const metricSpecifications = state.dataSpecifiers?.map(specifier => specifier.value); + + const primaryMetric = this.topologyDataSourceModelPropertiesService.getPrimaryEdgeMetric(); + const primaryMetricAggregation = getTopologyMetric(edge.data, primaryMetric?.specification); + const primaryMetricCategory = getPrimaryEdgeMetricCategory( + primaryMetricAggregation?.value, + primaryMetric?.categories + ); + const secondaryMetric = this.topologyDataSourceModelPropertiesService.geSecondaryEdgeMetric(); + const secondaryMetricAggregation = getTopologyMetric(edge.data, secondaryMetric?.specification); + const secondaryMetricCategory = getSecondaryEdgeMetricCategory( + secondaryMetricAggregation?.value, + secondaryMetric?.categories + ); this.updateEdgeMetric( selection, state.visibility, - getLatencyMetric(edge.data, metricSpecifications), - getErrorPercentageMetric(edge.data, metricSpecifications) + primaryMetricCategory, + secondaryMetricCategory, + primaryMetricAggregation, + secondaryMetricAggregation ); this.visibilityUpdater.updateVisibility(selection, state.visibility); @@ -107,22 +125,41 @@ export class EntityEdgeCurveRendererService implements TopologyEdgeRenderDelegat protected updateEdgeMetric( selection: Selection, visibility: TopologyElementVisibility, - latencyMetric?: PercentileLatencyMetricAggregation, - errorPercentageMetric?: ErrorPercentageMetricAggregation + primaryMetricCategory?: EdgeMetricCategory, + secondaryMetricCategory?: EdgeMetricCategory, + primaryMetricAggregation?: MetricAggregation, + secondaryMetricAggregation?: MetricAggregation ): void { - const edgeCategoryClass = this.getEdgeCategoryClass(latencyMetric, errorPercentageMetric); - selection.classed(this.getAllCategoryClasses().join(' '), false).classed(edgeCategoryClass, true); + const edgeFocusedCategory = this.isEmphasizedOrFocused(visibility) + ? this.getEdgeFocusedCategory(primaryMetricCategory, secondaryMetricCategory) + : undefined; + + selection + .select(selector(this.edgeLineClass)) + .select('.edge-path') + .attr('stroke', edgeFocusedCategory?.color ?? Color.Gray3); + selection + .select(selector(this.edgeMetricBubbleClass)) + .attr('fill', edgeFocusedCategory?.color ?? '') + .attr('stroke', edgeFocusedCategory?.color ?? 'none'); selection .select(selector(this.edgeMetricValueClass)) - .text(this.getMetricValueString(latencyMetric, errorPercentageMetric)); + .text( + this.getMetricValueString( + primaryMetricAggregation, + secondaryMetricAggregation, + primaryMetricCategory, + secondaryMetricCategory + ) + ); selection .select(selector(this.edgeLineClass)) .attr( 'marker-end', - visibility === TopologyElementVisibility.Emphasized || visibility === TopologyElementVisibility.Focused - ? `url(#${this.getMarkerIdForCategory(edgeCategoryClass)})` + this.isEmphasizedOrFocused(visibility) + ? `url(#${this.getMarkerIdForCategory(edgeFocusedCategory?.categoryClass ?? '')})` : 'none' ); } @@ -146,19 +183,22 @@ export class EntityEdgeCurveRendererService implements TopologyEdgeRenderDelegat this.d3Utils .select(this.svgUtils.addDefinitionDeclarationToSvgIfNotExists(edgeElement, domRenderer), domRenderer) .selectAll('marker') - .data(this.getAllCategoryClasses()) + .data(this.allEdgeCategories) .enter() .append('marker') - .attr('id', category => this.getMarkerIdForCategory(category)) + .attr('id', category => this.getMarkerIdForCategory(category.categoryClass!)) .attr('viewBox', '0 0 10 10') .attr('refX', 5) .attr('refY', 5) .attr('markerWidth', 12) .attr('markerHeight', 12) .attr('orient', 'auto-start-reverse') + .attr('fill', category => category.color) .append('path') .classed(this.edgeArrowClass, true) - .each((category, index, elements) => this.d3Utils.select(elements[index], domRenderer).classed(category, true)) + .each((category, index, elements) => + this.d3Utils.select(elements[index], domRenderer).classed(category.categoryClass!, true) + ) .attr('d', 'M2,2 L5,5 L2,8'); } @@ -227,41 +267,41 @@ export class EntityEdgeCurveRendererService implements TopologyEdgeRenderDelegat return 2; } - protected getAllCategoryClasses(): string[] { - return [ - ...allLatencyMetricCategories.map(getLatencyCategoryClass), - ...allErrorPercentageMetricCategories.map(getErrorPercentageCategoryClass) - ]; + private isEmphasizedOrFocused(visibility: TopologyElementVisibility): boolean { + return visibility === TopologyElementVisibility.Emphasized || visibility === TopologyElementVisibility.Focused; } - private getEdgeCategoryClass( - latencyMetric?: PercentileLatencyMetricAggregation, - errorPercentageMetric?: ErrorPercentageMetricAggregation - ): string { - if (errorPercentageMetric?.category === ErrorPercentageMetricValueCategory.GreaterThanOrEqualTo5) { - return getErrorPercentageCategoryClass(errorPercentageMetric?.category); + private getEdgeFocusedCategory( + primaryMetricCategory?: EdgeMetricCategory, + secondaryMetricCategory?: EdgeMetricCategory + ): EdgeMetricCategory | undefined { + if (secondaryMetricCategory?.value === SecondaryEdgeMetricCategoryValueType.GreaterThanOrEqualTo5) { + return secondaryMetricCategory; } - if (latencyMetric) { - return getLatencyCategoryClass(latencyMetric.category); + if (primaryMetricCategory) { + return primaryMetricCategory; } - return ''; + return undefined; } private getMetricValueString( - latencyMetric?: PercentileLatencyMetricAggregation, - errorPercentageMetric?: ErrorPercentageMetricAggregation + primaryMetricAggregation?: MetricAggregation, + secondaryMetricAggregation?: MetricAggregation, + primaryMetricCategory?: EdgeMetricCategory, + secondaryMetricCategory?: EdgeMetricCategory ): string { if ( - errorPercentageMetric && - (errorPercentageMetric.category === ErrorPercentageMetricValueCategory.GreaterThanOrEqualTo5 || !latencyMetric) + secondaryMetricCategory && + (secondaryMetricCategory.categoryClass === SecondaryEdgeMetricCategoryValueType.GreaterThanOrEqualTo5 || + !primaryMetricCategory) ) { - return this.formattedMetricValue(errorPercentageMetric.value, errorPercentageMetric.units); + return this.formattedMetricValue(secondaryMetricAggregation!.value, secondaryMetricAggregation?.units); } - if (latencyMetric) { - return this.formattedMetricValue(latencyMetric.value, latencyMetric.units); + if (primaryMetricCategory) { + return this.formattedMetricValue(primaryMetricAggregation!.value, primaryMetricAggregation?.units); } return '-'; diff --git a/projects/observability/src/shared/dashboard/widgets/topology/metric/edge-metric-category.ts b/projects/observability/src/shared/dashboard/widgets/topology/metric/edge-metric-category.ts new file mode 100644 index 000000000..46a5e3c22 --- /dev/null +++ b/projects/observability/src/shared/dashboard/widgets/topology/metric/edge-metric-category.ts @@ -0,0 +1,129 @@ +import { Color } from '@hypertrace/common'; +import { isEmpty } from 'lodash-es'; + +export interface EdgeMetricCategory { + value: EdgeMetricCategoryValueType; + categoryClass?: string; // Can be used as selector class + color: string; // Color for edge, arrow and bubble +} + +export type EdgeMetricCategoryValueType = PrimaryEdgeMetricCategoryValueType | SecondaryEdgeMetricCategoryValueType; + +export enum PrimaryEdgeMetricCategoryValueType { + LessThan20 = 'less-than-20', + From20To100 = 'from-20-to-100', + From100To500 = 'from-100-to-500', + From500To1000 = 'from-500-to-1000', + GreaterThanOrEqualTo1000 = 'greater-than-or-equal-to-1000', + NotSpecified = 'not-specified' +} + +export enum SecondaryEdgeMetricCategoryValueType { + LessThan5 = 'less-than-5', + GreaterThanOrEqualTo5 = 'greater-than-or-equal-to-5', + NotSpecified = 'not-specified' +} + +export const defaultPrimaryEdgeMetricCategories: EdgeMetricCategory[] = [ + { + value: PrimaryEdgeMetricCategoryValueType.LessThan20, + color: Color.BlueGray1, + categoryClass: 'less-than-20-primary-category' + }, + { + value: PrimaryEdgeMetricCategoryValueType.From20To100, + color: Color.BlueGray2, + categoryClass: 'from-20-to-100-primary-category' + }, + { + value: PrimaryEdgeMetricCategoryValueType.From100To500, + color: Color.BlueGray3, + categoryClass: 'from-100-to-500-primary-category' + }, + { + value: PrimaryEdgeMetricCategoryValueType.From500To1000, + color: Color.BlueGray4, + categoryClass: 'from-500-to-1000-primary-category' + }, + { + value: PrimaryEdgeMetricCategoryValueType.GreaterThanOrEqualTo1000, + color: Color.BlueGray5, + categoryClass: 'greater-than-or-equal-to-1000-primary-category' + }, + { + value: PrimaryEdgeMetricCategoryValueType.NotSpecified, + color: 'lightgray', + categoryClass: 'not-specified-primary-category' + } +]; + +export const defaultSecondaryEdgeMetricCategories: EdgeMetricCategory[] = [ + { + value: SecondaryEdgeMetricCategoryValueType.LessThan5, + color: Color.Gray2, + categoryClass: 'less-than-5-secondary-category' + }, + { + value: SecondaryEdgeMetricCategoryValueType.GreaterThanOrEqualTo5, + color: Color.Red5, + categoryClass: 'greater-than-or-equal-to-5-secondary-category' + }, + { + value: SecondaryEdgeMetricCategoryValueType.NotSpecified, + color: Color.Gray2, + categoryClass: 'not-specified-secondary-category' + } +]; + +export const getPrimaryEdgeMetricCategory = ( + value?: number, + categories?: EdgeMetricCategory[] +): EdgeMetricCategory | undefined => { + const primaryCategories = !isEmpty(categories) ? categories : defaultPrimaryEdgeMetricCategories; + if (value === undefined) { + return primaryCategories?.find(category => category.value === PrimaryEdgeMetricCategoryValueType.NotSpecified); + } + + if (value < 20) { + return primaryCategories?.find(category => category.value === PrimaryEdgeMetricCategoryValueType.LessThan20); + } + + if (value >= 20 && value < 100) { + return primaryCategories?.find(category => category.value === PrimaryEdgeMetricCategoryValueType.From20To100); + } + + if (value >= 100 && value < 500) { + return primaryCategories?.find(category => category.value === PrimaryEdgeMetricCategoryValueType.From100To500); + } + + if (value >= 500 && value < 1000) { + return primaryCategories?.find(category => category.value === PrimaryEdgeMetricCategoryValueType.From500To1000); + } + + return primaryCategories?.find( + category => category.value === PrimaryEdgeMetricCategoryValueType.GreaterThanOrEqualTo1000 + ); +}; + +export const getSecondaryEdgeMetricCategory = (value?: number, categories?: EdgeMetricCategory[]) => { + const secondaryCategories = !isEmpty(categories) ? categories : defaultSecondaryEdgeMetricCategories; + if (value === undefined) { + return secondaryCategories?.find(category => category.value === SecondaryEdgeMetricCategoryValueType.NotSpecified); + } + + if (value < 5) { + return secondaryCategories?.find(category => category.value === SecondaryEdgeMetricCategoryValueType.LessThan5); + } + + return secondaryCategories?.find( + category => category.value === SecondaryEdgeMetricCategoryValueType.GreaterThanOrEqualTo5 + ); +}; + +export const getAllCategories = ( + primaryCategories?: EdgeMetricCategory[], + secondaryCategories?: EdgeMetricCategory[] +): EdgeMetricCategory[] => [ + ...(!isEmpty(primaryCategories) ? primaryCategories! : defaultPrimaryEdgeMetricCategories), + ...(!isEmpty(secondaryCategories) ? secondaryCategories! : defaultSecondaryEdgeMetricCategories) +]; diff --git a/projects/observability/src/shared/dashboard/widgets/topology/metric/metric-category.ts b/projects/observability/src/shared/dashboard/widgets/topology/metric/metric-category.ts index 243ed62f4..710eed4e9 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/metric/metric-category.ts +++ b/projects/observability/src/shared/dashboard/widgets/topology/metric/metric-category.ts @@ -1,71 +1,35 @@ import { Dictionary } from '@hypertrace/common'; -import { MetricAggregation, MetricAggregationType } from '@hypertrace/distributed-tracing'; -import { - ErrorPercentageMetricAggregation, - ErrorPercentageMetricValueCategory -} from '../../../../graphql/model/schema/specifications/error-percentage-aggregation-specification'; +import { MetricAggregation } from '@hypertrace/distributed-tracing'; +import { isEmpty } from 'lodash-es'; import { MetricAggregationSpecification } from '../../../../graphql/model/schema/specifications/metric-aggregation-specification'; -import { - PercentileLatencyMetricAggregation, - PercentileLatencyMetricValueCategory -} from '../../../../graphql/model/schema/specifications/percentile-latency-aggregation-specification'; +import { EdgeMetricCategory } from './edge-metric-category'; +import { NodeMetricCategory } from './node-metric-category'; -export const getLatencyMetric = ( +export const getTopologyMetric = ( data: Dictionary, - metricSpecifications?: MetricAggregationSpecification[] -): PercentileLatencyMetricAggregation | undefined => { - const latencySpecification = metricSpecifications?.find( - specification => specification.name === 'p99Latency' && specification.aggregation === MetricAggregationType.P99 - ); - - return getTopologyMetric(data, latencySpecification); -}; - -export const getErrorPercentageMetric = ( - data: Dictionary, - metricSpecifications?: MetricAggregationSpecification[] -): ErrorPercentageMetricAggregation | undefined => { - const errorSpecification = metricSpecifications?.find( - specification => - specification.name === 'errorPercentage' && specification.aggregation === MetricAggregationType.Average - ); + spec?: MetricAggregationSpecification +): MetricAggregation | undefined => { + if (!spec) { + return undefined; + } - return getTopologyMetric(data, errorSpecification); + return data[spec.resultAlias()] as MetricAggregation; }; -export const getAllCategoryClasses = () => [ - ...allLatencyMetricCategories.map(getLatencyCategoryClass), - ...allErrorPercentageMetricCategories.map(getErrorPercentageCategoryClass) -]; +export type MetricCategory = NodeMetricCategory | EdgeMetricCategory; -export const getLatencyCategoryClass = (category?: PercentileLatencyMetricValueCategory): string => - category !== undefined ? `${category}-category` : ''; +export const resolveMetricCategories = ( + defaultCategories: MetricCategory[], + categories?: MetricCategory[] +): MetricCategory[] => { + const currentCategories = !isEmpty(categories) ? categories! : []; -export const getErrorPercentageCategoryClass = (category?: ErrorPercentageMetricValueCategory): string => - category !== undefined ? `${category}-error-category` : ''; - -export const allLatencyMetricCategories = [ - PercentileLatencyMetricValueCategory.LessThan20, - PercentileLatencyMetricValueCategory.From20To100, - PercentileLatencyMetricValueCategory.From100To500, - PercentileLatencyMetricValueCategory.From500To1000, - PercentileLatencyMetricValueCategory.GreaterThanOrEqualTo1000, - PercentileLatencyMetricValueCategory.NotSpecified -]; - -export const allErrorPercentageMetricCategories = [ - ErrorPercentageMetricValueCategory.LessThan5, - ErrorPercentageMetricValueCategory.GreaterThanOrEqualTo5, - ErrorPercentageMetricValueCategory.NotSpecified -]; - -const getTopologyMetric = ( - data: Dictionary, - spec?: MetricAggregationSpecification -): T | undefined => { - if (!spec) { - return undefined; - } + return defaultCategories.map(defaultCategory => { + const category = currentCategories.find(currentCategory => currentCategory.value === defaultCategory.value); + if (category) { + return { ...defaultCategory, ...category }; + } - return data[spec.resultAlias()] as T; + return defaultCategory; + }); }; diff --git a/projects/observability/src/shared/dashboard/widgets/topology/metric/metric.ts b/projects/observability/src/shared/dashboard/widgets/topology/metric/metric.ts new file mode 100644 index 000000000..e6c0cfe54 --- /dev/null +++ b/projects/observability/src/shared/dashboard/widgets/topology/metric/metric.ts @@ -0,0 +1,23 @@ +import { MetricAggregationSpecification } from '../../../../graphql/model/schema/specifications/metric-aggregation-specification'; +import { EdgeMetricCategory } from './edge-metric-category'; +import { NodeMetricCategory } from './node-metric-category'; + +export interface MetricData { + primary: MetricType; + secondary?: MetricType; + others?: MetricType[]; +} + +export type MetricType = NodeMetric | EdgeMetric; + +export interface Metric { + specification: MetricAggregationSpecification; +} + +export interface NodeMetric extends Metric { + categories?: NodeMetricCategory[]; +} + +export interface EdgeMetric extends Metric { + categories?: EdgeMetricCategory[]; +} diff --git a/projects/observability/src/shared/dashboard/widgets/topology/metric/node-metric-category.ts b/projects/observability/src/shared/dashboard/widgets/topology/metric/node-metric-category.ts new file mode 100644 index 000000000..4c884bf1e --- /dev/null +++ b/projects/observability/src/shared/dashboard/widgets/topology/metric/node-metric-category.ts @@ -0,0 +1,131 @@ +import { Color } from '@hypertrace/common'; +import { resolveMetricCategories } from './metric-category'; + +export interface NodeMetricCategory { + value: NodeMetricCategoryValueType; + categoryClass?: string; // Can be used as selector class + color: string; // Used for fill in node + focusedColor?: string; // Used for fill color when focus/emphasized in case of secondary node metrics + secondaryColor?: string; // Used for stroke in case of secondary node metrics +} + +export type NodeMetricCategoryValueType = PrimaryNodeMetricCategoryValueType | SecondaryNodeMetricCategoryValueType; + +export enum PrimaryNodeMetricCategoryValueType { + LessThan20 = 'less-than-20', + From20To100 = 'from-20-to-100', + From100To500 = 'from-100-to-500', + From500To1000 = 'from-500-to-1000', + GreaterThanOrEqualTo1000 = 'greater-than-or-equal-to-1000', + NotSpecified = 'not-specified' +} + +export enum SecondaryNodeMetricCategoryValueType { + LessThan5 = 'less-than-5', + GreaterThanOrEqualTo5 = 'greater-than-or-equal-to-5', + NotSpecified = 'not-specified' +} + +export const defaultPrimaryNodeMetricCategories: NodeMetricCategory[] = [ + { + value: PrimaryNodeMetricCategoryValueType.LessThan20, + color: Color.BlueGray1, + categoryClass: 'less-than-20-primary-category' + }, + { + value: PrimaryNodeMetricCategoryValueType.From20To100, + color: Color.BlueGray2, + categoryClass: 'from-20-to-100-primary-category' + }, + { + value: PrimaryNodeMetricCategoryValueType.From100To500, + color: Color.BlueGray3, + categoryClass: 'from-100-to-500-primary-category' + }, + { + value: PrimaryNodeMetricCategoryValueType.From500To1000, + color: Color.BlueGray4, + categoryClass: 'from-500-to-1000-primary-category' + }, + { + value: PrimaryNodeMetricCategoryValueType.GreaterThanOrEqualTo1000, + color: Color.BlueGray5, + categoryClass: 'greater-than-or-equal-to-1000-primary-category' + }, + { + value: PrimaryNodeMetricCategoryValueType.NotSpecified, + color: 'lightgray', + categoryClass: 'not-specified-primary-category' + } +]; + +export const defaultSecondaryNodeMetricCategories: NodeMetricCategory[] = [ + { + value: SecondaryNodeMetricCategoryValueType.LessThan5, + color: Color.Gray2, + categoryClass: 'less-than-5-secondary-category' + }, + { + value: SecondaryNodeMetricCategoryValueType.GreaterThanOrEqualTo5, + color: Color.Red1, + secondaryColor: Color.Red5, + focusedColor: Color.Red1, + categoryClass: 'greater-than-or-equal-to-5-secondary-category' + }, + { + value: SecondaryNodeMetricCategoryValueType.NotSpecified, + color: Color.Gray2, + categoryClass: 'not-specified-secondary-category' + } +]; + +export const getPrimaryNodeMetricCategory = ( + value?: number, + categories?: NodeMetricCategory[] +): NodeMetricCategory | undefined => { + const primaryCategories = resolveMetricCategories( + defaultPrimaryNodeMetricCategories, + categories + ) as NodeMetricCategory[]; + if (value === undefined) { + return primaryCategories.find(category => category.value === PrimaryNodeMetricCategoryValueType.NotSpecified); + } + + if (value < 20) { + return primaryCategories.find(category => category.value === PrimaryNodeMetricCategoryValueType.LessThan20); + } + + if (value >= 20 && value < 100) { + return primaryCategories.find(category => category.value === PrimaryNodeMetricCategoryValueType.From20To100); + } + + if (value >= 100 && value < 500) { + return primaryCategories.find(category => category.value === PrimaryNodeMetricCategoryValueType.From100To500); + } + + if (value >= 500 && value < 1000) { + return primaryCategories.find(category => category.value === PrimaryNodeMetricCategoryValueType.From500To1000); + } + + return primaryCategories.find( + category => category.value === PrimaryNodeMetricCategoryValueType.GreaterThanOrEqualTo1000 + ); +}; + +export const getSecondaryNodeMetricCategory = (value?: number, categories?: NodeMetricCategory[]) => { + const secondaryCategories = resolveMetricCategories( + defaultSecondaryNodeMetricCategories, + categories + ) as NodeMetricCategory[]; + if (value === undefined) { + return secondaryCategories.find(category => category.value === SecondaryNodeMetricCategoryValueType.NotSpecified); + } + + if (value < 5) { + return secondaryCategories.find(category => category.value === SecondaryNodeMetricCategoryValueType.LessThan5); + } + + return secondaryCategories.find( + category => category.value === SecondaryNodeMetricCategoryValueType.GreaterThanOrEqualTo5 + ); +}; diff --git a/projects/observability/src/shared/dashboard/widgets/topology/node/box/api-node-renderer/api-node-box-renderer.service.ts b/projects/observability/src/shared/dashboard/widgets/topology/node/box/api-node-renderer/api-node-box-renderer.service.ts index e1bae7654..81f682d0a 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/node/box/api-node-renderer/api-node-box-renderer.service.ts +++ b/projects/observability/src/shared/dashboard/widgets/topology/node/box/api-node-renderer/api-node-box-renderer.service.ts @@ -5,7 +5,7 @@ import { entityTypeKey, ObservabilityEntityType } from '../../../../../../graphq import { EntityNode } from '../../../../../../graphql/request/handlers/entities/query/topology/entity-topology-graphql-query-handler.service'; import { EntityNodeBoxRendererService } from '../entity-node-box-renderer.service'; -@Injectable({ providedIn: 'root' }) +@Injectable() export class ApiNodeBoxRendererService extends EntityNodeBoxRendererService { public matches(node: TopologyNode & Partial): node is EntityNode { return this.isEntityNode(node) && node.data[entityTypeKey] === ObservabilityEntityType.Api; diff --git a/projects/observability/src/shared/dashboard/widgets/topology/node/box/backend-node-renderer/backend-node-box-renderer.service.ts b/projects/observability/src/shared/dashboard/widgets/topology/node/box/backend-node-renderer/backend-node-box-renderer.service.ts index 0108d435e..012b31139 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/node/box/backend-node-renderer/backend-node-box-renderer.service.ts +++ b/projects/observability/src/shared/dashboard/widgets/topology/node/box/backend-node-renderer/backend-node-box-renderer.service.ts @@ -5,7 +5,7 @@ import { entityTypeKey, ObservabilityEntityType } from '../../../../../../graphq import { EntityNode } from '../../../../../../graphql/request/handlers/entities/query/topology/entity-topology-graphql-query-handler.service'; import { EntityNodeBoxRendererService } from '../entity-node-box-renderer.service'; -@Injectable({ providedIn: 'root' }) +@Injectable() export class BackendNodeBoxRendererService extends EntityNodeBoxRendererService { public matches(node: TopologyNode & Partial): node is EntityNode { return this.isEntityNode(node) && node.data[entityTypeKey] === ObservabilityEntityType.Backend; diff --git a/projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.scss b/projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.scss index 241c595c7..75dc2f7e7 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.scss +++ b/projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.scss @@ -36,7 +36,6 @@ @include transition; .entity-outer-band { filter: url(#entity-node-dropshadow-filter); - fill: white; } } @@ -44,8 +43,6 @@ @include transition; .entity-outer-band { filter: url(#entity-node-dropshadow-filter); - fill: white; - stroke: $blue-4; stroke-width: 1px; } @@ -72,10 +69,6 @@ stroke: $gray-2; } - .entity-outer-band { - fill: $gray-2; - } - .entity-metric-value { @include body-1-regular(white); text-anchor: middle; @@ -86,49 +79,6 @@ color: $gray-4; fill: currentColor; } - - &.less-than-20-category { - .metric-category { - fill: $blue-gray-1; - } - } - - &.from-20-to-100-category { - .metric-category { - fill: $blue-gray-2; - } - } - - &.from-100-to-500-category { - .metric-category { - fill: $blue-gray-3; - } - } - - &.from-500-to-1000-category { - .metric-category { - fill: $blue-gray-4; - } - } - - &.greater-than-or-equal-to-1000-category { - .metric-category { - fill: $blue-gray-5; - } - } - - &.not-specified-category { - .metric-category { - fill: lightgray; - } - } - - &.greater-than-or-equal-to-5-error-category { - .entity-outer-band { - fill: $red-1; - stroke: $red-5; - } - } } } } diff --git a/projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.service.ts b/projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.service.ts index d484c6de9..c848ae8d9 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.service.ts +++ b/projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.service.ts @@ -1,32 +1,37 @@ import { Injectable, Renderer2 } from '@angular/core'; -import { DomElementMeasurerService, NumericFormatter, selector } from '@hypertrace/common'; +import { Color, DomElementMeasurerService, NumericFormatter, selector } from '@hypertrace/common'; import { MetricAggregation, MetricHealth } from '@hypertrace/distributed-tracing'; import { select, Selection } from 'd3-selection'; import { Observable, Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; import { TopologyNodeRendererDelegate } from '../../../../../components/topology/renderers/node/topology-node-renderer.service'; -import { TopologyCoordinates, TopologyNode, TopologyNodeState } from '../../../../../components/topology/topology'; +import { + TopologyCoordinates, + TopologyElementVisibility, + TopologyNode, + TopologyNodeState +} from '../../../../../components/topology/topology'; import { D3UtilService } from '../../../../../components/utils/d3/d3-util.service'; import { SvgUtilService } from '../../../../../components/utils/svg/svg-util.service'; import { Entity } from '../../../../../graphql/model/schema/entity'; -import { ErrorPercentageMetricAggregation } from '../../../../../graphql/model/schema/specifications/error-percentage-aggregation-specification'; import { MetricAggregationSpecification } from '../../../../../graphql/model/schema/specifications/metric-aggregation-specification'; -import { PercentileLatencyMetricAggregation } from '../../../../../graphql/model/schema/specifications/percentile-latency-aggregation-specification'; import { EntityNode } from '../../../../../graphql/request/handlers/entities/query/topology/entity-topology-graphql-query-handler.service'; import { EntityIconLookupService } from '../../../../../services/entity/entity-icon-lookup.service'; import { EntityNavigationService } from '../../../../../services/navigation/entity/entity-navigation.service'; +import { getTopologyMetric } from '../../metric/metric-category'; import { - getAllCategoryClasses, - getErrorPercentageCategoryClass, - getErrorPercentageMetric, - getLatencyCategoryClass, - getLatencyMetric -} from '../../metric/metric-category'; + getPrimaryNodeMetricCategory, + getSecondaryNodeMetricCategory, + NodeMetricCategory +} from '../../metric/node-metric-category'; +import { TopologyDataSourceModelPropertiesService } from '../../topology-data-source-model-properties.service'; import { VisibilityUpdater } from '../../visibility-updater'; @Injectable() // Annotate so parameters still provide metadata for children export abstract class EntityNodeBoxRendererService implements TopologyNodeRendererDelegate { private readonly entityMetricClass: string = 'entity-metric-value'; + private readonly entityOuterBandClass: string = 'entity-outer-band'; + private readonly metricCategoryClass: string = 'metric-category'; private readonly dropshadowFilterId: string = 'entity-node-dropshadow-filter'; private readonly nodeSelectionMap: WeakMap< EntityNode, @@ -41,7 +46,8 @@ export abstract class EntityNodeBoxRendererService implements TopologyNodeRender private readonly domElementMeasurerService: DomElementMeasurerService, private readonly svgUtils: SvgUtilService, protected readonly d3Utils: D3UtilService, - private readonly entityIconLookupService: EntityIconLookupService + private readonly entityIconLookupService: EntityIconLookupService, + private readonly topologyDataSourceModelPropertiesService: TopologyDataSourceModelPropertiesService ) {} public abstract matches(node: TopologyNode & Partial): node is EntityNode; @@ -101,13 +107,21 @@ export abstract class EntityNodeBoxRendererService implements TopologyNodeRender domElementRenderer: Renderer2 ): void { const elementSelection = this.d3Utils.select(element, domElementRenderer); - const metricSpecifications = state.dataSpecifiers?.map(specifier => specifier.value); - this.updateNodeMetric( - elementSelection, - getLatencyMetric(node.data, metricSpecifications), - getErrorPercentageMetric(node.data, metricSpecifications) + const primaryMetric = this.topologyDataSourceModelPropertiesService.getPrimaryNodeMetric(); + const primaryMetricAggregation = getTopologyMetric(node.data, primaryMetric?.specification); + const primaryMetricCategory = getPrimaryNodeMetricCategory( + primaryMetricAggregation?.value, + primaryMetric?.categories + ); + const secondaryMetric = this.topologyDataSourceModelPropertiesService.geSecondaryNodeMetric(); + const secondaryMetricAggregation = getTopologyMetric(node.data, secondaryMetric?.specification); + const secondaryMetricCategory = getSecondaryNodeMetricCategory( + secondaryMetricAggregation?.value, + secondaryMetric?.categories ); + + this.updateNodeMetric(elementSelection, state.visibility, primaryMetricCategory, secondaryMetricCategory); this.visibilityUpdater.updateVisibility(elementSelection, state.visibility); elementSelection.classed('dragging', state.dragging); @@ -127,14 +141,35 @@ export abstract class EntityNodeBoxRendererService implements TopologyNodeRender protected updateNodeMetric( selection: Selection, - latencyMetric?: PercentileLatencyMetricAggregation, - errorPercentageMetric?: ErrorPercentageMetricAggregation + visibility: TopologyElementVisibility, + primaryMetricCategory?: NodeMetricCategory, + secondaryMetricCategory?: NodeMetricCategory ): void { selection - .classed(getAllCategoryClasses().join(' '), false) - .classed(getLatencyCategoryClass(latencyMetric?.category), true) - .classed(getErrorPercentageCategoryClass(errorPercentageMetric?.category), true) + .classed(primaryMetricCategory?.categoryClass ?? '', true) + .classed(secondaryMetricCategory?.categoryClass ?? '', true) .select(selector(this.entityMetricClass)); + + // For primary category + selection.select(selector(this.metricCategoryClass)).attr('fill', primaryMetricCategory?.color!); + + // For secondary category + selection + .select(selector(this.entityOuterBandClass)) + .attr('fill', () => { + if (visibility === TopologyElementVisibility.Focused || visibility === TopologyElementVisibility.Emphasized) { + return secondaryMetricCategory?.focusedColor ?? this.focusedOrEmphasizedColor(); + } + + return secondaryMetricCategory?.color!; + }) + .attr('stroke', () => { + if (visibility === TopologyElementVisibility.Focused) { + return secondaryMetricCategory?.secondaryColor ?? this.focusedBandColor(); + } + + return secondaryMetricCategory?.secondaryColor ?? ''; + }); } protected isEntityNode(node: TopologyNode & Partial): node is EntityNode { @@ -155,7 +190,7 @@ export abstract class EntityNodeBoxRendererService implements TopologyNodeRender protected addOuterBand(nodeSelection: Selection): void { nodeSelection .append('rect') - .classed('entity-outer-band', true) + .classed(this.entityOuterBandClass, true) .attr('x', 0) .attr('y', 0) .attr('width', this.boxWidth()) @@ -168,7 +203,7 @@ export abstract class EntityNodeBoxRendererService implements TopologyNodeRender protected addMetricCategory(nodeSelection: Selection): void { nodeSelection .append('circle') - .classed('metric-category', true) + .classed(this.metricCategoryClass, true) .attr('transform', `translate(${this.getPadding()}, ${(this.boxHeight() - this.metricCategoryWidth()) / 2})`) .attr('cx', '4') .attr('cy', '4') @@ -246,6 +281,14 @@ export abstract class EntityNodeBoxRendererService implements TopologyNodeRender .each((_datum, index, groups) => this.svgUtils.truncateText(groups[index], 30)); } + protected focusedOrEmphasizedColor(): string { + return Color.White; + } + + protected focusedBandColor(): string { + return Color.Blue4; + } + protected boxWidth(): number { return 240; } diff --git a/projects/observability/src/shared/dashboard/widgets/topology/node/box/service-node-renderer/service-node-box-renderer.service.ts b/projects/observability/src/shared/dashboard/widgets/topology/node/box/service-node-renderer/service-node-box-renderer.service.ts index d99ed8471..02955ed56 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/node/box/service-node-renderer/service-node-box-renderer.service.ts +++ b/projects/observability/src/shared/dashboard/widgets/topology/node/box/service-node-renderer/service-node-box-renderer.service.ts @@ -5,7 +5,7 @@ import { entityTypeKey, ObservabilityEntityType } from '../../../../../../graphq import { EntityNode } from '../../../../../../graphql/request/handlers/entities/query/topology/entity-topology-graphql-query-handler.service'; import { EntityNodeBoxRendererService } from '../entity-node-box-renderer.service'; -@Injectable({ providedIn: 'root' }) +@Injectable() export class ServiceNodeBoxRendererService extends EntityNodeBoxRendererService { public matches(node: TopologyNode & Partial): node is EntityNode { return this.isEntityNode(node) && node.data[entityTypeKey] === ObservabilityEntityType.Service; diff --git a/projects/observability/src/shared/dashboard/widgets/topology/topology-data-source-model-properties.service.ts b/projects/observability/src/shared/dashboard/widgets/topology/topology-data-source-model-properties.service.ts new file mode 100644 index 000000000..0f8101980 --- /dev/null +++ b/projects/observability/src/shared/dashboard/widgets/topology/topology-data-source-model-properties.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { Dictionary } from '@hypertrace/common'; +import { EdgeMetric, MetricData, NodeMetric } from './metric/metric'; + +@Injectable() +export class TopologyDataSourceModelPropertiesService { + private modelProperties: Dictionary = {}; + + public setModelProperties(modelProperties: Dictionary): void { + this.modelProperties = modelProperties; + } + + public getPrimaryNodeMetric(): NodeMetric | undefined { + return (this.modelProperties.nodeMetrics as MetricData)?.primary as NodeMetric; + } + + public geSecondaryNodeMetric(): NodeMetric | undefined { + return (this.modelProperties.nodeMetrics as MetricData)?.secondary as NodeMetric; + } + + public getPrimaryEdgeMetric(): EdgeMetric | undefined { + return (this.modelProperties.edgeMetrics as MetricData)?.primary as EdgeMetric; + } + + public geSecondaryEdgeMetric(): EdgeMetric | undefined { + return (this.modelProperties.edgeMetrics as MetricData)?.secondary as EdgeMetric; + } +} diff --git a/projects/observability/src/shared/dashboard/widgets/topology/topology-widget-renderer.component.ts b/projects/observability/src/shared/dashboard/widgets/topology/topology-widget-renderer.component.ts index 51ecb7cb5..f556b7907 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/topology-widget-renderer.component.ts +++ b/projects/observability/src/shared/dashboard/widgets/topology/topology-widget-renderer.component.ts @@ -4,6 +4,7 @@ import { WidgetRenderer } from '@hypertrace/dashboards'; import { MetadataService } from '@hypertrace/distributed-tracing'; import { Renderer } from '@hypertrace/hyperdash'; import { RendererApi, RENDERER_API } from '@hypertrace/hyperdash-angular'; +import { isNull } from 'lodash-es'; import { EMPTY, Observable, of } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; import { TopologyEdgeRendererService } from '../../../components/topology/renderers/edge/topology-edge-renderer.service'; @@ -20,6 +21,7 @@ import { ApiNodeBoxRendererService } from './node/box/api-node-renderer/api-node import { BackendNodeBoxRendererService } from './node/box/backend-node-renderer/backend-node-box-renderer.service'; import { ServiceNodeBoxRendererService } from './node/box/service-node-renderer/service-node-box-renderer.service'; import { TopologyEntityTooltipComponent } from './tooltip/topology-entity-tooltip.component'; +import { TopologyDataSourceModelPropertiesService } from './topology-data-source-model-properties.service'; import { TopologyWidgetModel } from './topology-widget.model'; @Renderer({ modelClass: TopologyWidgetModel }) @@ -30,7 +32,16 @@ import { TopologyWidgetModel } from './topology-widget.model'; './edge/curved/entity-edge-curve-renderer.scss', './topology-widget-renderer.component.scss' ], - providers: [TopologyNodeRendererService, TopologyEdgeRendererService, TopologyTooltipRendererService], + providers: [ + TopologyNodeRendererService, + TopologyEdgeRendererService, + TopologyTooltipRendererService, + EntityEdgeCurveRendererService, + ApiNodeBoxRendererService, + BackendNodeBoxRendererService, + ServiceNodeBoxRendererService, + TopologyDataSourceModelPropertiesService + ], changeDetection: ChangeDetectionStrategy.OnPush, template: `
@@ -81,6 +92,7 @@ export class TopologyWidgetRendererComponent extends WidgetRenderer ({ From 6d88cbc0e78063559c0603b4dbd072b575dd6ad6 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Fri, 2 Jul 2021 20:56:47 +0530 Subject: [PATCH 2/2] fix: bug fix for edge curve --- .../topology/edge/curved/entity-edge-curve-renderer.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts b/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts index ca43794aa..2bc1bf4ec 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts +++ b/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts @@ -294,7 +294,7 @@ export class EntityEdgeCurveRendererService implements TopologyEdgeRenderDelegat ): string { if ( secondaryMetricCategory && - (secondaryMetricCategory.categoryClass === SecondaryEdgeMetricCategoryValueType.GreaterThanOrEqualTo5 || + (secondaryMetricCategory.value === SecondaryEdgeMetricCategoryValueType.GreaterThanOrEqualTo5 || !primaryMetricCategory) ) { return this.formattedMetricValue(secondaryMetricAggregation!.value, secondaryMetricAggregation?.units);