Skip to content
26,601 changes: 26,562 additions & 39 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions projects/common/src/utilities/coercers/date-coercer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ describe('Date coercer', () => {
expect(basicCoercer.coerce(dateInMillis)).toEqual(dataAsDate);
});

test('coerces number in string', () => {
expect(basicCoercer.coerce(`${dateInMillis}`)).toEqual(dataAsDate);
});

test('coerces string', () => {
expect(basicCoercer.coerce(dateAsIsoString)).toEqual(dataAsDate);
expect(basicCoercer.coerce('Thu Apr 11 2019 09:33:47 GMT-0700 (Pacific Daylight Time)')).toEqual(
Expand Down
2 changes: 1 addition & 1 deletion projects/common/src/utilities/coercers/date-coercer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class DateCoercer extends Coercer<Date, DateCoercerOptions> {
return undefined;
}

const valueAsDate = new Date(value);
const valueAsDate = typeof value === 'string' && !isNaN(Number(value)) ? new Date(+value) : new Date(value);
Comment thread
anandtiwary marked this conversation as resolved.

if (isNaN(valueAsDate.getTime()) || !this.isDateInAllowableRange(valueAsDate)) {
return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export const traceDetailDashboard: DashboardDefaultConfiguration = {
// tslint:disable-next-line: no-invalid-template-strings
'trace-id': '${traceId}',
// tslint:disable-next-line: no-invalid-template-strings
'entry-span-id': '${spanId}'
'entry-span-id': '${spanId}',
// tslint:disable-next-line: no-invalid-template-strings
'start-time': '${startTime}'
}
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IconSize } from '@hypertrace/components';

import { Dashboard } from '@hypertrace/hyperdash';
import { Observable } from 'rxjs';

import { traceDetailDashboard } from './trace-detail.dashboard';
import { TraceDetails, TraceDetailService } from './trace-detail.service';
@Component({
Expand Down Expand Up @@ -57,6 +58,7 @@ import { TraceDetails, TraceDetailService } from './trace-detail.service';
})
export class TraceDetailPageComponent {
public static readonly TRACE_ID_PARAM_NAME: string = 'id';

public readonly traceDetails$: Observable<TraceDetails>;

public constructor(
Expand All @@ -72,6 +74,7 @@ export class TraceDetailPageComponent {
this.traceDetails$.subscribe(traceDetails => {
dashboard.setVariable('traceId', traceDetails.id);
dashboard.setVariable('spanId', traceDetails.entrySpanId);
dashboard.setVariable('startTime', traceDetails.startTime);
dashboard.refresh();
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ describe('TraceDetailService', () => {
providers: [
mockProvider(ActivatedRoute, {
paramMap: of({
get: () => 'test-id'
get: (param: string) => {
if (param === 'startTime') {
return 1608151401295;
}

return 'test-id';
}
})
}),
mockProvider(TimeRangeService, {
Expand Down Expand Up @@ -74,6 +80,7 @@ describe('TraceDetailService', () => {
x: {
id: 'test-id',
entrySpanId: 'test-id',
startTime: 1608151401295,
timeString: `${new DisplayDatePipe().transform(1576364117792, {
mode: DateFormatMode.DateAndTimeWithSeconds
})} for 20 ms`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Injectable, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { DateFormatMode, DateFormatter, ReplayObservable, TimeRange, TimeRangeService } from '@hypertrace/common';
import { DateCoercer, DateFormatMode, DateFormatter, ReplayObservable } from '@hypertrace/common';

import { GraphQlRequestService } from '@hypertrace/graphql-client';
import { combineLatest, Observable, Subject } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { map, shareReplay, switchMap, takeUntil } from 'rxjs/operators';
import { GraphQlTimeRange } from '../../shared/graphql/model/schema/timerange/graphql-time-range';
import { Trace, traceIdKey, TraceType, traceTypeKey } from '../../shared/graphql/model/schema/trace';
import { SpecificationBuilder } from '../../shared/graphql/request/builders/specification/specification-builder';
import {
Expand All @@ -18,22 +17,24 @@ import { MetadataService } from '../../shared/services/metadata/metadata.service
export class TraceDetailService implements OnDestroy {
private static readonly TRACE_ID_PARAM_NAME: string = 'id';
private static readonly SPAN_ID_PARAM_NAME: string = 'spanId';
private static readonly START_TIME_PARAM_NAME: string = 'startTime';

private readonly specificationBuilder: SpecificationBuilder = new SpecificationBuilder();
private readonly dateCoercer: DateCoercer = new DateCoercer();

private readonly routeIds$: ReplayObservable<TraceDetailRouteIdParams>;
private readonly destroyed$: Subject<void> = new Subject();

public constructor(
route: ActivatedRoute,
private readonly timeRangeService: TimeRangeService,
private readonly metadataService: MetadataService,
private readonly graphQlQueryService: GraphQlRequestService
) {
this.routeIds$ = route.paramMap.pipe(
map(paramMap => ({
traceId: paramMap.get(this.getTraceIdParamName())!,
spanId: paramMap.get(this.getSpanIdParamName()) as string | undefined
spanId: paramMap.get(this.getSpanIdParamName()) as string | undefined,
startTime: paramMap.get(this.getStartTimeParamName()) ?? undefined
})),
takeUntil(this.destroyed$),
shareReplay(1)
Expand All @@ -53,10 +54,14 @@ export class TraceDetailService implements OnDestroy {
return TraceDetailService.SPAN_ID_PARAM_NAME;
}

protected getStartTimeParamName(): string {
return TraceDetailService.START_TIME_PARAM_NAME;
}

public fetchTraceDetails(): Observable<TraceDetails> {
return combineLatest([this.timeRangeService.getTimeRangeAndChanges(), this.routeIds$]).pipe(
switchMap(([timeRange, routeIds]) =>
this.fetchTrace(timeRange, routeIds.traceId, routeIds.spanId).pipe(
return this.routeIds$.pipe(
switchMap(routeIds =>
this.fetchTrace(routeIds.traceId, routeIds.spanId, routeIds.startTime).pipe(
map(trace => [trace, routeIds] as [Trace, TraceDetailRouteIdParams])
)
),
Expand All @@ -65,6 +70,7 @@ export class TraceDetailService implements OnDestroy {
map(durationAttribute => ({
id: trace[traceIdKey],
entrySpanId: routeIds.spanId,
startTime: routeIds.startTime,
type: trace[traceTypeKey],
timeString: this.buildTimeString(trace, durationAttribute.units),
titleString: this.buildTitleString(trace)
Expand All @@ -76,11 +82,11 @@ export class TraceDetailService implements OnDestroy {
);
}

private fetchTrace(timeRange: TimeRange, traceId: string, spanId?: string): Observable<Trace> {
private fetchTrace(traceId: string, spanId?: string, startTime?: string | number): Observable<Trace> {
return this.graphQlQueryService.query<TraceGraphQlQueryHandlerService, Trace>({
requestType: TRACE_GQL_REQUEST,
traceId: traceId,
timeRange: new GraphQlTimeRange(timeRange.startTime, timeRange.endTime),
timestamp: this.dateCoercer.coerce(startTime),
traceProperties: [
this.specificationBuilder.attributeSpecificationForKey('startTime'),
this.specificationBuilder.attributeSpecificationForKey('duration')
Expand Down Expand Up @@ -114,10 +120,13 @@ export class TraceDetailService implements OnDestroy {
interface TraceDetailRouteIdParams {
traceId: string;
spanId?: string;
startTime?: string;
}

export interface TraceDetails {
id: string;
entrySpanId?: string;
startTime?: string;
type: TraceType;
timeString: string;
titleString: string;
Expand Down
3 changes: 3 additions & 0 deletions projects/distributed-tracing/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export * from './shared/graphql/model/metadata/attribute-metadata';
export * from './shared/graphql/model/metrics/metric-aggregation';
export * from './shared/graphql/model/metrics/metric-health';

// Navigation
export * from './shared/services/navigation/tracing-navigation.service';

// Schema
export * from './shared/graphql/model/schema/filter/field/graphql-field-filter';
export * from './shared/graphql/model/schema/filter/id/graphql-id-filter';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ARRAY_PROPERTY, Model, ModelProperty, STRING_PROPERTY } from '@hypertrace/hyperdash';
import { DateCoercer } from '@hypertrace/common';
import { ARRAY_PROPERTY, Model, ModelProperty, STRING_PROPERTY, UNKNOWN_PROPERTY } from '@hypertrace/hyperdash';
import { EMPTY, Observable, of } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { Trace, TraceType } from '../../../../../shared/graphql/model/schema/trace';
Expand Down Expand Up @@ -27,6 +28,13 @@ export class TraceDataSourceModel extends GraphQlDataSourceModel<Trace> {
})
public traceId!: string;

@ModelProperty({
key: 'start-time',
required: false,
type: UNKNOWN_PROPERTY.type
})
public startTime?: unknown;

@ModelProperty({
key: 'trace-attributes',
type: ARRAY_PROPERTY.type,
Expand All @@ -41,13 +49,15 @@ export class TraceDataSourceModel extends GraphQlDataSourceModel<Trace> {
})
public spansSpecifications: AttributeSpecificationModel[] = [];

private readonly dateCoercer: DateCoercer = new DateCoercer();

public getData(): Observable<Trace> {
return this.query<TraceGraphQlQueryHandlerService>({
requestType: TRACE_GQL_REQUEST,
traceType: this.traceType,
traceId: this.traceId,
spanLimit: 100,
timeRange: this.getTimeRangeOrThrow(),
timestamp: this.dateCoercer.coerce(this.startTime),
traceProperties: this.traceSpecifications,
spanProperties: this.spansSpecifications
}).pipe(mergeMap(response => (response ? of(response) : EMPTY)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { recordObservable, runFakeRxjs } from '@hypertrace/test-utils';
import { mockProvider } from '@ngneat/spectator/jest';
import { Observable, of } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { GraphQlTimeRange } from '../../../../graphql/model/schema/timerange/graphql-time-range';
import { Trace, traceIdKey, traceTypeKey, TRACE_SCOPE } from '../../../../graphql/model/schema/trace';
import { MetadataService } from '../../../../services/metadata/metadata.service';
import { WaterfallData } from '../../../widgets/waterfall/waterfall/waterfall-chart';
Expand Down Expand Up @@ -64,7 +63,64 @@ describe('Trace Waterfall data source model', () => {
requestType: TRACE_GQL_REQUEST,
traceId: 'test-id',
spanLimit: 1000,
timeRange: GraphQlTimeRange.fromTimeRange(mockTimeRange),
timestamp: undefined,
traceProperties: [],
spanProperties: [
expect.objectContaining({
name: 'displaySpanName'
}),
expect.objectContaining({
name: 'duration'
}),
expect.objectContaining({
name: 'endTime'
}),
expect.objectContaining({
name: 'parentSpanId'
}),
expect.objectContaining({
name: 'serviceName'
}),
expect.objectContaining({
name: 'spanTags'
}),
expect.objectContaining({
name: 'startTime'
}),
expect.objectContaining({
name: 'type'
}),
expect.objectContaining({
name: 'traceId'
})
]
}
});
});
});
test('should build expected query with startTime', () => {
const spectator = modelFactory(TraceWaterfallDataSourceModel, {
properties: {
traceId: 'test-id',
entrySpanId: 'span-id',
startTime: 1576364117792
},
api: {
getTimeRange: jest.fn().mockReturnValue(mockTimeRange)
}
});

const receivedQueries = recordObservable(spectator.model.query$.pipe(map(query => query.buildRequest([]))));

spectator.model.getData();

runFakeRxjs(({ expectObservable }) => {
expectObservable(receivedQueries).toBe('x', {
x: {
requestType: TRACE_GQL_REQUEST,
traceId: 'test-id',
spanLimit: 1000,
timestamp: new Date(1576364117792),
traceProperties: [],
spanProperties: [
expect.objectContaining({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Dictionary } from '@hypertrace/common';
import { Model, ModelProperty, STRING_PROPERTY } from '@hypertrace/hyperdash';
import { DateCoercer, Dictionary } from '@hypertrace/common';
import { Model, ModelProperty, STRING_PROPERTY, UNKNOWN_PROPERTY } from '@hypertrace/hyperdash';
import { ModelInject } from '@hypertrace/hyperdash-angular';
import { combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
Expand Down Expand Up @@ -34,10 +34,18 @@ export class TraceWaterfallDataSourceModel extends GraphQlDataSourceModel<Waterf
})
public entrySpanId?: string;

@ModelProperty({
key: 'start-time',
required: false,
type: UNKNOWN_PROPERTY.type
})
public startTime?: unknown;

@ModelInject(MetadataService)
private readonly metadataService!: MetadataService;

protected readonly specificationBuilder: SpecificationBuilder = new SpecificationBuilder();
private readonly dateCoercer: DateCoercer = new DateCoercer();

protected readonly spanSpecifications: Specification[] = [
this.specificationBuilder.attributeSpecificationForKey('displaySpanName'),
Expand All @@ -62,7 +70,7 @@ export class TraceWaterfallDataSourceModel extends GraphQlDataSourceModel<Waterf
requestType: TRACE_GQL_REQUEST,
traceId: this.traceId,
spanLimit: 1000,
timeRange: this.getTimeRangeOrThrow(),
timestamp: this.dateCoercer.coerce(this.startTime),
traceProperties: [],
spanProperties: this.spanSpecifications
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import { NavigationService } from '@hypertrace/common';
import { Model } from '@hypertrace/hyperdash';
import { ModelInject } from '@hypertrace/hyperdash-angular';
import { isNil } from 'lodash-es';
import { Observable, of } from 'rxjs';
import { Span, spanIdKey } from '../../../../graphql/model/schema/span';
import { InteractionHandler } from '../../interaction-handler';
import { TracingNavigationService } from './../../../../services/navigation/tracing-navigation.service';

@Model({
type: 'span-trace-navigation-handler'
})
export class SpanTraceNavigationHandlerModel implements InteractionHandler {
@ModelInject(NavigationService)
private readonly navigationService!: NavigationService;
@ModelInject(TracingNavigationService)
private readonly tracingNavigationService!: TracingNavigationService;

public execute(span: Span): Observable<void> {
if (!isNil(span.traceId)) {
this.navigationService.navigateWithinApp(['trace', span.traceId as string, { spanId: span[spanIdKey] }]);
this.tracingNavigationService.navigateToTraceDetail(
span.traceId as string,
span[spanIdKey],
span.startTime as string | undefined
);
}

return of();
Expand Down
Loading