Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
109 changes: 69 additions & 40 deletions projects/components/src/overlay/overlay.service.test.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,84 @@
import { Component } from '@angular/core';
import { fakeAsync, flush, tick } from '@angular/core/testing';
import { NavigationService } from '@hypertrace/common';
import { recordObservable, runFakeRxjs } from '@hypertrace/test-utils';
import { createServiceFactory, mockProvider, SpectatorService } from '@ngneat/spectator/jest';
import { Subject } from 'rxjs';
import { PopoverModule } from '../popover/popover.module';
import { OverlayService } from './overlay.service';
import { SheetSize } from './sheet/sheet';
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import { fakeAsync, flush } from '@angular/core/testing';
import { IconLibraryTestingModule } from '@hypertrace/assets-library';
import { GLOBAL_HEADER_HEIGHT, NavigationService } from '@hypertrace/common';
import { OverlayService, SheetRef, SheetSize } from '@hypertrace/components';
import { createHostFactory, mockProvider } from '@ngneat/spectator/jest';
import { EMPTY } from 'rxjs';
import { OverlayModule } from './overlay.module';
import { SHEET_DATA } from './sheet/sheet';

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

let spectator: SpectatorService<OverlayService>;
public onClose(): void {
this.sheetRef.close(this.data);
}
}

const createService = createServiceFactory({
service: OverlayService,
imports: [PopoverModule],
const createHost = createHostFactory({
component: Component({ selector: 'host', template: 'template' })(class {}),
declarations: [TestComponent],
entryComponents: [TestComponent],
imports: [OverlayModule, IconLibraryTestingModule],
providers: [
mockProvider(NavigationService, {
navigation$: navigation$
})
]
navigation$: EMPTY
}),
{
provide: GLOBAL_HEADER_HEIGHT,
useValue: 100
}
],
template: `<host></host>`
});

beforeEach(() => {
spectator = createService();
});

test('can close a sheet popover on navigation', fakeAsync(() => {
const popover = spectator.service.createSheet({
showHeader: false,
size: SheetSize.Medium,
content: Component({
selector: 'test-component',
template: `<div>TEST</div>`
})(class {})
test('can create a sheet with provided data', fakeAsync(() => {
const spectator = createHost();
spectator.inject(OverlayService).createSheet({
content: TestComponent,
size: SheetSize.Small,
title: 'Test title',
showHeader: true,
data: 'custom input'
});
popover.show();
popover.closeOnNavigation();
tick(); // CDK overlay is async

expect(popover.closed).toBe(false);
spectator.tick();

expect(spectator.query('.test-sheet-content', { root: true })).toContainText(
'Test Component Content Data: custom input'
);
}));

runFakeRxjs(({ expectObservable }) => {
expectObservable(popover.closed$).toBe('(x|)', { x: undefined });
expectObservable(recordObservable(popover.hidden$)).toBe('|'); // Record hidden/shown for test, since they're hot
expectObservable(recordObservable(popover.shown$)).toBe('|');
navigation$.next();
test('sheet can be closed and return a result', fakeAsync(() => {
const spectator = createHost();
const sheet: SheetRef<string> = spectator.inject(OverlayService).createSheet({
content: TestComponent,
size: SheetSize.Small,
data: 'custom input'
});
let result: string | undefined;
spectator.tick();
const subscription = sheet.closed$.subscribe(out => (result = out));
const closeButton = spectator.query('.test-close-button', { root: true })!;
expect(result).toBeUndefined();
expect(subscription.closed).toBe(false);

spectator.click(closeButton);
spectator.tick();

expect(spectator.query('.test-sheet-content', { root: true })).not.toExist();
expect(result).toBe('custom input');
expect(subscription.closed).toBe(true);

expect(popover.closed).toBe(true);
flush(); // CDK cleans up overlay async
flush(); // CDK timer to remove overlay
}));
});
24 changes: 19 additions & 5 deletions projects/components/src/overlay/overlay.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { Subscription } from 'rxjs';
import { PopoverFixedPositionLocation, PopoverPositionType } from '../popover/popover';
import { PopoverRef } from '../popover/popover-ref';
import { PopoverService } from '../popover/popover.service';
import { SheetOverlayConfig, SHEET_DATA } from './sheet/sheet';
import { DefaultSheetRef } from './sheet/default-sheet-ref';
import { SheetOverlayConfig, SheetRef, SHEET_DATA } from './sheet/sheet';
import { SheetOverlayComponent } from './sheet/sheet-overlay.component';

@Injectable({
Expand All @@ -16,10 +17,14 @@ export class OverlayService {

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

public createSheet(config: SheetOverlayConfig, injector: Injector = this.defaultInjector): PopoverRef {
public createSheet<TData = unknown, TResponse = unknown>(
config: SheetOverlayConfig<TData>,
injector: Injector = this.defaultInjector
): SheetRef<TResponse> {
this.activeSheetPopover?.close();

const metadata = this.buildMetadata(config, injector);
const sheetRef = new DefaultSheetRef();
const metadata = this.buildMetadata(config, injector, sheetRef);
const popover = this.popoverService.drawPopover({
componentOrTemplate: SheetOverlayComponent,
parentInjector: injector,
Expand All @@ -31,20 +36,29 @@ export class OverlayService {
});

popover.closeOnNavigation();
sheetRef.initialize(popover);

this.setActiveSheetPopover(popover);

return popover;
return sheetRef as SheetRef<TResponse>;
}

private buildMetadata(config: SheetOverlayConfig, parentInjector: Injector): SheetConstructionData {
private buildMetadata(
config: SheetOverlayConfig,
parentInjector: Injector,
sheetRef: SheetRef
): SheetConstructionData {
return {
config: config,
injector: Injector.create({
providers: [
{
provide: SHEET_DATA,
useValue: config.data
},
{
provide: SheetRef,
useValue: sheetRef
}
],
parent: parentInjector
Expand Down
32 changes: 32 additions & 0 deletions projects/components/src/overlay/sheet/default-sheet-ref.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Observable, Observer, ReplaySubject } from 'rxjs';
import { PopoverRef } from '../../popover/popover-ref';
import { SheetRef } from './sheet';

export class DefaultSheetRef extends SheetRef {
public readonly closed$: Observable<unknown>;

private readonly closedObserver: Observer<unknown>;
private popoverRef?: PopoverRef;

public constructor() {
super();
const closedSubject = new ReplaySubject<unknown>(1);
this.closedObserver = closedSubject;
this.closed$ = closedSubject.asObservable();
}

public initialize(popoverRef: PopoverRef): void {
this.popoverRef = popoverRef;
this.popoverRef.closed$.subscribe({
complete: () => this.closedObserver.complete(),
error: err => this.closedObserver.error(err)
});
}

public close(result?: unknown): void {
if (result !== undefined) {
this.closedObserver.next(result);
}
this.popoverRef?.close();
}
}
6 changes: 6 additions & 0 deletions projects/components/src/overlay/sheet/sheet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { InjectionToken } from '@angular/core';
import { Observable } from 'rxjs';
import { OverlayConfig } from './../overlay';

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

export const SHEET_DATA = new InjectionToken<unknown>('SHEET_DATA');

export abstract class SheetRef<TResult = unknown> {
public abstract readonly closed$: Observable<TResult>;
public abstract close(result?: TResult): void;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PopoverRef, SheetSize } from '@hypertrace/components';
import { SheetRef, SheetSize } from '@hypertrace/components';
import { EnumPropertyTypeInstance, ENUM_TYPE, ModelTemplatePropertyType } from '@hypertrace/dashboards';
import { BOOLEAN_PROPERTY, Model, ModelApi, ModelJson, ModelProperty, STRING_PROPERTY } from '@hypertrace/hyperdash';
import { ModelInject, MODEL_API } from '@hypertrace/hyperdash-angular';
Expand Down Expand Up @@ -53,7 +53,7 @@ export class DetailSheetInteractionHandlerModel implements InteractionHandler {
@ModelInject(DetailSheetInteractionHandlerService)
private readonly handlerService!: DetailSheetInteractionHandlerService;

private popover?: PopoverRef;
private sheet?: SheetRef;

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

this.popover = this.handlerService.showSheet(model, this.sheetSize, title, this.showHeader);
this.sheet = this.handlerService.showSheet(model, this.sheetSize, title, this.showHeader);
}

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

private clear(): void {
this.popover?.close();
this.sheet?.close();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { OverlayService, PopoverRef, SheetSize } from '@hypertrace/components';
import { OverlayService, SheetRef, SheetSize } from '@hypertrace/components';
import { DetailSheetInteractionContainerComponent } from './container/detail-sheet-interaction-container.component';

@Injectable({
Expand All @@ -13,7 +13,7 @@ export class DetailSheetInteractionHandlerService {
sheetSize: SheetSize = SheetSize.Medium,
title?: string,
showHeader: boolean = true
): PopoverRef {
): SheetRef {
return this.overlayService.createSheet({
content: DetailSheetInteractionContainerComponent,
size: sheetSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
ViewChild
} from '@angular/core';
import { IconType } from '@hypertrace/assets-library';
import { ButtonStyle, OverlayService, PopoverRef, SheetSize } from '@hypertrace/components';
import { ButtonStyle, OverlayService, SheetRef, SheetSize } from '@hypertrace/components';
import { WidgetRenderer } from '@hypertrace/dashboards';
import { Renderer } from '@hypertrace/hyperdash';
import { RendererApi, RENDERER_API } from '@hypertrace/hyperdash-angular';
Expand Down Expand Up @@ -83,7 +83,7 @@ export class WaterfallWidgetRendererComponent
@ViewChild('sidebarDetails', { static: true })
public sidebarDetails!: TemplateRef<unknown>;

private popoverRef?: PopoverRef;
private sheet?: SheetRef;
public selectedData?: WaterfallData;

public constructor(
Expand Down Expand Up @@ -111,21 +111,18 @@ export class WaterfallWidgetRendererComponent
}

private openSheet(selected: WaterfallData): void {
if (this.popoverRef !== undefined) {
this.popoverRef.close();
}

this.sheet?.close();
this.selectedData = selected;

this.popoverRef = this.overlayService.createSheet({
this.sheet = this.overlayService.createSheet({
showHeader: false,
size: SheetSize.ResponsiveExtraLarge,
content: this.sidebarDetails
});
}

public closeSheet(): void {
this.popoverRef?.close();
this.sheet?.close();
this.selectedData = undefined;
this.changeDetector.markForCheck(); // Need this for child table to remove selected-row class
}
Expand Down