From 0bea80a526dfeb79cf7594f92800d9bebc670423 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Wed, 9 Jun 2021 18:29:20 +0530 Subject: [PATCH 01/10] feat: log records in a new tab --- .../trace-detail.page.component.scss | 4 + .../trace-detail.page.component.ts | 36 ++-- .../trace-detail/trace-detail.page.module.ts | 27 ++- .../trace-detail/trace-detail.service.test.ts | 190 +++++++++++++----- .../trace-detail/trace-detail.service.ts | 55 ++++- .../distributed-tracing/src/public-api.ts | 4 + .../log-events-table.component.test.ts | 6 +- .../log-events/log-events-table.component.ts | 83 +++++--- .../span-detail/span-detail.component.ts | 5 +- .../trace-waterfall-data-source.model.test.ts | 12 +- .../trace-waterfall-data-source.model.ts | 14 +- ...an-name-table-cell-renderer.component.scss | 4 +- .../waterfall/waterfall/waterfall-chart.ts | 1 + .../api-trace-detail.page.component.scss | 4 + .../api-trace-detail.page.component.ts | 52 +---- .../api-trace-detail.page.module.ts | 27 ++- .../api-trace-detail.service.test.ts | 63 +++++- .../api-trace-detail.service.ts | 56 +++++- .../api-trace-waterfall-data-source.model.ts | 14 +- 19 files changed, 483 insertions(+), 174 deletions(-) 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..28cbe1dd0 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: 8px; + } } 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..f4716ea24 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 { TraceLogsComponent } from './logs/trace-logs.component'; +import { TraceSequenceComponent } from './sequence/trace-sequence.component'; import { traceDetailDashboard } from './trace-detail.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(traceDetailDashboard), + 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..62e8377ac 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,63 @@ 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; - } + providers: [] + }); - 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 map 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 +90,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..6a4e8b45e 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,12 @@ import { Injectable, OnDestroy } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { DateCoercer, DateFormatMode, DateFormatter, ReplayObservable } from '@hypertrace/common'; +import { DateCoercer, DateFormatMode, DateFormatter, Dictionary, 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 { Span } from '../../shared/graphql/model/schema/span'; import { Trace, traceIdKey, TraceType, traceTypeKey } from '../../shared/graphql/model/schema/trace'; import { SpecificationBuilder } from '../../shared/graphql/request/builders/specification/specification-builder'; import { @@ -101,6 +103,57 @@ export class TraceDetailService implements OnDestroy { ); } + public fetchLogEvents(): Observable { + return this.routeIds$.pipe( + switchMap(routeIds => + this.getLogEventsGqlResponse(routeIds.traceId, routeIds.startTime).pipe( + map((trace: Trace) => { + let logEvents: LogEvent[] = []; + trace.spans?.forEach((span: Span) => { + logEvents = [ + ...logEvents, + ...(span.logEvents as Dictionary).results.map(logEvent => ({ + ...logEvent, + $$spanName: { + serviceName: span.serviceName, + protocolName: span.protocolName, + apiName: span.displaySpanName + }, + spanStartTime: span.startTime as number + })) + ]; + }); + + return logEvents; + }) + ) + ), + takeUntil(this.destroyed$), + shareReplay(1) + ); + } + + protected getLogEventsGqlResponse(traceId: string, startTime?: string): Observable { + return this.graphQlQueryService.query({ + requestType: TRACE_GQL_REQUEST, + traceId: traceId, + timestamp: this.dateCoercer.coerce(startTime), + traceProperties: [], + spanProperties: [ + this.specificationBuilder.attributeSpecificationForKey('startTime'), + this.specificationBuilder.attributeSpecificationForKey('serviceName'), + this.specificationBuilder.attributeSpecificationForKey('displaySpanName'), + this.specificationBuilder.attributeSpecificationForKey('protocolName') + ], + logEventProperties: [ + this.specificationBuilder.attributeSpecificationForKey('attributes'), + this.specificationBuilder.attributeSpecificationForKey('timestamp'), + this.specificationBuilder.attributeSpecificationForKey('summary') + ], + spanLimit: 1000 + }); + } + 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..384981421 100644 --- a/projects/distributed-tracing/src/public-api.ts +++ b/projects/distributed-tracing/src/public-api.ts @@ -51,6 +51,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'; 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 810388f57..905d80b86 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,10 +47,7 @@ import { SpanDetailLayoutStyle } from './span-detail-layout-style'; - + 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..b4c13d67b 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 @@ -210,7 +210,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 +222,8 @@ describe('Trace Waterfall data source model', () => { displaySpanName: 'Span Name 2', serviceName: 'Service Name 2', type: SpanType.Exit, - spanTags: {} + spanTags: {}, + logEvents: [] } ] }) @@ -241,7 +243,8 @@ describe('Trace Waterfall data source model', () => { serviceName: 'Service Name 1', protocolName: undefined, spanType: SpanType.Entry, - tags: {} + tags: {}, + logEvents: [] }, { id: 'second-id', @@ -257,7 +260,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..62692ce84 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 @@ -1,6 +1,7 @@ import { DateCoercer, Dictionary } from '@hypertrace/common'; import { Model, ModelProperty, STRING_PROPERTY, UNKNOWN_PROPERTY } from '@hypertrace/hyperdash'; import { ModelInject } from '@hypertrace/hyperdash-angular'; +import { isEmpty } from 'lodash-es'; import { combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { AttributeMetadata } from '../../../../graphql/model/metadata/attribute-metadata'; @@ -88,6 +89,17 @@ export class TraceWaterfallDataSourceModel extends GraphQlDataSourceModel, startTime: number): LogEvent[] { + if (isEmpty(logEventsObject) || isEmpty(logEventsObject.results)) { + return []; + } + + return logEventsObject.results.map(logEvent => ({ + ...logEvent, + spanStartTime: startTime + })); + } + protected mapResponseObject(trace: Trace | undefined, duration: AttributeMetadata): WaterfallData[] { if (trace === undefined || trace.spans === undefined || trace.spans.length === 0) { return []; @@ -110,7 +122,7 @@ export class TraceWaterfallDataSourceModel extends GraphQlDataSourceModel, errorCount: span.errorCount as number, - logEvents: ((span.logEvents as Dictionary) ?? {}).results + logEvents: this.mapepdLogEvents(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/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..cd50538ca 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,39 @@ import { IconModule, LabelModule, LoadAsyncModule, + NavigableTabModule, SummaryValueModule } from '@hypertrace/components'; +import { LogEventsTableModule } 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'; 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 +51,9 @@ const ROUTE_CONFIG: TraceRoute[] = [ LoadAsyncModule, FormattingModule, ButtonModule, - CopyShareableLinkToClipboardModule + CopyShareableLinkToClipboardModule, + NavigableTabModule, + LogEventsTableModule ] }) 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..fcd60a894 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,57 @@ 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({ + 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..1fae15894 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 @@ -1,9 +1,11 @@ import { Injectable, OnDestroy } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { DateCoercer, DateFormatMode, DateFormatter, ReplayObservable } from '@hypertrace/common'; +import { DateCoercer, DateFormatMode, DateFormatter, Dictionary, ReplayObservable } from '@hypertrace/common'; import { AttributeMetadata, + LogEvent, MetadataService, + Span, SpecificationBuilder, Trace, TraceGraphQlQueryHandlerService, @@ -66,6 +68,36 @@ export class ApiTraceDetailService implements OnDestroy { ); } + public fetchLogEvents(): Observable { + return this.routeIds$.pipe( + switchMap(routeIds => + this.getLogEventsGqlResponse(routeIds.traceId, routeIds.startTime).pipe( + map((trace: Trace) => { + let logEvents: LogEvent[] = []; + trace.spans?.forEach((span: Span) => { + logEvents = [ + ...logEvents, + ...(span.logEvents as Dictionary).results.map(logEvent => ({ + ...logEvent, + $$spanName: { + serviceName: span.displayEntityName, + protocolName: span.protocolName, + apiName: span.displaySpanName + }, + spanStartTime: span.startTime as number + })) + ]; + }); + + return logEvents; + }) + ) + ), + takeUntil(this.destroyed$), + shareReplay(1) + ); + } + protected constructTraceDetails(trace: Trace, durationAttribute: AttributeMetadata): ApiTraceDetails { return { id: trace[traceIdKey], @@ -89,6 +121,28 @@ export class ApiTraceDetailService implements OnDestroy { }); } + protected getLogEventsGqlResponse(traceId: string, startTime?: string): Observable { + return this.graphQlQueryService.query({ + requestType: TRACE_GQL_REQUEST, + traceType: ObservabilityTraceType.Api, + traceId: traceId, + timestamp: this.dateCoercer.coerce(startTime), + traceProperties: [], + spanProperties: [ + this.specificationBuilder.attributeSpecificationForKey('startTime'), + this.specificationBuilder.attributeSpecificationForKey('displayEntityName'), + this.specificationBuilder.attributeSpecificationForKey('displaySpanName'), + this.specificationBuilder.attributeSpecificationForKey('protocolName') + ], + logEventProperties: [ + this.specificationBuilder.attributeSpecificationForKey('attributes'), + this.specificationBuilder.attributeSpecificationForKey('timestamp'), + this.specificationBuilder.attributeSpecificationForKey('summary') + ], + spanLimit: 1000 + }); + } + protected buildTimeString(trace: Trace, units: string): string { const formattedStartTime = new DateFormatter({ mode: DateFormatMode.DateAndTimeWithSeconds }).format( trace.startTime as number 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..f68a1ba3d 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 @@ -17,6 +17,7 @@ import { } from '@hypertrace/distributed-tracing'; import { Model, ModelProperty, STRING_PROPERTY, UNKNOWN_PROPERTY } from '@hypertrace/hyperdash'; import { ModelInject } from '@hypertrace/hyperdash-angular'; +import { isEmpty } from 'lodash-es'; import { combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ObservabilityTraceType } from '../../../../graphql/model/schema/observability-traces'; @@ -100,6 +101,17 @@ export class ApiTraceWaterfallDataSourceModel extends GraphQlDataSourceModel this.constructWaterfallData(span, trace, duration)); } + private mapepdLogEvents(logEventsObject: Dictionary, startTime: number): LogEvent[] { + if (isEmpty(logEventsObject) || isEmpty(logEventsObject.results)) { + return []; + } + + return logEventsObject.results.map(logEvent => ({ + ...logEvent, + spanStartTime: startTime + })); + } + protected constructWaterfallData(span: Span, trace: Trace, duration: AttributeMetadata): WaterfallData { return { id: span[spanIdKey], @@ -117,7 +129,7 @@ export class ApiTraceWaterfallDataSourceModel extends GraphQlDataSourceModel, errorCount: span.errorCount as number, - logEvents: ((span.logEvents as Dictionary) ?? {}).results ?? [] + logEvents: this.mapepdLogEvents(span.logEvents as Dictionary, span.startTime as number) }; } } From dbe6bf2edf99fe7e00361c88d45f66c1f28b002e Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Wed, 9 Jun 2021 18:32:17 +0530 Subject: [PATCH 02/10] feat: log records in a new tab --- .../trace-detail/logs/trace-logs.component.ts | 24 ++++++++ .../sequence/trace-sequence.component.ts | 42 +++++++++++++ .../logs/api-trace-logs.component.ts | 23 +++++++ .../sequence/api-trace-sequence.component.ts | 61 +++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 projects/distributed-tracing/src/pages/trace-detail/logs/trace-logs.component.ts create mode 100644 projects/distributed-tracing/src/pages/trace-detail/sequence/trace-sequence.component.ts create mode 100644 projects/observability/src/pages/api-trace-detail/logs/api-trace-logs.component.ts create mode 100644 projects/observability/src/pages/api-trace-detail/sequence/api-trace-sequence.component.ts 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..b35e6eacc --- /dev/null +++ b/projects/distributed-tracing/src/pages/trace-detail/sequence/trace-sequence.component.ts @@ -0,0 +1,42 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { SubscriptionLifecycle } from '@hypertrace/common'; + +import { Dashboard } from '@hypertrace/hyperdash'; +import { Observable } from 'rxjs'; +import { traceDetailDashboard } from '../trace-detail.dashboard'; +import { TraceDetails, TraceDetailService } from './../trace-detail.service'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [], + template: ` + + + ` +}) +export class TraceSequenceComponent { + public readonly traceDetails$: Observable; + + public constructor( + private readonly subscriptionLifecycle: SubscriptionLifecycle, + private readonly traceDetailService: TraceDetailService + ) { + this.traceDetails$ = this.traceDetailService.fetchTraceDetails(); + } + + 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(); + }) + ); + } +} 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..7456c0bcb --- /dev/null +++ b/projects/observability/src/pages/api-trace-detail/sequence/api-trace-sequence.component.ts @@ -0,0 +1,61 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { SubscriptionLifecycle } from '@hypertrace/common'; + +import { Dashboard, ModelJson } from '@hypertrace/hyperdash'; +import { Observable } from 'rxjs'; +import { ApiTraceDetails, ApiTraceDetailService } from './../api-trace-detail.service'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [], + template: ` + + + ` +}) +export class ApiTraceSequenceComponent { + 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 constructor( + private readonly subscriptionLifecycle: SubscriptionLifecycle, + 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(); + }) + ); + } +} From a3964110ce92c027381b6605430352a1f997c261 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Mon, 14 Jun 2021 17:05:54 +0530 Subject: [PATCH 03/10] fix: addressing review comments --- .../sequence/trace-sequence.component.ts | 1 - .../trace-detail/trace-detail.service.test.ts | 5 +-- .../trace-detail/trace-detail.service.ts | 42 +++++++++---------- .../span-detail/span-detail.component.ts | 2 +- .../trace-waterfall-data-source.model.ts | 4 +- .../api-trace-detail.service.ts | 42 +++++++++---------- .../sequence/api-trace-sequence.component.ts | 1 - .../api-trace-waterfall-data-source.model.ts | 4 +- 8 files changed, 45 insertions(+), 56 deletions(-) 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 index b35e6eacc..c150a1365 100644 --- 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 @@ -8,7 +8,6 @@ import { TraceDetails, TraceDetailService } from './../trace-detail.service'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, - providers: [], template: ` { const createService = createServiceFactory({ - service: TraceDetailService, - providers: [] + service: TraceDetailService }); - test('should map fetch and map trace details', () => { + test('should fetch and map trace details', () => { const spectator = createService({ providers: [ mockProvider(ActivatedRoute, { 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 6a4e8b45e..cc05a6940 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 @@ -105,29 +105,25 @@ export class TraceDetailService implements OnDestroy { public fetchLogEvents(): Observable { return this.routeIds$.pipe( - switchMap(routeIds => - this.getLogEventsGqlResponse(routeIds.traceId, routeIds.startTime).pipe( - map((trace: Trace) => { - let logEvents: LogEvent[] = []; - trace.spans?.forEach((span: Span) => { - logEvents = [ - ...logEvents, - ...(span.logEvents as Dictionary).results.map(logEvent => ({ - ...logEvent, - $$spanName: { - serviceName: span.serviceName, - protocolName: span.protocolName, - apiName: span.displaySpanName - }, - spanStartTime: span.startTime as number - })) - ]; - }); - - return logEvents; - }) - ) - ), + switchMap(routeIds => this.getLogEventsGqlResponse(routeIds.traceId, routeIds.startTime)), + map((trace: Trace) => { + const logEvents: LogEvent[] = []; + trace.spans?.forEach((span: Span) => { + logEvents.push( + ...(span.logEvents as Dictionary).results.map(logEvent => ({ + ...logEvent, + $$spanName: { + serviceName: span.serviceName, + protocolName: span.protocolName, + apiName: span.displaySpanName + }, + spanStartTime: span.startTime as number + })) + ); + }); + + return logEvents; + }), takeUntil(this.destroyed$), shareReplay(1) ); 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 10af8681b..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,7 +47,7 @@ import { SpanDetailTab } from './span-detail-tab'; - + 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 62692ce84..80114777c 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 @@ -89,7 +89,7 @@ export class TraceWaterfallDataSourceModel extends GraphQlDataSourceModel, startTime: number): LogEvent[] { + private getLogEventsWithSpanStartTime(logEventsObject: Dictionary, startTime: number): LogEvent[] { if (isEmpty(logEventsObject) || isEmpty(logEventsObject.results)) { return []; } @@ -122,7 +122,7 @@ export class TraceWaterfallDataSourceModel extends GraphQlDataSourceModel, errorCount: span.errorCount as number, - logEvents: this.mapepdLogEvents(span.logEvents as Dictionary, span.startTime as number) + logEvents: this.getLogEventsWithSpanStartTime(span.logEvents as Dictionary, span.startTime as number) })); } } 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 1fae15894..d15177d8c 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 @@ -70,29 +70,25 @@ export class ApiTraceDetailService implements OnDestroy { public fetchLogEvents(): Observable { return this.routeIds$.pipe( - switchMap(routeIds => - this.getLogEventsGqlResponse(routeIds.traceId, routeIds.startTime).pipe( - map((trace: Trace) => { - let logEvents: LogEvent[] = []; - trace.spans?.forEach((span: Span) => { - logEvents = [ - ...logEvents, - ...(span.logEvents as Dictionary).results.map(logEvent => ({ - ...logEvent, - $$spanName: { - serviceName: span.displayEntityName, - protocolName: span.protocolName, - apiName: span.displaySpanName - }, - spanStartTime: span.startTime as number - })) - ]; - }); - - return logEvents; - }) - ) - ), + switchMap(routeIds => this.getLogEventsGqlResponse(routeIds.traceId, routeIds.startTime)), + map((trace: Trace) => { + const logEvents: LogEvent[] = []; + trace.spans?.forEach((span: Span) => { + logEvents.push( + ...(span.logEvents as Dictionary).results.map(logEvent => ({ + ...logEvent, + $$spanName: { + serviceName: span.displayEntityName, + protocolName: span.protocolName, + apiName: span.displaySpanName + }, + spanStartTime: span.startTime as number + })) + ); + }); + + return logEvents; + }), takeUntil(this.destroyed$), shareReplay(1) ); 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 index 7456c0bcb..3c51f44e0 100644 --- 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 @@ -7,7 +7,6 @@ import { ApiTraceDetails, ApiTraceDetailService } from './../api-trace-detail.se @Component({ changeDetection: ChangeDetectionStrategy.OnPush, - providers: [], template: ` this.constructWaterfallData(span, trace, duration)); } - private mapepdLogEvents(logEventsObject: Dictionary, startTime: number): LogEvent[] { + private getLogEventsWithSpanStartTime(logEventsObject: Dictionary, startTime: number): LogEvent[] { if (isEmpty(logEventsObject) || isEmpty(logEventsObject.results)) { return []; } @@ -129,7 +129,7 @@ export class ApiTraceWaterfallDataSourceModel extends GraphQlDataSourceModel, errorCount: span.errorCount as number, - logEvents: this.mapepdLogEvents(span.logEvents as Dictionary, span.startTime as number) + logEvents: this.getLogEventsWithSpanStartTime(span.logEvents as Dictionary, span.startTime as number) }; } } From e0b6ffb8adbd62bfd86455baac2bcde319ce8f34 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Tue, 15 Jun 2021 12:16:28 +0530 Subject: [PATCH 04/10] fix: adding log events service --- .../trace-detail/trace-detail.service.ts | 52 ++---------- .../distributed-tracing/src/public-api.ts | 1 + .../trace-waterfall-data-source.model.ts | 21 ++--- .../services/log-events/log-events.service.ts | 79 +++++++++++++++++++ .../api-trace-detail.service.ts | 56 +++---------- projects/observability/src/public-api.ts | 1 + .../api-trace-waterfall-data-source.model.ts | 21 ++--- 7 files changed, 117 insertions(+), 114 deletions(-) create mode 100644 projects/distributed-tracing/src/shared/services/log-events/log-events.service.ts 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 cc05a6940..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,12 +1,10 @@ import { Injectable, OnDestroy } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { DateCoercer, DateFormatMode, DateFormatter, Dictionary, ReplayObservable } from '@hypertrace/common'; - +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 { Span } from '../../shared/graphql/model/schema/span'; import { Trace, traceIdKey, TraceType, traceTypeKey } from '../../shared/graphql/model/schema/trace'; import { SpecificationBuilder } from '../../shared/graphql/request/builders/specification/specification-builder'; import { @@ -17,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() @@ -34,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 => ({ @@ -105,51 +105,15 @@ export class TraceDetailService implements OnDestroy { public fetchLogEvents(): Observable { return this.routeIds$.pipe( - switchMap(routeIds => this.getLogEventsGqlResponse(routeIds.traceId, routeIds.startTime)), - map((trace: Trace) => { - const logEvents: LogEvent[] = []; - trace.spans?.forEach((span: Span) => { - logEvents.push( - ...(span.logEvents as Dictionary).results.map(logEvent => ({ - ...logEvent, - $$spanName: { - serviceName: span.serviceName, - protocolName: span.protocolName, - apiName: span.displaySpanName - }, - spanStartTime: span.startTime as number - })) - ); - }); - - return logEvents; - }), + switchMap(routeIds => + this.logEventsService.getLogEventsGqlResponseForTrace(routeIds.traceId, routeIds.startTime) + ), + map(trace => this.logEventsService.mapLogEvents(trace)), takeUntil(this.destroyed$), shareReplay(1) ); } - protected getLogEventsGqlResponse(traceId: string, startTime?: string): Observable { - return this.graphQlQueryService.query({ - requestType: TRACE_GQL_REQUEST, - traceId: traceId, - timestamp: this.dateCoercer.coerce(startTime), - traceProperties: [], - spanProperties: [ - this.specificationBuilder.attributeSpecificationForKey('startTime'), - this.specificationBuilder.attributeSpecificationForKey('serviceName'), - this.specificationBuilder.attributeSpecificationForKey('displaySpanName'), - this.specificationBuilder.attributeSpecificationForKey('protocolName') - ], - logEventProperties: [ - this.specificationBuilder.attributeSpecificationForKey('attributes'), - this.specificationBuilder.attributeSpecificationForKey('timestamp'), - this.specificationBuilder.attributeSpecificationForKey('summary') - ], - spanLimit: 1000 - }); - } - 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 384981421..b6357922b 100644 --- a/projects/distributed-tracing/src/public-api.ts +++ b/projects/distributed-tracing/src/public-api.ts @@ -87,6 +87,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'; 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 80114777c..eeceb52f8 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 @@ -1,7 +1,7 @@ import { DateCoercer, Dictionary } from '@hypertrace/common'; import { Model, ModelProperty, STRING_PROPERTY, UNKNOWN_PROPERTY } from '@hypertrace/hyperdash'; import { ModelInject } from '@hypertrace/hyperdash-angular'; -import { isEmpty } from 'lodash-es'; +import { LogEventsService } from '../../../../services/log-events/log-events.service'; import { combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { AttributeMetadata } from '../../../../graphql/model/metadata/attribute-metadata'; @@ -45,6 +45,9 @@ export class TraceWaterfallDataSourceModel extends GraphQlDataSourceModel, startTime: number): LogEvent[] { - if (isEmpty(logEventsObject) || isEmpty(logEventsObject.results)) { - return []; - } - - return logEventsObject.results.map(logEvent => ({ - ...logEvent, - spanStartTime: startTime - })); - } - protected mapResponseObject(trace: Trace | undefined, duration: AttributeMetadata): WaterfallData[] { if (trace === undefined || trace.spans === undefined || trace.spans.length === 0) { return []; @@ -122,7 +114,10 @@ export class TraceWaterfallDataSourceModel extends GraphQlDataSourceModel, errorCount: span.errorCount as number, - logEvents: this.getLogEventsWithSpanStartTime(span.logEvents as Dictionary, span.startTime as number) + logEvents: this.logEventsService.getLogEventsWithSpanStartTime( + span.logEvents as Dictionary, + span.startTime as number + ) })); } } 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..652a56020 --- /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' + ]; + + 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) => { + return (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.service.ts b/projects/observability/src/pages/api-trace-detail/api-trace-detail.service.ts index d15177d8c..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 @@ -1,11 +1,11 @@ import { Injectable, OnDestroy } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { DateCoercer, DateFormatMode, DateFormatter, Dictionary, ReplayObservable } from '@hypertrace/common'; +import { DateCoercer, DateFormatMode, DateFormatter, ReplayObservable } from '@hypertrace/common'; import { AttributeMetadata, LogEvent, + LogEventsService, MetadataService, - Span, SpecificationBuilder, Trace, TraceGraphQlQueryHandlerService, @@ -34,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 => ({ @@ -70,25 +71,14 @@ export class ApiTraceDetailService implements OnDestroy { public fetchLogEvents(): Observable { return this.routeIds$.pipe( - switchMap(routeIds => this.getLogEventsGqlResponse(routeIds.traceId, routeIds.startTime)), - map((trace: Trace) => { - const logEvents: LogEvent[] = []; - trace.spans?.forEach((span: Span) => { - logEvents.push( - ...(span.logEvents as Dictionary).results.map(logEvent => ({ - ...logEvent, - $$spanName: { - serviceName: span.displayEntityName, - protocolName: span.protocolName, - apiName: span.displaySpanName - }, - spanStartTime: span.startTime as number - })) - ); - }); - - return logEvents; - }), + switchMap(routeIds => + this.logEventsService.getLogEventsGqlResponseForTrace( + routeIds.traceId, + routeIds.startTime, + ObservabilityTraceType.Api + ) + ), + map(trace => this.logEventsService.mapLogEvents(trace)), takeUntil(this.destroyed$), shareReplay(1) ); @@ -117,28 +107,6 @@ export class ApiTraceDetailService implements OnDestroy { }); } - protected getLogEventsGqlResponse(traceId: string, startTime?: string): Observable { - return this.graphQlQueryService.query({ - requestType: TRACE_GQL_REQUEST, - traceType: ObservabilityTraceType.Api, - traceId: traceId, - timestamp: this.dateCoercer.coerce(startTime), - traceProperties: [], - spanProperties: [ - this.specificationBuilder.attributeSpecificationForKey('startTime'), - this.specificationBuilder.attributeSpecificationForKey('displayEntityName'), - this.specificationBuilder.attributeSpecificationForKey('displaySpanName'), - this.specificationBuilder.attributeSpecificationForKey('protocolName') - ], - logEventProperties: [ - this.specificationBuilder.attributeSpecificationForKey('attributes'), - this.specificationBuilder.attributeSpecificationForKey('timestamp'), - this.specificationBuilder.attributeSpecificationForKey('summary') - ], - spanLimit: 1000 - }); - } - protected buildTimeString(trace: Trace, units: string): string { const formattedStartTime = new DateFormatter({ mode: DateFormatMode.DateAndTimeWithSeconds }).format( trace.startTime as number diff --git a/projects/observability/src/public-api.ts b/projects/observability/src/public-api.ts index 462c84681..236858ad5 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'; 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 0203f5cfb..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, @@ -17,7 +18,6 @@ import { } from '@hypertrace/distributed-tracing'; import { Model, ModelProperty, STRING_PROPERTY, UNKNOWN_PROPERTY } from '@hypertrace/hyperdash'; import { ModelInject } from '@hypertrace/hyperdash-angular'; -import { isEmpty } from 'lodash-es'; import { combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ObservabilityTraceType } from '../../../../graphql/model/schema/observability-traces'; @@ -43,6 +43,9 @@ export class ApiTraceWaterfallDataSourceModel extends GraphQlDataSourceModel this.constructWaterfallData(span, trace, duration)); } - private getLogEventsWithSpanStartTime(logEventsObject: Dictionary, startTime: number): LogEvent[] { - if (isEmpty(logEventsObject) || isEmpty(logEventsObject.results)) { - return []; - } - - return logEventsObject.results.map(logEvent => ({ - ...logEvent, - spanStartTime: startTime - })); - } - protected constructWaterfallData(span: Span, trace: Trace, duration: AttributeMetadata): WaterfallData { return { id: span[spanIdKey], @@ -129,7 +121,10 @@ export class ApiTraceWaterfallDataSourceModel extends GraphQlDataSourceModel, errorCount: span.errorCount as number, - logEvents: this.getLogEventsWithSpanStartTime(span.logEvents as Dictionary, span.startTime as number) + logEvents: this.logEventsService.getLogEventsWithSpanStartTime( + span.logEvents as Dictionary, + span.startTime as number + ) }; } } From 8c757ba907d7c6b3c4f9dc7a2ab437fb3584677a Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Tue, 15 Jun 2021 12:34:47 +0530 Subject: [PATCH 05/10] fix: test cases --- .../waterfall/trace-waterfall-data-source.model.test.ts | 6 +++++- .../pages/api-trace-detail/api-trace-detail.service.test.ts | 1 + .../waterfall/api-trace-waterfall-data-source.model.test.ts | 6 +++++- 3 files changed, 11 insertions(+), 2 deletions(-) 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 b4c13d67b..8c60e1a95 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 @@ -1,6 +1,7 @@ import { createModelFactory } from '@hypertrace/dashboards/testing'; import { recordObservable, runFakeRxjs } from '@hypertrace/test-utils'; import { mockProvider } from '@ngneat/spectator/jest'; +import { LogEventsService } from '../../../../services/log-events/log-events.service'; import { Observable, of } from 'rxjs'; import { map, take } from 'rxjs/operators'; import { Trace, traceIdKey, traceTypeKey, TRACE_SCOPE } from '../../../../graphql/model/schema/trace'; @@ -21,7 +22,10 @@ describe('Trace Waterfall data source model', () => { }) ) }), - mockProvider(GraphQlQueryEventService) + mockProvider(GraphQlQueryEventService), + mockProvider(LogEventsService, { + getLogEventsWithSpanStartTime: jest.fn().mockReturnValue([]) + }) ] }); 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 fcd60a894..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 @@ -129,6 +129,7 @@ describe('Api TraceDetailService', () => { mockProvider(GraphQlRequestService, { query: jest.fn().mockReturnValue( of({ + [traceTypeKey]: 'API_TRACE', spans: [ { logEvents: { 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([]) + }) ] }); From 6d8d2c6419a6c9c5e81bf0a3884cf0cd9d163f98 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Tue, 15 Jun 2021 16:32:54 +0530 Subject: [PATCH 06/10] fix: test cases and lint errors --- .../trace-waterfall-data-source.model.test.ts | 2 +- .../trace-waterfall-data-source.model.ts | 2 +- .../log-events/log-events.service.test.ts | 116 ++++++++++++++++++ .../services/log-events/log-events.service.ts | 10 +- 4 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 projects/distributed-tracing/src/shared/services/log-events/log-events.service.test.ts 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 8c60e1a95..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 @@ -1,10 +1,10 @@ import { createModelFactory } from '@hypertrace/dashboards/testing'; import { recordObservable, runFakeRxjs } from '@hypertrace/test-utils'; import { mockProvider } from '@ngneat/spectator/jest'; -import { LogEventsService } from '../../../../services/log-events/log-events.service'; 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'; 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 eeceb52f8..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 @@ -1,7 +1,6 @@ import { DateCoercer, Dictionary } from '@hypertrace/common'; import { Model, ModelProperty, STRING_PROPERTY, UNKNOWN_PROPERTY } from '@hypertrace/hyperdash'; import { ModelInject } from '@hypertrace/hyperdash-angular'; -import { LogEventsService } from '../../../../services/log-events/log-events.service'; import { combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { AttributeMetadata } from '../../../../graphql/model/metadata/attribute-metadata'; @@ -13,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'; 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 index 652a56020..f19c8028d 100644 --- 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 @@ -26,7 +26,7 @@ export class LogEventsService { 'protocolName' ]; - constructor(private readonly graphQlQueryService: GraphQlRequestService) {} + public constructor(private readonly graphQlQueryService: GraphQlRequestService) {} public getLogEventsWithSpanStartTime(logEventsObject: Dictionary, startTime: number): LogEvent[] { if (isEmpty(logEventsObject) || isEmpty(logEventsObject.results)) { @@ -63,8 +63,8 @@ export class LogEventsService { public mapLogEvents(trace: Trace): LogEvent[] { return (trace.spans ?? []) - .map((span: Span) => { - return (span.logEvents as Dictionary).results.map(logEvent => ({ + .map((span: Span) => + (span.logEvents as Dictionary).results.map(logEvent => ({ ...logEvent, $$spanName: { serviceName: trace[traceTypeKey] === ObservabilityTraceType.Api ? span.displayEntityName : span.serviceName, @@ -72,8 +72,8 @@ export class LogEventsService { apiName: span.displaySpanName }, spanStartTime: span.startTime as number - })); - }) + })) + ) .flat(); } } From e055fb40c88de623a148c1cb3fe7277c675770c5 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Wed, 16 Jun 2021 15:16:57 +0530 Subject: [PATCH 07/10] fix: style fix --- .../src/pages/trace-detail/trace-detail.page.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 28cbe1dd0..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 @@ -47,6 +47,6 @@ } .tabs { - margin-top: 8px; + margin-top: 16px; } } From 54feaaecafb586da498f96e42c451b8a38bef7b5 Mon Sep 17 00:00:00 2001 From: Aaron Steinfeld Date: Wed, 16 Jun 2021 08:28:26 -0400 Subject: [PATCH 08/10] chore: suggested-log-tab-changes --- .../sequence/trace-sequence.component.ts | 45 ++++++------- .../trace-sequence.dashboard.ts} | 6 +- .../trace-detail/trace-detail.page.module.ts | 4 +- .../distributed-tracing/src/public-api.ts | 2 +- .../api-trace-detail.page.module.ts | 6 +- .../sequence/api-trace-sequence.component.ts | 67 ++++++------------- .../sequence/api-trace-sequence.dashboard.ts | 25 +++++++ 7 files changed, 79 insertions(+), 76 deletions(-) rename projects/distributed-tracing/src/pages/trace-detail/{trace-detail.dashboard.ts => sequence/trace-sequence.dashboard.ts} (74%) create mode 100644 projects/observability/src/pages/api-trace-detail/sequence/api-trace-sequence.dashboard.ts 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 index c150a1365..7c193b6e3 100644 --- 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 @@ -1,41 +1,40 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { SubscriptionLifecycle } from '@hypertrace/common'; - -import { Dashboard } from '@hypertrace/hyperdash'; import { Observable } from 'rxjs'; -import { traceDetailDashboard } from '../trace-detail.dashboard'; -import { TraceDetails, TraceDetailService } from './../trace-detail.service'; +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 traceDetails$: Observable; - - public constructor( - private readonly subscriptionLifecycle: SubscriptionLifecycle, - private readonly traceDetailService: TraceDetailService - ) { - this.traceDetails$ = this.traceDetailService.fetchTraceDetails(); - } + public readonly traceVariables$: Observable; - 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(); - }) + public constructor(private readonly traceDetailService: TraceDetailService) { + this.traceVariables$ = this.traceDetailService.fetchTraceDetails().pipe( + map(details => ({ + traceId: details.id, + traceType: details.type, + startTime: details.startTime, + spanId: details.entrySpanId + })) ); } } + +interface TraceDetailVariables { + traceId: string; + traceType: 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.module.ts b/projects/distributed-tracing/src/pages/trace-detail/trace-detail.page.module.ts index f4716ea24..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 @@ -17,7 +17,7 @@ import { NavigableDashboardModule } from '../../shared/dashboard/dashboard-wrapp import { TracingDashboardModule } from '../../shared/dashboard/tracing-dashboard.module'; import { TraceLogsComponent } from './logs/trace-logs.component'; import { TraceSequenceComponent } from './sequence/trace-sequence.component'; -import { traceDetailDashboard } from './trace-detail.dashboard'; +import { traceSequenceDashboard } from './sequence/trace-sequence.dashboard'; import { TraceDetailPageComponent } from './trace-detail.page.component'; const ROUTE_CONFIG: TraceRoute[] = [ @@ -56,7 +56,7 @@ const ROUTE_CONFIG: TraceRoute[] = [ FormattingModule, CopyShareableLinkToClipboardModule, DownloadJsonModule, - NavigableDashboardModule.withDefaultDashboards(traceDetailDashboard), + NavigableDashboardModule.withDefaultDashboards(traceSequenceDashboard), NavigableTabModule, LogEventsTableModule ] diff --git a/projects/distributed-tracing/src/public-api.ts b/projects/distributed-tracing/src/public-api.ts index b6357922b..489caa6e3 100644 --- a/projects/distributed-tracing/src/public-api.ts +++ b/projects/distributed-tracing/src/public-api.ts @@ -109,7 +109,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/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 cd50538ca..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 @@ -11,11 +11,12 @@ import { NavigableTabModule, SummaryValueModule } from '@hypertrace/components'; -import { LogEventsTableModule } from '@hypertrace/distributed-tracing'; +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[] = [ { @@ -53,7 +54,8 @@ const ROUTE_CONFIG: TraceRoute[] = [ ButtonModule, CopyShareableLinkToClipboardModule, NavigableTabModule, - LogEventsTableModule + LogEventsTableModule, + NavigableDashboardModule.withDefaultDashboards(apiTraceSequenceDashboard) ] }) export class ApiTraceDetailPageModule {} 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 index 3c51f44e0..27d2a17c2 100644 --- 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 @@ -1,60 +1,37 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { SubscriptionLifecycle } from '@hypertrace/common'; - -import { Dashboard, ModelJson } from '@hypertrace/hyperdash'; import { Observable } from 'rxjs'; -import { ApiTraceDetails, ApiTraceDetailService } from './../api-trace-detail.service'; +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 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 constructor( - private readonly subscriptionLifecycle: SubscriptionLifecycle, - private readonly apiTraceDetailService: ApiTraceDetailService - ) { - this.traceDetails$ = this.apiTraceDetailService.fetchTraceDetails(); - } + public readonly traceVariables$: Observable; - 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(); - }) + public constructor(private readonly apiTraceDetailService: ApiTraceDetailService) { + this.traceVariables$ = this.apiTraceDetailService.fetchTraceDetails().pipe( + map(details => ({ + traceId: details.id, + traceType: details.type, + startTime: details.startTime + })) ); } } + +interface ApiTraceDetailVariables { + traceId: string; + traceType: 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}' + } + } + ] + } +}; From 4625133f05fed80776e4afac4e9768fc4b590187 Mon Sep 17 00:00:00 2001 From: Aaron Steinfeld Date: Wed, 16 Jun 2021 08:33:20 -0400 Subject: [PATCH 09/10] refactor: remove unused trace types --- .../src/pages/trace-detail/sequence/trace-sequence.component.ts | 2 -- .../api-trace-detail/sequence/api-trace-sequence.component.ts | 2 -- 2 files changed, 4 deletions(-) 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 index 7c193b6e3..2df86724c 100644 --- 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 @@ -24,7 +24,6 @@ export class TraceSequenceComponent { this.traceVariables$ = this.traceDetailService.fetchTraceDetails().pipe( map(details => ({ traceId: details.id, - traceType: details.type, startTime: details.startTime, spanId: details.entrySpanId })) @@ -34,7 +33,6 @@ export class TraceSequenceComponent { interface TraceDetailVariables { traceId: string; - traceType: string; startTime?: string | number; spanId?: string; } 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 index 27d2a17c2..0a0ec4407 100644 --- 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 @@ -23,7 +23,6 @@ export class ApiTraceSequenceComponent { this.traceVariables$ = this.apiTraceDetailService.fetchTraceDetails().pipe( map(details => ({ traceId: details.id, - traceType: details.type, startTime: details.startTime })) ); @@ -32,6 +31,5 @@ export class ApiTraceSequenceComponent { interface ApiTraceDetailVariables { traceId: string; - traceType: string; startTime?: string | number; } From 78ee6ab2f33493d4564546b92d606c85cd4e2de2 Mon Sep 17 00:00:00 2001 From: Aaron Steinfeld Date: Fri, 25 Jun 2021 17:30:40 -0400 Subject: [PATCH 10/10] fix: respect global param changes in ht-link --- .../src/navigation/navigation.service.ts | 14 ++++++-- .../src/link/link.component.test.ts | 31 +++++++++-------- .../components/src/link/link.component.ts | 33 ++++++++----------- projects/components/src/link/link.module.ts | 3 +- 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/projects/common/src/navigation/navigation.service.ts b/projects/common/src/navigation/navigation.service.ts index 734576231..6da08b310 100644 --- a/projects/common/src/navigation/navigation.service.ts +++ b/projects/common/src/navigation/navigation.service.ts @@ -14,8 +14,8 @@ import { UrlTree } from '@angular/router'; import { from, Observable, of } from 'rxjs'; -import { distinctUntilChanged, filter, map, share, skip, startWith, take } from 'rxjs/operators'; -import { throwIfNil } from '../utilities/lang/lang-utils'; +import { distinctUntilChanged, filter, map, share, skip, startWith, switchMap, take } from 'rxjs/operators'; +import { isEqualIgnoreFunctions, throwIfNil } from '../utilities/lang/lang-utils'; import { Dictionary } from '../utilities/types/types'; import { TraceRoute } from './trace-route'; @@ -124,6 +124,16 @@ export class NavigationService { }; } + public buildNavigationParams$( + paramsOrUrl: NavigationParams | string + ): Observable<{ path: NavigationPath; extras?: NavigationExtras }> { + return this.navigation$.pipe( + switchMap(route => route.queryParams), + map(() => this.buildNavigationParams(paramsOrUrl)), + distinctUntilChanged(isEqualIgnoreFunctions) + ); + } + /** * Navigate within the app. * To be used for URLs with pattern '/partial_path' or 'partial_path' diff --git a/projects/components/src/link/link.component.test.ts b/projects/components/src/link/link.component.test.ts index 6e2161d37..4be21e8ac 100644 --- a/projects/components/src/link/link.component.test.ts +++ b/projects/components/src/link/link.component.test.ts @@ -2,6 +2,8 @@ import { RouterLinkWithHref } from '@angular/router'; import { NavigationService } from '@hypertrace/common'; import { createHostFactory, mockProvider, SpectatorHost } from '@ngneat/spectator/jest'; import { MockDirective } from 'ng-mocks'; +import { of } from 'rxjs'; +import { LetAsyncModule } from '../let-async/let-async.module'; import { LinkComponent } from './link.component'; describe('Link component', () => { @@ -9,6 +11,7 @@ describe('Link component', () => { const createHost = createHostFactory({ component: LinkComponent, + imports: [LetAsyncModule], providers: [mockProvider(NavigationService)], declarations: [MockDirective(RouterLinkWithHref)] }); @@ -32,16 +35,18 @@ describe('Link component', () => { }, providers: [ mockProvider(NavigationService, { - buildNavigationParams: jest.fn().mockReturnValue({ - path: [ - '/external', - { - url: 'http://test.hypertrace.ai', - navType: 'same_window' - } - ], - extras: { skipLocationChange: true } - }) + buildNavigationParams$: jest.fn().mockReturnValue( + of({ + path: [ + '/external', + { + url: 'http://test.hypertrace.ai', + navType: 'same_window' + } + ], + extras: { skipLocationChange: true } + }) + ) }) ] }); @@ -72,7 +77,7 @@ describe('Link component', () => { }, providers: [ mockProvider(NavigationService, { - buildNavigationParams: jest.fn().mockReturnValue({ path: ['test'], extras: {} }) + buildNavigationParams$: jest.fn().mockReturnValue(of({ path: ['test'], extras: {} })) }) ] }); @@ -97,7 +102,7 @@ describe('Link component', () => { }, providers: [ mockProvider(NavigationService, { - buildNavigationParams: jest.fn().mockReturnValue({ path: ['/test'], extras: {} }) + buildNavigationParams$: jest.fn().mockReturnValue(of({ path: ['/test'], extras: {} })) }) ] }); @@ -122,7 +127,7 @@ describe('Link component', () => { }, providers: [ mockProvider(NavigationService, { - buildNavigationParams: jest.fn().mockReturnValue({ path: ['/test'], extras: {} }) + buildNavigationParams$: jest.fn().mockReturnValue(of({ path: ['/test'], extras: {} })) }) ] }); diff --git a/projects/components/src/link/link.component.ts b/projects/components/src/link/link.component.ts index 33d4c9164..ab20a7c3d 100644 --- a/projects/components/src/link/link.component.ts +++ b/projects/components/src/link/link.component.ts @@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/c import { NavigationExtras } from '@angular/router'; import { NavigationParams, NavigationPath, NavigationService } from '@hypertrace/common'; import { isNil } from 'lodash-es'; +import { EMPTY, Observable } from 'rxjs'; @Component({ selector: 'ht-link', @@ -9,13 +10,14 @@ import { isNil } from 'lodash-es'; changeDetection: ChangeDetectionStrategy.OnPush, template: ` @@ -28,23 +30,16 @@ export class LinkComponent implements OnChanges { @Input() public disabled?: boolean; - public navigationPath?: NavigationPath; - public navigationOptions?: NavigationExtras; + public navData$: Observable = EMPTY; public constructor(private readonly navigationService: NavigationService) {} public ngOnChanges(): void { - this.setNavigationParams(); + this.navData$ = isNil(this.paramsOrUrl) ? EMPTY : this.navigationService.buildNavigationParams$(this.paramsOrUrl); } +} - private setNavigationParams(): void { - if (isNil(this.paramsOrUrl)) { - this.navigationPath = undefined; - this.navigationOptions = undefined; - } else { - const { path, extras } = this.navigationService.buildNavigationParams(this.paramsOrUrl); - this.navigationPath = path; - this.navigationOptions = extras; - } - } +interface NavData { + path: NavigationPath; + extras?: NavigationExtras; } diff --git a/projects/components/src/link/link.module.ts b/projects/components/src/link/link.module.ts index 6061c6792..f090d1c0b 100644 --- a/projects/components/src/link/link.module.ts +++ b/projects/components/src/link/link.module.ts @@ -1,11 +1,12 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; +import { LetAsyncModule } from '../let-async/let-async.module'; import { LinkComponent } from './link.component'; @NgModule({ declarations: [LinkComponent], exports: [LinkComponent], - imports: [CommonModule, RouterModule] + imports: [CommonModule, RouterModule, LetAsyncModule] }) export class LinkModule {}