Skip to content

fix: adding layout trigger to titled container #352

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 17, 2020
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
12 changes: 7 additions & 5 deletions projects/common/src/layout/layout-change.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ export class LayoutChangeService implements OnDestroy {
@SkipSelf() @Optional() private readonly parentLayoutChange?: LayoutChangeService
) {
this.boundingElement = hostElementRef.nativeElement;
// Get layout changes from our parent LayoutChangeService, or if it's root, collect from window
const parentChange$ = this.parentLayoutChange
? this.parentLayoutChange.layoutChangeSubject
: this.getWindowResizeEvents();

// Filter out layout changes from our parent if we didn't change size
parentChange$.pipe(filter(event => this.hasLayoutChanged(event))).subscribe(this.layoutChangeSubject);
(this.parentLayoutChange?.getLayoutChangeEventObservable() ?? this.getWindowResizeEvents())
.pipe(filter(event => this.hasLayoutChanged(event)))
.subscribe(this.layoutChangeSubject);
}

public getLayoutChangeEventObservable(): Observable<LayoutChangeEvent> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed this for testing purposes. Was unable to create mock service without it

return this.layoutChangeSubject;
}

public publishLayoutChange(): void {
Expand Down
7 changes: 2 additions & 5 deletions projects/components/src/layout/layout-change.directive.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { AfterViewInit, Directive, EventEmitter, Output } from '@angular/core';
import { Directive, EventEmitter, Output } from '@angular/core';
import { LayoutChangeService, SubscriptionLifecycle } from '@hypertrace/common';

@Directive({
selector: '[htLayoutChange]',
providers: [SubscriptionLifecycle, LayoutChangeService]
})
export class LayoutChangeDirective implements AfterViewInit {
export class LayoutChangeDirective {
@Output('htLayoutChange')
public readonly changeEmitter: EventEmitter<void> = new EventEmitter();

public constructor(private readonly layoutChange: LayoutChangeService, subscriptionLifecycle: SubscriptionLifecycle) {
subscriptionLifecycle.add(layoutChange.layout$.subscribe(this.changeEmitter));
}

public ngAfterViewInit(): void {
this.layoutChange.initialize();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@aaron-steinfeld This is the fix for the layout change issue. Since we initialize/register the bounding element after view init, during this time the bounding element already has the new width/height. When layout trigger emits a new dom change, such events get filtered out as it doesn't see any change in dimension.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm I think I understand, but if everything is sized correctly after view init, do we even need the layout change? We probably shouldn't do our first measurement in any rendering code until after view init.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not sized correctly. Here is what happens:

  1. Gauge component gets initialized.
  2. At this time it gets 200px height
  3. htlayoutChange directive gets initialized but the host element is not registered.
  4. On first input change, Gauge component renders the widget with 200px height
  5. Titled content components view changes and it reduces the height of gauge from 200 to 170px
  6. htlayoutChange now registers host element with layout service
  7. Gauge is now registered in layout service with 170px height
  8. htLayoutChangeTrigger triggers a layout change. This publish gets filtered out when it compares the current height of gauge with its registered height.
  9. Nothings gets emitted.

Copy link
Contributor

Choose a reason for hiding this comment

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

So if we move the first render in gauge afterViewInit would that fix the issue? And would it make sense? I'm thinking yes since it's sizing sensitive, it also prevents the double render.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have seen a delay when i tried this last week. But shouldn't we fix this from the layout directive itself as it is likely to affect other places as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we try it in onInit instead of the constructor? That would have the same affect, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

lemme try

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Didn't work.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
width: 100%;

.header {
max-height: 28px;
display: flex;
flex-direction: row;
align-items: center;
Expand All @@ -31,7 +32,7 @@
}

.content {
flex: 1 1;
flex: 1 1 auto;
overflow: hidden;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { fakeAsync } from '@angular/core/testing';
import { NavigationService } from '@hypertrace/common';
import { LayoutChangeService, NavigationService } from '@hypertrace/common';
import { createHostFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
import { MockDirective } from 'ng-mocks';
import { LayoutChangeTriggerDirective } from '../layout/layout-change-trigger.directive';
import { TitledContentComponent } from './titled-content.component';
import { TitledContentModule } from './titled-content.module';

Expand All @@ -11,6 +13,8 @@ describe('Titled content component', () => {
declareComponent: false,
component: TitledContentComponent,
imports: [TitledContentModule],
declarations: [MockDirective(LayoutChangeTriggerDirective)],
componentProviders: [LayoutChangeService],
providers: [mockProvider(NavigationService)]
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { TitledHeaderControlDirective } from './header-controls/titled-header-co
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="titled-content-container">
<div class="header">
<div class="header" [htLayoutChangeTrigger]="this.shouldShowHeader">
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using this trigger here fails a lot of test. We now have to provide layout change service almost everywhere. Mocking the directive didn't help. Was wondering if we could avoid it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd guess mostly dashboards - other tests shouldn't be using the real components. I was going to suggest adding it to mockDashboardProviders` but it looks like that was already done - did that help? Not every dashboard test may be using that mock set.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It didn't. It somehow still asking for LayoutService. Need to look more

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a new mockDashboardWidgetProviders.

<ht-label *ngIf="this.shouldShowTitleInHeader" [label]="this.title" class="title"></ht-label>
<ht-link [paramsOrUrl]="this.link" class="link" *ngIf="this.link">
<ht-button
Expand Down Expand Up @@ -51,6 +51,10 @@ export class TitledContentComponent {
@Input()
public linkLabel?: string;

public get shouldShowHeader(): boolean {
return this.shouldShowTitleInHeader || this.headerControl !== undefined;
}

private get shouldShowTitle(): boolean {
return !isEmpty(this.title) && !this.hideTitle;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ButtonModule } from '../button/button.module';
import { LabelModule } from '../label/label.module';
import { LayoutChangeModule } from '../layout/layout-change.module';
import { LinkModule } from '../link/link.module';
import { TitledHeaderControlDirective } from './header-controls/titled-header-control.directive';
import { TitledContentComponent } from './titled-content.component';

@NgModule({
declarations: [TitledContentComponent, TitledHeaderControlDirective],
exports: [TitledContentComponent, TitledHeaderControlDirective],
imports: [LabelModule, CommonModule, LinkModule, ButtonModule]
imports: [LabelModule, CommonModule, LinkModule, ButtonModule, LayoutChangeModule]
})
export class TitledContentModule {}
35 changes: 34 additions & 1 deletion projects/dashboards/src/test/dashboard-verification.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { StaticProvider } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import {
ColorService,
LayoutChangeService,
NavigationService,
RelativeTimeRange,
TimeDuration,
Expand All @@ -10,7 +12,7 @@ import {
import { MetadataService } from '@hypertrace/distributed-tracing';
import { GraphQlRequestService } from '@hypertrace/graphql-client';
import { ModelJson } from '@hypertrace/hyperdash';
import { DashboardManagerService, LoggerService } from '@hypertrace/hyperdash-angular';
import { DashboardManagerService, LoggerService, RENDERER_API } from '@hypertrace/hyperdash-angular';
import { getMockFlexLayoutProviders } from '@hypertrace/test-utils';
import { mockProvider, Spectator } from '@ngneat/spectator/jest';
import { EMPTY, of } from 'rxjs';
Expand All @@ -32,6 +34,9 @@ export const isValidModelJson = (
export const mockDashboardProviders = [
mockProvider(GraphQlRequestService),
mockProvider(ColorService),
mockProvider(LayoutChangeService, {
layout$: of()
}),
mockProvider(TimeRangeService, {
getTimeRangeAndChanges: () => EMPTY,
getCurrentTimeRange: jest.fn().mockReturnValue(new RelativeTimeRange(new TimeDuration(15, TimeUnit.Minute)))
Expand All @@ -53,3 +58,31 @@ export const mockDashboardProviders = [
}),
...getMockFlexLayoutProviders()
];

export const rendererApiFactoryBuilder = <TModel extends object>(model: TModel) => () => ({
getTimeRange: jest.fn(),
model: model,
change$: EMPTY,
dataRefresh$: EMPTY,
timeRangeChanged$: EMPTY
});

export const mockDashboardWidgetProviders: <T extends object>(model: T) => StaticProvider[] = model => [
{
provide: RENDERER_API,
useFactory: rendererApiFactoryBuilder(model)
},
mockProvider(GraphQlRequestService, {
query: jest.fn(() => EMPTY)
}),
mockProvider(ColorService),
mockProvider(LayoutChangeService, {
getLayoutChangeEventObservable: jest.fn().mockReturnValue(of({})),
layout$: of()
}),
mockProvider(NavigationService, {
navigation$: EMPTY,
getAllValuesForQueryParameter: () => []
}),
...getMockFlexLayoutProviders()
];
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
import { RENDERER_API } from '@hypertrace/hyperdash-angular';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { EMPTY } from 'rxjs';
import { mockDashboardWidgetProviders } from '../../test/dashboard-verification';
import { DividerWidgetRendererComponent } from './divider-widget-renderer.component';

describe('Divider Widget Renderer Component', () => {
let spectator: Spectator<DividerWidgetRendererComponent>;

const createComponent = createComponentFactory<DividerWidgetRendererComponent>({
component: DividerWidgetRendererComponent,
providers: [
{
provide: RENDERER_API,
useFactory: () => ({
model: {},
getTimeRange: jest.fn(),
change$: EMPTY,
dataRefresh$: EMPTY,
timeRangeChanged$: EMPTY
})
}
],
providers: [...mockDashboardWidgetProviders({})],
shallow: true
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { GreetingLabelComponent } from '@hypertrace/components';
import { RENDERER_API } from '@hypertrace/hyperdash-angular';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { EMPTY } from 'rxjs';
import { mockDashboardWidgetProviders } from '../../test/dashboard-verification';
import { GreetingLabelWidgetRendererComponent } from './greeting-label-widget-renderer.component';
import { GreetingLabelWidgetModel } from './greeting-label-widget.model';

Expand All @@ -11,19 +10,7 @@ describe('Greeting label widget renderer component', () => {
const createComponent = createComponentFactory<GreetingLabelWidgetRendererComponent>({
component: GreetingLabelWidgetRendererComponent,
entryComponents: [GreetingLabelComponent],
providers: [
{
provide: RENDERER_API,
useFactory: () => ({
model: mockModel,
getDataFromModelDataSource: EMPTY,
getTimeRange: jest.fn(),
change$: EMPTY,
dataRefresh$: EMPTY,
timeRangeChanged$: EMPTY
})
}
]
providers: [...mockDashboardWidgetProviders(mockModel)]
});

beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { RENDERER_API } from '@hypertrace/hyperdash-angular';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { EMPTY } from 'rxjs';
import { mockDashboardWidgetProviders } from '../../test/dashboard-verification';
import { LinkWidgetRendererComponent } from './link-widget-renderer.component';
import { LinkWidgetModel } from './link-widget.model';

Expand All @@ -9,18 +8,6 @@ describe('Link widget renderer component', () => {
let mockModel: Partial<LinkWidgetModel> = {};
const createComponent = createComponentFactory<LinkWidgetRendererComponent>({
component: LinkWidgetRendererComponent,
providers: [
{
provide: RENDERER_API,
useFactory: () => ({
model: mockModel,
getTimeRange: jest.fn(),
change$: EMPTY,
dataRefresh$: EMPTY,
timeRangeChanged$: EMPTY
})
}
],
shallow: true
});

Expand All @@ -31,15 +18,19 @@ describe('Link widget renderer component', () => {
test('Link should be displayed as expected if url and displayText are defined', () => {
mockModel.url = '#';
mockModel.displayText = 'Test';
spectator = createComponent();
spectator = createComponent({
providers: [...mockDashboardWidgetProviders(mockModel)]
});

expect(spectator.query('ht-link')).toExist();
expect(spectator.component.getDisplayText()).toEqual('Test');
});

test('Link should use url as displayText if displayText is undefined', () => {
mockModel.url = '#';
spectator = createComponent();
spectator = createComponent({
providers: [...mockDashboardWidgetProviders(mockModel)]
});

expect(spectator.query('ht-link')).toExist();
expect(spectator.component.getDisplayText()).toEqual(mockModel.url);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,21 @@
import { LoadAsyncModule, OverlayService } from '@hypertrace/components';
import { RENDERER_API } from '@hypertrace/hyperdash-angular';
import { mockDashboardWidgetProviders } from '@hypertrace/dashboards/testing';
import { createComponentFactory, mockProvider } from '@ngneat/spectator/jest';
import { MockComponent } from 'ng-mocks';
import { EMPTY, of } from 'rxjs';
import { of } from 'rxjs';
import { WaterfallWidgetRendererComponent } from './waterfall-widget-renderer.component';
import { WaterfallChartComponent } from './waterfall/waterfall-chart.component';

describe('Waterfall widget renderer component', () => {
const mockModel = {
getData: jest.fn(() => of([{}]))
};

const createComponent = createComponentFactory<WaterfallWidgetRendererComponent>({
component: WaterfallWidgetRendererComponent,
shallow: true,
imports: [LoadAsyncModule],
providers: [
{
provide: RENDERER_API,
useValue: {
getTimeRange: jest.fn(),
model: {
getData: jest.fn(() => of([{}]))
},
change$: EMPTY,
dataRefresh$: EMPTY,
timeRangeChanged$: EMPTY
}
},
mockProvider(OverlayService)
],
providers: [...mockDashboardWidgetProviders(mockModel), mockProvider(OverlayService)],
declarations: [MockComponent(WaterfallChartComponent)]
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { AttributeMetadata, AttributeMetadataType, MetricAggregationType } from '@hypertrace/distributed-tracing';
import { GraphQlRequestService } from '@hypertrace/graphql-client';
import { createHostFactory, mockProvider } from '@ngneat/spectator/jest';
import { of } from 'rxjs';
import { EMPTY, of } from 'rxjs';
import { ObservabilityTraceType } from '../../graphql/model/schema/observability-traces';
import { ExploreQueryEditorComponent } from './explore-query-editor.component';
import { ExploreQueryEditorModule } from './explore-query-editor.module';
Expand Down Expand Up @@ -86,7 +86,9 @@ describe('Explore query editor', () => {
getAutoDurationFromTimeDurations: () => new TimeDuration(15, TimeUnit.Second),
getAutoDuration: () => new TimeDuration(15, TimeUnit.Second)
}),
mockProvider(NavigationService)
mockProvider(NavigationService, {
navigation$: EMPTY
})
],
declareComponent: false
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { NavigationService } from '@hypertrace/common';
import { SelectComponent, SelectModule } from '@hypertrace/components';
import { AttributeMetadata, MetadataService } from '@hypertrace/distributed-tracing';
import { byText, createHostFactory, mockProvider } from '@ngneat/spectator/jest';
import { of } from 'rxjs';
import { EMPTY, of } from 'rxjs';
import { ObservabilityTraceType } from '../../../graphql/model/schema/observability-traces';
import { ExploreQueryGroupByEditorComponent } from './explore-query-group-by-editor.component';

Expand All @@ -20,7 +20,9 @@ describe('Explore Query Group by Editor component', () => {
getAttributeDisplayName: (attribute: AttributeMetadata) => attribute.name,
getGroupableAttributes: () => of(attributeMetadata)
}),
mockProvider(NavigationService)
mockProvider(NavigationService, {
navigation$: EMPTY
})
],
shallow: true
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
MetricAggregationType
} from '@hypertrace/distributed-tracing';
import { byText, createHostFactory, mockProvider } from '@ngneat/spectator/jest';
import { of } from 'rxjs';
import { EMPTY, of } from 'rxjs';
import { ObservabilityTraceType } from '../../../graphql/model/schema/observability-traces';
import { ExploreSpecificationBuilder } from '../../../graphql/request/builders/specification/explore/explore-specification-builder';
import { CartesianSeriesVisualizationType } from '../../cartesian/chart';
Expand All @@ -28,7 +28,9 @@ describe('Explore Query Series Editor component', () => {
getAttributeDisplayName: (attribute: AttributeMetadata) => attribute.name,
getSelectionAttributes: jest.fn(() => of(attributeMetadata()))
}),
mockProvider(NavigationService)
mockProvider(NavigationService, {
navigation$: EMPTY
})
],
shallow: true
});
Expand Down
Loading