Skip to content

Commit bd4d0b3

Browse files
authored
feat: adding sheet ref (#536)
* feat: adding sheet ref * refactor: adding tests
1 parent 7ad445c commit bd4d0b3

File tree

7 files changed

+137
-59
lines changed

7 files changed

+137
-59
lines changed
Lines changed: 69 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,84 @@
1-
import { Component } from '@angular/core';
2-
import { fakeAsync, flush, tick } from '@angular/core/testing';
3-
import { NavigationService } from '@hypertrace/common';
4-
import { recordObservable, runFakeRxjs } from '@hypertrace/test-utils';
5-
import { createServiceFactory, mockProvider, SpectatorService } from '@ngneat/spectator/jest';
6-
import { Subject } from 'rxjs';
7-
import { PopoverModule } from '../popover/popover.module';
8-
import { OverlayService } from './overlay.service';
9-
import { SheetSize } from './sheet/sheet';
1+
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
2+
import { fakeAsync, flush } from '@angular/core/testing';
3+
import { IconLibraryTestingModule } from '@hypertrace/assets-library';
4+
import { GLOBAL_HEADER_HEIGHT, NavigationService } from '@hypertrace/common';
5+
import { OverlayService, SheetRef, SheetSize } from '@hypertrace/components';
6+
import { createHostFactory, mockProvider } from '@ngneat/spectator/jest';
7+
import { EMPTY } from 'rxjs';
8+
import { OverlayModule } from './overlay.module';
9+
import { SHEET_DATA } from './sheet/sheet';
1010

1111
describe('Overlay service', () => {
12-
const navigation$: Subject<void> = new Subject<void>();
12+
@Component({
13+
changeDetection: ChangeDetectionStrategy.OnPush,
14+
template: `
15+
<div class="test-sheet-content">Test Component Content Data: {{ this.data }}</div>
16+
<button class="test-close-button" (click)="this.onClose()">Close</button>
17+
`
18+
})
19+
class TestComponent {
20+
public constructor(@Inject(SHEET_DATA) public readonly data: string, public readonly sheetRef: SheetRef) {}
1321

14-
let spectator: SpectatorService<OverlayService>;
22+
public onClose(): void {
23+
this.sheetRef.close(this.data);
24+
}
25+
}
1526

16-
const createService = createServiceFactory({
17-
service: OverlayService,
18-
imports: [PopoverModule],
27+
const createHost = createHostFactory({
28+
component: Component({ selector: 'host', template: 'template' })(class {}),
29+
declarations: [TestComponent],
30+
entryComponents: [TestComponent],
31+
imports: [OverlayModule, IconLibraryTestingModule],
1932
providers: [
2033
mockProvider(NavigationService, {
21-
navigation$: navigation$
22-
})
23-
]
34+
navigation$: EMPTY
35+
}),
36+
{
37+
provide: GLOBAL_HEADER_HEIGHT,
38+
useValue: 100
39+
}
40+
],
41+
template: `<host></host>`
2442
});
2543

26-
beforeEach(() => {
27-
spectator = createService();
28-
});
29-
30-
test('can close a sheet popover on navigation', fakeAsync(() => {
31-
const popover = spectator.service.createSheet({
32-
showHeader: false,
33-
size: SheetSize.Medium,
34-
content: Component({
35-
selector: 'test-component',
36-
template: `<div>TEST</div>`
37-
})(class {})
44+
test('can create a sheet with provided data', fakeAsync(() => {
45+
const spectator = createHost();
46+
spectator.inject(OverlayService).createSheet({
47+
content: TestComponent,
48+
size: SheetSize.Small,
49+
title: 'Test title',
50+
showHeader: true,
51+
data: 'custom input'
3852
});
39-
popover.show();
40-
popover.closeOnNavigation();
41-
tick(); // CDK overlay is async
4253

43-
expect(popover.closed).toBe(false);
54+
spectator.tick();
55+
56+
expect(spectator.query('.test-sheet-content', { root: true })).toContainText(
57+
'Test Component Content Data: custom input'
58+
);
59+
}));
4460

45-
runFakeRxjs(({ expectObservable }) => {
46-
expectObservable(popover.closed$).toBe('(x|)', { x: undefined });
47-
expectObservable(recordObservable(popover.hidden$)).toBe('|'); // Record hidden/shown for test, since they're hot
48-
expectObservable(recordObservable(popover.shown$)).toBe('|');
49-
navigation$.next();
61+
test('sheet can be closed and return a result', fakeAsync(() => {
62+
const spectator = createHost();
63+
const sheet: SheetRef<string> = spectator.inject(OverlayService).createSheet({
64+
content: TestComponent,
65+
size: SheetSize.Small,
66+
data: 'custom input'
5067
});
68+
let result: string | undefined;
69+
spectator.tick();
70+
const subscription = sheet.closed$.subscribe(out => (result = out));
71+
const closeButton = spectator.query('.test-close-button', { root: true })!;
72+
expect(result).toBeUndefined();
73+
expect(subscription.closed).toBe(false);
74+
75+
spectator.click(closeButton);
76+
spectator.tick();
77+
78+
expect(spectator.query('.test-sheet-content', { root: true })).not.toExist();
79+
expect(result).toBe('custom input');
80+
expect(subscription.closed).toBe(true);
5181

52-
expect(popover.closed).toBe(true);
53-
flush(); // CDK cleans up overlay async
82+
flush(); // CDK timer to remove overlay
5483
}));
5584
});

projects/components/src/overlay/overlay.service.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { Subscription } from 'rxjs';
33
import { PopoverFixedPositionLocation, PopoverPositionType } from '../popover/popover';
44
import { PopoverRef } from '../popover/popover-ref';
55
import { PopoverService } from '../popover/popover.service';
6-
import { SheetOverlayConfig, SHEET_DATA } from './sheet/sheet';
6+
import { DefaultSheetRef } from './sheet/default-sheet-ref';
7+
import { SheetOverlayConfig, SheetRef, SHEET_DATA } from './sheet/sheet';
78
import { SheetOverlayComponent } from './sheet/sheet-overlay.component';
89

910
@Injectable({
@@ -16,10 +17,14 @@ export class OverlayService {
1617

1718
public constructor(private readonly popoverService: PopoverService, private readonly defaultInjector: Injector) {}
1819

19-
public createSheet(config: SheetOverlayConfig, injector: Injector = this.defaultInjector): PopoverRef {
20+
public createSheet<TData = unknown, TResponse = unknown>(
21+
config: SheetOverlayConfig<TData>,
22+
injector: Injector = this.defaultInjector
23+
): SheetRef<TResponse> {
2024
this.activeSheetPopover?.close();
2125

22-
const metadata = this.buildMetadata(config, injector);
26+
const sheetRef = new DefaultSheetRef();
27+
const metadata = this.buildMetadata(config, injector, sheetRef);
2328
const popover = this.popoverService.drawPopover({
2429
componentOrTemplate: SheetOverlayComponent,
2530
parentInjector: injector,
@@ -31,20 +36,29 @@ export class OverlayService {
3136
});
3237

3338
popover.closeOnNavigation();
39+
sheetRef.initialize(popover);
3440

3541
this.setActiveSheetPopover(popover);
3642

37-
return popover;
43+
return sheetRef as SheetRef<TResponse>;
3844
}
3945

40-
private buildMetadata(config: SheetOverlayConfig, parentInjector: Injector): SheetConstructionData {
46+
private buildMetadata(
47+
config: SheetOverlayConfig,
48+
parentInjector: Injector,
49+
sheetRef: SheetRef
50+
): SheetConstructionData {
4151
return {
4252
config: config,
4353
injector: Injector.create({
4454
providers: [
4555
{
4656
provide: SHEET_DATA,
4757
useValue: config.data
58+
},
59+
{
60+
provide: SheetRef,
61+
useValue: sheetRef
4862
}
4963
],
5064
parent: parentInjector
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Observable, Observer, ReplaySubject } from 'rxjs';
2+
import { PopoverRef } from '../../popover/popover-ref';
3+
import { SheetRef } from './sheet';
4+
5+
export class DefaultSheetRef extends SheetRef {
6+
public readonly closed$: Observable<unknown>;
7+
8+
private readonly closedObserver: Observer<unknown>;
9+
private popoverRef?: PopoverRef;
10+
11+
public constructor() {
12+
super();
13+
const closedSubject = new ReplaySubject<unknown>(1);
14+
this.closedObserver = closedSubject;
15+
this.closed$ = closedSubject.asObservable();
16+
}
17+
18+
public initialize(popoverRef: PopoverRef): void {
19+
this.popoverRef = popoverRef;
20+
this.popoverRef.closed$.subscribe({
21+
complete: () => this.closedObserver.complete(),
22+
error: err => this.closedObserver.error(err)
23+
});
24+
}
25+
26+
public close(result?: unknown): void {
27+
if (result !== undefined) {
28+
this.closedObserver.next(result);
29+
}
30+
this.popoverRef?.close();
31+
}
32+
}

projects/components/src/overlay/sheet/sheet.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { InjectionToken } from '@angular/core';
2+
import { Observable } from 'rxjs';
23
import { OverlayConfig } from './../overlay';
34

45
export interface SheetOverlayConfig<TData = unknown> extends OverlayConfig {
@@ -15,3 +16,8 @@ export const enum SheetSize {
1516
}
1617

1718
export const SHEET_DATA = new InjectionToken<unknown>('SHEET_DATA');
19+
20+
export abstract class SheetRef<TResult = unknown> {
21+
public abstract readonly closed$: Observable<TResult>;
22+
public abstract close(result?: TResult): void;
23+
}

projects/distributed-tracing/src/shared/dashboard/interaction/detail-sheet/detail-sheet-interaction-handler.model.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { PopoverRef, SheetSize } from '@hypertrace/components';
1+
import { SheetRef, SheetSize } from '@hypertrace/components';
22
import { EnumPropertyTypeInstance, ENUM_TYPE, ModelTemplatePropertyType } from '@hypertrace/dashboards';
33
import { BOOLEAN_PROPERTY, Model, ModelApi, ModelJson, ModelProperty, STRING_PROPERTY } from '@hypertrace/hyperdash';
44
import { ModelInject, MODEL_API } from '@hypertrace/hyperdash-angular';
@@ -53,7 +53,7 @@ export class DetailSheetInteractionHandlerModel implements InteractionHandler {
5353
@ModelInject(DetailSheetInteractionHandlerService)
5454
private readonly handlerService!: DetailSheetInteractionHandlerService;
5555

56-
private popover?: PopoverRef;
56+
private sheet?: SheetRef;
5757

5858
public execute(data?: unknown): Observable<void> {
5959
if (isEmpty(data)) {
@@ -81,7 +81,7 @@ export class DetailSheetInteractionHandlerModel implements InteractionHandler {
8181
const title = get(source, this.titlePropertyPath ?? '');
8282
const model = this.getDetailModel(source);
8383

84-
this.popover = this.handlerService.showSheet(model, this.sheetSize, title, this.showHeader);
84+
this.sheet = this.handlerService.showSheet(model, this.sheetSize, title, this.showHeader);
8585
}
8686

8787
private getDetailModel(source: unknown): object {
@@ -92,6 +92,6 @@ export class DetailSheetInteractionHandlerModel implements InteractionHandler {
9292
}
9393

9494
private clear(): void {
95-
this.popover?.close();
95+
this.sheet?.close();
9696
}
9797
}

projects/distributed-tracing/src/shared/dashboard/interaction/detail-sheet/detail-sheet-interaction-handler.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Injectable } from '@angular/core';
2-
import { OverlayService, PopoverRef, SheetSize } from '@hypertrace/components';
2+
import { OverlayService, SheetRef, SheetSize } from '@hypertrace/components';
33
import { DetailSheetInteractionContainerComponent } from './container/detail-sheet-interaction-container.component';
44

55
@Injectable({
@@ -13,7 +13,7 @@ export class DetailSheetInteractionHandlerService {
1313
sheetSize: SheetSize = SheetSize.Medium,
1414
title?: string,
1515
showHeader: boolean = true
16-
): PopoverRef {
16+
): SheetRef {
1717
return this.overlayService.createSheet({
1818
content: DetailSheetInteractionContainerComponent,
1919
size: sheetSize,

projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall-widget-renderer.component.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
ViewChild
99
} from '@angular/core';
1010
import { IconType } from '@hypertrace/assets-library';
11-
import { ButtonStyle, OverlayService, PopoverRef, SheetSize } from '@hypertrace/components';
11+
import { ButtonStyle, OverlayService, SheetRef, SheetSize } from '@hypertrace/components';
1212
import { WidgetRenderer } from '@hypertrace/dashboards';
1313
import { Renderer } from '@hypertrace/hyperdash';
1414
import { RendererApi, RENDERER_API } from '@hypertrace/hyperdash-angular';
@@ -83,7 +83,7 @@ export class WaterfallWidgetRendererComponent
8383
@ViewChild('sidebarDetails', { static: true })
8484
public sidebarDetails!: TemplateRef<unknown>;
8585

86-
private popoverRef?: PopoverRef;
86+
private sheet?: SheetRef;
8787
public selectedData?: WaterfallData;
8888

8989
public constructor(
@@ -111,21 +111,18 @@ export class WaterfallWidgetRendererComponent
111111
}
112112

113113
private openSheet(selected: WaterfallData): void {
114-
if (this.popoverRef !== undefined) {
115-
this.popoverRef.close();
116-
}
117-
114+
this.sheet?.close();
118115
this.selectedData = selected;
119116

120-
this.popoverRef = this.overlayService.createSheet({
117+
this.sheet = this.overlayService.createSheet({
121118
showHeader: false,
122119
size: SheetSize.ResponsiveExtraLarge,
123120
content: this.sidebarDetails
124121
});
125122
}
126123

127124
public closeSheet(): void {
128-
this.popoverRef?.close();
125+
this.sheet?.close();
129126
this.selectedData = undefined;
130127
this.changeDetector.markForCheck(); // Need this for child table to remove selected-row class
131128
}

0 commit comments

Comments
 (0)