diff --git a/projects/distributed-tracing/src/shared/components/span-detail/exit-calls/span-exit-calls.component.test.ts b/projects/distributed-tracing/src/shared/components/span-detail/exit-calls/span-exit-calls.component.test.ts new file mode 100644 index 000000000..d4a7e07e1 --- /dev/null +++ b/projects/distributed-tracing/src/shared/components/span-detail/exit-calls/span-exit-calls.component.test.ts @@ -0,0 +1,49 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { fakeAsync, flush } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { IconLibraryTestingModule } from '@hypertrace/assets-library'; +import { NavigationService } from '@hypertrace/common'; +import { runFakeRxjs } from '@hypertrace/test-utils'; +import { createHostFactory, mockProvider, Spectator } from '@ngneat/spectator/jest'; +import { EMPTY } from 'rxjs'; +import { SpanExitCallsComponent } from './span-exit-calls.component'; +import { SpanExitCallsModule } from './span-exit-calls.module'; + +describe('SpanExitCallsComponent', () => { + let spectator: Spectator; + + const createHost = createHostFactory({ + component: SpanExitCallsComponent, + imports: [SpanExitCallsModule, HttpClientTestingModule, IconLibraryTestingModule], + declareComponent: false, + providers: [ + mockProvider(ActivatedRoute, { + queryParamMap: EMPTY + }), + mockProvider(NavigationService, { + navigation$: EMPTY + }) + ] + }); + + test('should render data correctly', fakeAsync(() => { + spectator = createHost(``, { + hostProps: { exitCalls: { 'name 1': '10', 'name 2': '11' } } + }); + + runFakeRxjs(({ expectObservable }) => { + expect(spectator.component.dataSource).toBeDefined(); + expectObservable(spectator.component.dataSource!.getData(undefined!)).toBe('(x|)', { + x: { + data: [ + { name: 'name 1', calls: '10' }, + { name: 'name 2', calls: '11' } + ], + totalCount: 2 + } + }); + + flush(); + }); + })); +}); diff --git a/projects/distributed-tracing/src/shared/components/span-detail/exit-calls/span-exit-calls.component.ts b/projects/distributed-tracing/src/shared/components/span-detail/exit-calls/span-exit-calls.component.ts new file mode 100644 index 000000000..136648ff3 --- /dev/null +++ b/projects/distributed-tracing/src/shared/components/span-detail/exit-calls/span-exit-calls.component.ts @@ -0,0 +1,52 @@ +import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { Dictionary } from '@hypertrace/common'; +import { TableColumnConfig, TableDataResponse, TableDataSource, TableRow } from '@hypertrace/components'; +import { Observable, of } from 'rxjs'; + +@Component({ + selector: 'ht-span-exit-calls', + changeDetection: ChangeDetectionStrategy.OnPush, + template: `
+ +
` +}) +export class SpanExitCallsComponent implements OnInit { + @Input() + public exitCalls?: Dictionary; + + public dataSource?: TableDataSource; + public columnConfigs: TableColumnConfig[] = [ + { + id: 'name', + name: 'name', + title: 'Service', + visible: true, + width: '80%', + sortable: false, + filterable: false + }, + { + id: 'calls', + name: 'calls', + title: 'Calls', + visible: true, + sortable: false, + filterable: false + } + ]; + + public ngOnInit(): void { + this.buildDataSource(); + } + + private buildDataSource(): void { + this.dataSource = { + getData: (): Observable> => + of({ + data: Object.entries(this.exitCalls ?? {}).map(item => ({ name: item[0], calls: item[1] })), + totalCount: Object.keys(this.exitCalls ?? {}).length + }), + getScope: () => undefined + }; + } +} diff --git a/projects/distributed-tracing/src/shared/components/span-detail/exit-calls/span-exit-calls.module.ts b/projects/distributed-tracing/src/shared/components/span-detail/exit-calls/span-exit-calls.module.ts new file mode 100644 index 000000000..90cb53328 --- /dev/null +++ b/projects/distributed-tracing/src/shared/components/span-detail/exit-calls/span-exit-calls.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { TableModule } from '@hypertrace/components'; +import { SpanExitCallsComponent } from './span-exit-calls.component'; + +@NgModule({ + declarations: [SpanExitCallsComponent], + exports: [SpanExitCallsComponent], + imports: [CommonModule, TableModule] +}) +export class SpanExitCallsModule {} diff --git a/projects/distributed-tracing/src/shared/components/span-detail/span-data.ts b/projects/distributed-tracing/src/shared/components/span-detail/span-data.ts index 02614b9bb..94100c3e8 100644 --- a/projects/distributed-tracing/src/shared/components/span-detail/span-data.ts +++ b/projects/distributed-tracing/src/shared/components/span-detail/span-data.ts @@ -11,4 +11,5 @@ export interface SpanData { responseBody: string; tags: Dictionary; requestUrl: string; + exitCallsBreakup?: Dictionary; } diff --git a/projects/distributed-tracing/src/shared/components/span-detail/span-detail.component.ts b/projects/distributed-tracing/src/shared/components/span-detail/span-detail.component.ts index 58565f967..8c518c2fc 100644 --- a/projects/distributed-tracing/src/shared/components/span-detail/span-detail.component.ts +++ b/projects/distributed-tracing/src/shared/components/span-detail/span-detail.component.ts @@ -43,6 +43,9 @@ import { SpanDetailLayoutStyle } from './span-detail-layout-style'; + + + ` @@ -62,11 +65,13 @@ export class SpanDetailComponent implements OnChanges { public showRequestTab?: boolean; public showResponseTab?: boolean; + public showExitCallsTab?: boolean; public ngOnChanges(changes: TypedSimpleChanges): void { if (changes.spanData) { this.showRequestTab = !isEmpty(this.spanData?.requestHeaders) || !isEmpty(this.spanData?.requestBody); this.showResponseTab = !isEmpty(this.spanData?.responseHeaders) || !isEmpty(this.spanData?.responseBody); + this.showExitCallsTab = !isEmpty(this.spanData?.exitCallsBreakup); } } } diff --git a/projects/distributed-tracing/src/shared/components/span-detail/span-detail.module.ts b/projects/distributed-tracing/src/shared/components/span-detail/span-detail.module.ts index 944c63dd0..2bd41a72b 100644 --- a/projects/distributed-tracing/src/shared/components/span-detail/span-detail.module.ts +++ b/projects/distributed-tracing/src/shared/components/span-detail/span-detail.module.ts @@ -11,6 +11,7 @@ import { ToggleButtonModule, TooltipModule } from '@hypertrace/components'; +import { SpanExitCallsModule } from './exit-calls/span-exit-calls.module'; import { SpanDetailTitleHeaderModule } from './headers/title/span-detail-title-header.module'; import { SpanRequestDetailModule } from './request/span-request-detail.module'; import { SpanResponseDetailModule } from './response/span-response-detail.module'; @@ -32,7 +33,8 @@ import { SpanTagsDetailModule } from './tags/span-tags-detail.module'; JsonViewerModule, LoadAsyncModule, ListViewModule, - SpanDetailTitleHeaderModule + SpanDetailTitleHeaderModule, + SpanExitCallsModule ], declarations: [SpanDetailComponent], exports: [SpanDetailComponent] diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/trace-detail/data/api-trace-detail-data-source.model.test.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/trace-detail/data/api-trace-detail-data-source.model.test.ts index 98e609844..ddf0d0e2e 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/trace-detail/data/api-trace-detail-data-source.model.test.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/trace-detail/data/api-trace-detail-data-source.model.test.ts @@ -79,7 +79,8 @@ describe('Trace detail data source model', () => { traceProperties: expect.arrayContaining([ expect.objectContaining({ name: 'tags' }), expect.objectContaining({ name: 'traceId' }), - expect.objectContaining({ name: 'statusCode' }) + expect.objectContaining({ name: 'statusCode' }), + expect.objectContaining({ name: 'apiCalleeNameCount' }) ]) }) ); @@ -99,7 +100,8 @@ describe('Trace detail data source model', () => { traceProperties: expect.arrayContaining([ expect.objectContaining({ name: 'tags' }), expect.objectContaining({ name: 'traceId' }), - expect.objectContaining({ name: 'statusCode' }) + expect.objectContaining({ name: 'statusCode' }), + expect.objectContaining({ name: 'apiCalleeNameCount' }) ]) }) ); diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/trace-detail/data/api-trace-detail-data-source.model.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/trace-detail/data/api-trace-detail-data-source.model.ts index 2ee627732..ca733ca0c 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/trace-detail/data/api-trace-detail-data-source.model.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/trace-detail/data/api-trace-detail-data-source.model.ts @@ -1,6 +1,7 @@ import { Model } from '@hypertrace/hyperdash'; import { Trace, traceIdKey } from '../../../../graphql/model/schema/trace'; +import { Dictionary } from '@hypertrace/common'; import { TraceDetailData, TraceDetailDataSourceModel } from './trace-detail-data-source.model'; @Model({ @@ -8,18 +9,20 @@ import { TraceDetailData, TraceDetailDataSourceModel } from './trace-detail-data }) export class ApiTraceDetailDataSourceModel extends TraceDetailDataSourceModel { protected getTraceAttributes(): string[] { - return [...super.getTraceAttributes(), 'traceId']; + return [...super.getTraceAttributes(), 'traceId', 'apiCalleeNameCount']; } protected constructTraceDetailData(trace: Trace): ApiTraceDetailData { return { ...super.constructTraceDetailData(trace), traceId: trace.traceId as string, // For API Trace traceId is real Trace ID. NOT Symbol('traceId'). - entrySpanId: trace[traceIdKey] // API Trace Symbol('traceId') same as apiTraceId which is actually Entry Span ID + entrySpanId: trace[traceIdKey], // API Trace Symbol('traceId') same as apiTraceId which is actually Entry Span ID, + exitCallsBreakup: trace.apiCalleeNameCount as Dictionary }; } } export interface ApiTraceDetailData extends TraceDetailData { entrySpanId: string; + exitCallsBreakup: Dictionary; }