diff --git a/projects/distributed-tracing/src/pages/trace-detail/logs/trace-logs.component.ts b/projects/distributed-tracing/src/pages/trace-detail/logs/trace-logs.component.ts
new file mode 100644
index 000000000..ea34f9136
--- /dev/null
+++ b/projects/distributed-tracing/src/pages/trace-detail/logs/trace-logs.component.ts
@@ -0,0 +1,24 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { Observable } from 'rxjs';
+import { LogEventsTableViewType } from '../../../shared/components/log-events/log-events-table.component';
+import { LogEvent } from '../../../shared/dashboard/widgets/waterfall/waterfall/waterfall-chart';
+import { TraceDetailService } from './../trace-detail.service';
+
+@Component({
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+
+
+ `
+})
+export class TraceLogsComponent {
+ public readonly logEvents$: Observable;
+
+ public constructor(private readonly traceDetailService: TraceDetailService) {
+ this.logEvents$ = this.traceDetailService.fetchLogEvents();
+ }
+}
diff --git a/projects/distributed-tracing/src/pages/trace-detail/sequence/trace-sequence.component.ts b/projects/distributed-tracing/src/pages/trace-detail/sequence/trace-sequence.component.ts
new file mode 100644
index 000000000..2df86724c
--- /dev/null
+++ b/projects/distributed-tracing/src/pages/trace-detail/sequence/trace-sequence.component.ts
@@ -0,0 +1,38 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { TraceDetailService } from './../trace-detail.service';
+import { traceSequenceDashboard } from './trace-sequence.dashboard';
+
+@Component({
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+
+ `
+})
+export class TraceSequenceComponent {
+ public readonly traceVariables$: Observable;
+
+ public constructor(private readonly traceDetailService: TraceDetailService) {
+ this.traceVariables$ = this.traceDetailService.fetchTraceDetails().pipe(
+ map(details => ({
+ traceId: details.id,
+ startTime: details.startTime,
+ spanId: details.entrySpanId
+ }))
+ );
+ }
+}
+
+interface TraceDetailVariables {
+ traceId: string;
+ startTime?: string | number;
+ spanId?: string;
+}
diff --git a/projects/distributed-tracing/src/pages/trace-detail/trace-detail.dashboard.ts b/projects/distributed-tracing/src/pages/trace-detail/sequence/trace-sequence.dashboard.ts
similarity index 74%
rename from projects/distributed-tracing/src/pages/trace-detail/trace-detail.dashboard.ts
rename to projects/distributed-tracing/src/pages/trace-detail/sequence/trace-sequence.dashboard.ts
index bd6770239..8d4d32acd 100644
--- a/projects/distributed-tracing/src/pages/trace-detail/trace-detail.dashboard.ts
+++ b/projects/distributed-tracing/src/pages/trace-detail/sequence/trace-sequence.dashboard.ts
@@ -1,7 +1,7 @@
-import { DashboardDefaultConfiguration } from '../../shared/dashboard/dashboard-wrapper/navigable-dashboard.module';
+import { DashboardDefaultConfiguration } from '../../../shared/dashboard/dashboard-wrapper/navigable-dashboard.module';
-export const traceDetailDashboard: DashboardDefaultConfiguration = {
- location: 'TRACE_DETAIL',
+export const traceSequenceDashboard: DashboardDefaultConfiguration = {
+ location: 'TRACE_SEQUENCE',
json: {
type: 'container-widget',
layout: {
diff --git a/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.component.scss b/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.component.scss
index 628045ccb..491d2f9b1 100644
--- a/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.component.scss
+++ b/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.component.scss
@@ -45,4 +45,8 @@
.summary-value {
margin-right: 24px;
}
+
+ .tabs {
+ margin-top: 16px;
+ }
}
diff --git a/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.component.ts b/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.component.ts
index 64abd18ed..0cfae196b 100644
--- a/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.component.ts
+++ b/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.component.ts
@@ -2,11 +2,8 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IconType } from '@hypertrace/assets-library';
import { NavigationService, SubscriptionLifecycle } from '@hypertrace/common';
import { IconSize } from '@hypertrace/components';
-
-import { Dashboard } from '@hypertrace/hyperdash';
import { Observable } from 'rxjs';
-
-import { traceDetailDashboard } from './trace-detail.dashboard';
+import { LogEvent } from '../../shared/dashboard/widgets/waterfall/waterfall/waterfall-chart';
import { TraceDetails, TraceDetailService } from './trace-detail.service';
@Component({
styleUrls: ['./trace-detail.page.component.scss'],
@@ -53,13 +50,16 @@ import { TraceDetails, TraceDetailService } from './trace-detail.service';
-
-
+
+ Sequence
+
+ Logs
+
+
+
+
+
+
`
})
@@ -68,25 +68,15 @@ export class TraceDetailPageComponent {
public readonly traceDetails$: Observable;
public readonly exportSpans$: Observable;
+ public readonly logEvents$: Observable;
public constructor(
- private readonly subscriptionLifecycle: SubscriptionLifecycle,
private readonly navigationService: NavigationService,
private readonly traceDetailService: TraceDetailService
) {
this.traceDetails$ = this.traceDetailService.fetchTraceDetails();
this.exportSpans$ = this.traceDetailService.fetchExportSpans();
- }
-
- public onDashboardReady(dashboard: Dashboard): void {
- this.subscriptionLifecycle.add(
- this.traceDetails$.subscribe(traceDetails => {
- dashboard.setVariable('traceId', traceDetails.id);
- dashboard.setVariable('spanId', traceDetails.entrySpanId);
- dashboard.setVariable('startTime', traceDetails.startTime);
- dashboard.refresh();
- })
- );
+ this.logEvents$ = this.traceDetailService.fetchLogEvents();
}
public onClickBack(): void {
diff --git a/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.module.ts b/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.module.ts
index 541fb3332..576bb1def 100644
--- a/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.module.ts
+++ b/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.module.ts
@@ -8,23 +8,42 @@ import {
IconModule,
LabelModule,
LoadAsyncModule,
+ NavigableTabModule,
SummaryValueModule,
TooltipModule
} from '@hypertrace/components';
+import { LogEventsTableModule } from '../../shared/components/log-events/log-events-table.module';
import { NavigableDashboardModule } from '../../shared/dashboard/dashboard-wrapper/navigable-dashboard.module';
import { TracingDashboardModule } from '../../shared/dashboard/tracing-dashboard.module';
-import { traceDetailDashboard } from './trace-detail.dashboard';
+import { TraceLogsComponent } from './logs/trace-logs.component';
+import { TraceSequenceComponent } from './sequence/trace-sequence.component';
+import { traceSequenceDashboard } from './sequence/trace-sequence.dashboard';
import { TraceDetailPageComponent } from './trace-detail.page.component';
const ROUTE_CONFIG: TraceRoute[] = [
{
path: `:${TraceDetailPageComponent.TRACE_ID_PARAM_NAME}`,
- component: TraceDetailPageComponent
+ component: TraceDetailPageComponent,
+ children: [
+ {
+ path: '',
+ redirectTo: 'sequence',
+ pathMatch: 'full'
+ },
+ {
+ path: 'sequence',
+ component: TraceSequenceComponent
+ },
+ {
+ path: 'logs',
+ component: TraceLogsComponent
+ }
+ ]
}
];
@NgModule({
- declarations: [TraceDetailPageComponent],
+ declarations: [TraceDetailPageComponent, TraceSequenceComponent, TraceLogsComponent],
imports: [
RouterModule.forChild(ROUTE_CONFIG),
CommonModule,
@@ -37,7 +56,9 @@ const ROUTE_CONFIG: TraceRoute[] = [
FormattingModule,
CopyShareableLinkToClipboardModule,
DownloadJsonModule,
- NavigableDashboardModule.withDefaultDashboards(traceDetailDashboard)
+ NavigableDashboardModule.withDefaultDashboards(traceSequenceDashboard),
+ NavigableTabModule,
+ LogEventsTableModule
]
})
export class TraceDetailPageModule {}
diff --git a/projects/distributed-tracing/src/pages/trace-detail/trace-detail.service.test.ts b/projects/distributed-tracing/src/pages/trace-detail/trace-detail.service.test.ts
index 234a68953..39723895b 100644
--- a/projects/distributed-tracing/src/pages/trace-detail/trace-detail.service.test.ts
+++ b/projects/distributed-tracing/src/pages/trace-detail/trace-detail.service.test.ts
@@ -9,7 +9,7 @@ import {
} from '@hypertrace/common';
import { GraphQlRequestService } from '@hypertrace/graphql-client';
import { runFakeRxjs } from '@hypertrace/test-utils';
-import { createServiceFactory, mockProvider, SpectatorService } from '@ngneat/spectator/jest';
+import { createServiceFactory, mockProvider } from '@ngneat/spectator/jest';
import { of } from 'rxjs';
import { AttributeMetadataType } from '../../shared/graphql/model/metadata/attribute-metadata';
import { spanIdKey } from '../../shared/graphql/model/schema/span';
@@ -18,63 +18,62 @@ import { MetadataService } from '../../shared/services/metadata/metadata.service
import { TraceDetailService } from './trace-detail.service';
describe('TraceDetailService', () => {
- let spectator: SpectatorService;
-
const createService = createServiceFactory({
- service: TraceDetailService,
- providers: [
- mockProvider(ActivatedRoute, {
- paramMap: of({
- get: (param: string) => {
- if (param === 'startTime') {
- return 1608151401295;
- }
+ service: TraceDetailService
+ });
- return 'test-id';
- }
- })
- }),
- mockProvider(TimeRangeService, {
- getTimeRangeAndChanges: jest.fn().mockReturnValue(of(new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour))))
- }),
- mockProvider(GraphQlRequestService, {
- query: jest.fn().mockReturnValue(
- of({
- [traceTypeKey]: TRACE_SCOPE,
- [traceIdKey]: 'test-id',
- startTime: 1576364117792,
- duration: 20,
- spans: [
- {
- [spanIdKey]: 'test-id',
- serviceName: 'test service',
- displaySpanName: 'egress'
+ test('should fetch and map trace details', () => {
+ const spectator = createService({
+ providers: [
+ mockProvider(ActivatedRoute, {
+ paramMap: of({
+ get: (param: string) => {
+ if (param === 'startTime') {
+ return 1608151401295;
}
- ]
- })
- )
- }),
- mockProvider(MetadataService, {
- getAttribute: (scope: string, attributeKey: string) =>
- of({
- name: attributeKey,
- displayName: 'Latency',
- units: 'ms',
- type: AttributeMetadataType.Number,
- scope: scope,
- onlySupportsAggregation: false,
- onlySupportsGrouping: false,
- allowedAggregations: []
- })
- })
- ]
- });
- beforeEach(() => {
- spectator = createService();
- });
+ return 'test-id';
+ }
+ })
+ }),
+ mockProvider(TimeRangeService, {
+ getTimeRangeAndChanges: jest
+ .fn()
+ .mockReturnValue(of(new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour))))
+ }),
+ mockProvider(GraphQlRequestService, {
+ query: jest.fn().mockReturnValue(
+ of({
+ [traceTypeKey]: TRACE_SCOPE,
+ [traceIdKey]: 'test-id',
+ startTime: 1576364117792,
+ duration: 20,
+ spans: [
+ {
+ [spanIdKey]: 'test-id',
+ serviceName: 'test service',
+ displaySpanName: 'egress'
+ }
+ ]
+ })
+ )
+ }),
+ mockProvider(MetadataService, {
+ getAttribute: (scope: string, attributeKey: string) =>
+ of({
+ name: attributeKey,
+ displayName: 'Latency',
+ units: 'ms',
+ type: AttributeMetadataType.Number,
+ scope: scope,
+ onlySupportsAggregation: false,
+ onlySupportsGrouping: false,
+ allowedAggregations: []
+ })
+ })
+ ]
+ });
- test('should map fetch and map trace details', () => {
runFakeRxjs(({ expectObservable }) => {
expectObservable(spectator.service.fetchTraceDetails()).toBe('(x|)', {
x: {
@@ -90,4 +89,90 @@ describe('TraceDetailService', () => {
});
});
});
+
+ test('should fetch export spans', () => {
+ const spectator = createService({
+ providers: [
+ mockProvider(ActivatedRoute, {
+ paramMap: of({
+ get: (param: string) => {
+ if (param === 'startTime') {
+ return 1608151401295;
+ }
+
+ return 'test-id';
+ }
+ })
+ }),
+ mockProvider(GraphQlRequestService, {
+ query: jest.fn().mockReturnValue(of('result'))
+ })
+ ]
+ });
+
+ runFakeRxjs(({ expectObservable }) => {
+ expectObservable(spectator.service.fetchExportSpans()).toBe('(x|)', {
+ x: 'result'
+ });
+ });
+ });
+
+ test('should fetch and map logEvents', () => {
+ const spectator = createService({
+ providers: [
+ mockProvider(ActivatedRoute, {
+ paramMap: of({
+ get: (param: string) => {
+ if (param === 'startTime') {
+ return 1608151401295;
+ }
+
+ return 'test-id';
+ }
+ })
+ }),
+ mockProvider(GraphQlRequestService, {
+ query: jest.fn().mockReturnValue(
+ of({
+ spans: [
+ {
+ logEvents: {
+ results: [
+ {
+ timestamp: 'time',
+ attributes: undefined,
+ summary: 'summary'
+ }
+ ]
+ },
+ serviceName: 'service',
+ displaySpanName: 'span',
+ protocolName: 'protocol',
+ startTime: 1608151401295
+ }
+ ]
+ })
+ )
+ })
+ ]
+ });
+
+ runFakeRxjs(({ expectObservable }) => {
+ expectObservable(spectator.service.fetchLogEvents()).toBe('(x|)', {
+ x: [
+ {
+ timestamp: 'time',
+ attributes: undefined,
+ summary: 'summary',
+ $$spanName: {
+ serviceName: 'service',
+ protocolName: 'protocol',
+ apiName: 'span'
+ },
+ spanStartTime: 1608151401295
+ }
+ ]
+ });
+ });
+ });
});
diff --git a/projects/distributed-tracing/src/pages/trace-detail/trace-detail.service.ts b/projects/distributed-tracing/src/pages/trace-detail/trace-detail.service.ts
index 1efc4bd24..cc45f88d0 100644
--- a/projects/distributed-tracing/src/pages/trace-detail/trace-detail.service.ts
+++ b/projects/distributed-tracing/src/pages/trace-detail/trace-detail.service.ts
@@ -1,10 +1,10 @@
import { Injectable, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { DateCoercer, DateFormatMode, DateFormatter, ReplayObservable } from '@hypertrace/common';
-
import { GraphQlRequestService } from '@hypertrace/graphql-client';
import { Observable, Subject } from 'rxjs';
import { map, shareReplay, switchMap, takeUntil } from 'rxjs/operators';
+import { LogEvent } from '../../shared/dashboard/widgets/waterfall/waterfall/waterfall-chart';
import { Trace, traceIdKey, TraceType, traceTypeKey } from '../../shared/graphql/model/schema/trace';
import { SpecificationBuilder } from '../../shared/graphql/request/builders/specification/specification-builder';
import {
@@ -15,6 +15,7 @@ import {
TraceGraphQlQueryHandlerService,
TRACE_GQL_REQUEST
} from '../../shared/graphql/request/handlers/traces/trace-graphql-query-handler.service';
+import { LogEventsService } from '../../shared/services/log-events/log-events.service';
import { MetadataService } from '../../shared/services/metadata/metadata.service';
@Injectable()
@@ -32,7 +33,8 @@ export class TraceDetailService implements OnDestroy {
public constructor(
route: ActivatedRoute,
private readonly metadataService: MetadataService,
- private readonly graphQlQueryService: GraphQlRequestService
+ private readonly graphQlQueryService: GraphQlRequestService,
+ private readonly logEventsService: LogEventsService
) {
this.routeIds$ = route.paramMap.pipe(
map(paramMap => ({
@@ -101,6 +103,17 @@ export class TraceDetailService implements OnDestroy {
);
}
+ public fetchLogEvents(): Observable {
+ return this.routeIds$.pipe(
+ switchMap(routeIds =>
+ this.logEventsService.getLogEventsGqlResponseForTrace(routeIds.traceId, routeIds.startTime)
+ ),
+ map(trace => this.logEventsService.mapLogEvents(trace)),
+ takeUntil(this.destroyed$),
+ shareReplay(1)
+ );
+ }
+
private fetchTrace(traceId: string, spanId?: string, startTime?: string | number): Observable {
return this.graphQlQueryService.query({
requestType: TRACE_GQL_REQUEST,
diff --git a/projects/distributed-tracing/src/public-api.ts b/projects/distributed-tracing/src/public-api.ts
index a65c188ae..e900e2bda 100644
--- a/projects/distributed-tracing/src/public-api.ts
+++ b/projects/distributed-tracing/src/public-api.ts
@@ -7,6 +7,8 @@ export * from './shared/graphql/model/schema/enriched-attribute';
// Pages
export * from './pages/trace-detail/trace-detail.page.module';
+export * from './pages/trace-detail/logs/trace-logs.component';
+export * from './pages/trace-detail/sequence/trace-sequence.component';
export * from './pages/spans/span-list.page.module';
// Builders
@@ -51,6 +53,10 @@ export * from './shared/icons/tracing-icon-library.module';
// Interaction Handler - Deprecated
export { InteractionHandler } from './shared/dashboard/interaction/interaction-handler';
+// Log Events Table
+export * from './shared/components/log-events/log-events-table.component';
+export * from './shared/components/log-events/log-events-table.module';
+
// Metadata
export * from './shared/services/metadata/metadata.service';
export * from './shared/services/metadata/metadata.service.module';
@@ -83,6 +89,7 @@ export * from './shared/graphql/model/schema/trace';
// Services
export * from './pages/trace-detail/trace-detail.service';
+export * from './shared/services/log-events/log-events.service';
// Span Detail
export { SpanData } from './shared/components/span-detail/span-data';
@@ -104,7 +111,7 @@ export * from './shared/components/table/tracing-table-cell-type';
// Waterfall
export { WaterfallData } from './shared/dashboard/widgets/waterfall/waterfall/waterfall-chart';
export { TraceWaterfallDataSourceModel } from './shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model';
-export { traceDetailDashboard } from './pages/trace-detail/trace-detail.dashboard';
+export { traceSequenceDashboard } from './pages/trace-detail/sequence/trace-sequence.dashboard';
export { TraceDetailPageComponent } from './pages/trace-detail/trace-detail.page.component';
export { LogEvent } from './shared/dashboard/widgets/waterfall/waterfall/waterfall-chart';
diff --git a/projects/distributed-tracing/src/shared/components/log-events/log-events-table.component.test.ts b/projects/distributed-tracing/src/shared/components/log-events/log-events-table.component.test.ts
index f8a5c53ab..ac311b73d 100644
--- a/projects/distributed-tracing/src/shared/components/log-events/log-events-table.component.test.ts
+++ b/projects/distributed-tracing/src/shared/components/log-events/log-events-table.component.test.ts
@@ -29,14 +29,14 @@ describe('LogEventsTableComponent', () => {
test('should render data correctly for sheet view', fakeAsync(() => {
spectator = createHost(
- ``,
+ ``,
{
hostProps: {
logEvents: [
{ attributes: { attr1: 1, attr2: 2 }, summary: 'test', timestamp: '2021-04-30T12:23:57.889149Z' }
],
logEventsTableViewType: LogEventsTableViewType.Condensed,
- spanStartTime: 1619785437887
+ startTime: 1619785437887
}
}
);
@@ -52,7 +52,7 @@ describe('LogEventsTableComponent', () => {
id: 'summary'
})
]);
- expect(spectator.query(TableComponent)!.pageable).toBe(false);
+ expect(spectator.query(TableComponent)!.pageable).toBe(true);
expect(spectator.query(TableComponent)!.detailContent).not.toBeNull();
runFakeRxjs(({ expectObservable }) => {
diff --git a/projects/distributed-tracing/src/shared/components/log-events/log-events-table.component.ts b/projects/distributed-tracing/src/shared/components/log-events/log-events-table.component.ts
index 92debd388..ce6b22552 100644
--- a/projects/distributed-tracing/src/shared/components/log-events/log-events-table.component.ts
+++ b/projects/distributed-tracing/src/shared/components/log-events/log-events-table.component.ts
@@ -13,6 +13,7 @@ import {
import { LogEvent } from '@hypertrace/distributed-tracing';
import { isEmpty } from 'lodash-es';
import { Observable, of } from 'rxjs';
+import { WaterfallTableCellType } from '../../dashboard/widgets/waterfall/waterfall/span-name/span-name-cell-type';
export const enum LogEventsTableViewType {
Condensed = 'condensed',
@@ -28,7 +29,7 @@ export const enum LogEventsTableViewType {
({
...logEvent,
timestamp: this.dateCoercer.coerce(logEvent.timestamp),
- baseTimestamp: this.dateCoercer.coerce(this.spanStartTime)
+ baseTimestamp: this.dateCoercer.coerce(logEvent.spanStartTime)
})),
totalCount: this.logEvents.length
}),
@@ -93,29 +91,58 @@ export class LogEventsTableComponent implements OnChanges {
}
private getTableColumnConfigs(): TableColumnConfig[] {
- if (this.logEventsTableViewType === LogEventsTableViewType.Condensed) {
- return [
- {
- id: 'timestamp',
- name: 'timestamp',
- title: 'Timestamp',
- display: CoreTableCellRendererType.RelativeTimestamp,
- visible: true,
- width: '150px',
- sortable: false,
- filterable: false
- },
- {
- id: 'summary',
- name: 'summary',
- title: 'Summary',
- visible: true,
- sortable: false,
- filterable: false
- }
- ];
+ switch (this.logEventsTableViewType) {
+ case LogEventsTableViewType.Condensed:
+ return [
+ {
+ id: 'timestamp',
+ name: 'timestamp',
+ title: 'Timestamp',
+ display: CoreTableCellRendererType.RelativeTimestamp,
+ visible: true,
+ width: '150px',
+ sortable: false,
+ filterable: false
+ },
+ {
+ id: 'summary',
+ name: 'summary',
+ title: 'Summary',
+ visible: true,
+ sortable: false,
+ filterable: false
+ }
+ ];
+ case LogEventsTableViewType.Detailed:
+ return [
+ {
+ id: 'timestamp',
+ title: 'Timestamp',
+ display: CoreTableCellRendererType.RelativeTimestamp,
+ visible: true,
+ width: '150px',
+ sortable: false,
+ filterable: false
+ },
+ {
+ id: 'summary',
+ title: 'Summary',
+ visible: true,
+ sortable: false,
+ filterable: false
+ },
+ {
+ id: '$$spanName',
+ title: 'Span',
+ display: WaterfallTableCellType.SpanName,
+ visible: true,
+ width: '30%',
+ sortable: false,
+ filterable: false
+ }
+ ];
+ default:
+ return [];
}
-
- return [];
}
}
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 13f988aa9..44c67aaf1 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
@@ -47,11 +47,8 @@ import { SpanDetailTab } from './span-detail-tab';
-
-
+
+
diff --git a/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.test.ts b/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.test.ts
index 97b1a9b74..a02b3ae74 100644
--- a/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.test.ts
+++ b/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.test.ts
@@ -4,6 +4,7 @@ import { mockProvider } from '@ngneat/spectator/jest';
import { Observable, of } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { Trace, traceIdKey, traceTypeKey, TRACE_SCOPE } from '../../../../graphql/model/schema/trace';
+import { LogEventsService } from '../../../../services/log-events/log-events.service';
import { MetadataService } from '../../../../services/metadata/metadata.service';
import { WaterfallData } from '../../../widgets/waterfall/waterfall/waterfall-chart';
import { GraphQlQueryEventService } from '../graphql-query-event.service';
@@ -21,7 +22,10 @@ describe('Trace Waterfall data source model', () => {
})
)
}),
- mockProvider(GraphQlQueryEventService)
+ mockProvider(GraphQlQueryEventService),
+ mockProvider(LogEventsService, {
+ getLogEventsWithSpanStartTime: jest.fn().mockReturnValue([])
+ })
]
});
@@ -210,7 +214,8 @@ describe('Trace Waterfall data source model', () => {
displaySpanName: 'Span Name 1',
serviceName: 'Service Name 1',
type: SpanType.Entry,
- spanTags: {}
+ spanTags: {},
+ logEvents: []
},
{
[spanIdKey]: 'second-id',
@@ -221,7 +226,8 @@ describe('Trace Waterfall data source model', () => {
displaySpanName: 'Span Name 2',
serviceName: 'Service Name 2',
type: SpanType.Exit,
- spanTags: {}
+ spanTags: {},
+ logEvents: []
}
]
})
@@ -241,7 +247,8 @@ describe('Trace Waterfall data source model', () => {
serviceName: 'Service Name 1',
protocolName: undefined,
spanType: SpanType.Entry,
- tags: {}
+ tags: {},
+ logEvents: []
},
{
id: 'second-id',
@@ -257,7 +264,8 @@ describe('Trace Waterfall data source model', () => {
serviceName: 'Service Name 2',
protocolName: undefined,
spanType: SpanType.Exit,
- tags: {}
+ tags: {},
+ logEvents: []
}
]
});
diff --git a/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.ts b/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.ts
index 30fccf333..615c28296 100644
--- a/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.ts
+++ b/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.ts
@@ -12,6 +12,7 @@ import {
TraceGraphQlQueryHandlerService,
TRACE_GQL_REQUEST
} from '../../../../graphql/request/handlers/traces/trace-graphql-query-handler.service';
+import { LogEventsService } from '../../../../services/log-events/log-events.service';
import { MetadataService } from '../../../../services/metadata/metadata.service';
import { LogEvent, WaterfallData } from '../../../widgets/waterfall/waterfall/waterfall-chart';
import { GraphQlDataSourceModel } from '../graphql-data-source.model';
@@ -44,6 +45,9 @@ export class TraceWaterfallDataSourceModel extends GraphQlDataSourceModel,
errorCount: span.errorCount as number,
- logEvents: ((span.logEvents as Dictionary) ?? {}).results
+ logEvents: this.logEventsService.getLogEventsWithSpanStartTime(
+ span.logEvents as Dictionary,
+ span.startTime as number
+ )
}));
}
}
diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.scss b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.scss
index 874d26418..848088827 100644
--- a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.scss
+++ b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.scss
@@ -7,12 +7,12 @@
.span-title {
display: grid;
- grid-template-columns: 3px min-content min-content min-content min-content auto;
+ grid-template-columns: repeat(5, min-content) auto;
grid-template-rows: 1fr;
column-gap: 4px;
.color-bar {
- width: 100%;
+ width: 3px;
height: 16px;
border-radius: 3px;
align-self: center;
diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.ts
index c5077dc3a..d9b1ca14d 100644
--- a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.ts
+++ b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.ts
@@ -40,6 +40,7 @@ export interface WaterfallChartState {
}
export interface LogEvent {
+ spanStartTime?: number;
attributes: Dictionary;
timestamp: string;
summary: string;
diff --git a/projects/distributed-tracing/src/shared/services/log-events/log-events.service.test.ts b/projects/distributed-tracing/src/shared/services/log-events/log-events.service.test.ts
new file mode 100644
index 000000000..e0f597cb4
--- /dev/null
+++ b/projects/distributed-tracing/src/shared/services/log-events/log-events.service.test.ts
@@ -0,0 +1,116 @@
+import { Dictionary } from '@hypertrace/common';
+import { GraphQlRequestService } from '@hypertrace/graphql-client';
+import { runFakeRxjs } from '@hypertrace/test-utils';
+import { createServiceFactory, mockProvider } from '@ngneat/spectator/jest';
+import { Observable, of } from 'rxjs';
+import { LogEvent } from '../../dashboard/widgets/waterfall/waterfall/waterfall-chart';
+import { spanIdKey } from '../../graphql/model/schema/span';
+import { Trace, traceIdKey, traceTypeKey } from '../../graphql/model/schema/trace';
+import { LogEventsService } from './log-events.service';
+
+describe('Log Events Service', () => {
+ const mockTrace: Trace = {
+ [traceIdKey]: 'test-id',
+ [traceTypeKey]: 'API_TRACE',
+ spans: []
+ };
+
+ const mockTraceWithLogEvents: Trace = {
+ [traceIdKey]: 'test-id',
+ [traceTypeKey]: 'TRACE',
+ spans: [
+ {
+ [spanIdKey]: 'test-span',
+ logEvents: {
+ results: [
+ {
+ timestamp: 'time',
+ attributes: undefined,
+ summary: 'summary'
+ }
+ ]
+ },
+ serviceName: 'service',
+ displaySpanName: 'span',
+ protocolName: 'protocol',
+ startTime: 1608151401295
+ }
+ ]
+ };
+
+ const createService = createServiceFactory({
+ service: LogEventsService,
+ providers: [
+ mockProvider(GraphQlRequestService, {
+ query: jest.fn().mockReturnValue(of({}))
+ })
+ ]
+ });
+
+ const expectSingleValueObservable = (observable: Observable, value: T): void => {
+ runFakeRxjs(({ expectObservable }) => {
+ expectObservable(observable).toBe('(x|)', { x: value });
+ });
+ };
+
+ test('should return empty array for zero log Events', () => {
+ const spectator = createService();
+ expect(spectator.service.getLogEventsWithSpanStartTime({ results: [] }, 1608151401295)).toMatchObject([]);
+ });
+
+ test('should return array with start time for log Events', () => {
+ const spectator = createService();
+ const logEvents: Dictionary = {
+ results: [
+ {
+ timestamp: 'time',
+ attributes: {},
+ summary: 'summary'
+ }
+ ]
+ };
+ const logEventsWithSpanStartTime = [
+ {
+ timestamp: 'time',
+ attributes: {},
+ summary: 'summary',
+ spanStartTime: 1608151401295
+ }
+ ];
+ expect(spectator.service.getLogEventsWithSpanStartTime(logEvents, 1608151401295)).toMatchObject(
+ logEventsWithSpanStartTime
+ );
+ });
+
+ test('should return trace and spans', () => {
+ const spectator = createService({
+ providers: [
+ mockProvider(GraphQlRequestService, {
+ query: jest.fn().mockReturnValue(of(mockTrace))
+ })
+ ]
+ });
+ expectSingleValueObservable(
+ spectator.service.getLogEventsGqlResponseForTrace('test', '1608151401295', 'API_TRACE'),
+ mockTrace
+ );
+ });
+
+ test('should map log events', () => {
+ const spectator = createService();
+ const mappedLogEvents = [
+ {
+ timestamp: 'time',
+ attributes: undefined,
+ summary: 'summary',
+ $$spanName: {
+ serviceName: 'service',
+ apiName: 'span',
+ protocolName: 'protocol'
+ },
+ spanStartTime: 1608151401295
+ }
+ ];
+ expect(spectator.service.mapLogEvents(mockTraceWithLogEvents)).toMatchObject(mappedLogEvents);
+ });
+});
diff --git a/projects/distributed-tracing/src/shared/services/log-events/log-events.service.ts b/projects/distributed-tracing/src/shared/services/log-events/log-events.service.ts
new file mode 100644
index 000000000..f19c8028d
--- /dev/null
+++ b/projects/distributed-tracing/src/shared/services/log-events/log-events.service.ts
@@ -0,0 +1,79 @@
+import { Injectable } from '@angular/core';
+import { DateCoercer, Dictionary } from '@hypertrace/common';
+import { GraphQlRequestService } from '@hypertrace/graphql-client';
+import { ObservabilityTraceType } from '@hypertrace/observability';
+import { isEmpty } from 'lodash-es';
+import { Observable } from 'rxjs';
+import { LogEvent } from '../../dashboard/widgets/waterfall/waterfall/waterfall-chart';
+import { Span } from '../../graphql/model/schema/span';
+import { Trace, TraceType, traceTypeKey } from '../../graphql/model/schema/trace';
+import { SpecificationBuilder } from '../../graphql/request/builders/specification/specification-builder';
+import {
+ TraceGraphQlQueryHandlerService,
+ TRACE_GQL_REQUEST
+} from '../../graphql/request/handlers/traces/trace-graphql-query-handler.service';
+
+@Injectable({ providedIn: 'root' })
+export class LogEventsService {
+ private readonly specificationBuilder: SpecificationBuilder = new SpecificationBuilder();
+ private readonly dateCoercer: DateCoercer = new DateCoercer();
+ private readonly logEventProperties: string[] = ['attributes', 'timestamp', 'summary'];
+ private readonly spanPropertiesForTrace: string[] = ['startTime', 'serviceName', 'displaySpanName', 'protocolName'];
+ private readonly spanPropertiesForApiTrace: string[] = [
+ 'startTime',
+ 'displayEntityName',
+ 'displaySpanName',
+ 'protocolName'
+ ];
+
+ public constructor(private readonly graphQlQueryService: GraphQlRequestService) {}
+
+ public getLogEventsWithSpanStartTime(logEventsObject: Dictionary, startTime: number): LogEvent[] {
+ if (isEmpty(logEventsObject) || isEmpty(logEventsObject.results)) {
+ return [];
+ }
+
+ return logEventsObject.results.map(logEvent => ({
+ ...logEvent,
+ spanStartTime: startTime
+ }));
+ }
+
+ public getLogEventsGqlResponseForTrace(
+ traceId: string,
+ startTime?: string,
+ traceType?: TraceType
+ ): Observable {
+ return this.graphQlQueryService.query({
+ requestType: TRACE_GQL_REQUEST,
+ traceType: traceType,
+ traceId: traceId,
+ timestamp: this.dateCoercer.coerce(startTime),
+ traceProperties: [],
+ spanProperties: (traceType === ObservabilityTraceType.Api
+ ? this.spanPropertiesForApiTrace
+ : this.spanPropertiesForTrace
+ ).map(property => this.specificationBuilder.attributeSpecificationForKey(property)),
+ logEventProperties: this.logEventProperties.map(property =>
+ this.specificationBuilder.attributeSpecificationForKey(property)
+ ),
+ spanLimit: 1000
+ });
+ }
+
+ public mapLogEvents(trace: Trace): LogEvent[] {
+ return (trace.spans ?? [])
+ .map((span: Span) =>
+ (span.logEvents as Dictionary).results.map(logEvent => ({
+ ...logEvent,
+ $$spanName: {
+ serviceName: trace[traceTypeKey] === ObservabilityTraceType.Api ? span.displayEntityName : span.serviceName,
+ protocolName: span.protocolName,
+ apiName: span.displaySpanName
+ },
+ spanStartTime: span.startTime as number
+ }))
+ )
+ .flat();
+ }
+}
diff --git a/projects/observability/src/pages/api-trace-detail/api-trace-detail.page.component.scss b/projects/observability/src/pages/api-trace-detail/api-trace-detail.page.component.scss
index 01e0c2494..9a4ce3371 100644
--- a/projects/observability/src/pages/api-trace-detail/api-trace-detail.page.component.scss
+++ b/projects/observability/src/pages/api-trace-detail/api-trace-detail.page.component.scss
@@ -47,4 +47,8 @@
.summary-value {
margin-right: 24px;
}
+
+ .tabs {
+ margin-top: 8px;
+ }
}
diff --git a/projects/observability/src/pages/api-trace-detail/api-trace-detail.page.component.ts b/projects/observability/src/pages/api-trace-detail/api-trace-detail.page.component.ts
index 64d612848..7ad23db4f 100644
--- a/projects/observability/src/pages/api-trace-detail/api-trace-detail.page.component.ts
+++ b/projects/observability/src/pages/api-trace-detail/api-trace-detail.page.component.ts
@@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IconType } from '@hypertrace/assets-library';
import { NavigationService, SubscriptionLifecycle } from '@hypertrace/common';
import { ButtonRole, ButtonStyle, IconSize } from '@hypertrace/components';
-
-import { Dashboard, ModelJson } from '@hypertrace/hyperdash';
+import { LogEvent } from '@hypertrace/distributed-tracing';
import { Observable } from 'rxjs';
import { ApiTraceDetails, ApiTraceDetailService } from './api-trace-detail.service';
@@ -53,13 +52,15 @@ import { ApiTraceDetails, ApiTraceDetailService } from './api-trace-detail.servi
+
+ Sequence
+
+ Logs
+
+
+
-
-
+
`
@@ -68,45 +69,14 @@ export class ApiTraceDetailPageComponent {
public static readonly TRACE_ID_PARAM_NAME: string = 'id';
public readonly traceDetails$: Observable;
-
- public readonly defaultJson: ModelJson = {
- type: 'container-widget',
- layout: {
- type: 'auto-container-layout',
- 'enable-style': false
- },
- children: [
- {
- type: 'waterfall-widget',
- title: 'Sequence Diagram',
- data: {
- type: 'api-trace-waterfall-data-source',
- // tslint:disable-next-line: no-invalid-template-strings
- 'trace-id': '${traceId}',
- // tslint:disable-next-line: no-invalid-template-strings
- 'start-time': '${startTime}'
- }
- }
- ]
- };
+ public readonly logEvents$: Observable;
public constructor(
- private readonly subscriptionLifecycle: SubscriptionLifecycle,
protected readonly navigationService: NavigationService,
private readonly apiTraceDetailService: ApiTraceDetailService
) {
this.traceDetails$ = this.apiTraceDetailService.fetchTraceDetails();
- }
-
- public onDashboardReady(dashboard: Dashboard): void {
- this.subscriptionLifecycle.add(
- this.traceDetails$.subscribe(traceDetails => {
- dashboard.setVariable('traceId', traceDetails.id);
- dashboard.setVariable('traceType', traceDetails.type);
- dashboard.setVariable('startTime', traceDetails.startTime);
- dashboard.refresh();
- })
- );
+ this.logEvents$ = this.apiTraceDetailService.fetchLogEvents();
}
public onClickBack(): void {
diff --git a/projects/observability/src/pages/api-trace-detail/api-trace-detail.page.module.ts b/projects/observability/src/pages/api-trace-detail/api-trace-detail.page.module.ts
index 328463a6b..c5a636468 100644
--- a/projects/observability/src/pages/api-trace-detail/api-trace-detail.page.module.ts
+++ b/projects/observability/src/pages/api-trace-detail/api-trace-detail.page.module.ts
@@ -8,20 +8,40 @@ import {
IconModule,
LabelModule,
LoadAsyncModule,
+ NavigableTabModule,
SummaryValueModule
} from '@hypertrace/components';
+import { LogEventsTableModule, NavigableDashboardModule } from '@hypertrace/distributed-tracing';
import { ObservabilityDashboardModule } from '../../shared/dashboard/observability-dashboard.module';
import { ApiTraceDetailPageComponent } from './api-trace-detail.page.component';
+import { ApiTraceLogsComponent } from './logs/api-trace-logs.component';
+import { ApiTraceSequenceComponent } from './sequence/api-trace-sequence.component';
+import { apiTraceSequenceDashboard } from './sequence/api-trace-sequence.dashboard';
const ROUTE_CONFIG: TraceRoute[] = [
{
path: `:${ApiTraceDetailPageComponent.TRACE_ID_PARAM_NAME}`,
- component: ApiTraceDetailPageComponent
+ component: ApiTraceDetailPageComponent,
+ children: [
+ {
+ path: '',
+ redirectTo: 'sequence',
+ pathMatch: 'full'
+ },
+ {
+ path: 'sequence',
+ component: ApiTraceSequenceComponent
+ },
+ {
+ path: 'logs',
+ component: ApiTraceLogsComponent
+ }
+ ]
}
];
@NgModule({
- declarations: [ApiTraceDetailPageComponent],
+ declarations: [ApiTraceDetailPageComponent, ApiTraceSequenceComponent, ApiTraceLogsComponent],
imports: [
RouterModule.forChild(ROUTE_CONFIG),
CommonModule,
@@ -32,7 +52,10 @@ const ROUTE_CONFIG: TraceRoute[] = [
LoadAsyncModule,
FormattingModule,
ButtonModule,
- CopyShareableLinkToClipboardModule
+ CopyShareableLinkToClipboardModule,
+ NavigableTabModule,
+ LogEventsTableModule,
+ NavigableDashboardModule.withDefaultDashboards(apiTraceSequenceDashboard)
]
})
export class ApiTraceDetailPageModule {}
diff --git a/projects/observability/src/pages/api-trace-detail/api-trace-detail.service.test.ts b/projects/observability/src/pages/api-trace-detail/api-trace-detail.service.test.ts
index b263e4257..02623c3e0 100644
--- a/projects/observability/src/pages/api-trace-detail/api-trace-detail.service.test.ts
+++ b/projects/observability/src/pages/api-trace-detail/api-trace-detail.service.test.ts
@@ -16,14 +16,12 @@ import {
} from '@hypertrace/distributed-tracing';
import { GraphQlRequestService } from '@hypertrace/graphql-client';
import { runFakeRxjs } from '@hypertrace/test-utils';
-import { createServiceFactory, mockProvider, SpectatorService } from '@ngneat/spectator/jest';
+import { createServiceFactory, mockProvider } from '@ngneat/spectator/jest';
import { of } from 'rxjs';
import { ObservabilityTraceType } from '../../shared/graphql/model/schema/observability-traces';
import { ApiTraceDetailService } from './api-trace-detail.service';
describe('Api TraceDetailService', () => {
- let spectator: SpectatorService;
-
const createService = createServiceFactory({
service: ApiTraceDetailService,
providers: [
@@ -65,11 +63,8 @@ describe('Api TraceDetailService', () => {
]
});
- beforeEach(() => {
- spectator = createService();
- });
-
test('should map fetch and map trace details', () => {
+ const spectator = createService();
runFakeRxjs(({ expectObservable }) => {
expectObservable(spectator.service.fetchTraceDetails()).toBe('(x|)', {
x: {
@@ -87,6 +82,7 @@ describe('Api TraceDetailService', () => {
});
test('should build correct query request with timestamp', () => {
+ const spectator = createService();
const graphqlService = spectator.inject(GraphQlRequestService);
graphqlService.query.mockClear();
@@ -121,4 +117,58 @@ describe('Api TraceDetailService', () => {
spanLimit: 1
});
});
+
+ test('should fetch and map logEvents', () => {
+ const spectator = createService({
+ providers: [
+ mockProvider(ActivatedRoute, {
+ paramMap: of({
+ get: (key: string) => (key === 'id' ? 'test-id' : '1576364117792')
+ })
+ }),
+ mockProvider(GraphQlRequestService, {
+ query: jest.fn().mockReturnValue(
+ of({
+ [traceTypeKey]: 'API_TRACE',
+ spans: [
+ {
+ logEvents: {
+ results: [
+ {
+ timestamp: 'time',
+ attributes: undefined,
+ summary: 'summary'
+ }
+ ]
+ },
+ displayEntityName: 'service',
+ displaySpanName: 'span',
+ protocolName: 'protocol',
+ startTime: 1608151401295
+ }
+ ]
+ })
+ )
+ })
+ ]
+ });
+
+ runFakeRxjs(({ expectObservable }) => {
+ expectObservable(spectator.service.fetchLogEvents()).toBe('(x|)', {
+ x: [
+ {
+ timestamp: 'time',
+ attributes: undefined,
+ summary: 'summary',
+ $$spanName: {
+ serviceName: 'service',
+ protocolName: 'protocol',
+ apiName: 'span'
+ },
+ spanStartTime: 1608151401295
+ }
+ ]
+ });
+ });
+ });
});
diff --git a/projects/observability/src/pages/api-trace-detail/api-trace-detail.service.ts b/projects/observability/src/pages/api-trace-detail/api-trace-detail.service.ts
index b33293a08..fc5e24c5c 100644
--- a/projects/observability/src/pages/api-trace-detail/api-trace-detail.service.ts
+++ b/projects/observability/src/pages/api-trace-detail/api-trace-detail.service.ts
@@ -3,6 +3,8 @@ import { ActivatedRoute } from '@angular/router';
import { DateCoercer, DateFormatMode, DateFormatter, ReplayObservable } from '@hypertrace/common';
import {
AttributeMetadata,
+ LogEvent,
+ LogEventsService,
MetadataService,
SpecificationBuilder,
Trace,
@@ -32,7 +34,8 @@ export class ApiTraceDetailService implements OnDestroy {
public constructor(
route: ActivatedRoute,
private readonly metadataService: MetadataService,
- private readonly graphQlQueryService: GraphQlRequestService
+ private readonly graphQlQueryService: GraphQlRequestService,
+ private readonly logEventsService: LogEventsService
) {
this.routeIds$ = route.paramMap.pipe(
map(paramMap => ({
@@ -66,6 +69,21 @@ export class ApiTraceDetailService implements OnDestroy {
);
}
+ public fetchLogEvents(): Observable {
+ return this.routeIds$.pipe(
+ switchMap(routeIds =>
+ this.logEventsService.getLogEventsGqlResponseForTrace(
+ routeIds.traceId,
+ routeIds.startTime,
+ ObservabilityTraceType.Api
+ )
+ ),
+ map(trace => this.logEventsService.mapLogEvents(trace)),
+ takeUntil(this.destroyed$),
+ shareReplay(1)
+ );
+ }
+
protected constructTraceDetails(trace: Trace, durationAttribute: AttributeMetadata): ApiTraceDetails {
return {
id: trace[traceIdKey],
diff --git a/projects/observability/src/pages/api-trace-detail/logs/api-trace-logs.component.ts b/projects/observability/src/pages/api-trace-detail/logs/api-trace-logs.component.ts
new file mode 100644
index 000000000..1baf2d1b0
--- /dev/null
+++ b/projects/observability/src/pages/api-trace-detail/logs/api-trace-logs.component.ts
@@ -0,0 +1,23 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { LogEvent, LogEventsTableViewType } from '@hypertrace/distributed-tracing';
+import { Observable } from 'rxjs';
+import { ApiTraceDetailService } from './../api-trace-detail.service';
+
+@Component({
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+
+
+ `
+})
+export class ApiTraceLogsComponent {
+ public readonly logEvents$: Observable;
+
+ public constructor(private readonly apiTraceDetailService: ApiTraceDetailService) {
+ this.logEvents$ = this.apiTraceDetailService.fetchLogEvents();
+ }
+}
diff --git a/projects/observability/src/pages/api-trace-detail/sequence/api-trace-sequence.component.ts b/projects/observability/src/pages/api-trace-detail/sequence/api-trace-sequence.component.ts
new file mode 100644
index 000000000..0a0ec4407
--- /dev/null
+++ b/projects/observability/src/pages/api-trace-detail/sequence/api-trace-sequence.component.ts
@@ -0,0 +1,35 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { ApiTraceDetailService } from './../api-trace-detail.service';
+import { apiTraceSequenceDashboard } from './api-trace-sequence.dashboard';
+
+@Component({
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+
+ `
+})
+export class ApiTraceSequenceComponent {
+ public readonly traceVariables$: Observable;
+
+ public constructor(private readonly apiTraceDetailService: ApiTraceDetailService) {
+ this.traceVariables$ = this.apiTraceDetailService.fetchTraceDetails().pipe(
+ map(details => ({
+ traceId: details.id,
+ startTime: details.startTime
+ }))
+ );
+ }
+}
+
+interface ApiTraceDetailVariables {
+ traceId: string;
+ startTime?: string | number;
+}
diff --git a/projects/observability/src/pages/api-trace-detail/sequence/api-trace-sequence.dashboard.ts b/projects/observability/src/pages/api-trace-detail/sequence/api-trace-sequence.dashboard.ts
new file mode 100644
index 000000000..0b127c5c5
--- /dev/null
+++ b/projects/observability/src/pages/api-trace-detail/sequence/api-trace-sequence.dashboard.ts
@@ -0,0 +1,25 @@
+import { DashboardDefaultConfiguration } from '@hypertrace/distributed-tracing';
+
+export const apiTraceSequenceDashboard: DashboardDefaultConfiguration = {
+ location: 'API_TRACE_SEQUENCE',
+ json: {
+ type: 'container-widget',
+ layout: {
+ type: 'auto-container-layout',
+ 'enable-style': false
+ },
+ children: [
+ {
+ type: 'waterfall-widget',
+ title: 'Sequence Diagram',
+ data: {
+ type: 'api-trace-waterfall-data-source',
+ // tslint:disable-next-line: no-invalid-template-strings
+ 'trace-id': '${traceId}',
+ // tslint:disable-next-line: no-invalid-template-strings
+ 'start-time': '${startTime}'
+ }
+ }
+ ]
+ }
+};
diff --git a/projects/observability/src/public-api.ts b/projects/observability/src/public-api.ts
index 462c84681..c9010ab30 100644
--- a/projects/observability/src/public-api.ts
+++ b/projects/observability/src/public-api.ts
@@ -10,6 +10,7 @@ export * from './shared/graphql/model/schema/filter/entity/graphql-entity-filter
export * from './shared/graphql/model/schema/neighbor';
export * from './shared/graphql/model/schema/specifications/entity-specification';
export * from './shared/graphql/model/schema/specifications/explore-specification';
+export * from './shared/graphql/model/schema/observability-traces';
// Builders
export * from './shared/graphql/request/builders/argument/graphql-observability-argument-builder';
@@ -116,6 +117,9 @@ export * from './pages/apis/backend-detail/traces/backend-trace-list.module';
export * from './pages/explorer/explorer.module';
+export * from './pages/api-trace-detail/logs/api-trace-logs.component';
+export * from './pages/api-trace-detail/sequence/api-trace-sequence.component';
+export * from './pages/api-trace-detail/sequence/api-trace-sequence.dashboard';
export * from './pages/api-trace-detail/api-trace-detail.page.module';
// Icon Types
diff --git a/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.test.ts b/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.test.ts
index 425196294..33ae7156a 100644
--- a/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.test.ts
+++ b/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.test.ts
@@ -1,6 +1,7 @@
import { createModelFactory } from '@hypertrace/dashboards/testing';
import {
GraphQlQueryEventService,
+ LogEventsService,
MetadataService,
spanIdKey,
SpanType,
@@ -27,7 +28,10 @@ describe('Api Trace Waterfall data source model', () => {
})
)
}),
- mockProvider(GraphQlQueryEventService)
+ mockProvider(GraphQlQueryEventService),
+ mockProvider(LogEventsService, {
+ getLogEventsWithSpanStartTime: jest.fn().mockReturnValue([])
+ })
]
});
diff --git a/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.ts b/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.ts
index 1298fa4e7..07a77ec4f 100644
--- a/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.ts
+++ b/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.ts
@@ -3,6 +3,7 @@ import {
AttributeMetadata,
GraphQlDataSourceModel,
LogEvent,
+ LogEventsService,
MetadataService,
Span,
spanIdKey,
@@ -42,6 +43,9 @@ export class ApiTraceWaterfallDataSourceModel extends GraphQlDataSourceModel,
errorCount: span.errorCount as number,
- logEvents: ((span.logEvents as Dictionary) ?? {}).results ?? []
+ logEvents: this.logEventsService.getLogEventsWithSpanStartTime(
+ span.logEvents as Dictionary,
+ span.startTime as number
+ )
};
}
}