diff --git a/projects/common/src/navigation/ht-route-data.ts b/projects/common/src/navigation/ht-route-data.ts index 72af980d3..5365c6569 100644 --- a/projects/common/src/navigation/ht-route-data.ts +++ b/projects/common/src/navigation/ht-route-data.ts @@ -7,4 +7,5 @@ export interface HtRouteData { features?: string[]; title?: string; defaultTimeRange?: TimeRange; + shouldSavePageTimeRange?: boolean; } diff --git a/projects/common/src/time/page-time-range-preference-service.test.ts b/projects/common/src/time/page-time-range-preference-service.test.ts index d9f4f4b17..55ca18ae1 100644 --- a/projects/common/src/time/page-time-range-preference-service.test.ts +++ b/projects/common/src/time/page-time-range-preference-service.test.ts @@ -21,9 +21,7 @@ describe('Page time range preference service', () => { service: PageTimeRangePreferenceService, providers: [ mockProvider(NavigationService, { - getCurrentActivatedRoute: jest - .fn() - .mockReturnValue({ snapshot: { data: { defaultTimeRange: defaultPageTimeRange } } }) + getRouteConfig: jest.fn().mockReturnValue({ data: { defaultTimeRange: defaultPageTimeRange } }) }), mockProvider(FeatureStateResolver, { getFeatureState: jest.fn().mockReturnValue(of(FeatureState.Enabled)) diff --git a/projects/common/src/time/page-time-range-preference.service.ts b/projects/common/src/time/page-time-range-preference.service.ts index 9978b4f42..a30aae325 100644 --- a/projects/common/src/time/page-time-range-preference.service.ts +++ b/projects/common/src/time/page-time-range-preference.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; import { isNil } from 'lodash-es'; import { combineLatest, Observable } from 'rxjs'; import { map, shareReplay, take } from 'rxjs/operators'; @@ -38,7 +37,7 @@ export class PageTimeRangePreferenceService { map(([pageTimeRangeStringDictionary, featureState]) => { if (featureState === FeatureState.Enabled) { if (isNil(pageTimeRangeStringDictionary[rootLevelPath])) { - return () => this.getDefaultTimeRangeForCurrentRoute(); + return () => this.getDefaultTimeRangeForPath(rootLevelPath); } return () => this.timeRangeService.timeRangeFromUrlString(pageTimeRangeStringDictionary[rootLevelPath]); @@ -78,11 +77,14 @@ export class PageTimeRangePreferenceService { .pipe(shareReplay(1)); } - public getDefaultTimeRangeForCurrentRoute(): TimeRange { - const currentRoute: ActivatedRoute = this.navigationService.getCurrentActivatedRoute(); - // Right side for when FF is enabled but 'defaultTimeRange' is not set on AR data + public getDefaultTimeRangeForPath(rootLevelPath: string): TimeRange { + const routeConfigForPath = this.navigationService.getRouteConfig( + [rootLevelPath], + this.navigationService.rootRoute() + ); - return currentRoute.snapshot.data?.defaultTimeRange ?? this.getGlobalDefaultTimeRange(); + // Right side for when FF is enabled but 'defaultTimeRange' is not set on AR data + return routeConfigForPath?.data?.defaultTimeRange ?? this.getGlobalDefaultTimeRange(); } public getGlobalDefaultTimeRange(): TimeRange { diff --git a/projects/common/src/time/time-range.service.test.ts b/projects/common/src/time/time-range.service.test.ts index f6b37c48e..dde30abaa 100644 --- a/projects/common/src/time/time-range.service.test.ts +++ b/projects/common/src/time/time-range.service.test.ts @@ -1,13 +1,13 @@ import { ActivatedRoute, convertToParamMap } from '@angular/router'; -import { runFakeRxjs } from '@hypertrace/test-utils'; +import { recordObservable, runFakeRxjs } from '@hypertrace/test-utils'; import { createServiceFactory, mockProvider } from '@ngneat/spectator/jest'; -import { NEVER, Observable, of } from 'rxjs'; +import { NEVER, Observable, of, Subject } from 'rxjs'; import { map } from 'rxjs/operators'; -import { NavigationService } from '../navigation/navigation.service'; +import { NavigationService, QueryParamObject } from '../navigation/navigation.service'; import { FixedTimeRange } from './fixed-time-range'; import { TimeRangeService } from './time-range.service'; -describe('Time range service', () => { +describe('Time range(TR) service', () => { let timeRange$: Observable = NEVER; const buildService = createServiceFactory({ service: TimeRangeService, @@ -18,12 +18,19 @@ describe('Time range service', () => { map( initialTrString => // tslint:disable-next-line: no-object-literal-type-assertion - ({ - queryParamMap: of(convertToParamMap({ time: initialTrString })) - } as ActivatedRoute) + (({ + queryParamMap: of(convertToParamMap({ time: initialTrString, refresh: 'true' })), + snapshot: { queryParamMap: convertToParamMap({ time: initialTrString, refresh: 'true' }) } + } as unknown) as ActivatedRoute) ) ); - } + }, + getQueryParameter: jest + .fn() + .mockReturnValueOnce('1573255100253-1573255111159') + .mockReturnValue('1573255111159-1573455111990'), + getCurrentActivatedRoute: () => + (({ snapshot: { queryParams: { time: 'test-value' } } } as unknown) as ActivatedRoute) }) ] }); @@ -34,57 +41,120 @@ describe('Time range service', () => { }); test('returns time range when requested after init', () => { - timeRange$ = of('1573255100253-1573255111159'); - const spectator = buildService(); - expect(spectator.service.getCurrentTimeRange()).toEqual( - new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)) - ); - }); - - test('returns observable that emits future time range changes including initialization', () => { - const lateArrivingTimeRange = new FixedTimeRange(new Date(1573255111159), new Date(1573255111160)); runFakeRxjs(({ cold, expectObservable }) => { - timeRange$ = cold('1ms x', { + timeRange$ = cold('x|', { x: '1573255100253-1573255111159' }); const spectator = buildService(); + expect(() => spectator.service.getCurrentTimeRange()).toThrow(); - cold('5ms x').subscribe(() => - spectator.service.setFixedRange(lateArrivingTimeRange.startTime, lateArrivingTimeRange.endTime) - ); + expectObservable(spectator.service.getTimeRangeAndChanges()).toBe('x', { + x: new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)) + }); + }); + }); - expectObservable(spectator.service.getTimeRangeAndChanges()).toBe('1ms x 3ms y', { + test('returns observable that emits future time range changes including initialization', () => { + const firstArrivingTimeRange = new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)); + const secondArrivingTimeRange = new FixedTimeRange(new Date(1573255111159), new Date(1573455111990)); + + runFakeRxjs(({ cold, expectObservable }) => { + const spectator = buildService({ + providers: [ + mockProvider(NavigationService, { + navigation$: cold('-x---y', { + x: ({ + queryParamMap: of(convertToParamMap({ time: firstArrivingTimeRange.toUrlString(), refresh: 'true' })), + snapshot: { + queryParamMap: convertToParamMap({ time: firstArrivingTimeRange.toUrlString(), refresh: 'true' }) + } + } as unknown) as ActivatedRoute, + y: ({ + queryParamMap: of(convertToParamMap({ time: secondArrivingTimeRange.toUrlString(), refresh: 'true' })), + snapshot: { + queryParamMap: convertToParamMap({ time: secondArrivingTimeRange.toUrlString(), refresh: 'true' }) + } + } as unknown) as ActivatedRoute + }), + getQueryParameter: jest + .fn() + .mockReturnValueOnce(firstArrivingTimeRange.toUrlString()) + .mockReturnValue(secondArrivingTimeRange.toUrlString()), + getCurrentActivatedRoute: () => + (({ snapshot: { queryParams: { time: 'test-value' } } } as unknown) as ActivatedRoute) + }) + ] + }); + + const recordedTimeRanges = recordObservable(spectator.service.getTimeRangeAndChanges()); + + expect(() => spectator.service.getCurrentTimeRange()).toThrow(); + + expectObservable(recordedTimeRanges).toBe('-x----y', { x: new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)), - y: lateArrivingTimeRange + y: secondArrivingTimeRange }); }); }); - test('returns observable that emits current time range and later changes', () => { - const lateArrivingTimeRange = new FixedTimeRange(new Date(1573255111159), new Date(1573255111160)); - runFakeRxjs(({ cold, expectObservable }) => { - timeRange$ = of('1573255100253-1573255111159'); + test('Emits default TR when set, then subsequent first and second TRs from query param changes', () => { + const defaultTimeRange = new FixedTimeRange(new Date(1573277100277), new Date(1573277100277)); + const firstArrivingTimeRange = new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)); + const secondArrivingTimeRange = new FixedTimeRange(new Date(1573255111159), new Date(1573455111990)); + const mockNavigation$ = new Subject(); + runFakeRxjs(({ expectObservable, cold }) => { + const spectator = buildService({ + providers: [ + mockProvider(NavigationService, { + navigation$: mockNavigation$.asObservable().pipe( + map( + timeRangeString => + (({ + queryParamMap: of(convertToParamMap({ time: timeRangeString, refresh: 'true' })), + snapshot: { + queryParamMap: convertToParamMap({ time: timeRangeString, refresh: 'true' }) + } + } as unknown) as ActivatedRoute) + ) + ), + addQueryParametersToUrl: (newParams: QueryParamObject) => mockNavigation$.next(newParams.time as string), + getQueryParameter: jest + .fn() + .mockReturnValueOnce('1573277100277-1573277100277') + .mockReturnValueOnce('1573255100253-1573255111159') + .mockReturnValue('1573255111159-1573455111990'), + getCurrentActivatedRoute: () => + (({ snapshot: { queryParams: { time: 'test-value' } } } as unknown) as ActivatedRoute), + replaceQueryParametersInUrl: jest.fn() + }) + ] + }); - const spectator = buildService(); - expect(spectator.service.getCurrentTimeRange()).toEqual( - new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)) + cold('x').subscribe(() => spectator.service.setDefaultTimeRange(defaultTimeRange)); + + cold('2ms y').subscribe(() => + spectator.service.setFixedRange(firstArrivingTimeRange.startTime, firstArrivingTimeRange.endTime) ); - cold('5ms x').subscribe(() => - spectator.service.setFixedRange(lateArrivingTimeRange.startTime, lateArrivingTimeRange.endTime) + + cold('5ms z').subscribe(() => + spectator.service.setFixedRange(secondArrivingTimeRange.startTime, secondArrivingTimeRange.endTime) ); - expectObservable(spectator.service.getTimeRangeAndChanges()).toBe('x 4ms y', { - x: new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)), - y: lateArrivingTimeRange + expectObservable(spectator.service.getTimeRangeAndChanges()).toBe('x 1ms y 2ms z', { + x: defaultTimeRange, + y: firstArrivingTimeRange, + z: secondArrivingTimeRange }); }); }); test('returns custom time filter', () => { const spectator = buildService(); - expect(spectator.service.toQueryParams(new Date(1642296703000), new Date(1642396703000))).toStrictEqual({ + expect( + spectator.service.toQueryParams(new FixedTimeRange(new Date(1642296703000), new Date(1642396703000))) + ).toStrictEqual({ ['time']: new FixedTimeRange(new Date(1642296703000), new Date(1642396703000)).toUrlString() }); }); diff --git a/projects/common/src/time/time-range.service.ts b/projects/common/src/time/time-range.service.ts index 95195734b..ead04524c 100644 --- a/projects/common/src/time/time-range.service.ts +++ b/projects/common/src/time/time-range.service.ts @@ -1,7 +1,8 @@ import { Injectable } from '@angular/core'; -import { isEmpty, isNil } from 'lodash-es'; -import { EMPTY, ReplaySubject } from 'rxjs'; -import { catchError, filter, map, switchMap, take } from 'rxjs/operators'; +import { ParamMap } from '@angular/router'; +import { isEmpty, isNil, omit } from 'lodash-es'; +import { concat, EMPTY, Observable, ReplaySubject } from 'rxjs'; +import { catchError, distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators'; import { NavigationService, QueryParamObject } from '../navigation/navigation.service'; import { ReplayObservable } from '../utilities/rxjs/rxjs-utils'; import { FixedTimeRange } from './fixed-time-range'; @@ -16,6 +17,7 @@ import { TimeUnit } from './time-unit.type'; }) export class TimeRangeService { private static readonly TIME_RANGE_QUERY_PARAM: string = 'time'; + private static readonly REFRESH_ON_NAVIGATION: string = 'refresh'; private readonly timeRangeSubject$: ReplaySubject = new ReplaySubject(1); private currentTimeRange?: TimeRange; @@ -51,31 +53,65 @@ export class TimeRangeService { } public setRelativeRange(value: number, unit: TimeUnit): this { - return this.setTimeRange(TimeRangeService.toRelativeTimeRange(value, unit)); + return this.setTimeRangeInUrl(TimeRangeService.toRelativeTimeRange(value, unit)); } public setFixedRange(startTime: Date, endTime: Date): this { - return this.setTimeRange(TimeRangeService.toFixedTimeRange(startTime, endTime)); + return this.setTimeRangeInUrl(TimeRangeService.toFixedTimeRange(startTime, endTime)); } public refresh(): void { - this.setTimeRange(this.getCurrentTimeRange()); + const currentStringTimeRange = this.getCurrentTimeRange().toUrlString(); + this.applyTimeRangeChange(this.timeRangeFromUrlString(currentStringTimeRange)); + } + + private getInitialTimeRange(): Observable { + return this.navigationService.navigation$.pipe( + take(1), // Wait for first navigation + switchMap(activatedRoute => activatedRoute.queryParamMap), // Get the params from it + take(1), // Only the first set of params + map(paramMap => paramMap.get(TimeRangeService.TIME_RANGE_QUERY_PARAM)), // Extract the time range value from it + filter((timeRangeString): timeRangeString is string => !isEmpty(timeRangeString)), // Only valid time ranges + map(timeRangeString => this.timeRangeFromUrlString(timeRangeString)), + catchError(() => EMPTY) + ); + } + + private getPageTimeRangeChanges(): Observable { + return this.navigationService.navigation$.pipe( + map(activeRoute => activeRoute.snapshot.queryParamMap), + filter(queryParamMap => !isNil(queryParamMap.get(TimeRangeService.TIME_RANGE_QUERY_PARAM))), + distinctUntilChanged((prevParamMap, currParamMap) => this.shouldNotUpdateTimeRange(prevParamMap, currParamMap)), + map(currQueryParamMap => { + const timeRangeQueryParamString = currQueryParamMap.get(TimeRangeService.TIME_RANGE_QUERY_PARAM); + + return this.timeRangeFromUrlString(timeRangeQueryParamString!); + }) + ); } private initializeTimeRange(): void { - this.navigationService.navigation$ - .pipe( - take(1), // Wait for first navigation - switchMap(activatedRoute => activatedRoute.queryParamMap), // Get the params from it - take(1), // Only the first set of params - map(paramMap => paramMap.get(TimeRangeService.TIME_RANGE_QUERY_PARAM)), // Extract the time range value from it - filter((timeRangeString): timeRangeString is string => !isEmpty(timeRangeString)), // Only valid time ranges - map(timeRangeString => this.timeRangeFromUrlString(timeRangeString)), - catchError(() => EMPTY) - ) - .subscribe(timeRange => { - this.setTimeRange(timeRange); - }); + concat(this.getInitialTimeRange(), this.getPageTimeRangeChanges()).subscribe(timeRange => { + if (!this.timeRangeMatchesCurrentUrl(timeRange)) { + this.setTimeRangeInUrl(timeRange); + } else { + this.applyTimeRangeChange(timeRange); + + const queryParams = this.navigationService.getCurrentActivatedRoute().snapshot.queryParams; + if (TimeRangeService.REFRESH_ON_NAVIGATION in queryParams) { + this.navigationService.replaceQueryParametersInUrl(omit(queryParams, TimeRangeService.REFRESH_ON_NAVIGATION)); + } + } + }); + } + + private shouldNotUpdateTimeRange(prevParamMap: ParamMap, currParamMap: ParamMap): boolean { + const refreshQueryParam = currParamMap.get(TimeRangeService.REFRESH_ON_NAVIGATION); + + return ( + prevParamMap.get(TimeRangeService.TIME_RANGE_QUERY_PARAM) === + currParamMap.get(TimeRangeService.TIME_RANGE_QUERY_PARAM) && isEmpty(refreshQueryParam) + ); } public timeRangeFromUrlString(timeRangeFromUrl: string): TimeRange { @@ -91,11 +127,16 @@ export class TimeRangeService { throw new Error(); // Caught in observable } - private setTimeRange(newTimeRange: TimeRange): this { + private applyTimeRangeChange(newTimeRange: TimeRange): this { this.currentTimeRange = newTimeRange; this.timeRangeSubject$.next(newTimeRange); + + return this; + } + + private setTimeRangeInUrl(timeRange: TimeRange): this { this.navigationService.addQueryParametersToUrl({ - [TimeRangeService.TIME_RANGE_QUERY_PARAM]: newTimeRange.toUrlString() + [TimeRangeService.TIME_RANGE_QUERY_PARAM]: timeRange.toUrlString() }); return this; @@ -103,7 +144,7 @@ export class TimeRangeService { public setDefaultTimeRange(timeRange: TimeRange): void { if (!this.currentTimeRange) { - this.setTimeRange(timeRange); + this.setTimeRangeInUrl(timeRange); } } @@ -115,12 +156,22 @@ export class TimeRangeService { return new FixedTimeRange(startTime, endTime); } - public toQueryParams(startTime: Date, endTime: Date): QueryParamObject { - const newTimeRange = new FixedTimeRange(startTime, endTime); - - return { - [TimeRangeService.TIME_RANGE_QUERY_PARAM]: newTimeRange.toUrlString() + public toQueryParams(timeRange: TimeRange, refreshTimeOnNavigationParam?: boolean): QueryParamObject { + const queryParams: QueryParamObject = { + [TimeRangeService.TIME_RANGE_QUERY_PARAM]: timeRange.toUrlString() }; + + if (refreshTimeOnNavigationParam) { + return { ...queryParams, [TimeRangeService.REFRESH_ON_NAVIGATION]: true }; + } + + return queryParams; + } + + private timeRangeMatchesCurrentUrl(timeRange: TimeRange): boolean { + return ( + this.navigationService.getQueryParameter(TimeRangeService.TIME_RANGE_QUERY_PARAM, '') === timeRange.toUrlString() + ); } public isInitialized(): boolean { diff --git a/projects/components/src/navigation/nav-item/nav-item.component.test.ts b/projects/components/src/navigation/nav-item/nav-item.component.test.ts index ed04911d1..c9ac3bcd9 100644 --- a/projects/components/src/navigation/nav-item/nav-item.component.test.ts +++ b/projects/components/src/navigation/nav-item/nav-item.component.test.ts @@ -33,7 +33,8 @@ describe('Navigation Item Component', () => { getCurrentActivatedRoute: jest.fn().mockReturnValue(of(activatedRoute)) }), mockProvider(FeatureStateResolver, { - getCombinedFeatureState: () => of(FeatureState.Enabled) + getCombinedFeatureState: () => of(FeatureState.Enabled), + getFeatureState: () => of(FeatureState.Enabled) }) ] }); diff --git a/projects/components/src/navigation/nav-item/nav-item.component.ts b/projects/components/src/navigation/nav-item/nav-item.component.ts index f201a573c..92df8587b 100644 --- a/projects/components/src/navigation/nav-item/nav-item.component.ts +++ b/projects/components/src/navigation/nav-item/nav-item.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { FeatureState, NavigationParams, NavigationParamsType } from '@hypertrace/common'; +import { FeatureState, NavigationParams, NavigationParamsType, TimeRangeService } from '@hypertrace/common'; import { IconSize } from '../../icon/icon-size'; import { NavItemLinkConfig, NavViewStyle } from '../navigation.config'; @@ -57,12 +57,26 @@ export class NavItemComponent { @Input() public readonly navItemViewStyle?: NavViewStyle; - public buildNavigationParam = (item: NavItemLinkConfig): NavigationParams => ({ - navType: NavigationParamsType.InApp, - path: item.matchPaths[0], - relativeTo: this.activatedRoute, - replaceCurrentHistory: item.replaceCurrentHistory - }); + public buildNavigationParam = (item: NavItemLinkConfig): NavigationParams => { + const navParams: NavigationParams = { + navType: NavigationParamsType.InApp, + path: item.matchPaths[0], + relativeTo: this.activatedRoute, + replaceCurrentHistory: item.replaceCurrentHistory + }; - public constructor(private readonly activatedRoute: ActivatedRoute) {} + if (this.config.pageLevelTimeRangeIsEnabled && this.config.timeRangeResolver) { + return { + ...navParams, + queryParams: this.timeRangeService.toQueryParams(this.config.timeRangeResolver(), true) + }; + } + + return navParams; + }; + + public constructor( + private readonly activatedRoute: ActivatedRoute, + private readonly timeRangeService: TimeRangeService + ) {} } diff --git a/projects/components/src/navigation/navigation-list.component.ts b/projects/components/src/navigation/navigation-list.component.ts index ba7238b5a..ab88b3d6f 100644 --- a/projects/components/src/navigation/navigation-list.component.ts +++ b/projects/components/src/navigation/navigation-list.component.ts @@ -39,7 +39,6 @@ import { = new EventEmitter(); - @Output() public readonly activeItemChange: EventEmitter = new EventEmitter(); diff --git a/projects/components/src/page-time-range/page-time-range.component.ts b/projects/components/src/page-time-range/page-time-range.component.ts index d416b2f97..128a04012 100644 --- a/projects/components/src/page-time-range/page-time-range.component.ts +++ b/projects/components/src/page-time-range/page-time-range.component.ts @@ -35,7 +35,7 @@ export class PageTimeRangeComponent { } public shouldSavePageTimeRange(currentRoute: ActivatedRoute): boolean { - return !isNil(currentRoute.snapshot.data?.defaultTimeRange); + return currentRoute.snapshot.data?.shouldSavePageTimeRange ?? false; } public savePageTimeRange(selectedTimeRange: TimeRange, segment: UrlSegment): void { diff --git a/projects/components/src/page-time-range/page-time-range.test.ts b/projects/components/src/page-time-range/page-time-range.test.ts index ff1160af0..fbf8c79df 100644 --- a/projects/components/src/page-time-range/page-time-range.test.ts +++ b/projects/components/src/page-time-range/page-time-range.test.ts @@ -18,7 +18,7 @@ describe('Page time range component', () => { const route = { snapshot: { data: { - defaultTimeRange: undefined + shouldSavePageTimeRange: undefined } }, pathFromRoot: { flatMap: jest.fn().mockReturnValue(['foo']) } @@ -52,14 +52,14 @@ describe('Page time range component', () => { expect(spectator.component.savePageTimeRange).not.toHaveBeenCalled(); }); - test('should not attempt to save time range when route is not a first level route', () => { + test('should not attempt to save time range when saving flag property is not set', () => { spectator = createHost(``, { providers: [ mockProvider(NavigationService, { getCurrentActivatedRoute: jest.fn().mockReturnValue({ snapshot: { data: { - defaultTimeRange: undefined + shouldSavePageTimeRange: undefined } }, pathFromRoot: { flatMap: jest.fn().mockReturnValue(['parent-path', 'child-path']) } @@ -77,14 +77,14 @@ describe('Page time range component', () => { expect(spectator.component.savePageTimeRange).not.toHaveBeenCalled(); }); - test('should save time range when route is first level, and the defaultTimeRange property is present', () => { + test('should save time range when route has truthy property shouldSavePageTimeRange', () => { spectator = createHost(``, { providers: [ mockProvider(NavigationService, { getCurrentActivatedRoute: jest.fn().mockReturnValue({ snapshot: { data: { - defaultTimeRange: new RelativeTimeRange(new TimeDuration(30, TimeUnit.Minute)) + shouldSavePageTimeRange: true } }, pathFromRoot: { flatMap: () => [{ path: 'parent-path' }] as UrlSegment[] } diff --git a/projects/components/src/time-range/time-range.component.test.ts b/projects/components/src/time-range/time-range.component.test.ts index 7e54ccecf..3843dbe43 100644 --- a/projects/components/src/time-range/time-range.component.test.ts +++ b/projects/components/src/time-range/time-range.component.test.ts @@ -131,7 +131,7 @@ describe('Time range component', () => { test('should publish new time range when refresh is clicked', () => { const spectator = createComponent(); - const spy = jest.spyOn(spectator.inject(TimeRangeService), 'setRelativeRange'); + const spy = jest.spyOn(spectator.inject(TimeRangeService), 'refresh'); spectator.click('.refresh'); expect(spy).toHaveBeenCalled(); }); diff --git a/projects/components/src/time-range/time-range.component.ts b/projects/components/src/time-range/time-range.component.ts index 411d45311..49fb23c81 100644 --- a/projects/components/src/time-range/time-range.component.ts +++ b/projects/components/src/time-range/time-range.component.ts @@ -125,7 +125,7 @@ export class TimeRangeComponent { text$: of('Refresh'), role: ButtonRole.Tertiary, isEmphasized: false, - onClick: () => this.onRefresh(timeRange) + onClick: () => this.onRefresh() }), this.ngZone.runOutsideAngular(() => // Long running timer will prevent zone from stabilizing @@ -143,7 +143,7 @@ export class TimeRangeComponent { ), role: ButtonRole.Primary, isEmphasized: true, - onClick: () => this.onRefresh(timeRange) + onClick: () => this.onRefresh() })) ) ) @@ -153,8 +153,8 @@ export class TimeRangeComponent { return EMPTY; }; - private onRefresh(timeRange: RelativeTimeRange): void { - this.timeRangeService.setRelativeRange(timeRange.duration.value, timeRange.duration.unit); + private onRefresh(): void { + this.timeRangeService.refresh(); } } diff --git a/projects/observability/src/shared/components/explore-query-editor/explore-visualization-builder.test.ts b/projects/observability/src/shared/components/explore-query-editor/explore-visualization-builder.test.ts index d46c5bd84..74570a11d 100644 --- a/projects/observability/src/shared/components/explore-query-editor/explore-visualization-builder.test.ts +++ b/projects/observability/src/shared/components/explore-query-editor/explore-visualization-builder.test.ts @@ -1,6 +1,14 @@ import { fakeAsync, tick } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { FixedTimeRange, IntervalDurationService, TimeDuration, TimeRangeService, TimeUnit } from '@hypertrace/common'; +import { + FeatureState, + FeatureStateResolver, + FixedTimeRange, + IntervalDurationService, + TimeDuration, + TimeRangeService, + TimeUnit +} from '@hypertrace/common'; import { patchRouterNavigateForTest, recordObservable, runFakeRxjs } from '@hypertrace/test-utils'; import { createServiceFactory, mockProvider, SpectatorService } from '@ngneat/spectator/jest'; import { of } from 'rxjs'; @@ -27,6 +35,9 @@ describe('Explore visualization builder', () => { }), mockProvider(IntervalDurationService, { getAutoDuration: () => new TimeDuration(3, TimeUnit.Minute) + }), + mockProvider(FeatureStateResolver, { + getFeatureState: () => of(FeatureState.Enabled) }) ] }); diff --git a/projects/observability/src/shared/dashboard/widgets/charts/cartesian-widget/interactions/cartesian-explorer-navigation.service.ts b/projects/observability/src/shared/dashboard/widgets/charts/cartesian-widget/interactions/cartesian-explorer-navigation.service.ts index d33c6d167..722f24d2e 100644 --- a/projects/observability/src/shared/dashboard/widgets/charts/cartesian-widget/interactions/cartesian-explorer-navigation.service.ts +++ b/projects/observability/src/shared/dashboard/widgets/charts/cartesian-widget/interactions/cartesian-explorer-navigation.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { NavigationParamsType, NavigationService, TimeRangeService } from '@hypertrace/common'; +import { FixedTimeRange, NavigationParamsType, NavigationService, TimeRangeService } from '@hypertrace/common'; @Injectable({ providedIn: 'root' @@ -12,7 +12,7 @@ export class CartesainExplorerNavigationService { public navigateToExplorer(start: Date, end: Date): void { this.timeRangeService.setFixedRange(start, end); - const params = this.timeRangeService.toQueryParams(start, end); + const params = this.timeRangeService.toQueryParams(new FixedTimeRange(start, end)); this.navigationService.navigate({ navType: NavigationParamsType.InApp, diff --git a/src/app/routes/root-routing.module.ts b/src/app/routes/root-routing.module.ts index d5500aa18..bb6fc4b5d 100644 --- a/src/app/routes/root-routing.module.ts +++ b/src/app/routes/root-routing.module.ts @@ -26,7 +26,8 @@ const ROUTE_CONFIG: HtRoute[] = [ icon: IconType.Dashboard, label: 'Dashboard' }, - defaultTimeRange: new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour)) + defaultTimeRange: new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour)), + shouldSavePageTimeRange: true }, loadChildren: () => import('../home/home.module').then(module => module.HomeModule) }, @@ -37,7 +38,8 @@ const ROUTE_CONFIG: HtRoute[] = [ icon: ObservabilityIconType.ApplicationFlow, label: 'Application Flow' }, - defaultTimeRange: new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour)) + defaultTimeRange: new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour)), + shouldSavePageTimeRange: true }, loadChildren: () => import('./application-flow/application-flow-routing.module').then( @@ -51,7 +53,8 @@ const ROUTE_CONFIG: HtRoute[] = [ icon: ObservabilityIconType.Backend, label: 'Backends' }, - defaultTimeRange: new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour)) + defaultTimeRange: new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour)), + shouldSavePageTimeRange: true }, loadChildren: () => import('./backends/backends-routing.module').then(module => module.BackendsRoutingModule) @@ -63,7 +66,8 @@ const ROUTE_CONFIG: HtRoute[] = [ icon: ObservabilityIconType.Service, label: 'Services' }, - defaultTimeRange: new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour)) + defaultTimeRange: new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour)), + shouldSavePageTimeRange: true }, loadChildren: () => import('./services/services-routing.module').then(module => module.ServicesRoutingModule) @@ -75,7 +79,8 @@ const ROUTE_CONFIG: HtRoute[] = [ icon: ObservabilityIconType.Api, label: 'Endpoints' }, - defaultTimeRange: new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour)) + defaultTimeRange: new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour)), + shouldSavePageTimeRange: true }, loadChildren: () => import('./endpoints/endpoint-routing.module').then(module => module.EndpointRoutingModule) @@ -95,7 +100,8 @@ const ROUTE_CONFIG: HtRoute[] = [ icon: IconType.Search, label: 'Explorer' }, - defaultTimeRange: new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour)) + defaultTimeRange: new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour)), + shouldSavePageTimeRange: true }, loadChildren: () => import('./explorer/explorer-routing.module').then(module => module.ExplorerRoutingModule) diff --git a/src/app/shared/feature-resolver/feature-resolver.service.ts b/src/app/shared/feature-resolver/feature-resolver.service.ts index c8e56b1cf..4f41101ec 100644 --- a/src/app/shared/feature-resolver/feature-resolver.service.ts +++ b/src/app/shared/feature-resolver/feature-resolver.service.ts @@ -1,10 +1,15 @@ import { Injectable } from '@angular/core'; -import { FeatureState, FeatureStateResolver } from '@hypertrace/common'; +import { ApplicationFeature, FeatureState, FeatureStateResolver } from '@hypertrace/common'; import { Observable, of } from 'rxjs'; @Injectable() export class FeatureResolverService extends FeatureStateResolver { - public getFeatureState(_: string): Observable { - return of(FeatureState.Enabled); + public getFeatureState(flag: string): Observable { + switch (flag) { + case ApplicationFeature.PageTimeRange: + return of(FeatureState.Disabled); + default: + return of(FeatureState.Enabled); + } } } diff --git a/src/app/shared/navigation/navigation.component.ts b/src/app/shared/navigation/navigation.component.ts index eabe574a0..b309a2a09 100644 --- a/src/app/shared/navigation/navigation.component.ts +++ b/src/app/shared/navigation/navigation.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { IconType } from '@hypertrace/assets-library'; -import { FixedTimeRange, PreferenceService, RelativeTimeRange, TimeRangeService } from '@hypertrace/common'; +import { PreferenceService, TimeRangeService } from '@hypertrace/common'; import { NavigationListComponentService, NavigationListService, @@ -10,7 +10,6 @@ import { NavItemType } from '@hypertrace/components'; import { ObservabilityIconType } from '@hypertrace/observability'; -import { isNil } from 'lodash-es'; import { Observable } from 'rxjs'; @Component({ @@ -24,7 +23,6 @@ import { Observable } from 'rxjs'; *htLetAsync="this.isCollapsed$ as isCollapsed" [collapsed]="isCollapsed" (collapsedChange)="this.onViewToggle($event)" - (navItemClick)="this.setPageTimeRangeForSelectedNavItem($event)" (activeItemChange)="this.updateDefaultTimeRangeIfUnset($event)" > @@ -101,17 +99,6 @@ export class NavigationComponent { this.isCollapsed$ = this.preferenceService.get(NavigationComponent.COLLAPSED_PREFERENCE, false); } - public setPageTimeRangeForSelectedNavItem(navItemLink: NavItemLinkConfig): void { - if (!isNil(navItemLink.timeRangeResolver) && navItemLink.pageLevelTimeRangeIsEnabled) { - const timeRange = navItemLink.timeRangeResolver(); - if (timeRange instanceof FixedTimeRange) { - this.timeRangeService.setFixedRange(timeRange.startTime, timeRange.endTime); - } else if (timeRange instanceof RelativeTimeRange) { - this.timeRangeService.setRelativeRange(timeRange.duration.value, timeRange.duration.unit); - } - } - } - public updateDefaultTimeRangeIfUnset(activeItem: NavItemLinkConfig): void { // Initialize the time range service // Depending on FF status, the TR will be either global or page level for the init