diff --git a/projects/distributed-tracing/src/shared/graphql/request/handlers/traces/traces-graphql-query-handler.service.ts b/projects/distributed-tracing/src/shared/graphql/request/handlers/traces/traces-graphql-query-handler.service.ts index 9e88f9da1..f7f883c89 100644 --- a/projects/distributed-tracing/src/shared/graphql/request/handlers/traces/traces-graphql-query-handler.service.ts +++ b/projects/distributed-tracing/src/shared/graphql/request/handlers/traces/traces-graphql-query-handler.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Dictionary, forkJoinSafeEmpty } from '@hypertrace/common'; import { GraphQlHandlerType, GraphQlQueryHandler, GraphQlSelection } from '@hypertrace/graphql-client'; import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { defaultIfEmpty, map } from 'rxjs/operators'; import { MetadataService } from '../../../../services/metadata/metadata.service'; import { GlobalGraphQlFilterService } from '../../../model/schema/filter/global-graphql-filter.service'; import { GraphQlFilter } from '../../../model/schema/filter/graphql-filter'; @@ -98,9 +98,10 @@ export class TracesGraphQlQueryHandlerService implements GraphQlQueryHandler { - return this.metadataService - .getAttribute(scope, specification.name) - .pipe(map(attribute => (attribute.units !== '' ? attribute.units : undefined))); + return this.metadataService.getAttribute(scope, specification.name).pipe( + map(attribute => (attribute.units !== '' ? attribute.units : undefined)), + defaultIfEmpty(undefined) + ); } } diff --git a/projects/observability/src/pages/apis/api-detail/traces/api-trace-list.dashboard.ts b/projects/observability/src/pages/apis/api-detail/traces/api-trace-list.dashboard.ts index eb5758f0a..e125d7ae0 100644 --- a/projects/observability/src/pages/apis/api-detail/traces/api-trace-list.dashboard.ts +++ b/projects/observability/src/pages/apis/api-detail/traces/api-trace-list.dashboard.ts @@ -1,5 +1,6 @@ import { CoreTableCellRendererType, TableMode, TableSortDirection, TableStyle } from '@hypertrace/components'; import { TracingTableCellType } from '@hypertrace/distributed-tracing'; +import { ObservabilityTableCellType } from '../../../../shared/components/table/observability-table-cell-type'; import { ObservabilityTraceType } from '../../../../shared/graphql/model/schema/observability-traces'; export const apiTraceListDashboard = { @@ -25,9 +26,20 @@ export const apiTraceListDashboard = { type: 'table-widget-column', title: 'Exit Calls', filterable: true, + display: ObservabilityTableCellType.ExitCalls, value: { - type: 'attribute-specification', - attribute: 'apiExitCalls' + type: 'composite-specification', + specifications: [ + { + type: 'attribute-specification', + attribute: 'apiExitCalls' + }, + { + type: 'attribute-specification', + attribute: 'apiCalleeNameCount' + } + ], + 'order-by': 'apiExitCalls' }, 'click-handler': { type: 'api-trace-navigation-handler' diff --git a/projects/observability/src/pages/apis/service-detail/traces/service-trace-list.dashboard.ts b/projects/observability/src/pages/apis/service-detail/traces/service-trace-list.dashboard.ts index 7f2c49002..b7621cdd5 100644 --- a/projects/observability/src/pages/apis/service-detail/traces/service-trace-list.dashboard.ts +++ b/projects/observability/src/pages/apis/service-detail/traces/service-trace-list.dashboard.ts @@ -1,5 +1,6 @@ import { CoreTableCellRendererType, TableMode, TableSortDirection, TableStyle } from '@hypertrace/components'; import { TracingTableCellType } from '@hypertrace/distributed-tracing'; +import { ObservabilityTableCellType } from '../../../../shared/components/table/observability-table-cell-type'; import { ObservabilityTraceType } from '../../../../shared/graphql/model/schema/observability-traces'; export const serviceTraceListDashboard = { @@ -51,9 +52,20 @@ export const serviceTraceListDashboard = { type: 'table-widget-column', title: 'Exit Calls', filterable: true, + display: ObservabilityTableCellType.ExitCalls, value: { - type: 'attribute-specification', - attribute: 'apiExitCalls' + type: 'composite-specification', + specifications: [ + { + type: 'attribute-specification', + attribute: 'apiExitCalls' + }, + { + type: 'attribute-specification', + attribute: 'apiCalleeNameCount' + } + ], + 'order-by': 'apiExitCalls' }, 'click-handler': { type: 'api-trace-navigation-handler' diff --git a/projects/observability/src/shared/components/table/data-cell/exit-calls/exit-calls-table-cell-renderer.component.scss b/projects/observability/src/shared/components/table/data-cell/exit-calls/exit-calls-table-cell-renderer.component.scss new file mode 100644 index 000000000..5f7075ee5 --- /dev/null +++ b/projects/observability/src/shared/components/table/data-cell/exit-calls/exit-calls-table-cell-renderer.component.scss @@ -0,0 +1,29 @@ +@import 'color-palette'; +@import 'font'; + +.exit-calls-count { + @include body-1-regular($gray-7); +} + +.api-callee-name-count { + @include body-small($gray-3); + display: flex; + align-items: center; + justify-content: space-between; + padding: 2px; + + .api-callee-name { + @include ellipsis-overflow(); + max-width: 200px; + } + + .api-callee-count { + color: white; + margin-left: 50px; + } +} + +.remaining-api-callee { + @include body-small($gray-3); + padding: 2px; +} diff --git a/projects/observability/src/shared/components/table/data-cell/exit-calls/exit-calls-table-cell-renderer.component.test.ts b/projects/observability/src/shared/components/table/data-cell/exit-calls/exit-calls-table-cell-renderer.component.test.ts new file mode 100644 index 000000000..a18c75e68 --- /dev/null +++ b/projects/observability/src/shared/components/table/data-cell/exit-calls/exit-calls-table-cell-renderer.component.test.ts @@ -0,0 +1,43 @@ +import { + tableCellDataProvider, + TableCellNoOpParser, + tableCellProviders, + TooltipDirective +} from '@hypertrace/components'; +import { createComponentFactory } from '@ngneat/spectator/jest'; +import { MockComponent } from 'ng-mocks'; +import { ExitCallsTableCellRendererComponent } from './exit-calls-table-cell-renderer.component'; + +describe('Exit Calls table cell renderer component', () => { + const buildComponent = createComponentFactory({ + component: ExitCallsTableCellRendererComponent, + providers: [ + tableCellProviders( + { + id: 'test' + }, + new TableCellNoOpParser(undefined!) + ) + ], + declarations: [MockComponent(TooltipDirective)], + shallow: true + }); + + test('testing component properties', () => { + const value = { + key1: '1', + key2: '2' + }; + const spectator = buildComponent({ + providers: [tableCellDataProvider({ value: [3, value] })] + }); + + expect(spectator.queryAll('.exit-calls-count')[0]).toContainText('3'); + expect(spectator.component.apiCalleeNameCount).toMatchObject([ + ['key1', '1'], + ['key2', '2'] + ]); + expect(spectator.component.totalCountOfDifferentApiCallee).toBe(2); + expect(spectator.component.apiExitCalls).toBe(3); + }); +}); diff --git a/projects/observability/src/shared/components/table/data-cell/exit-calls/exit-calls-table-cell-renderer.component.ts b/projects/observability/src/shared/components/table/data-cell/exit-calls/exit-calls-table-cell-renderer.component.ts new file mode 100644 index 000000000..374424dcc --- /dev/null +++ b/projects/observability/src/shared/components/table/data-cell/exit-calls/exit-calls-table-cell-renderer.component.ts @@ -0,0 +1,74 @@ +import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core'; +import { Dictionary } from '@hypertrace/common'; +import { + CoreTableCellParserType, + TableCellAlignmentType, + TableCellParserBase, + TableCellRenderer, + TableCellRendererBase, + TableColumnConfig, + TABLE_CELL_DATA, + TABLE_COLUMN_CONFIG, + TABLE_COLUMN_INDEX, + TABLE_DATA_PARSER, + TABLE_ROW_DATA +} from '@hypertrace/components'; +import { Trace } from '@hypertrace/distributed-tracing'; +import { ObservabilityTableCellType } from '../../observability-table-cell-type'; + +interface CellData { + units: number; + value: [number, Dictionary]; +} +@Component({ + selector: 'ht-exit-calls-table-cell-renderer', + styleUrls: ['./exit-calls-table-cell-renderer.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+ {{ this.apiExitCalls }} + + + +
+ {{ item[0] }} + {{ item[1] }} +
+
+ and {{ this.totalCountOfDifferentApiCallee - this.maxShowApiCalleeNameCount }} more +
+
+ No exit calls +
+
+ ` +}) +@TableCellRenderer({ + type: ObservabilityTableCellType.ExitCalls, + alignment: TableCellAlignmentType.Left, + parser: CoreTableCellParserType.NoOp +}) +export class ExitCallsTableCellRendererComponent extends TableCellRendererBase implements OnInit { + public readonly apiCalleeNameCount: string[][]; + public readonly apiExitCalls: number; + public readonly maxShowApiCalleeNameCount: number = 10; + public readonly totalCountOfDifferentApiCallee!: number; + + public constructor( + @Inject(TABLE_COLUMN_CONFIG) columnConfig: TableColumnConfig, + @Inject(TABLE_COLUMN_INDEX) index: number, + @Inject(TABLE_DATA_PARSER) + parser: TableCellParserBase, + @Inject(TABLE_CELL_DATA) cellData: CellData, + @Inject(TABLE_ROW_DATA) rowData: Trace + ) { + super(columnConfig, index, parser, cellData, rowData); + const apiCalleeNameCount: string[][] = Object.entries(cellData.value[1]); + this.totalCountOfDifferentApiCallee = apiCalleeNameCount.length; + this.apiCalleeNameCount = apiCalleeNameCount.slice(0, this.maxShowApiCalleeNameCount); + this.apiExitCalls = cellData.value[0]; + } +} diff --git a/projects/observability/src/shared/components/table/observability-table-cell-renderer.module.ts b/projects/observability/src/shared/components/table/observability-table-cell-renderer.module.ts index d0958ada3..5f2855f7e 100644 --- a/projects/observability/src/shared/components/table/observability-table-cell-renderer.module.ts +++ b/projects/observability/src/shared/components/table/observability-table-cell-renderer.module.ts @@ -1,20 +1,28 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { TableModule } from '@hypertrace/components'; +import { TableModule, TooltipModule } from '@hypertrace/components'; import { BackendIconTableCellParser } from './data-cell/backend-icon/backend-icon-table-cell-parser'; import { BackendIconTableCellRendererComponent } from './data-cell/backend-icon/backend-icon-table-cell-renderer.component'; import { BackendIconTableCellRendererModule } from './data-cell/backend-icon/backend-icon-table-cell-renderer.module'; import { EntityTableCellParser } from './data-cell/entity/entity-table-cell-parser'; import { EntityTableCellRendererComponent } from './data-cell/entity/entity-table-cell-renderer.component'; import { EntityTableCellRendererModule } from './data-cell/entity/entity-table-cell-renderer.module'; +import { ExitCallsTableCellRendererComponent } from './data-cell/exit-calls/exit-calls-table-cell-renderer.component'; @NgModule({ imports: [ CommonModule, TableModule.withCellParsers([EntityTableCellParser, BackendIconTableCellParser]), - TableModule.withCellRenderers([EntityTableCellRendererComponent, BackendIconTableCellRendererComponent]), + TableModule.withCellRenderers([ + EntityTableCellRendererComponent, + BackendIconTableCellRendererComponent, + ExitCallsTableCellRendererComponent + ]), EntityTableCellRendererModule, - BackendIconTableCellRendererModule - ] + BackendIconTableCellRendererModule, + TooltipModule + ], + declarations: [ExitCallsTableCellRendererComponent], + exports: [ExitCallsTableCellRendererComponent] }) export class ObservabilityTableCellRendererModule {} diff --git a/projects/observability/src/shared/components/table/observability-table-cell-type.ts b/projects/observability/src/shared/components/table/observability-table-cell-type.ts index f16334ff3..3c7f686c0 100644 --- a/projects/observability/src/shared/components/table/observability-table-cell-type.ts +++ b/projects/observability/src/shared/components/table/observability-table-cell-type.ts @@ -1,4 +1,5 @@ export const enum ObservabilityTableCellType { Entity = 'entity', - BackendIcon = 'backend-icon' + BackendIcon = 'backend-icon', + ExitCalls = 'exit-calls' }