Skip to content
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
dd20f13
refactor: time range styles
Feb 8, 2022
a8523ab
Merge branch 'main' of github.com:hypertrace/hypertrace-ui into Bread…
Feb 9, 2022
4bfa6d6
feat: initial browser stored page time range
Feb 11, 2022
63a665e
fix: removed defaultTimeRange null check for throwing error
Feb 11, 2022
b81ae1a
refactor: time range logic to be query param centric
Feb 14, 2022
6e84fbe
refactor: update effected tests
Feb 14, 2022
ab466fe
Merge branch 'main' into BreadcrumbsTimeRange
Feb 14, 2022
3043f45
fix: replace arrow function for compiler
Feb 14, 2022
56c574b
fix: time range isCustom correction
Feb 15, 2022
c1be553
refactor: moved time range logic to sub component
Feb 16, 2022
8eb3a52
fix: adjusted time range style to match other header buttons
Feb 16, 2022
ffe3d6b
fix: linting
Feb 16, 2022
3f98a99
refactor: cleaned up nav params observable
Feb 16, 2022
f0d9ffe
fix: linting
Feb 16, 2022
e1bea69
refactor: feature flag added
Feb 17, 2022
b359fcd
test: tests for new time range
Feb 18, 2022
ab36992
test: linter
Feb 18, 2022
cd870bc
Merge branch 'main' into BreadcrumbsTimeRange
Feb 18, 2022
7a92d50
test: added new test for time range service
Feb 22, 2022
b241e0e
fix: pr changes
Feb 23, 2022
7e279e4
fix: linting
Feb 23, 2022
10e8225
Merge branch 'main' into BreadcrumbsTimeRange
Feb 23, 2022
1738b10
Merge branch 'main' of github.com:hypertrace/hypertrace-ui into Bread…
Feb 23, 2022
c44b03b
Merge branch 'main' of github.com:hypertrace/hypertrace-ui into Bread…
Feb 28, 2022
bd1cbbd
Merge branch 'BreadcrumbsTimeRange' of github.com:hypertrace/hypertra…
Feb 28, 2022
3adc661
fix: requested changes
Mar 1, 2022
6e78e54
fix: export feature enum
Mar 1, 2022
c225cf9
refactor: replaced time range icon with calendar
Mar 2, 2022
c2d0287
Merge branch 'main' of github.com:hypertrace/hypertrace-ui into Bread…
Mar 3, 2022
7bc71c7
fix: requested changes
Mar 3, 2022
b4b4722
fix: backwards compatability and depency issue changes
Mar 4, 2022
a528987
fix: requested changes
Mar 7, 2022
63c4bd0
fix: requested changes
Mar 8, 2022
8742a2b
fix: requested changes
Mar 9, 2022
b795d39
fix: linting
Mar 9, 2022
9ed2b22
Merge branch 'main' of github.com:hypertrace/hypertrace-ui into Bread…
Mar 9, 2022
67c8bfe
fix: moved tr selector back to components
Mar 9, 2022
0854000
fix: update test
Mar 9, 2022
62d1041
refactor: requested changes
Mar 9, 2022
5037200
refactor: requested changes
Christian862 Mar 10, 2022
11b0991
refactor: requested changes
Christian862 Mar 10, 2022
50153f9
refactor: requested changes
Christian862 Mar 10, 2022
12ab84d
Merge branch 'main' of github.com:hypertrace/hypertrace-ui into Bread…
Christian862 Mar 10, 2022
ea6357a
refactor: requested changes
Christian862 Mar 11, 2022
b558d41
fix: linting
Christian862 Mar 11, 2022
0989a6a
fix: requested changes - naming and TR service
Christian862 Mar 11, 2022
3845357
Merge branch 'main' of github.com:hypertrace/hypertrace-ui into Bread…
Christian862 Mar 14, 2022
a37a477
fix: requested changes
Christian862 Mar 14, 2022
00f785a
Merge branch 'main' of github.com:hypertrace/hypertrace-ui into Bread…
Christian862 Mar 14, 2022
82a9612
fix: requested changes - broken
Christian862 Mar 15, 2022
9714df8
fix: requested changes
Christian862 Mar 15, 2022
4b0b74e
test: testing support for new TR init
Christian862 Mar 15, 2022
44a67b6
refactor: requested changes
Christian862 Mar 15, 2022
bfcb9b8
refactor: requested changes, updated route data for new changes
Christian862 Mar 16, 2022
38c5e82
Merge branch 'main' of github.com:hypertrace/hypertrace-ui into Bread…
Christian862 Mar 16, 2022
efd4d15
refactor: requested changes
Christian862 Mar 18, 2022
5e5dcf5
refactor: requested changes
Christian862 Mar 18, 2022
7172091
Merge branch 'main' of github.com:hypertrace/hypertrace-ui into Bread…
Christian862 Mar 18, 2022
505d5d7
Merge branch 'main' of github.com:hypertrace/hypertrace-ui into Bread…
Christian862 Mar 18, 2022
748743e
refactor: requested changes
Christian862 Mar 18, 2022
09cf639
refactor: requested changes
Christian862 Mar 18, 2022
5c0cfe6
refactor: fix FF string
Christian862 Mar 18, 2022
541555f
Merge branch 'main' of github.com:hypertrace/hypertrace-ui into Bread…
Christian862 Mar 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions projects/assets-library/assets/icons/calendar-dates.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions projects/assets-library/src/icons/icon-library.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const iconsRootPath = 'assets/icons';
{ key: IconType.ArrowUpLeft, url: `${iconsRootPath}/arrow-up-left.svg` },
{ key: IconType.ArrowUpRight, url: `${iconsRootPath}/arrow-up-right.svg` },
{ key: IconType.Calls, url: `${iconsRootPath}/calls.svg` },
{ key: IconType.Calendar, url: `${iconsRootPath}/calendar-dates.svg` },
{ key: IconType.CheckCircle, url: `${iconsRootPath}/check-circle.svg` },
{ key: IconType.CheckCircleFill, url: `${iconsRootPath}/check-circle-fill.svg` },
{ key: IconType.ChevronDown, url: `${iconsRootPath}/chevron-down.svg` },
Expand Down
1 change: 1 addition & 0 deletions projects/assets-library/src/icons/icon-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const enum IconType {
ArrowUpLeft = 'svg:arrow-up-left',
ArrowUpRight = 'svg:arrow-up-right',
Calls = 'svg:calls',
Calendar = 'svg:calendar-dates',
Cancel = 'cancel',
CheckCircle = 'svg:check-circle',
CheckCircleFill = 'svg:check-circle-fill',
Expand Down
5 changes: 5 additions & 0 deletions projects/common/src/constants/application-constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { InjectionToken } from '@angular/core';

export const GLOBAL_HEADER_HEIGHT = new InjectionToken<string>('Global Header Height');

export const enum ApplicationFeature {
PageTimeRange = 'ui.page-time-range',
NavigationRedesign = 'ui.navigation-version-2'
}
2 changes: 2 additions & 0 deletions projects/common/src/navigation/ht-route-data.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Observable } from 'rxjs';
import { TimeRange } from '../time/time-range';
import { Breadcrumb } from './breadcrumb';

export interface HtRouteData {
breadcrumb?: Breadcrumb | Observable<Breadcrumb>;
features?: string[];
title?: string;
defaultTimeRange?: TimeRange;
}
1 change: 1 addition & 0 deletions projects/common/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export * from './time/time-range.service';
export * from './time/time-range.type';
export * from './time/time-unit.type';
export * from './time/time';
export * from './time/page-time-range-preference.service';

// Validators
export * from './utilities/validators';
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
FixedTimeRange,
NavigationService,
RelativeTimeRange,
TimeDuration,
TimeRange,
TimeRangeService,
TimeUnit
} from '@hypertrace/common';
import { runFakeRxjs } from '@hypertrace/test-utils';
import { createServiceFactory, mockProvider } from '@ngneat/spectator/jest';
import { PageTimeRangePreferenceService } from './page-time-range-preference.service';

describe('Page time range preference service', () => {
const defaultPageTimeRange = new RelativeTimeRange(new TimeDuration(2, TimeUnit.Hour));
const serviceFactory = createServiceFactory({
service: PageTimeRangePreferenceService,
providers: [
mockProvider(NavigationService, {
getRouteConfig: jest.fn().mockReturnValue({ data: { defaultTimeRange: defaultPageTimeRange } })
})
]
});

test('Setting fixed time range emits corresponding time range from preferences', () => {
runFakeRxjs(({ expectObservable, cold }) => {
const timeRange: TimeRange = new FixedTimeRange(new Date(1573255100253), new Date(1573255111159));
const spectator = serviceFactory({
providers: [
mockProvider(TimeRangeService, {
timeRangeFromUrlString: jest.fn().mockReturnValue(timeRange)
})
]
});

cold('-a|', {
a: () => spectator.service.setTimeRangePreferenceForPage('foo', timeRange)
}).subscribe(update => update());

expectObservable(spectator.service.getTimeRangePreferenceForPage('foo')).toBe('da', {
d: defaultPageTimeRange,
a: timeRange
});
});
});

test('Setting relative time range emits corresponding time range from preferences', () => {
runFakeRxjs(({ expectObservable, cold }) => {
const timeRange: TimeRange = new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour));
const spectator = serviceFactory({
providers: [
mockProvider(TimeRangeService, {
timeRangeFromUrlString: jest.fn().mockReturnValue(timeRange)
})
]
});

cold('-b|', {
b: () => spectator.service.setTimeRangePreferenceForPage('bar', timeRange)
}).subscribe(update => update());

expectObservable(spectator.service.getTimeRangePreferenceForPage('bar')).toBe('db', {
d: defaultPageTimeRange,
b: timeRange
});
});
});
});
93 changes: 93 additions & 0 deletions projects/common/src/time/page-time-range-preference.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Injectable } from '@angular/core';
import { isNil } from 'lodash-es';
import { combineLatest, Observable } from 'rxjs';
import { map, shareReplay, take } from 'rxjs/operators';
import { ApplicationFeature } from '../constants/application-constants';
import { FeatureStateResolver } from '../feature/state/feature-state.resolver';
import { FeatureState } from '../feature/state/feature.state';
import { NavigationService } from '../navigation/navigation.service';
import { PreferenceService, StorageType } from '../preference/preference.service';
import { RelativeTimeRange } from './relative-time-range';
import { TimeDuration } from './time-duration';
import { TimeRange } from './time-range';
import { TimeRangeService } from './time-range.service';
import { TimeUnit } from './time-unit.type';

@Injectable({ providedIn: 'root' })
export class PageTimeRangePreferenceService {
private static readonly STORAGE_TYPE: StorageType = StorageType.Local;
private static readonly TIME_RANGE_PREFERENCE_KEY: string = 'page-time-range';

private readonly globalDefaultTimeRange: TimeRange = new RelativeTimeRange(new TimeDuration(30, TimeUnit.Minute));

private readonly pageTimeRangeStringDictionary$: Observable<PageTimeRangeStringDictionary>;

public constructor(
private readonly preferenceService: PreferenceService,
private readonly timeRangeService: TimeRangeService,
private readonly navigationService: NavigationService,
private readonly featureStateResolver: FeatureStateResolver
) {
this.pageTimeRangeStringDictionary$ = this.buildPageTimeRangeObservable();
}

public getTimeRangePreferenceForPage(rootLevelPath: string): Observable<TimeRange> {
return combineLatest([
this.pageTimeRangeStringDictionary$,
this.featureStateResolver.getCombinedFeatureState([
ApplicationFeature.PageTimeRange,
ApplicationFeature.NavigationRedesign
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the nav redesign relevant here? The page time range I thought was independent of nav changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PageTimeRange is responsible for bringing the time range down to the the page level, on the headers, however it still uses the original time range component to maintain original time range behaviour . The NavigationRedesign feature enables the time range to behave with page level functionality. This came after talks with @anandtiwary, and i believe this feature dependency structure is why he suggested to create something like this on the backend.

We could just use NavigationRedesign in this instance, however in the case that we disabled page time range and enable NavigationRedesign the ui will be broken because

  1. The top nav bar won't be present (in the coming nav2.0 PR) so no TR component will exist.
  2. The nav items will be attempting to use page level time range logic when they shouldn't be(changing TR on click).

Copy link
Contributor

@aaron-steinfeld aaron-steinfeld Mar 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NavigationRedesign feature enables the time range to behave with page level functionality

Oh interesting, so this is not what I would expect from the naming. I would have expected the behavior changes (the time range is scoped to the page) to come from the PageTimeRange flag, as well as the relocation of the control at the same time - basically all the work in this PR. I thought the Navigation redesign was dependent on that (since the new structure has no time range selector), but otherwise unrelated.

So first off, if we really want that separation, it seems like there's a flag rename in order (but let's see).

We could just use NavigationRedesign in this instance, however in the case that we disabled page time range and enable NavigationRedesign the ui will be broken because

We shouldn't try to add complexity to allow any matrix of flags to work. We can assume dependencies if it saves us significant work, I think that was mentioned earlier. In that scenario you described though, how are we solving the lack of TR? If we've removed top and disabled showing it on the page too, it's just not a valid state. And In that same scenario, given the time range page behavior is part of the Nav redesign flag as you described, we would actually want 2.

])
]).pipe(
map(([pageTimeRangeStringDictionary, featureState]) => {
if (featureState === FeatureState.Enabled) {
if (isNil(pageTimeRangeStringDictionary[rootLevelPath])) {
// Right side for when FFs are enabled but page path doesn't have 'defaultTimeRange' set on AR data
return this.getDefaultPageTimeRange() ?? this.globalDefaultTimeRange;
}

return this.timeRangeService.timeRangeFromUrlString(pageTimeRangeStringDictionary[rootLevelPath]);
}

// When FFs are disabled
return this.globalDefaultTimeRange;
})
);
}

public setTimeRangePreferenceForPage(rootLevelPath: string, value: TimeRange): void {
this.pageTimeRangeStringDictionary$.pipe(take(1)).subscribe(currentPageTimeRangeDictionary => {
this.setPreferenceServicePageTimeRange(currentPageTimeRangeDictionary, rootLevelPath, value);
});
}

private setPreferenceServicePageTimeRange(
currentTimeRangeDictionary: PageTimeRangeStringDictionary,
rootLevelPath: string,
timeRange: TimeRange
): void {
this.preferenceService.set(
PageTimeRangePreferenceService.TIME_RANGE_PREFERENCE_KEY,
{ ...currentTimeRangeDictionary, [rootLevelPath]: timeRange.toUrlString() },
PageTimeRangePreferenceService.STORAGE_TYPE
);
}

private buildPageTimeRangeObservable(): Observable<PageTimeRangeStringDictionary> {
return this.preferenceService
.get<PageTimeRangeStringDictionary>(
PageTimeRangePreferenceService.TIME_RANGE_PREFERENCE_KEY,
{},
PageTimeRangePreferenceService.STORAGE_TYPE
)
.pipe(shareReplay(1));
}

private getDefaultPageTimeRange(): TimeRange | undefined {
return this.navigationService.getCurrentActivatedRoute().snapshot.data?.defaultTimeRange;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just trying to understand how this works - if I click on A on the left nav, and it routes be to A/B, we need the time range to be defined on B right? But in the dictionary, if I changed it, it would be stored under A? If so, I think that's confusing. If we think A is the important bit there, then the time range being defined there would make sense too, so if I change the default routing it doesn't touch the time range. To do that, you might need to change this to walk up the tree looking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that is how it works. @anandtiwary and I briefly looked into traversing the routes to find the correct activated route but it's a little tricky. Because all the navItem time ranges are resolved on initial load, you can't use ActivatedRoute. That's why before i was using navigationService.getRouteConfig - however, this method does not traverse lazy loaded child routes, so we can't use it here either.

One solution would be to keep it as-is with the A/B functionality you described above, and to create a future work item out of this. Another option is to change the timing of when the navItems are decorated to being on click, but that might be a timely effort, requiring a rework in the nav-list component.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looked into traversing the routes to find the correct activated route but it's a little tricky

We do have this tree walk in a number of places today. For example the feature guard I believe starts from the activated route, and walks up the tree checking the config for each parent route.

Because all the navItem time ranges are resolved on initial load, you can't use ActivatedRoute. That's why before i was using navigationService.getRouteConfig

Not sure why that would differ though - the activated route just gives you another way to get to the route config - either way it's resolved at load time, which is a very good point. We need to rebuild relative time ranges at the point in time where they take effect (will take a look back at the PR with that in mind).

One solution would be to keep it as-is with the A/B functionality you described above, and to create a future work item out of this. Another option is to change the timing of when the navItems are decorated to being on click, but that might be a timely effort, requiring a rework in the nav-list component.

Don't think the latter is either worth it or would work. I think I would go for putting the time range on the root route's config, and changing the lookup to walk up the tree like we do in FF, but up to you if you feel it makes sense to do that now or later.

}
}

interface PageTimeRangeStringDictionary {
[path: string]: string;
}
20 changes: 14 additions & 6 deletions projects/common/src/time/time-range.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { isEmpty } from 'lodash-es';
import { isEmpty, isNil } from 'lodash-es';
import { EMPTY, ReplaySubject } from 'rxjs';
import { catchError, defaultIfEmpty, filter, map, switchMap, take } from 'rxjs/operators';
import { catchError, 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';
Expand All @@ -17,7 +17,6 @@ import { TimeUnit } from './time-unit.type';
export class TimeRangeService {
private static readonly TIME_RANGE_QUERY_PARAM: string = 'time';

private readonly defaultTimeRange: TimeRange = new RelativeTimeRange(new TimeDuration(1, TimeUnit.Hour));
private readonly timeRangeSubject$: ReplaySubject<TimeRange> = new ReplaySubject(1);
private currentTimeRange?: TimeRange;

Expand Down Expand Up @@ -72,15 +71,14 @@ export class TimeRangeService {
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),
defaultIfEmpty(this.defaultTimeRange)
catchError(() => EMPTY)
)
.subscribe(timeRange => {
this.setTimeRange(timeRange);
});
}

private timeRangeFromUrlString(timeRangeFromUrl: string): TimeRange {
public timeRangeFromUrlString(timeRangeFromUrl: string): TimeRange {
const duration = this.timeDurationService.durationFromString(timeRangeFromUrl);
if (duration) {
return new RelativeTimeRange(duration);
Expand All @@ -103,6 +101,12 @@ export class TimeRangeService {
return this;
}

public setDefaultTimeRange(timeRange: TimeRange): void {
if (!this.currentTimeRange) {
this.setTimeRange(timeRange);
}
}

public static toRelativeTimeRange(value: number, unit: TimeUnit): RelativeTimeRange {
return new RelativeTimeRange(new TimeDuration(value, unit));
}
Expand All @@ -118,4 +122,8 @@ export class TimeRangeService {
[TimeRangeService.TIME_RANGE_QUERY_PARAM]: newTimeRange.toUrlString()
};
}

public isInitialized(): boolean {
return !isNil(this.currentTimeRange);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { ChangeDetectionStrategy, Component, Inject, Input } from '@angular/core';
import { GLOBAL_HEADER_HEIGHT, NavigationService } from '@hypertrace/common';
import {
ApplicationFeature,
FeatureState,
FeatureStateResolver,
GLOBAL_HEADER_HEIGHT,
NavigationService
} from '@hypertrace/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Component({
selector: 'ht-application-header',
Expand All @@ -15,9 +23,11 @@ import { GLOBAL_HEADER_HEIGHT, NavigationService } from '@hypertrace/common';
<div class="left-side-content">
<ng-content select="[left]"></ng-content>
</div>
<div class="time-range" *ngIf="this.showTimeRange">
<ht-time-range></ht-time-range>
</div>
<ng-container *ngIf="this.showTimeRange">
<div class="time-range" *ngIf="this.pageLevelTimeRangeDisabled$ | async">
<ht-time-range></ht-time-range>
</div>
</ng-container>
</div>
<div class="right-side-content">
<ng-content></ng-content>
Expand All @@ -30,10 +40,17 @@ export class ApplicationHeaderComponent {
@Input()
public showTimeRange: boolean = true;

public pageLevelTimeRangeDisabled$: Observable<boolean>;

public constructor(
@Inject(GLOBAL_HEADER_HEIGHT) public readonly height: string,
private readonly navigationService: NavigationService
) {}
private readonly navigationService: NavigationService,
private readonly featureStateResolver: FeatureStateResolver
) {
this.pageLevelTimeRangeDisabled$ = this.featureStateResolver
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inverted FF logic here so that two time ranges are not shown on screen at once. Time range is hidden here when FF is Enabled and shown when disabled.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should move the invertFF logic to ht. @aaron-steinfeld

.getFeatureState(ApplicationFeature.PageTimeRange)
.pipe(map(featureState => featureState === FeatureState.Disabled));
}

public onLogoClick(): void {
this.navigationService.navigateWithinApp(['']); // Empty route so we go to default screen
Expand Down
23 changes: 19 additions & 4 deletions projects/components/src/header/page/page-header.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
margin: 24px 24px 0;
display: flex;
flex-direction: column;

.column-alignment {
display: flex;
flex-direction: column;
Expand All @@ -16,12 +15,14 @@
justify-content: space-between;
}

&.bottom-border {
padding-bottom: 16px;
border-bottom: 1px solid $color-border;
.primary-row {
display: flex;
justify-content: flex-end;
width: 100%;
}

.breadcrumb-container {
margin-right: auto;
align-items: center;

.breadcrumb-separator {
Expand All @@ -43,6 +44,20 @@
}
}

.time-range {
margin-left: 10px;
}

.row-alignment {
display: flex;
justify-content: space-between;
}

&.bottom-border {
padding-bottom: 16px;
border-bottom: 1px solid $color-border;
}

.tabs {
padding-top: 16px;
}
Expand Down
Loading