Skip to content

Commit e680f9d

Browse files
Trace drilldown to explorer screen (#1098)
1 parent 594a8db commit e680f9d

12 files changed

+253
-52
lines changed

projects/observability/src/pages/api-trace-detail/api-trace-detail.page.component.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
display: flex;
3636
align-items: center;
3737

38+
.filterable-summary-value {
39+
display: flex;
40+
align-items: center;
41+
}
42+
3843
.separation {
3944
flex: 1 1 auto;
4045
}
@@ -45,7 +50,7 @@
4550
}
4651

4752
.summary-value {
48-
margin-right: 24px;
53+
margin-right: 8px;
4954
}
5055

5156
.tabs {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { fakeAsync, tick } from '@angular/core/testing';
2+
import { MemoizeModule, NavigationService } from '@hypertrace/common';
3+
import { LoadAsyncModule } from '@hypertrace/components';
4+
import { createComponentFactory, mockProvider } from '@ngneat/spectator/jest';
5+
import { MockComponent } from 'ng-mocks';
6+
import { of } from 'rxjs';
7+
import { ExploreFilterLinkComponent } from '../../shared/components/explore-filter-link/explore-filter-link.component';
8+
import { ExplorerService } from '../explorer/explorer-service';
9+
import { ApiTraceDetailPageComponent } from './api-trace-detail.page.component';
10+
import { ApiTraceDetails, ApiTraceDetailService } from './api-trace-detail.service';
11+
describe('Api Trace Details Page Component', () => {
12+
const mockTraceDetails: ApiTraceDetails = {
13+
id: 'test-id',
14+
traceId: 'test-123',
15+
type: 'trace-type',
16+
timeString: 'test-time-string',
17+
titleString: 'test-title',
18+
startTime: 'test-start-time'
19+
};
20+
21+
const createComponent = createComponentFactory({
22+
component: ApiTraceDetailPageComponent,
23+
shallow: true,
24+
providers: [
25+
mockProvider(NavigationService),
26+
mockProvider(ExplorerService, {
27+
buildNavParamsWithFilters: jest.fn().mockReturnValue(of('traceId_eq_test-123'))
28+
})
29+
],
30+
imports: [LoadAsyncModule, MemoizeModule],
31+
componentProviders: [
32+
mockProvider(ApiTraceDetailService, {
33+
fetchTraceDetails: jest.fn().mockReturnValue(of(mockTraceDetails))
34+
})
35+
],
36+
declarations: [MockComponent(ExploreFilterLinkComponent)]
37+
});
38+
39+
test('should render content correctly', fakeAsync(() => {
40+
const spectator = createComponent();
41+
42+
spectator.click('.label');
43+
tick();
44+
expect(spectator.inject(NavigationService).navigateBack).toHaveBeenCalled();
45+
46+
spectator.click('.full-trace-button');
47+
tick();
48+
expect(spectator.inject(NavigationService).navigateWithinApp).toHaveBeenCalledWith([
49+
'/trace',
50+
mockTraceDetails.traceId,
51+
{ startTime: mockTraceDetails.startTime }
52+
]);
53+
}));
54+
55+
test('should render explorer link component', fakeAsync(() => {
56+
const spectator = createComponent();
57+
expect(spectator.query(ExploreFilterLinkComponent)?.paramsOrUrl).toBe('traceId_eq_test-123');
58+
}));
59+
});

projects/observability/src/pages/api-trace-detail/api-trace-detail.page.component.ts

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { ChangeDetectionStrategy, Component } from '@angular/core';
22
import { IconType } from '@hypertrace/assets-library';
3-
import { NavigationService, SubscriptionLifecycle } from '@hypertrace/common';
4-
import { ButtonRole, ButtonStyle, IconSize } from '@hypertrace/components';
3+
import { NavigationParams, NavigationService, SubscriptionLifecycle } from '@hypertrace/common';
4+
import { ButtonRole, ButtonStyle, FilterOperator, IconSize } from '@hypertrace/components';
55
import { Observable } from 'rxjs';
66
import { LogEvent } from '../../shared/dashboard/widgets/waterfall/waterfall/waterfall-chart';
7+
import { ExplorerService } from '../explorer/explorer-service';
8+
import { ScopeQueryParam } from '../explorer/explorer.component';
79
import { ApiTraceDetails, ApiTraceDetailService } from './api-trace-detail.service';
810

911
@Component({
@@ -31,12 +33,21 @@ import { ApiTraceDetails, ApiTraceDetailService } from './api-trace-detail.servi
3133
icon="${IconType.Time}"
3234
[value]="traceDetails.timeString"
3335
></ht-summary-value>
34-
<ht-summary-value
35-
class="summary-value"
36-
icon="${IconType.TraceId}"
37-
label="Trace ID"
38-
[value]="traceDetails.traceId"
39-
></ht-summary-value>
36+
37+
<div class="filterable-summary-value">
38+
<ht-summary-value
39+
class="summary-value"
40+
icon="${IconType.TraceId}"
41+
label="Trace ID"
42+
[value]="traceDetails.traceId"
43+
></ht-summary-value>
44+
<ht-explore-filter-link
45+
class="filter-link"
46+
[paramsOrUrl]="getExplorerNavigationParams | htMemoize: traceDetails | async"
47+
htTooltip="See traces in Explorer"
48+
>
49+
</ht-explore-filter-link>
50+
</div>
4051
4152
<div class="separation"></div>
4253
@@ -50,17 +61,17 @@ import { ApiTraceDetails, ApiTraceDetailService } from './api-trace-detail.servi
5061
(click)="this.navigateToFullTrace(traceDetails.traceId, traceDetails.startTime)"
5162
></ht-button>
5263
</div>
53-
</div>
5464
55-
<ht-navigable-tab-group class="tabs">
56-
<ht-navigable-tab path="sequence"> Sequence </ht-navigable-tab>
57-
<ng-container *ngIf="this.logEvents$ | async as logEvents">
58-
<ht-navigable-tab path="logs" [labelTag]="logEvents.length"> Logs </ht-navigable-tab>
59-
</ng-container>
60-
</ht-navigable-tab-group>
65+
<ht-navigable-tab-group class="tabs">
66+
<ht-navigable-tab path="sequence"> Sequence </ht-navigable-tab>
67+
<ng-container *ngIf="this.logEvents$ | async as logEvents">
68+
<ht-navigable-tab path="logs" [labelTag]="logEvents.length"> Logs </ht-navigable-tab>
69+
</ng-container>
70+
</ht-navigable-tab-group>
6171
62-
<div class="scrollable-container">
63-
<router-outlet></router-outlet>
72+
<div class="scrollable-container">
73+
<router-outlet></router-outlet>
74+
</div>
6475
</div>
6576
</div>
6677
`
@@ -73,7 +84,8 @@ export class ApiTraceDetailPageComponent {
7384

7485
public constructor(
7586
protected readonly navigationService: NavigationService,
76-
private readonly apiTraceDetailService: ApiTraceDetailService
87+
private readonly apiTraceDetailService: ApiTraceDetailService,
88+
private readonly explorerService: ExplorerService
7789
) {
7890
this.traceDetails$ = this.apiTraceDetailService.fetchTraceDetails();
7991
this.logEvents$ = this.apiTraceDetailService.fetchLogEvents();
@@ -86,4 +98,9 @@ export class ApiTraceDetailPageComponent {
8698
public navigateToFullTrace(traceId: string, startTime: string): void {
8799
this.navigationService.navigateWithinApp(['/trace', traceId, { startTime: startTime }]);
88100
}
101+
102+
public getExplorerNavigationParams = (traceDetails: ApiTraceDetails): Observable<NavigationParams> =>
103+
this.explorerService.buildNavParamsWithFilters(ScopeQueryParam.EndpointTraces, [
104+
{ field: 'traceId', operator: FilterOperator.Equals, value: traceDetails.traceId }
105+
]);
89106
}

projects/observability/src/pages/api-trace-detail/api-trace-detail.page.module.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { CommonModule } from '@angular/common';
22
import { NgModule } from '@angular/core';
33
import { RouterModule } from '@angular/router';
4-
import { FormattingModule, HtRoute } from '@hypertrace/common';
4+
import { FormattingModule, HtRoute, MemoizeModule } from '@hypertrace/common';
55
import {
66
ButtonModule,
77
CopyShareableLinkToClipboardModule,
88
IconModule,
99
LabelModule,
1010
LoadAsyncModule,
1111
NavigableTabModule,
12-
SummaryValueModule
12+
SummaryValueModule,
13+
TooltipModule
1314
} from '@hypertrace/components';
15+
import { ExploreFilterLinkModule } from '../../shared/components/explore-filter-link/explore-filter-link.module';
1416
import { LogEventsTableModule } from '../../shared/components/log-events/log-events-table.module';
1517
import { NavigableDashboardModule } from '../../shared/dashboard/dashboard-wrapper/navigable-dashboard.module';
1618
import { ObservabilityDashboardModule } from '../../shared/dashboard/observability-dashboard.module';
@@ -51,8 +53,11 @@ const ROUTE_CONFIG: HtRoute[] = [
5153
IconModule,
5254
SummaryValueModule,
5355
LoadAsyncModule,
56+
MemoizeModule,
5457
FormattingModule,
5558
ButtonModule,
59+
TooltipModule,
60+
ExploreFilterLinkModule,
5661
CopyShareableLinkToClipboardModule,
5762
NavigableTabModule,
5863
LogEventsTableModule,

projects/observability/src/pages/trace-detail/trace-detail.page.component.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,18 @@
3737
height: 18px;
3838
align-items: center;
3939

40+
.filterable-summary-value {
41+
display: flex;
42+
align-items: center;
43+
}
44+
4045
.separation {
4146
flex: 1 1 auto;
4247
}
4348
}
4449

4550
.summary-value {
46-
margin-right: 24px;
51+
margin-right: 8px;
4752
}
4853

4954
.tabs {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { fakeAsync, tick } from '@angular/core/testing';
2+
import { MemoizeModule, NavigationService } from '@hypertrace/common';
3+
import { LoadAsyncModule } from '@hypertrace/components';
4+
import { createComponentFactory, mockProvider } from '@ngneat/spectator/jest';
5+
import { MockComponent } from 'ng-mocks';
6+
import { of } from 'rxjs';
7+
import { ExploreFilterLinkComponent } from '../../shared/components/explore-filter-link/explore-filter-link.component';
8+
import { ExplorerService } from '../explorer/explorer-service';
9+
import { TraceDetailPageComponent } from './trace-detail.page.component';
10+
import { TraceDetails, TraceDetailService } from './trace-detail.service';
11+
describe('Trace Details Page Component', () => {
12+
const mockTraceDetails: TraceDetails = {
13+
id: 'test-id',
14+
entrySpanId: 'test-123',
15+
type: 'trace-type',
16+
timeString: 'test-time-string',
17+
titleString: 'test-title',
18+
startTime: 'test-start-time'
19+
};
20+
21+
const createComponent = createComponentFactory({
22+
component: TraceDetailPageComponent,
23+
shallow: true,
24+
providers: [
25+
mockProvider(NavigationService),
26+
mockProvider(ExplorerService, {
27+
buildNavParamsWithFilters: jest.fn().mockReturnValue(of('traceId_eq_test-123'))
28+
})
29+
],
30+
imports: [LoadAsyncModule, MemoizeModule],
31+
componentProviders: [
32+
mockProvider(TraceDetailService, {
33+
fetchTraceDetails: jest.fn().mockReturnValue(of(mockTraceDetails))
34+
})
35+
],
36+
declarations: [MockComponent(ExploreFilterLinkComponent)]
37+
});
38+
39+
test('should render content correctly', fakeAsync(() => {
40+
const spectator = createComponent();
41+
42+
spectator.click('.label');
43+
tick();
44+
expect(spectator.inject(NavigationService).navigateBack).toHaveBeenCalled();
45+
}));
46+
47+
test('should render explorer link component', fakeAsync(() => {
48+
const spectator = createComponent();
49+
expect(spectator.query(ExploreFilterLinkComponent)?.paramsOrUrl).toBe('traceId_eq_test-123');
50+
}));
51+
});

projects/observability/src/pages/trace-detail/trace-detail.page.component.ts

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { ChangeDetectionStrategy, Component } from '@angular/core';
22
import { IconType } from '@hypertrace/assets-library';
3-
import { NavigationService, SubscriptionLifecycle } from '@hypertrace/common';
4-
import { IconSize } from '@hypertrace/components';
3+
import { NavigationParams, NavigationService, SubscriptionLifecycle } from '@hypertrace/common';
4+
import { FilterOperator, IconSize } from '@hypertrace/components';
55
import { Observable } from 'rxjs';
66
import { LogEvent } from '../../shared/dashboard/widgets/waterfall/waterfall/waterfall-chart';
7+
import { ApiTraceDetails } from '../api-trace-detail/api-trace-detail.service';
8+
import { ExplorerService } from '../explorer/explorer-service';
9+
import { ScopeQueryParam } from '../explorer/explorer.component';
710
import { TraceDetails, TraceDetailService } from './trace-detail.service';
811
@Component({
912
styleUrls: ['./trace-detail.page.component.scss'],
@@ -30,12 +33,21 @@ import { TraceDetails, TraceDetailService } from './trace-detail.service';
3033
icon="${IconType.Time}"
3134
[value]="traceDetails.timeString"
3235
></ht-summary-value>
33-
<ht-summary-value
34-
class="summary-value"
35-
icon="${IconType.TraceId}"
36-
label="Trace ID"
37-
[value]="traceDetails.id"
38-
></ht-summary-value>
36+
37+
<div class="filterable-summary-value">
38+
<ht-summary-value
39+
class="summary-value"
40+
icon="${IconType.TraceId}"
41+
label="Trace ID"
42+
[value]="traceDetails.id"
43+
></ht-summary-value>
44+
<ht-explore-filter-link
45+
class="filter-link"
46+
[paramsOrUrl]="getExplorerNavigationParams | htMemoize: traceDetails | async"
47+
htTooltip="See traces in Explorer"
48+
>
49+
</ht-explore-filter-link>
50+
</div>
3951
4052
<div class="separation"></div>
4153
@@ -48,17 +60,17 @@ import { TraceDetails, TraceDetailService } from './trace-detail.service';
4860
htTooltip="Download Trace as Json"
4961
></ht-download-json>
5062
</div>
51-
</div>
5263
53-
<ht-navigable-tab-group class="tabs">
54-
<ht-navigable-tab path="sequence"> Sequence </ht-navigable-tab>
55-
<ng-container *ngIf="this.logEvents$ | async as logEvents">
56-
<ht-navigable-tab path="logs" [labelTag]="logEvents.length"> Logs </ht-navigable-tab>
57-
</ng-container>
58-
</ht-navigable-tab-group>
64+
<ht-navigable-tab-group class="tabs">
65+
<ht-navigable-tab path="sequence"> Sequence </ht-navigable-tab>
66+
<ng-container *ngIf="this.logEvents$ | async as logEvents">
67+
<ht-navigable-tab path="logs" [labelTag]="logEvents.length"> Logs </ht-navigable-tab>
68+
</ng-container>
69+
</ht-navigable-tab-group>
5970
60-
<div class="scrollable-container">
61-
<router-outlet></router-outlet>
71+
<div class="scrollable-container">
72+
<router-outlet></router-outlet>
73+
</div>
6274
</div>
6375
</div>
6476
`
@@ -72,7 +84,8 @@ export class TraceDetailPageComponent {
7284

7385
public constructor(
7486
private readonly navigationService: NavigationService,
75-
private readonly traceDetailService: TraceDetailService
87+
private readonly traceDetailService: TraceDetailService,
88+
private readonly explorerService: ExplorerService
7689
) {
7790
this.traceDetails$ = this.traceDetailService.fetchTraceDetails();
7891
this.exportSpans$ = this.traceDetailService.fetchExportSpans();
@@ -82,4 +95,9 @@ export class TraceDetailPageComponent {
8295
public onClickBack(): void {
8396
this.navigationService.navigateBack();
8497
}
98+
99+
public getExplorerNavigationParams = (traceDetails: ApiTraceDetails): Observable<NavigationParams> =>
100+
this.explorerService.buildNavParamsWithFilters(ScopeQueryParam.EndpointTraces, [
101+
{ field: 'traceId', operator: FilterOperator.Equals, value: traceDetails.id }
102+
]);
85103
}

projects/observability/src/pages/trace-detail/trace-detail.page.module.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { CommonModule } from '@angular/common';
22
import { NgModule } from '@angular/core';
33
import { RouterModule } from '@angular/router';
4-
import { FormattingModule, HtRoute } from '@hypertrace/common';
4+
import { FormattingModule, HtRoute, MemoizeModule } from '@hypertrace/common';
55
import {
66
CopyShareableLinkToClipboardModule,
77
DownloadJsonModule,
@@ -12,6 +12,7 @@ import {
1212
SummaryValueModule,
1313
TooltipModule
1414
} from '@hypertrace/components';
15+
import { ExploreFilterLinkModule } from '../../shared/components/explore-filter-link/explore-filter-link.module';
1516
import { LogEventsTableModule } from '../../shared/components/log-events/log-events-table.module';
1617
import { NavigableDashboardModule } from '../../shared/dashboard/dashboard-wrapper/navigable-dashboard.module';
1718
import { TracingDashboardModule } from '../../shared/dashboard/tracing-dashboard.module';
@@ -47,10 +48,12 @@ const ROUTE_CONFIG: HtRoute[] = [
4748
imports: [
4849
RouterModule.forChild(ROUTE_CONFIG),
4950
CommonModule,
51+
ExploreFilterLinkModule,
5052
LabelModule,
5153
TracingDashboardModule,
5254
IconModule,
5355
SummaryValueModule,
56+
MemoizeModule,
5457
TooltipModule,
5558
LoadAsyncModule,
5659
FormattingModule,

0 commit comments

Comments
 (0)