Skip to content

Commit

Permalink
Kickstart UI: Refactor step description changes to use events
Browse files Browse the repository at this point in the history
  • Loading branch information
swalner-vmware committed Jan 11, 2022
1 parent 96d3537 commit 2b3c557
Show file tree
Hide file tree
Showing 51 changed files with 1,371 additions and 840 deletions.
410 changes: 205 additions & 205 deletions pkg/v1/tkg/manifest/server/zz_generated.bindata.go

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion pkg/v1/tkg/web/src/app/shared/service/Messenger.ts
Expand Up @@ -49,7 +49,15 @@ export enum TkgEventType {
// APP
BRANDING_CHANGED,
CONFIG_FILE_IMPORTED,
CONFIG_FILE_IMPORT_ERROR
CONFIG_FILE_IMPORT_ERROR,
STEP_DESCRIPTION_CHANGE,
}

// The payload structure expected on a STEP_NAME_CHANGE event
export interface StepDescriptionChangePayload {
wizard: string,
step: string,
description: string,
}

/**
Expand Down
Expand Up @@ -61,7 +61,7 @@ export default class DataServiceRegistrar {
};
// we subscribe to the messenger to ensure that whenever the target event is broadcast, we go fetch the data
AppServices.messenger.getSubject(eventType)
.subscribe((event) => this.fetchData<OBJ>(eventType, event.payload));
.subscribe((event) => this.fetchData<OBJ>(eventType, event.payload ? event.payload : {}));
}

// subscribe() is called by those consuming data services. This is typically a step that relies on whatever data
Expand Down
@@ -0,0 +1,28 @@
import DataServiceRegistrar from '../shared/service/data-service-registrar';
import { TkgEventType } from '../shared/service/Messenger';
import { Observable } from 'rxjs';

export class DataServiceRegistrarTestExtension extends DataServiceRegistrar {
public hasEntry(eventType: TkgEventType): boolean {
const eventEntry = this.getEntry(eventType);
return eventEntry !== null && eventEntry !== undefined;
}

public simulateError(eventType: TkgEventType, errMsg: string) {
if (!this.hasEntry(eventType)) {
console.log('No event registration found for ' + eventType);
}
this.getEntry(eventType).errorStream.next(errMsg);
}

public simulateData(eventType: TkgEventType, data: any) {
if (!this.hasEntry(eventType)) {
console.log('No event registration found for ' + eventType);
}
this.getEntry(eventType).dataStream.next(data);
}

public simulateRegistration<OBJ>(eventType: TkgEventType) {
this.register<OBJ>(eventType, () => new Observable<OBJ[]>());
}
}
@@ -1,9 +1,8 @@
// Angular imports
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { FormBuilder } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
// App imports
import { APIClient } from '../../../swagger/api-client.service';
Expand Down Expand Up @@ -113,78 +112,6 @@ describe('AwsWizardComponent', () => {
expect(component).toBeTruthy();
});

describe('should return correct description', () => {
it('is for provider step', () => {
const description = component.describeStep(component.AwsProviderForm.name, component.AwsProviderForm.description);
expect(description).toBe('Validate the AWS provider account for Tanzu');
});

it('is for vpc step', () => {
const stepInstance = TestBed.createComponent(VpcStepComponent).componentInstance;
component.registerStep(component.AwsVpcForm.name, stepInstance);
stepInstance.formGroup.addControl('vpc', new FormControl(''));
stepInstance.formGroup.addControl('publicNodeCidr', new FormControl(''));
stepInstance.formGroup.addControl('privateNodeCidr', new FormControl(''));
stepInstance.formGroup.addControl('awsNodeAz', new FormControl(''));

let description = component.describeStep(component.AwsVpcForm.name, component.AwsVpcForm.description);
expect(description).toBe('Specify VPC settings for AWS');

component.form.get('vpcForm').get('vpc').setValue('10.0.0.0/16');
component.form.get('vpcForm').get('publicNodeCidr').setValue('1.1.1.1/23');
component.form.get('vpcForm').get('privateNodeCidr').setValue('2.2.2.2/23');
component.form.get('vpcForm').get('awsNodeAz').setValue('awsNodeAz1');
description = component.describeStep(component.AwsVpcForm.name, component.AwsVpcForm.description);
expect(description).toBe('VPC CIDR: 10.0.0.0/16, Public Node CIDR: 1.1.1.1/23, ' +
'Private Node CIDR: 2.2.2.2/23, Node AZ: awsNodeAz1');
});

it('is for nodeSetting step', () => {
const stepInstance = TestBed.createComponent(NodeSettingStepComponent).componentInstance;
stepInstance.clusterTypeDescriptor = 'management';
component.registerStep(component.AwsNodeSettingForm.name, stepInstance);
stepInstance.formGroup.addControl('controlPlaneSetting', new FormControl(''));

const controlPlaneField = component.form.get(component.AwsNodeSettingForm.name).get('controlPlaneSetting');
let description = component.describeStep(component.AwsNodeSettingForm.name, component.AwsNodeSettingForm.description);
expect(description).toBe('Specify the resources backing the management cluster');

controlPlaneField.setValue('prod');
description = component.describeStep(component.AwsNodeSettingForm.name, component.AwsNodeSettingForm.description);
expect(description).toBe('Production cluster selected: 3 node control plane');

controlPlaneField.setValue('dev');
description = component.describeStep(component.AwsNodeSettingForm.name, component.AwsNodeSettingForm.description);
expect(description).toBe('Development cluster selected: 1 node control plane');
});

it('is for network step', () => {
const stepInstance = TestBed.createComponent(SharedNetworkStepComponent).componentInstance;
component.registerStep(component.AwsNetworkForm.name, stepInstance);
stepInstance.formGroup.addControl('clusterPodCidr', new FormControl(''));

let description = component.describeStep(component.AwsNetworkForm.name, component.AwsNetworkForm.description);
expect(description).toBe('Specify the cluster Pod CIDR');
component.form.get(component.AwsNetworkForm.name).get('clusterPodCidr').setValue('10.10.10.10/23');
description = component.describeStep(component.AwsNetworkForm.name, component.AwsNetworkForm.description);
expect(description).toBe('Cluster Pod CIDR: 10.10.10.10/23');
});

it('is for metadata step', () => {
const stepInstance = TestBed.createComponent(MetadataStepComponent).componentInstance;
stepInstance.clusterTypeDescriptor = 'management';
component.registerStep(component.MetadataForm.name, stepInstance);
stepInstance.formGroup.addControl('clusterLocation', new FormControl(''));

let description = component.describeStep(component.MetadataForm.name, component.MetadataForm.description);
expect(description).toBe('Specify metadata for the management cluster');

component.form.get(WizardForm.METADATA).get('clusterLocation').setValue('us-west');
description = component.describeStep(component.MetadataForm.name, component.MetadataForm.description);
expect(description).toBe('Location: us-west');
});
});

it('should return management cluster name', () => {
const stepInstance = TestBed.createComponent(NodeSettingStepComponent).componentInstance;
component.registerStep(AwsForm.NODESETTING, stepInstance);
Expand Down
Expand Up @@ -65,6 +65,10 @@ export class AwsWizardComponent extends WizardBaseDirective implements OnInit {
super(router, el, formMetaDataService, titleService, formBuilder);
}

protected supplyWizardName(): string {
return 'AWS Wizard';
}

protected supplyStepData(): FormDataForHTML[] {
return [
this.AwsProviderForm,
Expand Down Expand Up @@ -332,7 +336,7 @@ export class AwsWizardComponent extends WizardBaseDirective implements OnInit {
return this.getOsImageForm(AwsOsImageStepComponent);
}
get AwsNetworkForm(): FormDataForHTML {
return FormUtility.formOverrideDescription(this.NetworkForm, 'Specify the cluster Pod CIDR');
return FormUtility.formWithOverrides(this.NetworkForm, {description: 'Specify the cluster Pod CIDR'});
}
//
// HTML convenience methods
Expand Down
@@ -1,18 +1,19 @@
// Angular imports
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
// App imports
import { APIClient } from '../../../../swagger/api-client.service';
import AppServices from 'src/app/shared/service/appServices';
import { FieldMapUtilities } from '../../wizard/shared/field-mapping/FieldMapUtilities';
import { NodeSettingStepComponent } from './node-setting-step.component';
import { NodeSettingStepComponent, NodeType } from './node-setting-step.component';
import { Messenger, TkgEventType } from 'src/app/shared/service/Messenger';
import { SharedModule } from '../../../../shared/shared.module';
import { ValidationService } from '../../wizard/shared/validation/validation.service';
import { DataServiceRegistrarTestExtension } from '../../../../testing/data-service-registrar.testextension';
import { AWSSubnet } from '../../../../swagger/models';
import { AwsField, AwsForm } from '../aws-wizard.constants';

describe('NodeSettingStepComponent', () => {
let component: NodeSettingStepComponent;
Expand Down Expand Up @@ -44,11 +45,9 @@ describe('NodeSettingStepComponent', () => {
beforeEach(() => {
AppServices.messenger = new Messenger();
AppServices.dataServiceRegistrar = new DataServiceRegistrarTestExtension();
const fb = new FormBuilder();
fixture = TestBed.createComponent(NodeSettingStepComponent);
component = fixture.componentInstance;
component.formGroup = fb.group({
});
component.setInputs('SquashWizard', AwsForm.NODESETTING, new FormBuilder().group({}));

fixture.detectChanges();
});
Expand Down Expand Up @@ -176,7 +175,7 @@ describe('NodeSettingStepComponent', () => {
isPublic: false
}];

component.filterSubnets('awsNodeAz1', 'us-west-a');
component.filterSubnetsByAZ('awsNodeAz1', 'us-west-a');
expect(component.filteredAzs['awsNodeAz1']).toEqual({
publicSubnets: [{
availabilityZoneId: 'us-west-a',
Expand Down Expand Up @@ -294,4 +293,44 @@ describe('NodeSettingStepComponent', () => {
expect(component.privateSubnets).toEqual([{cidr: '100.64.0.0/14', isPublic: false}]);
expect(spySavedSubnet).toHaveBeenCalled();
});

it('should announce description change', () => {
const msgSpy = spyOn(AppServices.messenger, 'publish').and.callThrough();

component.ngOnInit();
component.nodeType = '';
const description = component.dynamicDescription();
expect(description).toEqual('Specify the resources backing the cluster');

component.setClusterTypeDescriptor('CARAMEL');
expect(msgSpy).toHaveBeenCalledWith({
type: TkgEventType.STEP_DESCRIPTION_CHANGE,
payload: {
wizard: 'SquashWizard',
step: AwsForm.NODESETTING,
description: 'Specify the resources backing the CARAMEL cluster',
}
});

const controlPlaneSettingControl = component.formGroup.controls[AwsField.NODESETTING_CONTROL_PLANE_SETTING];
controlPlaneSettingControl.setValue(NodeType.DEV);
expect(msgSpy).toHaveBeenCalledWith({
type: TkgEventType.STEP_DESCRIPTION_CHANGE,
payload: {
wizard: 'SquashWizard',
step: AwsForm.NODESETTING,
description: 'Development cluster selected: 1 node control plane',
}
});

controlPlaneSettingControl.setValue(NodeType.PROD);
expect(msgSpy).toHaveBeenCalledWith({
type: TkgEventType.STEP_DESCRIPTION_CHANGE,
payload: {
wizard: 'SquashWizard',
step: AwsForm.NODESETTING,
description: 'Production cluster selected: 3 node control plane',
}
});
});
});
Expand Up @@ -65,7 +65,7 @@ const PRIVATE_SUBNET = [
];
const VPC_SUBNETS = [...PUBLIC_SUBNETS, ...PRIVATE_SUBNET];

enum NodeType {
export enum NodeType {
DEV = 'dev',
PROD = 'prod'
}
Expand Down Expand Up @@ -142,6 +142,7 @@ export class NodeSettingStepComponent extends StepFormDirective implements OnIni
}

private customizeForm() {
this.registerStepDescriptionTriggers({clusterTypeDescriptor: true, fields: ['controlPlaneSetting']});
AppServices.messenger.getSubject(TkgEventType.AWS_AIRGAPPED_VPC_CHANGE).subscribe(event => {
this.airgappedVPC = event.payload;
if (this.airgappedVPC) { // public subnet IDs shouldn't be provided
Expand Down Expand Up @@ -196,7 +197,7 @@ export class NodeSettingStepComponent extends StepFormDirective implements OnIni

AzRelatedFieldsArray.forEach(azRelatedFields => {
this.registerOnValueChange(azRelatedFields.az, (newlySelectedAz) => {
this.filterSubnets(azRelatedFields.az, newlySelectedAz);
this.filterSubnetsByAZ(azRelatedFields.az, newlySelectedAz);
this.setSubnetFieldsWithOnlyOneOption(azRelatedFields.az);
this.updateWorkerNodeInstanceTypes(azRelatedFields.az, newlySelectedAz, azRelatedFields.workerNodeInstanceType);
});
Expand All @@ -209,6 +210,7 @@ export class NodeSettingStepComponent extends StepFormDirective implements OnIni
this.setControlPlaneToProd();
}
this.updateVpcSubnets();
this.triggerStepDescriptionChange();
});
}

Expand All @@ -226,7 +228,7 @@ export class NodeSettingStepComponent extends StepFormDirective implements OnIni
private onFetchedSubnets(subnets: Array<AWSSubnet>) {
this.publicSubnets = subnets.filter(obj => { return obj.isPublic });
this.privateSubnets = subnets.filter(obj => { return !obj.isPublic });
AZS.forEach(az => { this.filterSubnets(az, this.getFieldValue(az)); });
AZS.forEach(az => { this.filterSubnetsByAZ(az, this.getFieldValue(az)); });
this.setSubnetFieldsFromSavedValues();
}

Expand Down Expand Up @@ -416,22 +418,22 @@ export class NodeSettingStepComponent extends StepFormDirective implements OnIni
}

/**
* @method filterSubnets
* @method filterSubnetsByAZ
* helper method that filters larger lists of public and private subnets and returns filtered
* lists based on match of availability zone name
* @param $event
*/
filterSubnets(azControlName, az): void {
filterSubnetsByAZ(azControlName, az): void {
if (this.vpcType === vpcType.EXISTING && azControlName !== '' && az !== '') {
this.filteredAzs[azControlName].publicSubnets = this.publicSubnets.filter(obj => {
return obj.availabilityZoneName === az;
});
this.filteredAzs[azControlName].privateSubnets = this.privateSubnets.filter(obj => {
return obj.availabilityZoneName === az;
});
this.filteredAzs[azControlName].publicSubnets = this.filterSubnetArrayByAZ(az, this.publicSubnets);
this.filteredAzs[azControlName].privateSubnets = this.filterSubnetArrayByAZ(az, this.privateSubnets);
}
}

private filterSubnetArrayByAZ(az: string, subnets: AWSSubnet[]): AWSSubnet[] {
return (!subnets) ? [] : subnets.filter(subnet => { return subnet.availabilityZoneName === az; });
}

private setSubnetFieldsWithOnlyOneOption(azControlName) {
if (this.vpcType === vpcType.EXISTING && azControlName !== '') {
const filteredPublicSubnets = this.filteredAzs[azControlName].publicSubnets;
Expand Down Expand Up @@ -564,15 +566,14 @@ export class NodeSettingStepComponent extends StepFormDirective implements OnIni
}
}

protected dynamicDescription(): string {
const ctlPlaneFlavor = this.getFieldValue('controlPlaneSetting', true);
if (ctlPlaneFlavor) {
dynamicDescription(): string {
if (this.nodeType) {
let mode = 'Development cluster selected: 1 node control plane';
if (ctlPlaneFlavor === 'prod') {
if (this.nodeType === 'prod') {
mode = 'Production cluster selected: 3 node control plane';
}
return mode;
}
return `Specify the resources backing the ${this.clusterTypeDescriptor} cluster`;
return 'Specify the resources backing the ' + this.clusterTypeDescriptor + ' cluster';
}
}
Expand Up @@ -120,8 +120,10 @@ describe('AwsProviderStepComponent', () => {
}));

it("should show an error when connect button clicked and endpoint throws error", async(() => {
mockedApiService.setAWSEndpoint.and.returnValue(throwError({ status: 400,
error: {message: 'failed to set aws endpoint' }}));
mockedApiService.setAWSEndpoint.and.returnValue(throwError({
status: 400,
error: {message: 'failed to set aws endpoint'}
}));

component.ngOnInit();
component.setFieldValue(AwsField.PROVIDER_PROFILE_NAME, 'profile2');
Expand All @@ -131,7 +133,7 @@ describe('AwsProviderStepComponent', () => {
component.setFieldValue(AwsField.PROVIDER_REGION, 'region1');
expect(component.errorNotification).toBeFalsy();

fixture.whenStable().then( () => {
fixture.whenStable().then(() => {
fixture.detectChanges(); // this is necessary to pick up the field changes above and thereby activate the connect btn
const connectBtn = fixture.debugElement.query(By.css("button.btn-primary"));
expect(connectBtn.nativeElement.disabled).toBeFalsy('connect button is unexpectedly deactivated');
Expand All @@ -143,4 +145,6 @@ describe('AwsProviderStepComponent', () => {
});
});
}));
})

// NOTE: there is no test for changing the step description because this step only has a static step description.
});
Expand Up @@ -7,15 +7,15 @@ <h4 i18n="vpc for aws" aria-level="2">
<div class="clr-col-8">
<clr-radio-container clrInline>
<clr-radio-wrapper>
<input type="radio" name="vpcType" clrRadio value="new" formControlName="vpcType">
<input type="radio" name="vpcType" clrRadio value="existing" formControlName="vpcType">
<label>
Create new VPC on AWS
Select an existing VPC
</label>
</clr-radio-wrapper>
<clr-radio-wrapper>
<input type="radio" name="vpcType" clrRadio value="existing" formControlName="vpcType">
<input type="radio" name="vpcType" clrRadio value="new" formControlName="vpcType">
<label>
Select an existing VPC
Create new VPC on AWS
</label>
</clr-radio-wrapper>
</clr-radio-container>
Expand Down

0 comments on commit 2b3c557

Please sign in to comment.