Skip to content

Commit

Permalink
mgr/dashboard: Cluster Creation Workflow followups
Browse files Browse the repository at this point in the history
1. Fix bug in the modal where going forward one step on the wizard and coming back opens up the add host modal.
2. Rename Create Cluster to Expand Cluster as per the discussions
3. A skip confirmation modal to warn the user when he tries to skip the
   cluster creation
4. Adapted all the tests
5. Did some UI improvements like fixing and aligning the styles,
   colors..

Fixes: https://tracker.ceph.com/issues/51640
Signed-off-by: Nizamudeen A <nia@redhat.com>
  • Loading branch information
nizamial09 committed Jul 19, 2021
1 parent 5f8e0ba commit 54ab5c6
Show file tree
Hide file tree
Showing 18 changed files with 132 additions and 80 deletions.
1 change: 1 addition & 0 deletions qa/suites/rados/dashboard/tasks/dashboard.yaml
Expand Up @@ -39,6 +39,7 @@ tasks:
- tasks.mgr.dashboard.test_auth
- tasks.mgr.dashboard.test_cephfs
- tasks.mgr.dashboard.test_cluster_configuration
- tasks.mgr.dashboard.test_cluster
- tasks.mgr.dashboard.test_crush_rule
- tasks.mgr.dashboard.test_erasure_code_profile
- tasks.mgr.dashboard.test_ganesha
Expand Down
2 changes: 1 addition & 1 deletion src/pybind/mgr/dashboard/controllers/host.py
Expand Up @@ -284,7 +284,7 @@ def list(self, sources=None):

@raise_if_no_orchestrator([OrchFeature.HOST_LIST, OrchFeature.HOST_CREATE])
@handle_orchestrator_error('host')
@host_task('create', {'hostname': '{hostname}'})
@host_task('add', {'hostname': '{hostname}'})
@EndpointDoc('',
parameters={
'hostname': (str, 'Hostname'),
Expand Down
11 changes: 2 additions & 9 deletions src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
Expand Up @@ -91,23 +91,16 @@ const routes: Routes = [

// Cluster
{
path: 'create-cluster',
path: 'expand-cluster',
component: CreateClusterComponent,
canActivate: [ModuleStatusGuardService],
children: [
{
path: URLVerbs.ADD,
component: HostFormComponent,
outlet: 'modal'
}
],
data: {
moduleStatusGuardConfig: {
apiPath: 'orchestrator',
redirectTo: 'dashboard',
backend: 'cephadm'
},
breadcrumbs: 'Create Cluster'
breadcrumbs: 'Expand Cluster'
}
},
{
Expand Down
Expand Up @@ -5,6 +5,7 @@ import { RouterModule } from '@angular/router';

import { TreeModule } from '@circlon/angular-tree-component';
import {
NgbActiveModal,
NgbDatepickerModule,
NgbDropdownModule,
NgbNavModule,
Expand Down Expand Up @@ -114,6 +115,7 @@ import { TelemetryComponent } from './telemetry/telemetry.component';
OsdFlagsIndivModalComponent,
PlacementPipe,
CreateClusterComponent
]
],
providers: [NgbActiveModal]
})
export class ClusterModule {}
Expand Up @@ -11,12 +11,12 @@

<div class="m-4">
<h4 class="text-center"
i18n>Please proceed to complete the cluster creation</h4>
<div class="offset-md-3">
<button class="btn btn-accent m-3"
name="create-cluster"
i18n>Please expand your cluster first</h4>
<div class="offset-md-2">
<button class="btn btn-accent m-2"
name="expand-cluster"
(click)="createCluster()"
i18n>Create Cluster</button>
i18n>Expand Cluster</button>
<button class="btn btn-light"
name="skip-cluster-creation"
(click)="skipClusterCreation()"
Expand All @@ -30,7 +30,7 @@
<div class="card"
*ngIf="startClusterCreation">
<div class="card-header"
i18n>Create Cluster</div>
i18n>Expand Cluster</div>
<div class="container-fluid">
<cd-wizard [stepsTitle]="stepTitles"></cd-wizard>
<div class="card-body vertical-line">
Expand Down Expand Up @@ -63,3 +63,11 @@
[name]="showCancelButtonLabel()"></cd-back-button>
</div>
</div>

<ng-template #skipConfirmTpl>
<span i18n>You are about to skip the cluster expansion process.
You’ll need to <strong>navigate through the menu to add hosts and services.</strong></span>

<div class="mt-4"
i18n>Are you sure you want to continue?</div>
</ng-template>
Expand Up @@ -3,17 +3,22 @@
.container-fluid {
align-items: flex-start;
display: flex;
padding-left: 0;
width: 100%;
}

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

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

cd-wizard {
width: 15%;
}

cd-hosts {
::ng-deep .nav {
display: none;
Expand Down
Expand Up @@ -7,8 +7,10 @@ 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 { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component';
import { AppConstants } from '~/app/shared/constants/app.constants';
import { ModalService } from '~/app/shared/services/modal.service';
import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
import { SharedModule } from '~/app/shared/shared.module';
import { configureTestBed } from '~/testing/unit-test-helper';
Expand All @@ -17,9 +19,10 @@ import { CreateClusterComponent } from './create-cluster.component';
describe('CreateClusterComponent', () => {
let component: CreateClusterComponent;
let fixture: ComponentFixture<CreateClusterComponent>;
let clusterService: ClusterService;
let wizardStepService: WizardStepsService;
let hostService: HostService;
let modalServiceShowSpy: jasmine.Spy;
const projectConstants: typeof AppConstants = AppConstants;

configureTestBed({
imports: [
Expand All @@ -35,26 +38,28 @@ describe('CreateClusterComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(CreateClusterComponent);
component = fixture.componentInstance;
clusterService = TestBed.inject(ClusterService);
wizardStepService = TestBed.inject(WizardStepsService);
hostService = TestBed.inject(HostService);
modalServiceShowSpy = spyOn(TestBed.inject(ModalService), 'show').and.returnValue({
// mock the close function, it might be called if there are async tests.
close: jest.fn()
});
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should have the heading "Welcome to Ceph Dashboard"', () => {
it('should have project name as heading in welcome screen', () => {
const heading = fixture.debugElement.query(By.css('h3')).nativeElement;
expect(heading.innerHTML).toBe('Welcome to Ceph Dashboard');
expect(heading.innerHTML).toBe(`Welcome to ${projectConstants.projectName}`);
});

it('should call updateStatus when cluster creation is skipped', () => {
const clusterServiceSpy = spyOn(clusterService, 'updateStatus').and.callThrough();
expect(clusterServiceSpy).not.toHaveBeenCalled();
it('should show confirmation modal when cluster creation is skipped', () => {
component.skipClusterCreation();
expect(clusterServiceSpy).toHaveBeenCalledTimes(1);
expect(modalServiceShowSpy.calls.any()).toBeTruthy();
expect(modalServiceShowSpy.calls.first().args[0]).toBe(ConfirmationModalComponent);
});

it('should show the wizard when cluster creation is started', () => {
Expand Down Expand Up @@ -103,7 +108,7 @@ describe('CreateClusterComponent', () => {
component.onNextStep();
fixture.detectChanges();
submitBtnLabel = component.showSubmitButtonLabel();
expect(submitBtnLabel).toEqual('Create Cluster');
expect(submitBtnLabel).toEqual('Expand Cluster');
cancelBtnLabel = component.showCancelButtonLabel();
expect(cancelBtnLabel).toEqual('Back');
});
Expand Down
@@ -1,16 +1,19 @@
import { Component, OnDestroy } from '@angular/core';
import { Component, OnDestroy, TemplateRef, ViewChild } from '@angular/core';
import { Router } from '@angular/router';

import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { forkJoin, Subscription } from 'rxjs';
import { finalize } from 'rxjs/operators';

import { ClusterService } from '~/app/shared/api/cluster.service';
import { HostService } from '~/app/shared/api/host.service';
import { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component';
import { ActionLabelsI18n, AppConstants } from '~/app/shared/constants/app.constants';
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
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 { ModalService } from '~/app/shared/services/modal.service';
import { NotificationService } from '~/app/shared/services/notification.service';
import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';

Expand All @@ -20,14 +23,16 @@ import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
styleUrls: ['./create-cluster.component.scss']
})
export class CreateClusterComponent implements OnDestroy {
@ViewChild('skipConfirmTpl', { static: true })
skipConfirmTpl: TemplateRef<any>;
currentStep: WizardStepModel;
currentStepSub: Subscription;
permissions: Permissions;
projectConstants: typeof AppConstants = AppConstants;
hosts: Array<object> = [];
stepTitles = ['Add Hosts', 'Review'];
startClusterCreation = false;
observables: any = [];
modalRef: NgbModalRef;

constructor(
private authStorageService: AuthStorageService,
Expand All @@ -36,7 +41,8 @@ export class CreateClusterComponent implements OnDestroy {
private hostService: HostService,
private notificationService: NotificationService,
private actionLabels: ActionLabelsI18n,
private clusterService: ClusterService
private clusterService: ClusterService,
private modalService: ModalService
) {
this.permissions = this.authStorageService.getPermissions();
this.currentStepSub = this.stepsService.getCurrentStep().subscribe((step: WizardStepModel) => {
Expand All @@ -50,13 +56,27 @@ export class CreateClusterComponent implements OnDestroy {
}

skipClusterCreation() {
this.clusterService.updateStatus('POST_INSTALLED').subscribe(() => {
this.notificationService.show(
NotificationType.info,
$localize`Cluster creation skipped by user`
);
this.router.navigate(['/dashboard']);
});
const modalVariables = {
titleText: $localize`Warning`,
buttonText: $localize`Continue`,
warning: true,
bodyTpl: this.skipConfirmTpl,
showSubmit: true,
onSubmit: () => {
this.clusterService.updateStatus('POST_INSTALLED').subscribe({
error: () => this.modalRef.close(),
complete: () => {
this.notificationService.show(
NotificationType.info,
$localize`Cluster expansion skipped by user`
);
this.router.navigate(['/dashboard']);
this.modalRef.close();
}
});
}
};
this.modalRef = this.modalService.show(ConfirmationModalComponent, modalVariables);
}

onSubmit() {
Expand All @@ -66,7 +86,7 @@ export class CreateClusterComponent implements OnDestroy {
this.clusterService.updateStatus('POST_INSTALLED').subscribe(() => {
this.notificationService.show(
NotificationType.success,
$localize`Cluster creation was successful`
$localize`Cluster expansion was successful`
);
this.router.navigate(['/dashboard']);
})
Expand Down Expand Up @@ -101,7 +121,7 @@ export class CreateClusterComponent implements OnDestroy {
}

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

showCancelButtonLabel() {
Expand Down
@@ -1,4 +1,5 @@
<cd-modal [pageURL]="pageURL">
<cd-modal [pageURL]="pageURL"
[modalRef]="activeModal">
<span class="modal-title"
i18n>{{ action | titlecase }} {{ resource | upperFirst }}</span>

Expand Down
Expand Up @@ -3,6 +3,7 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';

import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrModule } from 'ngx-toastr';

import { LoadingPanelComponent } from '~/app/shared/components/loading-panel/loading-panel.component';
Expand All @@ -24,7 +25,8 @@ describe('HostFormComponent', () => {
ReactiveFormsModule,
ToastrModule.forRoot()
],
declarations: [HostFormComponent]
declarations: [HostFormComponent],
providers: [NgbActiveModal]
},
[LoadingPanelComponent]
);
Expand Down
Expand Up @@ -2,6 +2,8 @@ import { Component, OnInit } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';

import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';

import { HostService } from '~/app/shared/api/host.service';
import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
Expand Down Expand Up @@ -36,15 +38,18 @@ export class HostFormComponent extends CdForm implements OnInit {
private router: Router,
private actionLabels: ActionLabelsI18n,
private hostService: HostService,
private taskWrapper: TaskWrapperService
private taskWrapper: TaskWrapperService,
public activeModal: NgbActiveModal
) {
super();
this.resource = $localize`host`;
this.action = this.actionLabels.ADD;
}

ngOnInit() {
this.pageURL = this.router.url.includes('hosts') ? 'hosts' : 'create-cluster';
if (this.router.url.includes('hosts')) {
this.pageURL = 'hosts';
}
this.createForm();
this.hostService.list().subscribe((resp: any[]) => {
this.hostnames = resp.map((host) => {
Expand Down Expand Up @@ -80,7 +85,7 @@ export class HostFormComponent extends CdForm implements OnInit {
this.allLabels = this.hostForm.get('labels').value;
this.taskWrapper
.wrapTaskAroundCall({
task: new FinishedTask('host/' + URLVerbs.CREATE, {
task: new FinishedTask('host/' + URLVerbs.ADD, {
hostname: hostname
}),
call: this.hostService.create(hostname, this.addr, this.allLabels, this.status)
Expand All @@ -90,7 +95,9 @@ export class HostFormComponent extends CdForm implements OnInit {
this.hostForm.setErrors({ cdSubmitButton: true });
},
complete: () => {
this.router.navigate([this.pageURL, { outlets: { modal: null } }]);
this.pageURL === 'hosts'
? this.router.navigate([this.pageURL, { outlets: { modal: null } }])
: this.activeModal.close();
}
});
}
Expand Down

0 comments on commit 54ab5c6

Please sign in to comment.