Skip to content

Commit

Permalink
mgr/dashboard: Cluster Creation Add Host Section
Browse files Browse the repository at this point in the history
Add host section of the cluster creation workflow.

Fixes: https://tracker.ceph.com/issues/50565
Signed-off-by: Nizamudeen A <nia@redhat.com>
  • Loading branch information
nizamial09 committed Jul 6, 2021
1 parent 9de2da1 commit 03075c5
Show file tree
Hide file tree
Showing 21 changed files with 451 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ const routes: Routes = [
path: 'create-cluster',
component: CreateClusterComponent,
canActivate: [ModuleStatusGuardService],
children: [
{
path: URLVerbs.ADD,
component: HostFormComponent,
outlet: 'modal'
}
],
data: {
moduleStatusGuardConfig: {
apiPath: 'orchestrator',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ import { TelemetryComponent } from './telemetry/telemetry.component';
OsdCreationPreviewModalComponent,
RulesListComponent,
ActiveAlertListComponent,
HostFormComponent,
ServiceDetailsComponent,
ServiceDaemonListComponent,
TelemetryComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<div class="container h-75">
<div class="container h-75"
*ngIf="!startClusterCreation">
<div class="row h-100 justify-content-center align-items-center">
<div class="blank-page">
<!-- htmllint img-req-src="false" -->
Expand All @@ -14,16 +15,51 @@
<div class="offset-md-3">
<button class="btn btn-accent m-3"
name="create-cluster"
[routerLink]="'/dashboard'"
(click)="createCluster()"
i18n>Create Cluster</button>
<button class="btn btn-light"
name="skip-cluster-creation"
[routerLink]="'/dashboard'"
(click)="skipClusterCreation()"
i18n>Skip</button>
</div>
</div>
</div>
</div>
</div>

<div class="card"
*ngIf="startClusterCreation">
<div class="card-header"
i18n>Create Cluster</div>
<div class="container-fluid">
<cd-wizard [stepsTitle]="stepTitles"></cd-wizard>
<div class="card-body vertical-line">
<ng-container [ngSwitch]="currentStep?.stepIndex">
<div *ngSwitchCase="'1'"
class="ml-5">
<h4 class="title"
i18n>Add Hosts</h4>
<br>
<cd-hosts [clusterCreation]="true"></cd-hosts>
</div>
<div *ngSwitchCase="'2'"
class="ml-5">
<h4 class="title"
i18n>Review</h4>
<br>
<p>To be implemented</p>
</div>
</ng-container>
</div>
</div>
<div class="card-footer">
<button class="btn btn-accent m-2 float-right"
(click)="onNextStep()"
aria-label="Next"
i18n>{{ showSubmitButtonLabel() }}</button>
<cd-back-button class="m-2 float-right"
aria-label="Close"
(backAction)="onPreviousStep()"
[name]="showCancelButtonLabel()"></cd-back-button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@use './src/styles/vendor/variables' as vv;

.container-fluid {
align-items: flex-start;
display: flex;
width: 100%;
}

.card-body {
max-width: 90%;
}

.vertical-line {
border-left: 1px solid vv.$gray-400;
}

cd-hosts {
::ng-deep .nav {
display: none;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { RouterTestingModule } from '@angular/router/testing';

import { ToastrModule } from 'ngx-toastr';

import { CephModule } from '~/app/ceph/ceph.module';
import { CoreModule } from '~/app/core/core.module';
import { ClusterService } from '~/app/shared/api/cluster.service';
import { HostService } from '~/app/shared/api/host.service';
import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
import { SharedModule } from '~/app/shared/shared.module';
import { configureTestBed } from '~/testing/unit-test-helper';
import { CreateClusterComponent } from './create-cluster.component';
Expand All @@ -14,16 +18,26 @@ describe('CreateClusterComponent', () => {
let component: CreateClusterComponent;
let fixture: ComponentFixture<CreateClusterComponent>;
let clusterService: ClusterService;
let wizardStepService: WizardStepsService;
let hostService: HostService;

configureTestBed({
declarations: [CreateClusterComponent],
imports: [HttpClientTestingModule, RouterTestingModule, ToastrModule.forRoot(), SharedModule]
imports: [
HttpClientTestingModule,
RouterTestingModule,
ToastrModule.forRoot(),
SharedModule,
CoreModule,
CephModule
]
});

beforeEach(() => {
fixture = TestBed.createComponent(CreateClusterComponent);
component = fixture.componentInstance;
clusterService = TestBed.inject(ClusterService);
wizardStepService = TestBed.inject(WizardStepsService);
hostService = TestBed.inject(HostService);
fixture.detectChanges();
});

Expand All @@ -42,4 +56,55 @@ describe('CreateClusterComponent', () => {
component.skipClusterCreation();
expect(clusterServiceSpy).toHaveBeenCalledTimes(1);
});

it('should show the wizard when cluster creation is started', () => {
component.createCluster();
fixture.detectChanges();
const nativeEl = fixture.debugElement.nativeElement;
expect(nativeEl.querySelector('cd-wizard')).not.toBe(null);
});

it('should have title Add Hosts', () => {
component.createCluster();
fixture.detectChanges();
const heading = fixture.debugElement.query(By.css('.title')).nativeElement;
expect(heading.innerHTML).toBe('Add Hosts');
});

it('should show the host list when cluster creation as first step', () => {
component.createCluster();
fixture.detectChanges();
const nativeEl = fixture.debugElement.nativeElement;
expect(nativeEl.querySelector('cd-hosts')).not.toBe(null);
});

it('should move to next step and show the second page', () => {
const wizardStepServiceSpy = spyOn(wizardStepService, 'moveToNextStep').and.callThrough();
const hostServiceSpy = spyOn(hostService, 'list').and.callThrough();
component.createCluster();
fixture.detectChanges();
component.onNextStep();
fixture.detectChanges();
const heading = fixture.debugElement.query(By.css('.title')).nativeElement;
expect(wizardStepServiceSpy).toHaveBeenCalledTimes(1);
expect(hostServiceSpy).toBeCalledTimes(1);
expect(heading.innerHTML).toBe('Review');
});

it('should show the button labels correctly', () => {
component.createCluster();
fixture.detectChanges();
let submitBtnLabel = component.showSubmitButtonLabel();
expect(submitBtnLabel).toEqual('Next');
let cancelBtnLabel = component.showCancelButtonLabel();
expect(cancelBtnLabel).toEqual('Cancel');

// Last page of the wizard
component.onNextStep();
fixture.detectChanges();
submitBtnLabel = component.showSubmitButtonLabel();
expect(submitBtnLabel).toEqual('Create Cluster');
cancelBtnLabel = component.showCancelButtonLabel();
expect(cancelBtnLabel).toEqual('Back');
});
});
Original file line number Diff line number Diff line change
@@ -1,36 +1,52 @@
import { Component } from '@angular/core';
import { Component, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';

import { forkJoin, Subscription } from 'rxjs';
import { finalize } from 'rxjs/operators';

import { ClusterService } from '~/app/shared/api/cluster.service';
import { AppConstants } from '~/app/shared/constants/app.constants';
import { HostService } from '~/app/shared/api/host.service';
import { ActionLabelsI18n, AppConstants } from '~/app/shared/constants/app.constants';
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
import { Permission } from '~/app/shared/models/permissions';
import { Permissions } from '~/app/shared/models/permissions';
import { WizardStepModel } from '~/app/shared/models/wizard-steps';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import { NotificationService } from '~/app/shared/services/notification.service';
import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';

@Component({
selector: 'cd-create-cluster',
templateUrl: './create-cluster.component.html',
styleUrls: ['./create-cluster.component.scss']
})
export class CreateClusterComponent {
permission: Permission;
orchStatus = false;
featureAvailable = false;
export class CreateClusterComponent implements OnDestroy {
currentStep: WizardStepModel;
currentStepSub: Subscription;
permissions: Permissions;
projectConstants: typeof AppConstants = AppConstants;
hosts: Array<object> = [];
stepTitles = ['Add Hosts', 'Review'];
startClusterCreation = false;
observables: any = [];

constructor(
private authStorageService: AuthStorageService,
private clusterService: ClusterService,
private notificationService: NotificationService
private stepsService: WizardStepsService,
private router: Router,
private hostService: HostService,
private notificationService: NotificationService,
private actionLabels: ActionLabelsI18n,
private clusterService: ClusterService
) {
this.permission = this.authStorageService.getPermissions().configOpt;
this.permissions = this.authStorageService.getPermissions();
this.currentStepSub = this.stepsService.getCurrentStep().subscribe((step: WizardStepModel) => {
this.currentStep = step;
});
this.currentStep.stepIndex = 1;
}

createCluster() {
this.notificationService.show(
NotificationType.error,
$localize`Cluster creation feature not implemented`
);
this.startClusterCreation = true;
}

skipClusterCreation() {
Expand All @@ -39,6 +55,60 @@ export class CreateClusterComponent {
NotificationType.info,
$localize`Cluster creation skipped by user`
);
this.router.navigate(['/dashboard']);
});
}

onSubmit() {
forkJoin(this.observables)
.pipe(
finalize(() =>
this.clusterService.updateStatus('POST_INSTALLED').subscribe(() => {
this.notificationService.show(
NotificationType.success,
$localize`Cluster creation was successful`
);
this.router.navigate(['/dashboard']);
})
)
)
.subscribe({
error: (error) => error.preventDefault()
});
}

onNextStep() {
if (!this.stepsService.isLastStep()) {
this.hostService.list().subscribe((hosts) => {
hosts.forEach((host) => {
if (host['status'] === 'maintenance') {
this.observables.push(this.hostService.update(host['hostname'], false, [], true));
}
});
});
this.stepsService.moveToNextStep();
} else {
this.onSubmit();
}
}

onPreviousStep() {
if (!this.stepsService.isFirstStep()) {
this.stepsService.moveToPreviousStep();
} else {
this.router.navigate(['/dashboard']);
}
}

showSubmitButtonLabel() {
return !this.stepsService.isLastStep() ? this.actionLabels.NEXT : $localize`Create Cluster`;
}

showCancelButtonLabel() {
return !this.stepsService.isFirstStep() ? this.actionLabels.BACK : this.actionLabels.CANCEL;
}

ngOnDestroy(): void {
this.currentStepSub.unsubscribe();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<cd-modal pageURL="hosts">
<cd-modal [pageURL]="pageURL">
<span class="modal-title"
i18n>{{ action | titlecase }} {{ resource | upperFirst }}</span>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('HostFormComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(HostFormComponent);
component = fixture.componentInstance;
component.ngOnInit();
formHelper = new FormHelper(component.hostForm);
fixture.detectChanges();
});
Expand All @@ -40,6 +41,11 @@ describe('HostFormComponent', () => {
expect(component).toBeTruthy();
});

it('should open the form in a modal', () => {
const nativeEl = fixture.debugElement.nativeElement;
expect(nativeEl.querySelector('cd-modal')).not.toBe(null);
});

it('should validate the network address is valid', fakeAsync(() => {
formHelper.setValue('addr', '115.42.150.37', true);
tick();
Expand Down
Loading

0 comments on commit 03075c5

Please sign in to comment.