Skip to content
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

perf: Improve rendering performance for large configuration models [CXSPA-7477] #18997

Merged
merged 116 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from 98 commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
d06c19a
feat: define productConfigurationDeltaRendering toggle [CXSPA-7713]
Uli-Tiger Jun 24, 2024
d3d8bb5
feat: do not merge prices, when performance optimization is active
Uli-Tiger Jun 25, 2024
7a941af
feat: trackByFn for group menu
Uli-Tiger Jun 25, 2024
52b67d2
feat: trackByFn retrieving attribute key if performance optimization …
Uli-Tiger Jun 25, 2024
cfc91fe
feat: only re-render attribute, if it was changed content wise
Uli-Tiger Jun 26, 2024
6d282bf
prettier
Uli-Tiger Jun 26, 2024
310e8d0
rename flag
Uli-Tiger Jun 26, 2024
43df512
feat: price-async skeleton
Uli-Tiger Jun 27, 2024
1d67331
new async pricing component and tests
Uli-Tiger Jun 27, 2024
fef29ac
Add license header
github-actions[bot] Jun 27, 2024
a064a51
add unit test for new service
Uli-Tiger Jun 27, 2024
2ed8153
Merge branch 'feature/CXSPA-7477' of https://github.com/SAP/spartacus…
Uli-Tiger Jun 27, 2024
227402c
fix sonar
Uli-Tiger Jun 27, 2024
6eb785a
use async pricing components for RBs
Uli-Tiger Jun 28, 2024
f7d2a17
revert changes
Uli-Tiger Jun 28, 2024
feb3376
add some tests
Uli-Tiger Jun 28, 2024
7b8e4ba
work on DDLB
Uli-Tiger Jun 28, 2024
d52d42a
fix DDLB value price not shown
Uli-Tiger Jun 28, 2024
4049d9d
duplicate styling for async component
Uli-Tiger Jun 28, 2024
4e8e523
fix absolute import
Uli-Tiger Jun 28, 2024
975243b
render price async for CBL
Uli-Tiger Jul 1, 2024
8528256
async pricing for read-only component
Uli-Tiger Jul 1, 2024
5bbad67
async pricing for CB component
Uli-Tiger Jul 1, 2024
9093c0f
add missing module declaration for CB
Uli-Tiger Jul 1, 2024
422d18e
add async pricing single selection component
Uli-Tiger Jul 1, 2024
a606b3b
multi selection image async pricing
Uli-Tiger Jul 1, 2024
28c802f
update RB aria texts when price changes
Uli-Tiger Jul 2, 2024
81a72e9
update A11y labels on price change for CBL
Uli-Tiger Jul 2, 2024
16dd8a8
reformat
Uli-Tiger Jul 2, 2024
92deec8
remove console
Uli-Tiger Jul 2, 2024
27c6fbd
only emit price change event if price changed
Uli-Tiger Jul 2, 2024
eb166d1
only send price changed event, if price really changed
Uli-Tiger Jul 2, 2024
4570a29
additional unit test
Uli-Tiger Jul 2, 2024
6a77531
another test
Uli-Tiger Jul 2, 2024
5a12680
fix sonar
Uli-Tiger Jul 2, 2024
19d412b
remove invalid prices
Uli-Tiger Jul 3, 2024
fd9a206
re-render DDLB if prices change
Uli-Tiger Jul 3, 2024
be20c69
remove import
Uli-Tiger Jul 3, 2024
74820c8
fix aria labels for readOnly with perf mode on
Uli-Tiger Jul 3, 2024
3eb192e
fix sonar issues
Uli-Tiger Jul 3, 2024
5c2d664
handle aria label for Check box in perf mode
Uli-Tiger Jul 3, 2024
dc4c3da
fix sonar
Uli-Tiger Jul 3, 2024
c0b67b8
refactor and a11y text for single selection image
Uli-Tiger Jul 3, 2024
b078cd5
multi level image components aria label
Uli-Tiger Jul 3, 2024
43e2ade
sonar fixes
Uli-Tiger Jul 3, 2024
943b196
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Jul 3, 2024
3623625
rename: productConfigurationDeltaRendering -> productConfiguratorDelt…
Uli-Tiger Jul 5, 2024
500608b
rework some typescript docs
Uli-Tiger Jul 5, 2024
68892b6
refactor method
Uli-Tiger Jul 5, 2024
935cbd5
enable test
Uli-Tiger Jul 5, 2024
816fbbd
refactor
Uli-Tiger Jul 5, 2024
3f993d5
remove whitespace
Uli-Tiger Jul 5, 2024
2042d21
fix sonar finding
Uli-Tiger Jul 5, 2024
427513c
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Jul 5, 2024
10fed87
Merge branch 'develop' into feature/CXSPA-7477
ChristophHi Jul 9, 2024
25873da
review feedback and fix build issues
Uli-Tiger Jul 9, 2024
e9b4b7c
Merge branch 'feature/CXSPA-7477' of https://github.com/SAP/spartacus…
Uli-Tiger Jul 9, 2024
1b56506
review feedback #2
Uli-Tiger Jul 9, 2024
78a4973
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Jul 9, 2024
2a27125
review: minor changes
Uli-Tiger Jul 10, 2024
f3a5fd5
review feedback # 2
Uli-Tiger Jul 10, 2024
8fef0f4
fix: display of value price for image based components
Uli-Tiger Jul 10, 2024
26908c7
add missing test
Uli-Tiger Jul 10, 2024
6083e93
add missing test
Uli-Tiger Jul 10, 2024
d753b8c
fix test
Uli-Tiger Jul 11, 2024
f48eda8
Feature/cxspa 7477 review refactoring (#19046)
Uli-Tiger Jul 12, 2024
5e5bbaa
revert unrequired changes
Uli-Tiger Jul 12, 2024
f4b1c37
review feedback
Uli-Tiger Jul 15, 2024
d518912
fix build:libs
Uli-Tiger Jul 15, 2024
b880d14
simplify
Uli-Tiger Jul 15, 2024
9c0bd58
simplify #2
Uli-Tiger Jul 15, 2024
38acb01
refactor
Uli-Tiger Jul 15, 2024
b775e21
revert unintended change
Uli-Tiger Jul 15, 2024
28e7756
additional test
Uli-Tiger Jul 16, 2024
a408331
delta rendering tets
Uli-Tiger Jul 16, 2024
df62376
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Jul 16, 2024
b7c92d4
Add license header
github-actions[bot] Jul 16, 2024
3261a21
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Jul 16, 2024
147ebbb
simplify aria label logic
Uli-Tiger Jul 16, 2024
cf9aa47
move duplicate method into base class
Uli-Tiger Jul 16, 2024
6c5456b
fix sonar finding
Uli-Tiger Jul 16, 2024
9e75277
review
Uli-Tiger Jul 17, 2024
2d7f881
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Jul 17, 2024
b169ccb
radio buttons - add aria-live tag if delta rendering is active
Uli-Tiger Jul 17, 2024
142470d
remove public
Uli-Tiger Jul 17, 2024
ec26b1d
review feedback
Uli-Tiger Jul 18, 2024
2aa646e
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Jul 18, 2024
0686a2b
Merge branch 'develop' into feature/CXSPA-7477
Larisa-Staroverova Jul 19, 2024
a555aef
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Jul 19, 2024
867cb8d
add aria-live attribute to last selected value
steinsebastian Jul 19, 2024
947e797
set last selected only if delta rendering was active
Uli-Tiger Jul 19, 2024
64e2e23
fix sonar and add missing test
Uli-Tiger Jul 19, 2024
af01657
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Jul 19, 2024
a147383
spelling
Uli-Tiger Jul 19, 2024
a0e0ab7
Merge branch 'feature/CXSPA-7477' of https://github.com/SAP/spartacus…
Uli-Tiger Jul 19, 2024
07d1f20
review feedback
Uli-Tiger Jul 22, 2024
a4306c0
fix missing prices after navigating back from OV
Uli-Tiger Jul 22, 2024
8006847
Merge branch 'develop' into feature/CXSPA-7477
ChristophHi Jul 22, 2024
0a583f5
fix: broken conflict solver navigation if perf flag is active
Uli-Tiger Jul 23, 2024
e669c04
Merge branch 'feature/CXSPA-7477' of https://github.com/SAP/spartacus…
Uli-Tiger Jul 23, 2024
241e3f5
spelling
Uli-Tiger Jul 23, 2024
155d2ac
fix sonar
Uli-Tiger Jul 23, 2024
e54c658
Merge branch 'develop' into feature/CXSPA-7477
ChristophHi Jul 25, 2024
e77d268
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Jul 29, 2024
204c3e0
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Jul 30, 2024
c32020c
review feedback - rename delta pricing service to attribute price cha…
Uli-Tiger Jul 31, 2024
41b71ff
adapt test names as well
Uli-Tiger Jul 31, 2024
cc6448e
rename isDeltaRendering -> isPricingAsync and check the feature flag …
Uli-Tiger Jul 31, 2024
706c5f6
fix sonar issue
Uli-Tiger Jul 31, 2024
d7f20c0
rename folder
Uli-Tiger Jul 31, 2024
307005f
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Jul 31, 2024
2bf130a
final rename
Uli-Tiger Jul 31, 2024
59d6807
make featureConfigService private
Uli-Tiger Jul 31, 2024
17ad050
make another instance of featureConfigService private
Uli-Tiger Jul 31, 2024
6762668
review feedback
Uli-Tiger Aug 1, 2024
87caedc
Merge branch 'develop' into feature/CXSPA-7477
Uli-Tiger Aug 1, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { Type, ViewContainerRef } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { FeatureConfigService, LoggerService } from '@spartacus/core';
import { ConfiguratorTestUtils } from '../../../testing/configurator-test-utils';
import { ConfiguratorAttributeCompositionConfig } from './configurator-attribute-composition.config';
import { ConfiguratorAttributeCompositionDirective } from './configurator-attribute-composition.directive';
import createSpy = jasmine.createSpy;

class TestComponent {}

class MockViewContainerRef {
clear = createSpy('vcr.clear');
createComponent = createSpy('vcr.createComponent');
}

let productConfiguratorDeltaRenderingEnabled = false;
class MockFeatureConfigService {
isEnabled(name: string): boolean {
if (name === 'productConfiguratorDeltaRendering') {
return productConfiguratorDeltaRenderingEnabled;
}
return false;
}
}

describe('ConfiguratorAttributeCompositionDirective', () => {
let configuratorAttributeCompositionTestConfig: ConfiguratorAttributeCompositionConfig;
let classUnderTest: ConfiguratorAttributeCompositionDirective;
let viewContainerRef: ViewContainerRef;
let loggerService: LoggerService;

beforeEach(() => {
configuratorAttributeCompositionTestConfig = {
productConfigurator: { assignment: { testComponent: TestComponent } },
};
TestBed.configureTestingModule({
providers: [
ConfiguratorAttributeCompositionDirective,
{
provide: ConfiguratorAttributeCompositionConfig,
useValue: configuratorAttributeCompositionTestConfig,
},
{
provide: ViewContainerRef,
useClass: MockViewContainerRef,
},
{
provide: FeatureConfigService,
useClass: MockFeatureConfigService,
},
],
});
classUnderTest = TestBed.inject(
ConfiguratorAttributeCompositionDirective as Type<ConfiguratorAttributeCompositionDirective>
);
viewContainerRef = TestBed.inject(
ViewContainerRef as Type<ViewContainerRef>
);
loggerService = TestBed.inject(LoggerService as Type<LoggerService>);
spyOn(loggerService, 'warn').and.callThrough();

classUnderTest['context'] = ConfiguratorTestUtils.getAttributeContext();
Uli-Tiger marked this conversation as resolved.
Show resolved Hide resolved
productConfiguratorDeltaRenderingEnabled = false;
});

it('should create', () => {
expect(classUnderTest).toBeDefined();
});

it('should handle missing assignment config', () => {
(
configuratorAttributeCompositionTestConfig.productConfigurator ?? {}
).assignment = undefined;
classUnderTest = TestBed.inject(
ConfiguratorAttributeCompositionDirective as Type<ConfiguratorAttributeCompositionDirective>
);
expect(classUnderTest['attrCompAssignment']).toBeDefined();
});

describe('ngOnInit', () => {
it('should render view if performance feature toggle is off', () => {
classUnderTest.ngOnInit();
expectComponentRendered(1);
});

it('should log if performance feature toggle is off but no component found', () => {
classUnderTest['context'].componentKey = 'not.existing';
classUnderTest.ngOnInit();
expectComponentNotRendered(true);
});

it('should do nothing if performance feature toggle is on', () => {
productConfiguratorDeltaRenderingEnabled = true;
classUnderTest.ngOnInit();
expectComponentNotRendered(false);
});
});

describe('ngOnChanges', () => {
it('should render view if performance feature toggle is on', () => {
productConfiguratorDeltaRenderingEnabled = true;
classUnderTest.ngOnChanges();
expectComponentRendered(1);
});

it('should render the attribute only once if it did not change', () => {
productConfiguratorDeltaRenderingEnabled = true;
classUnderTest.ngOnChanges();
// re-create another context with the same attribute
classUnderTest['context'] = ConfiguratorTestUtils.getAttributeContext();
classUnderTest.ngOnChanges();
expectComponentRendered(1);
});

it('should re-render the attribute if it changed', () => {
productConfiguratorDeltaRenderingEnabled = true;
classUnderTest.ngOnChanges();
// re-create another context with the different attribute
classUnderTest['context'] = ConfiguratorTestUtils.getAttributeContext();
classUnderTest['context'].attribute.selectedSingleValue = 'changed';
classUnderTest.ngOnChanges();
expectComponentRendered(2);
});

it('should log if performance feature toggle is on but no component found', () => {
productConfiguratorDeltaRenderingEnabled = true;
classUnderTest['context'].componentKey = 'not.existing';
classUnderTest.ngOnChanges();
expectComponentNotRendered(true);
});

it('should do nothing if performance feature toggle is off', () => {
productConfiguratorDeltaRenderingEnabled = false;
classUnderTest.ngOnChanges();
expectComponentNotRendered(false);
});
});

function expectComponentRendered(times: number) {
expect(viewContainerRef.clear).toHaveBeenCalledTimes(times);
expect(viewContainerRef.createComponent).toHaveBeenCalledTimes(times);
expect(loggerService.warn).not.toHaveBeenCalled();
}

function expectComponentNotRendered(expectLog: boolean) {
expect(viewContainerRef.clear).not.toHaveBeenCalled();
expect(viewContainerRef.createComponent).not.toHaveBeenCalled();
if (expectLog) {
expect(loggerService.warn).toHaveBeenCalled();
} else {
expect(loggerService.warn).not.toHaveBeenCalled();
}
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,78 @@ import {
Injector,
Input,
isDevMode,
OnChanges,
OnInit,
ViewContainerRef,
} from '@angular/core';
import { LoggerService } from '@spartacus/core';
import { ConfiguratorAttributeCompositionConfig } from './configurator-attribute-composition.config';
import {
FeatureConfigService,
LoggerService,
ObjectComparisonUtils,
} from '@spartacus/core';
import {
AttributeComponentAssignment,
ConfiguratorAttributeCompositionConfig,
} from './configurator-attribute-composition.config';
import { ConfiguratorAttributeCompositionContext } from './configurator-attribute-composition.model';
import { Configurator } from '../../../core/model/configurator.model';

@Directive({
selector: '[cxConfiguratorAttributeComponent]',
})
export class ConfiguratorAttributeCompositionDirective implements OnInit {
export class ConfiguratorAttributeCompositionDirective
implements OnInit, OnChanges
{
@Input('cxConfiguratorAttributeComponent')
context: ConfiguratorAttributeCompositionContext;

protected lastRenderedAttribute: Configurator.Attribute;

protected logger = inject(LoggerService);
protected featureConfigService = inject(FeatureConfigService);

protected readonly attrCompAssignment: AttributeComponentAssignment =
Uli-Tiger marked this conversation as resolved.
Show resolved Hide resolved
Uli-Tiger marked this conversation as resolved.
Show resolved Hide resolved
this.configuratorAttributeCompositionConfig.productConfigurator
Uli-Tiger marked this conversation as resolved.
Show resolved Hide resolved
?.assignment ?? [];

constructor(
protected vcr: ViewContainerRef,
protected configuratorAttributeCompositionConfig: ConfiguratorAttributeCompositionConfig
) {}

ngOnInit(): void {
const componentKey = this.context.componentKey;
if (
!this.featureConfigService.isEnabled('productConfiguratorDeltaRendering')
) {
const componentKey = this.context.componentKey;
this.renderComponent(this.attrCompAssignment[componentKey], componentKey);
}
}

const composition =
this.configuratorAttributeCompositionConfig.productConfigurator
?.assignment;
if (composition) {
this.renderComponent(composition[componentKey], componentKey);
/*
* Each time we update the configuration a completely new configuration state is emitted, including new attribute objects,
* regardless of whether an attribute actually changed or not. Hence, we compare the last rendered attribute with the current state
* and only destroy and re-create the attribute component, if there are actual changes to its data. This improves performance significantly.
*/
ngOnChanges(): void {
if (
this.featureConfigService.isEnabled(
'productConfiguratorDeltaRendering'
) &&
!ObjectComparisonUtils.deepEqualObjects(
this.lastRenderedAttribute,
this.context.attribute
)
) {
const componentKey = this.context.componentKey;
this.renderComponent(this.attrCompAssignment[componentKey], componentKey);
}
}

protected renderComponent(component: any, componentKey: string) {
if (component) {
this.lastRenderedAttribute = this.context.attribute;
this.vcr.clear();
this.vcr.createComponent(component, {
injector: this.getComponentInjector(),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export class ConfiguratorAttributeCompositionContext {
language: string;
expMode: boolean;
isNavigationToGroupEnabled?: boolean;
isDeltaRendering?: boolean;
}
Loading
Loading