Skip to content

Commit 31c3be2

Browse files
authored
feat: display exit calls breakup in span detail (#724)
* feat: show exit calls breakup in span detail
1 parent 2ff545a commit 31c3be2

File tree

8 files changed

+130
-5
lines changed

8 files changed

+130
-5
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { HttpClientTestingModule } from '@angular/common/http/testing';
2+
import { fakeAsync, flush } from '@angular/core/testing';
3+
import { ActivatedRoute } from '@angular/router';
4+
import { IconLibraryTestingModule } from '@hypertrace/assets-library';
5+
import { NavigationService } from '@hypertrace/common';
6+
import { runFakeRxjs } from '@hypertrace/test-utils';
7+
import { createHostFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
8+
import { EMPTY } from 'rxjs';
9+
import { SpanExitCallsComponent } from './span-exit-calls.component';
10+
import { SpanExitCallsModule } from './span-exit-calls.module';
11+
12+
describe('SpanExitCallsComponent', () => {
13+
let spectator: Spectator<SpanExitCallsComponent>;
14+
15+
const createHost = createHostFactory({
16+
component: SpanExitCallsComponent,
17+
imports: [SpanExitCallsModule, HttpClientTestingModule, IconLibraryTestingModule],
18+
declareComponent: false,
19+
providers: [
20+
mockProvider(ActivatedRoute, {
21+
queryParamMap: EMPTY
22+
}),
23+
mockProvider(NavigationService, {
24+
navigation$: EMPTY
25+
})
26+
]
27+
});
28+
29+
test('should render data correctly', fakeAsync(() => {
30+
spectator = createHost(`<ht-span-exit-calls [exitCalls]="exitCalls"></ht-span-exit-calls>`, {
31+
hostProps: { exitCalls: { 'name 1': '10', 'name 2': '11' } }
32+
});
33+
34+
runFakeRxjs(({ expectObservable }) => {
35+
expect(spectator.component.dataSource).toBeDefined();
36+
expectObservable(spectator.component.dataSource!.getData(undefined!)).toBe('(x|)', {
37+
x: {
38+
data: [
39+
{ name: 'name 1', calls: '10' },
40+
{ name: 'name 2', calls: '11' }
41+
],
42+
totalCount: 2
43+
}
44+
});
45+
46+
flush();
47+
});
48+
}));
49+
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
2+
import { Dictionary } from '@hypertrace/common';
3+
import { TableColumnConfig, TableDataResponse, TableDataSource, TableRow } from '@hypertrace/components';
4+
import { Observable, of } from 'rxjs';
5+
6+
@Component({
7+
selector: 'ht-span-exit-calls',
8+
changeDetection: ChangeDetectionStrategy.OnPush,
9+
template: `<div class="span-exit-calls">
10+
<ht-table [columnConfigs]="this.columnConfigs" [data]="this.dataSource" [pageable]="false"></ht-table>
11+
</div> `
12+
})
13+
export class SpanExitCallsComponent implements OnInit {
14+
@Input()
15+
public exitCalls?: Dictionary<string>;
16+
17+
public dataSource?: TableDataSource<TableRow>;
18+
public columnConfigs: TableColumnConfig[] = [
19+
{
20+
id: 'name',
21+
name: 'name',
22+
title: 'Service',
23+
visible: true,
24+
width: '80%',
25+
sortable: false,
26+
filterable: false
27+
},
28+
{
29+
id: 'calls',
30+
name: 'calls',
31+
title: 'Calls',
32+
visible: true,
33+
sortable: false,
34+
filterable: false
35+
}
36+
];
37+
38+
public ngOnInit(): void {
39+
this.buildDataSource();
40+
}
41+
42+
private buildDataSource(): void {
43+
this.dataSource = {
44+
getData: (): Observable<TableDataResponse<TableRow>> =>
45+
of({
46+
data: Object.entries(this.exitCalls ?? {}).map(item => ({ name: item[0], calls: item[1] })),
47+
totalCount: Object.keys(this.exitCalls ?? {}).length
48+
}),
49+
getScope: () => undefined
50+
};
51+
}
52+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { CommonModule } from '@angular/common';
2+
import { NgModule } from '@angular/core';
3+
import { TableModule } from '@hypertrace/components';
4+
import { SpanExitCallsComponent } from './span-exit-calls.component';
5+
6+
@NgModule({
7+
declarations: [SpanExitCallsComponent],
8+
exports: [SpanExitCallsComponent],
9+
imports: [CommonModule, TableModule]
10+
})
11+
export class SpanExitCallsModule {}

projects/distributed-tracing/src/shared/components/span-detail/span-data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export interface SpanData {
1111
responseBody: string;
1212
tags: Dictionary<unknown>;
1313
requestUrl: string;
14+
exitCallsBreakup?: Dictionary<string>;
1415
}

projects/distributed-tracing/src/shared/components/span-detail/span-detail.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ import { SpanDetailLayoutStyle } from './span-detail-layout-style';
4343
<ht-tab label="Attributes" class="attributes">
4444
<ht-span-tags-detail [tags]="this.spanData.tags"></ht-span-tags-detail>
4545
</ht-tab>
46+
<ht-tab label="Exit Calls" *ngIf="this.showExitCallsTab">
47+
<ht-span-exit-calls [exitCalls]="this.spanData.exitCallsBreakup"></ht-span-exit-calls>
48+
</ht-tab>
4649
</ht-tab-group>
4750
</div>
4851
`
@@ -62,11 +65,13 @@ export class SpanDetailComponent implements OnChanges {
6265

6366
public showRequestTab?: boolean;
6467
public showResponseTab?: boolean;
68+
public showExitCallsTab?: boolean;
6569

6670
public ngOnChanges(changes: TypedSimpleChanges<this>): void {
6771
if (changes.spanData) {
6872
this.showRequestTab = !isEmpty(this.spanData?.requestHeaders) || !isEmpty(this.spanData?.requestBody);
6973
this.showResponseTab = !isEmpty(this.spanData?.responseHeaders) || !isEmpty(this.spanData?.responseBody);
74+
this.showExitCallsTab = !isEmpty(this.spanData?.exitCallsBreakup);
7075
}
7176
}
7277
}

projects/distributed-tracing/src/shared/components/span-detail/span-detail.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ToggleButtonModule,
1212
TooltipModule
1313
} from '@hypertrace/components';
14+
import { SpanExitCallsModule } from './exit-calls/span-exit-calls.module';
1415
import { SpanDetailTitleHeaderModule } from './headers/title/span-detail-title-header.module';
1516
import { SpanRequestDetailModule } from './request/span-request-detail.module';
1617
import { SpanResponseDetailModule } from './response/span-response-detail.module';
@@ -32,7 +33,8 @@ import { SpanTagsDetailModule } from './tags/span-tags-detail.module';
3233
JsonViewerModule,
3334
LoadAsyncModule,
3435
ListViewModule,
35-
SpanDetailTitleHeaderModule
36+
SpanDetailTitleHeaderModule,
37+
SpanExitCallsModule
3638
],
3739
declarations: [SpanDetailComponent],
3840
exports: [SpanDetailComponent]

projects/distributed-tracing/src/shared/dashboard/widgets/trace-detail/data/api-trace-detail-data-source.model.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ describe('Trace detail data source model', () => {
7979
traceProperties: expect.arrayContaining([
8080
expect.objectContaining({ name: 'tags' }),
8181
expect.objectContaining({ name: 'traceId' }),
82-
expect.objectContaining({ name: 'statusCode' })
82+
expect.objectContaining({ name: 'statusCode' }),
83+
expect.objectContaining({ name: 'apiCalleeNameCount' })
8384
])
8485
})
8586
);
@@ -99,7 +100,8 @@ describe('Trace detail data source model', () => {
99100
traceProperties: expect.arrayContaining([
100101
expect.objectContaining({ name: 'tags' }),
101102
expect.objectContaining({ name: 'traceId' }),
102-
expect.objectContaining({ name: 'statusCode' })
103+
expect.objectContaining({ name: 'statusCode' }),
104+
expect.objectContaining({ name: 'apiCalleeNameCount' })
103105
])
104106
})
105107
);
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
import { Model } from '@hypertrace/hyperdash';
22
import { Trace, traceIdKey } from '../../../../graphql/model/schema/trace';
33

4+
import { Dictionary } from '@hypertrace/common';
45
import { TraceDetailData, TraceDetailDataSourceModel } from './trace-detail-data-source.model';
56

67
@Model({
78
type: 'api-trace-detail-data-source'
89
})
910
export class ApiTraceDetailDataSourceModel extends TraceDetailDataSourceModel {
1011
protected getTraceAttributes(): string[] {
11-
return [...super.getTraceAttributes(), 'traceId'];
12+
return [...super.getTraceAttributes(), 'traceId', 'apiCalleeNameCount'];
1213
}
1314

1415
protected constructTraceDetailData(trace: Trace): ApiTraceDetailData {
1516
return {
1617
...super.constructTraceDetailData(trace),
1718
traceId: trace.traceId as string, // For API Trace traceId is real Trace ID. NOT Symbol('traceId').
18-
entrySpanId: trace[traceIdKey] // API Trace Symbol('traceId') same as apiTraceId which is actually Entry Span ID
19+
entrySpanId: trace[traceIdKey], // API Trace Symbol('traceId') same as apiTraceId which is actually Entry Span ID,
20+
exitCallsBreakup: trace.apiCalleeNameCount as Dictionary<string>
1921
};
2022
}
2123
}
2224

2325
export interface ApiTraceDetailData extends TraceDetailData {
2426
entrySpanId: string;
27+
exitCallsBreakup: Dictionary<string>;
2528
}

0 commit comments

Comments
 (0)