]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Rename `DeletionModalComponent` to `CriticalConfirmationModalComponent`
authorPatrick Nawracay <pnawracay@suse.com>
Mon, 15 Oct 2018 13:23:29 +0000 (15:23 +0200)
committerPatrick Nawracay <pnawracay@suse.com>
Wed, 24 Oct 2018 08:02:31 +0000 (10:02 +0200)
Signed-off-by: Patrick Nawracay <pnawracay@suse.com>
18 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.html [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.scss [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.ts [deleted file]

index d0498dc3cd75ff77a19ce8553d8eee0c30d2140a..346bdb40ef260dbd32aa510915c7f3dba6d924b9 100644 (file)
@@ -5,7 +5,7 @@ import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
 
 import { RbdService } from '../../../shared/api/rbd.service';
 import { ConfirmationModalComponent } from '../../../shared/components/confirmation-modal/confirmation-modal.component';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
 import { TableComponent } from '../../../shared/datatable/table/table.component';
 import { CellTemplate } from '../../../shared/enum/cell-template.enum';
 import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum';
@@ -256,7 +256,7 @@ export class RbdListComponent implements OnInit {
     const poolName = this.selection.first().pool_name;
     const imageName = this.selection.first().name;
 
-    this.modalRef = this.modalService.show(DeletionModalComponent, {
+    this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
       initialState: {
         itemDescription: 'RBD',
         submitActionObservable: () =>
index 014ecc447a5e7d7bfbd8e21e72cf8f17f0e54aee..f6d3cbd7067bac5cea3ca1667a664c7a90324cdc 100644 (file)
@@ -6,7 +6,7 @@ import { of } from 'rxjs';
 
 import { RbdService } from '../../../shared/api/rbd.service';
 import { ConfirmationModalComponent } from '../../../shared/components/confirmation-modal/confirmation-modal.component';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
 import { CellTemplate } from '../../../shared/enum/cell-template.enum';
 import { CdTableAction } from '../../../shared/models/cd-table-action';
 import { CdTableColumn } from '../../../shared/models/cd-table-column';
@@ -269,7 +269,7 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges {
 
   deleteSnapshotModal() {
     const snapshotName = this.selection.selected[0].name;
-    this.modalRef = this.modalService.show(DeletionModalComponent, {
+    this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
       initialState: {
         itemDescription: 'RBD snapshot',
         submitAction: () => this._asyncTask('deleteSnapshot', 'rbd/snap/delete', snapshotName)
index 7c569757b7064fbfd84d6ef4c4c89988974102fd..ac6059d5bbc35b8f84effa4b0aca3aa98ce6a75c 100644 (file)
@@ -5,7 +5,7 @@ import * as moment from 'moment';
 import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
 
 import { RbdService } from '../../../shared/api/rbd.service';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
 import { TableComponent } from '../../../shared/datatable/table/table.component';
 import { CellTemplate } from '../../../shared/enum/cell-template.enum';
 import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum';
@@ -177,7 +177,7 @@ export class RbdTrashListComponent implements OnInit {
     const imageId = this.selection.first().id;
     const expiresAt = this.selection.first().deferment_end_time;
 
-    this.modalRef = this.modalService.show(DeletionModalComponent, {
+    this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
       initialState: {
         itemDescription: 'RBD',
         bodyTemplate: this.deleteTpl,
index ee549602aec49357e54bcb42480252d10a700540..f4992636280fe10de1e3d40f95092bef55de59ee 100644 (file)
@@ -3,6 +3,7 @@ import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
 import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
 
 import { OsdService } from '../../../../shared/api/osd.service';
+import { ConfirmationModalComponent } from '../../../../shared/components/confirmation-modal/confirmation-modal.component';
 import { TableComponent } from '../../../../shared/datatable/table/table.component';
 import { CellTemplate } from '../../../../shared/enum/cell-template.enum';
 import { CdTableAction } from '../../../../shared/models/cd-table-action';
index 9faa989df7c63346dc7240c89ab5c17ee7245499..6e1c2ea976e804501f31b174019a7243ee5091a4 100644 (file)
@@ -3,7 +3,6 @@ import { Component, OnInit, ViewChild } from '@angular/core';
 import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
 
 import { PoolService } from '../../../shared/api/pool.service';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
 import { TableComponent } from '../../../shared/datatable/table/table.component';
 import { CellTemplate } from '../../../shared/enum/cell-template.enum';
 import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum';
@@ -17,6 +16,7 @@ import { AuthStorageService } from '../../../shared/services/auth-storage.servic
 import { TaskListService } from '../../../shared/services/task-list.service';
 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
 import { Pool } from '../pool';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
 
 @Component({
   selector: 'cd-pool-list',
@@ -133,7 +133,7 @@ export class PoolListComponent implements OnInit {
 
   deletePoolModal() {
     const name = this.selection.first().pool_name;
-    this.modalRef = this.modalService.show(DeletionModalComponent, {
+    this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
       initialState: {
         itemDescription: 'Pool',
         submitActionObservable: () =>
index 1c9f4b6e3cf5145daeb12e4db984afd3062fd03e..170835f05db023c3f036137e1b32c4c33fa870de 100644 (file)
@@ -4,7 +4,7 @@ import { BsModalService } from 'ngx-bootstrap/modal';
 import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs';
 
 import { RgwBucketService } from '../../../shared/api/rgw-bucket.service';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
 import { TableComponent } from '../../../shared/datatable/table/table.component';
 import { CdTableAction } from '../../../shared/models/cd-table-action';
 import { CdTableColumn } from '../../../shared/models/cd-table-column';
@@ -85,7 +85,7 @@ export class RgwBucketListComponent {
   }
 
   deleteAction() {
-    this.bsModalService.show(DeletionModalComponent, {
+    this.bsModalService.show(CriticalConfirmationModalComponent, {
       initialState: {
         itemDescription: this.selection.hasSingleSelection ? 'bucket' : 'buckets',
         submitActionObservable: () => {
index 32f820ef76bc260cbab467cc94c535fd9f0306b0..29397ebdb965622947b5f4f2a94f2641f9da4d2c 100644 (file)
@@ -4,7 +4,7 @@ import { BsModalService } from 'ngx-bootstrap/modal';
 import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs';
 
 import { RgwUserService } from '../../../shared/api/rgw-user.service';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
 import { TableComponent } from '../../../shared/datatable/table/table.component';
 import { CellTemplate } from '../../../shared/enum/cell-template.enum';
 import { CdTableAction } from '../../../shared/models/cd-table-action';
@@ -101,7 +101,7 @@ export class RgwUserListComponent {
   }
 
   deleteAction() {
-    this.bsModalService.show(DeletionModalComponent, {
+    this.bsModalService.show(CriticalConfirmationModalComponent, {
       initialState: {
         itemDescription: this.selection.hasSingleSelection ? 'user' : 'users',
         submitActionObservable: (): Observable<any> => {
index 427715b2fdd6f04982c78c1a4fd43e314205d80c..2f1836408ef05a5e70e168647c67babd5d24e1aa 100644 (file)
@@ -5,7 +5,7 @@ import { forkJoin } from 'rxjs';
 
 import { RoleService } from '../../../shared/api/role.service';
 import { ScopeService } from '../../../shared/api/scope.service';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
 import { EmptyPipe } from '../../../shared/empty.pipe';
 import { CellTemplate } from '../../../shared/enum/cell-template.enum';
 import { NotificationType } from '../../../shared/enum/notification-type.enum';
@@ -115,7 +115,7 @@ export class RoleListComponent implements OnInit {
 
   deleteRoleModal() {
     const name = this.selection.first().name;
-    this.modalRef = this.modalService.show(DeletionModalComponent, {
+    this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
       initialState: {
         itemDescription: 'Role',
         submitAction: () => this.deleteRole(name)
index 47b2882c24a068cb489992f827e71360b30e1081..1152c7e795db199148513e753cb0c5971976a210 100644 (file)
@@ -3,7 +3,7 @@ import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
 import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
 
 import { UserService } from '../../../shared/api/user.service';
-import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
 import { EmptyPipe } from '../../../shared/empty.pipe';
 import { NotificationType } from '../../../shared/enum/notification-type.enum';
 import { CdTableAction } from '../../../shared/models/cd-table-action';
@@ -122,7 +122,7 @@ export class UserListComponent implements OnInit {
       );
       return;
     }
-    this.modalRef = this.modalService.show(DeletionModalComponent, {
+    this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
       initialState: {
         itemDescription: 'User',
         submitAction: () => this.deleteUser(username)
index 9c33a3d89367fd40772ea080e61ab61c162e57f7..9ad9d4895ec7e6aae930929a92f3354391b53ed6 100644 (file)
@@ -12,7 +12,7 @@ import { TooltipModule } from 'ngx-bootstrap/tooltip';
 import { DirectivesModule } from '../directives/directives.module';
 import { PipesModule } from '../pipes/pipes.module';
 import { ConfirmationModalComponent } from './confirmation-modal/confirmation-modal.component';
-import { DeletionModalComponent } from './deletion-modal/deletion-modal.component';
+import { CriticalConfirmationModalComponent } from './critical-confirmation-modal/critical-confirmation-modal.component';
 import { ErrorPanelComponent } from './error-panel/error-panel.component';
 import { GrafanaComponent } from './grafana/grafana.component';
 import { HelperComponent } from './helper/helper.component';
@@ -52,7 +52,7 @@ import { WarningPanelComponent } from './warning-panel/warning-panel.component';
     LoadingPanelComponent,
     InfoPanelComponent,
     ModalComponent,
-    DeletionModalComponent,
+    CriticalConfirmationModalComponent,
     ConfirmationModalComponent,
     WarningPanelComponent,
     GrafanaComponent
@@ -72,6 +72,10 @@ import { WarningPanelComponent } from './warning-panel/warning-panel.component';
     WarningPanelComponent,
     GrafanaComponent
   ],
-  entryComponents: [ModalComponent, DeletionModalComponent, ConfirmationModalComponent]
+  entryComponents: [
+    ModalComponent,
+    CriticalConfirmationModalComponent,
+    ConfirmationModalComponent
+  ]
 })
 export class ComponentsModule {}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html
new file mode 100644 (file)
index 0000000..d12d900
--- /dev/null
@@ -0,0 +1,54 @@
+<cd-modal #modal
+          [modalRef]="modalRef">
+  <ng-container class="modal-title">
+    <ng-container *ngTemplateOutlet="deletionHeading"></ng-container>
+  </ng-container>
+
+  <ng-container class="modal-content">
+    <form name="deletionForm"
+          #formDir="ngForm"
+          [formGroup]="deletionForm"
+          novalidate>
+      <div class="modal-body">
+        <ng-container *ngTemplateOutlet="bodyTemplate; context: bodyContext"></ng-container>
+        <div class="question">
+          <p i18n>
+            Are you sure that you want to {{ actionDescription | lowercase }} the selected {{ itemDescription }}?
+          </p>
+          <div class="form-group"
+               [ngClass]="{'has-error': deletionForm.showError('confirmation', formDir)}">
+            <div class="checkbox checkbox-primary">
+              <input type="checkbox"
+                     name="confirmation"
+                     id="confirmation"
+                     formControlName="confirmation"
+                     autofocus>
+              <label i18n
+                     for="confirmation">
+                Yes, I am sure.
+              </label>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <cd-submit-button #submitButton
+                          [form]="deletionForm"
+                          (submitAction)="callSubmitAction()">
+          <ng-container *ngTemplateOutlet="deletionHeading"></ng-container>
+        </cd-submit-button>
+        <button class="btn btn-link btn-sm"
+                (click)="hideModal()"
+                i18n>
+          Cancel
+        </button>
+      </div>
+    </form>
+  </ng-container>
+</cd-modal>
+
+<ng-template #deletionHeading>
+  <ng-container i18n>
+    {{ actionDescription | titlecase }} {{ itemDescription }}
+  </ng-container>
+</ng-template>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.scss
new file mode 100644 (file)
index 0000000..aff598d
--- /dev/null
@@ -0,0 +1,6 @@
+.modal-body .question {
+  font-weight: bold;
+}
+.modal-body .question .checkbox {
+  padding-top: 7px;
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..0189fce
--- /dev/null
@@ -0,0 +1,254 @@
+import { Component, NgModule, NO_ERRORS_SCHEMA, TemplateRef, ViewChild } from '@angular/core';
+import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { NgForm, ReactiveFormsModule } from '@angular/forms';
+import { By } from '@angular/platform-browser';
+
+import { BsModalRef, BsModalService, ModalModule } from 'ngx-bootstrap/modal';
+import { Observable, Subscriber, timer as observableTimer } from 'rxjs';
+
+import { configureTestBed } from '../../../../testing/unit-test-helper';
+import { DirectivesModule } from '../../directives/directives.module';
+import { CriticalConfirmationModalComponent } from './critical-confirmation-modal.component';
+
+@NgModule({
+  entryComponents: [CriticalConfirmationModalComponent]
+})
+export class MockModule {}
+
+@Component({
+  template: `
+    <button type="button"
+        class="btn btn-sm btn-primary"
+        (click)="openCtrlDriven()">
+      <i class="fa fa-fw fa-times"></i>Deletion Ctrl-Test
+      <ng-template #ctrlDescription>
+        The spinner is handled by the controller if you have use the modal as ViewChild in order to
+        use it's functions to stop the spinner or close the dialog.
+      </ng-template>
+    </button>
+
+    <button type="button"
+            class="btn btn-sm btn-primary"
+            (click)="openModalDriven()">
+      <i class="fa fa-fw fa-times"></i>Deletion Modal-Test
+      <ng-template #modalDescription>
+        The spinner is handled by the modal if your given deletion function returns a Observable.
+      </ng-template>
+    </button>
+  `
+})
+class MockComponent {
+  @ViewChild('ctrlDescription')
+  ctrlDescription: TemplateRef<any>;
+  @ViewChild('modalDescription')
+  modalDescription: TemplateRef<any>;
+  someData = [1, 2, 3, 4, 5];
+  finished: number[];
+  ctrlRef: BsModalRef;
+  modalRef: BsModalRef;
+
+  // Normally private - public was needed for the tests
+  constructor(public modalService: BsModalService) {}
+
+  openCtrlDriven() {
+    this.ctrlRef = this.modalService.show(CriticalConfirmationModalComponent, {
+      initialState: {
+        submitAction: this.fakeDeleteController.bind(this),
+        bodyTemplate: this.ctrlDescription
+      }
+    });
+  }
+
+  openModalDriven() {
+    this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+      initialState: {
+        submitActionObservable: this.fakeDelete(),
+        bodyTemplate: this.modalDescription
+      }
+    });
+  }
+
+  finish() {
+    this.finished = [6, 7, 8, 9];
+  }
+
+  fakeDelete() {
+    return (): Observable<any> => {
+      return new Observable((observer: Subscriber<any>) => {
+        observableTimer(100).subscribe(() => {
+          observer.next(this.finish());
+          observer.complete();
+        });
+      });
+    };
+  }
+
+  fakeDeleteController() {
+    observableTimer(100).subscribe(() => {
+      this.finish();
+      this.ctrlRef.hide();
+    });
+  }
+}
+
+describe('DeletionModalComponent', () => {
+  let mockComponent: MockComponent;
+  let component: CriticalConfirmationModalComponent;
+  let mockFixture: ComponentFixture<MockComponent>;
+  let fixture: ComponentFixture<CriticalConfirmationModalComponent>;
+
+  configureTestBed({
+    declarations: [MockComponent, CriticalConfirmationModalComponent],
+    schemas: [NO_ERRORS_SCHEMA],
+    imports: [ModalModule.forRoot(), ReactiveFormsModule, MockModule, DirectivesModule],
+    providers: [BsModalRef]
+  });
+
+  beforeEach(() => {
+    mockFixture = TestBed.createComponent(MockComponent);
+    mockComponent = mockFixture.componentInstance;
+    // Mocking the modals as a lot would be left over
+    spyOn(mockComponent.modalService, 'show').and.callFake((modalComp, config) => {
+      const ref = new BsModalRef();
+      fixture = TestBed.createComponent(CriticalConfirmationModalComponent);
+      component = fixture.componentInstance;
+      if (config.initialState) {
+        component = Object.assign(component, config.initialState);
+      }
+      fixture.detectChanges();
+      ref.content = component;
+      return ref;
+    });
+    mockComponent.openCtrlDriven();
+    mockFixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should focus the checkbox form field', () => {
+    fixture.detectChanges();
+    fixture.whenStable().then(() => {
+      const focused = fixture.debugElement.query(By.css(':focus'));
+      expect(focused.attributes.id).toBe('confirmation');
+      expect(focused.attributes.type).toBe('checkbox');
+      const element = document.getElementById('confirmation');
+      expect(element === document.activeElement).toBeTruthy();
+    });
+  });
+
+  it('should throw an error if no action is defined', () => {
+    component = Object.assign(component, {
+      submitAction: null,
+      submitActionObservable: null
+    });
+    expect(() => component.ngOnInit()).toThrowError('No submit action defined');
+  });
+
+  it('should test if the ctrl driven mock is set correctly through mock component', () => {
+    expect(component.bodyTemplate).toBeTruthy();
+    expect(component.submitAction).toBeTruthy();
+    expect(component.submitActionObservable).not.toBeTruthy();
+  });
+
+  it('should test if the modal driven mock is set correctly through mock component', () => {
+    mockComponent.openModalDriven();
+    expect(component.bodyTemplate).toBeTruthy();
+    expect(component.submitActionObservable).toBeTruthy();
+    expect(component.submitAction).not.toBeTruthy();
+  });
+
+  describe('component functions', () => {
+    const changeValue = (value) => {
+      const ctrl = component.deletionForm.get('confirmation');
+      ctrl.setValue(value);
+      ctrl.markAsDirty();
+      ctrl.updateValueAndValidity();
+      fixture.detectChanges();
+    };
+
+    it('should test hideModal', () => {
+      expect(component.modalRef).toBeTruthy();
+      expect(component.hideModal).toBeTruthy();
+      spyOn(component.modalRef, 'hide').and.callThrough();
+      expect(component.modalRef.hide).not.toHaveBeenCalled();
+      component.hideModal();
+      expect(component.modalRef.hide).toHaveBeenCalled();
+    });
+
+    describe('validate confirmation', () => {
+      const testValidation = (submitted: boolean, error: string, expected: boolean) => {
+        expect(
+          component.deletionForm.showError('confirmation', <NgForm>{ submitted: submitted }, error)
+        ).toBe(expected);
+      };
+
+      beforeEach(() => {
+        component.deletionForm.reset();
+      });
+
+      it('should test empty values', () => {
+        component.deletionForm.reset();
+        testValidation(false, undefined, false);
+        testValidation(true, 'required', true);
+        component.deletionForm.reset();
+        changeValue(true);
+        changeValue(false);
+        testValidation(true, 'required', true);
+      });
+    });
+
+    describe('deletion call', () => {
+      beforeEach(() => {
+        spyOn(component, 'stopLoadingSpinner').and.callThrough();
+        spyOn(component, 'hideModal').and.callThrough();
+      });
+
+      describe('Controller driven', () => {
+        beforeEach(() => {
+          spyOn(component, 'submitAction').and.callThrough();
+          spyOn(mockComponent.ctrlRef, 'hide').and.callThrough();
+        });
+
+        it('should test fake deletion that closes modal', <any>fakeAsync(() => {
+          // Before deletionCall
+          expect(component.submitAction).not.toHaveBeenCalled();
+          // During deletionCall
+          component.callSubmitAction();
+          expect(component.stopLoadingSpinner).not.toHaveBeenCalled();
+          expect(component.hideModal).not.toHaveBeenCalled();
+          expect(mockComponent.ctrlRef.hide).not.toHaveBeenCalled();
+          expect(component.submitAction).toHaveBeenCalled();
+          expect(mockComponent.finished).toBe(undefined);
+          // After deletionCall
+          tick(2000);
+          expect(component.hideModal).not.toHaveBeenCalled();
+          expect(mockComponent.ctrlRef.hide).toHaveBeenCalled();
+          expect(mockComponent.finished).toEqual([6, 7, 8, 9]);
+        }));
+      });
+
+      describe('Modal driven', () => {
+        beforeEach(() => {
+          mockComponent.openModalDriven();
+          spyOn(component, 'stopLoadingSpinner').and.callThrough();
+          spyOn(component, 'hideModal').and.callThrough();
+          spyOn(mockComponent, 'fakeDelete').and.callThrough();
+        });
+
+        it('should delete and close modal', <any>fakeAsync(() => {
+          // During deletionCall
+          component.callSubmitAction();
+          expect(mockComponent.finished).toBe(undefined);
+          expect(component.hideModal).not.toHaveBeenCalled();
+          // After deletionCall
+          tick(2000);
+          expect(mockComponent.finished).toEqual([6, 7, 8, 9]);
+          expect(component.stopLoadingSpinner).not.toHaveBeenCalled();
+          expect(component.hideModal).toHaveBeenCalled();
+        }));
+      });
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts
new file mode 100644 (file)
index 0000000..27d39f3
--- /dev/null
@@ -0,0 +1,57 @@
+import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { FormControl, Validators } from '@angular/forms';
+
+import { BsModalRef } from 'ngx-bootstrap/modal';
+import { Observable } from 'rxjs';
+
+import { CdFormGroup } from '../../forms/cd-form-group';
+import { SubmitButtonComponent } from '../submit-button/submit-button.component';
+
+@Component({
+  selector: 'cd-deletion-modal',
+  templateUrl: './critical-confirmation-modal.component.html',
+  styleUrls: ['./critical-confirmation-modal.component.scss']
+})
+export class CriticalConfirmationModalComponent implements OnInit {
+  @ViewChild(SubmitButtonComponent)
+  submitButton: SubmitButtonComponent;
+  bodyTemplate: TemplateRef<any>;
+  bodyContext: any;
+  submitActionObservable: () => Observable<any>;
+  submitAction: Function;
+  deletionForm: CdFormGroup;
+  itemDescription: 'entry';
+  actionDescription = 'delete';
+
+  constructor(public modalRef: BsModalRef) {}
+
+  ngOnInit() {
+    this.deletionForm = new CdFormGroup({
+      confirmation: new FormControl(false, [Validators.requiredTrue])
+    });
+
+    if (!(this.submitAction || this.submitActionObservable)) {
+      throw new Error('No submit action defined');
+    }
+  }
+
+  callSubmitAction() {
+    if (this.submitActionObservable) {
+      this.submitActionObservable().subscribe(
+        null,
+        this.stopLoadingSpinner.bind(this),
+        this.hideModal.bind(this)
+      );
+    } else {
+      this.submitAction();
+    }
+  }
+
+  hideModal() {
+    this.modalRef.hide();
+  }
+
+  stopLoadingSpinner() {
+    this.deletionForm.setErrors({ cdSubmitButton: true });
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.html
deleted file mode 100644 (file)
index d12d900..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<cd-modal #modal
-          [modalRef]="modalRef">
-  <ng-container class="modal-title">
-    <ng-container *ngTemplateOutlet="deletionHeading"></ng-container>
-  </ng-container>
-
-  <ng-container class="modal-content">
-    <form name="deletionForm"
-          #formDir="ngForm"
-          [formGroup]="deletionForm"
-          novalidate>
-      <div class="modal-body">
-        <ng-container *ngTemplateOutlet="bodyTemplate; context: bodyContext"></ng-container>
-        <div class="question">
-          <p i18n>
-            Are you sure that you want to {{ actionDescription | lowercase }} the selected {{ itemDescription }}?
-          </p>
-          <div class="form-group"
-               [ngClass]="{'has-error': deletionForm.showError('confirmation', formDir)}">
-            <div class="checkbox checkbox-primary">
-              <input type="checkbox"
-                     name="confirmation"
-                     id="confirmation"
-                     formControlName="confirmation"
-                     autofocus>
-              <label i18n
-                     for="confirmation">
-                Yes, I am sure.
-              </label>
-            </div>
-          </div>
-        </div>
-      </div>
-      <div class="modal-footer">
-        <cd-submit-button #submitButton
-                          [form]="deletionForm"
-                          (submitAction)="callSubmitAction()">
-          <ng-container *ngTemplateOutlet="deletionHeading"></ng-container>
-        </cd-submit-button>
-        <button class="btn btn-link btn-sm"
-                (click)="hideModal()"
-                i18n>
-          Cancel
-        </button>
-      </div>
-    </form>
-  </ng-container>
-</cd-modal>
-
-<ng-template #deletionHeading>
-  <ng-container i18n>
-    {{ actionDescription | titlecase }} {{ itemDescription }}
-  </ng-container>
-</ng-template>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.scss
deleted file mode 100644 (file)
index aff598d..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-.modal-body .question {
-  font-weight: bold;
-}
-.modal-body .question .checkbox {
-  padding-top: 7px;
-}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.spec.ts
deleted file mode 100644 (file)
index 3115a6a..0000000
+++ /dev/null
@@ -1,254 +0,0 @@
-import { Component, NgModule, NO_ERRORS_SCHEMA, TemplateRef, ViewChild } from '@angular/core';
-import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
-import { NgForm, ReactiveFormsModule } from '@angular/forms';
-import { By } from '@angular/platform-browser';
-
-import { BsModalRef, BsModalService, ModalModule } from 'ngx-bootstrap/modal';
-import { Observable, Subscriber, timer as observableTimer } from 'rxjs';
-
-import { configureTestBed } from '../../../../testing/unit-test-helper';
-import { DirectivesModule } from '../../directives/directives.module';
-import { DeletionModalComponent } from './deletion-modal.component';
-
-@NgModule({
-  entryComponents: [DeletionModalComponent]
-})
-export class MockModule {}
-
-@Component({
-  template: `
-    <button type="button"
-        class="btn btn-sm btn-primary"
-        (click)="openCtrlDriven()">
-      <i class="fa fa-fw fa-times"></i>Deletion Ctrl-Test
-      <ng-template #ctrlDescription>
-        The spinner is handled by the controller if you have use the modal as ViewChild in order to
-        use it's functions to stop the spinner or close the dialog.
-      </ng-template>
-    </button>
-
-    <button type="button"
-            class="btn btn-sm btn-primary"
-            (click)="openModalDriven()">
-      <i class="fa fa-fw fa-times"></i>Deletion Modal-Test
-      <ng-template #modalDescription>
-        The spinner is handled by the modal if your given deletion function returns a Observable.
-      </ng-template>
-    </button>
-  `
-})
-class MockComponent {
-  @ViewChild('ctrlDescription')
-  ctrlDescription: TemplateRef<any>;
-  @ViewChild('modalDescription')
-  modalDescription: TemplateRef<any>;
-  someData = [1, 2, 3, 4, 5];
-  finished: number[];
-  ctrlRef: BsModalRef;
-  modalRef: BsModalRef;
-
-  // Normally private - public was needed for the tests
-  constructor(public modalService: BsModalService) {}
-
-  openCtrlDriven() {
-    this.ctrlRef = this.modalService.show(DeletionModalComponent, {
-      initialState: {
-        submitAction: this.fakeDeleteController.bind(this),
-        bodyTemplate: this.ctrlDescription
-      }
-    });
-  }
-
-  openModalDriven() {
-    this.modalRef = this.modalService.show(DeletionModalComponent, {
-      initialState: {
-        submitActionObservable: this.fakeDelete(),
-        bodyTemplate: this.modalDescription
-      }
-    });
-  }
-
-  finish() {
-    this.finished = [6, 7, 8, 9];
-  }
-
-  fakeDelete() {
-    return (): Observable<any> => {
-      return new Observable((observer: Subscriber<any>) => {
-        observableTimer(100).subscribe(() => {
-          observer.next(this.finish());
-          observer.complete();
-        });
-      });
-    };
-  }
-
-  fakeDeleteController() {
-    observableTimer(100).subscribe(() => {
-      this.finish();
-      this.ctrlRef.hide();
-    });
-  }
-}
-
-describe('DeletionModalComponent', () => {
-  let mockComponent: MockComponent;
-  let component: DeletionModalComponent;
-  let mockFixture: ComponentFixture<MockComponent>;
-  let fixture: ComponentFixture<DeletionModalComponent>;
-
-  configureTestBed({
-    declarations: [MockComponent, DeletionModalComponent],
-    schemas: [NO_ERRORS_SCHEMA],
-    imports: [ModalModule.forRoot(), ReactiveFormsModule, MockModule, DirectivesModule],
-    providers: [BsModalRef]
-  });
-
-  beforeEach(() => {
-    mockFixture = TestBed.createComponent(MockComponent);
-    mockComponent = mockFixture.componentInstance;
-    // Mocking the modals as a lot would be left over
-    spyOn(mockComponent.modalService, 'show').and.callFake((modalComp, config) => {
-      const ref = new BsModalRef();
-      fixture = TestBed.createComponent(DeletionModalComponent);
-      component = fixture.componentInstance;
-      if (config.initialState) {
-        component = Object.assign(component, config.initialState);
-      }
-      fixture.detectChanges();
-      ref.content = component;
-      return ref;
-    });
-    mockComponent.openCtrlDriven();
-    mockFixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-
-  it('should focus the checkbox form field', () => {
-    fixture.detectChanges();
-    fixture.whenStable().then(() => {
-      const focused = fixture.debugElement.query(By.css(':focus'));
-      expect(focused.attributes.id).toBe('confirmation');
-      expect(focused.attributes.type).toBe('checkbox');
-      const element = document.getElementById('confirmation');
-      expect(element === document.activeElement).toBeTruthy();
-    });
-  });
-
-  it('should throw an error if no action is defined', () => {
-    component = Object.assign(component, {
-      submitAction: null,
-      submitActionObservable: null
-    });
-    expect(() => component.ngOnInit()).toThrowError('No submit action defined');
-  });
-
-  it('should test if the ctrl driven mock is set correctly through mock component', () => {
-    expect(component.bodyTemplate).toBeTruthy();
-    expect(component.submitAction).toBeTruthy();
-    expect(component.submitActionObservable).not.toBeTruthy();
-  });
-
-  it('should test if the modal driven mock is set correctly through mock component', () => {
-    mockComponent.openModalDriven();
-    expect(component.bodyTemplate).toBeTruthy();
-    expect(component.submitActionObservable).toBeTruthy();
-    expect(component.submitAction).not.toBeTruthy();
-  });
-
-  describe('component functions', () => {
-    const changeValue = (value) => {
-      const ctrl = component.deletionForm.get('confirmation');
-      ctrl.setValue(value);
-      ctrl.markAsDirty();
-      ctrl.updateValueAndValidity();
-      fixture.detectChanges();
-    };
-
-    it('should test hideModal', () => {
-      expect(component.modalRef).toBeTruthy();
-      expect(component.hideModal).toBeTruthy();
-      spyOn(component.modalRef, 'hide').and.callThrough();
-      expect(component.modalRef.hide).not.toHaveBeenCalled();
-      component.hideModal();
-      expect(component.modalRef.hide).toHaveBeenCalled();
-    });
-
-    describe('validate confirmation', () => {
-      const testValidation = (submitted: boolean, error: string, expected: boolean) => {
-        expect(
-          component.deletionForm.showError('confirmation', <NgForm>{ submitted: submitted }, error)
-        ).toBe(expected);
-      };
-
-      beforeEach(() => {
-        component.deletionForm.reset();
-      });
-
-      it('should test empty values', () => {
-        component.deletionForm.reset();
-        testValidation(false, undefined, false);
-        testValidation(true, 'required', true);
-        component.deletionForm.reset();
-        changeValue(true);
-        changeValue(false);
-        testValidation(true, 'required', true);
-      });
-    });
-
-    describe('deletion call', () => {
-      beforeEach(() => {
-        spyOn(component, 'stopLoadingSpinner').and.callThrough();
-        spyOn(component, 'hideModal').and.callThrough();
-      });
-
-      describe('Controller driven', () => {
-        beforeEach(() => {
-          spyOn(component, 'submitAction').and.callThrough();
-          spyOn(mockComponent.ctrlRef, 'hide').and.callThrough();
-        });
-
-        it('should test fake deletion that closes modal', <any>fakeAsync(() => {
-          // Before deletionCall
-          expect(component.submitAction).not.toHaveBeenCalled();
-          // During deletionCall
-          component.callSubmitAction();
-          expect(component.stopLoadingSpinner).not.toHaveBeenCalled();
-          expect(component.hideModal).not.toHaveBeenCalled();
-          expect(mockComponent.ctrlRef.hide).not.toHaveBeenCalled();
-          expect(component.submitAction).toHaveBeenCalled();
-          expect(mockComponent.finished).toBe(undefined);
-          // After deletionCall
-          tick(2000);
-          expect(component.hideModal).not.toHaveBeenCalled();
-          expect(mockComponent.ctrlRef.hide).toHaveBeenCalled();
-          expect(mockComponent.finished).toEqual([6, 7, 8, 9]);
-        }));
-      });
-
-      describe('Modal driven', () => {
-        beforeEach(() => {
-          mockComponent.openModalDriven();
-          spyOn(component, 'stopLoadingSpinner').and.callThrough();
-          spyOn(component, 'hideModal').and.callThrough();
-          spyOn(mockComponent, 'fakeDelete').and.callThrough();
-        });
-
-        it('should delete and close modal', <any>fakeAsync(() => {
-          // During deletionCall
-          component.callSubmitAction();
-          expect(mockComponent.finished).toBe(undefined);
-          expect(component.hideModal).not.toHaveBeenCalled();
-          // After deletionCall
-          tick(2000);
-          expect(mockComponent.finished).toEqual([6, 7, 8, 9]);
-          expect(component.stopLoadingSpinner).not.toHaveBeenCalled();
-          expect(component.hideModal).toHaveBeenCalled();
-        }));
-      });
-    });
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/deletion-modal/deletion-modal.component.ts
deleted file mode 100644 (file)
index 4b66795..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
-import { FormControl, Validators } from '@angular/forms';
-
-import { BsModalRef } from 'ngx-bootstrap/modal';
-import { Observable } from 'rxjs';
-
-import { CdFormGroup } from '../../forms/cd-form-group';
-import { SubmitButtonComponent } from '../submit-button/submit-button.component';
-
-@Component({
-  selector: 'cd-deletion-modal',
-  templateUrl: './deletion-modal.component.html',
-  styleUrls: ['./deletion-modal.component.scss']
-})
-export class DeletionModalComponent implements OnInit {
-  @ViewChild(SubmitButtonComponent)
-  submitButton: SubmitButtonComponent;
-  bodyTemplate: TemplateRef<any>;
-  bodyContext: any;
-  submitActionObservable: () => Observable<any>;
-  submitAction: Function;
-  deletionForm: CdFormGroup;
-  itemDescription: 'entry';
-  actionDescription = 'delete';
-
-  constructor(public modalRef: BsModalRef) {}
-
-  ngOnInit() {
-    this.deletionForm = new CdFormGroup({
-      confirmation: new FormControl(false, [Validators.requiredTrue])
-    });
-
-    if (!(this.submitAction || this.submitActionObservable)) {
-      throw new Error('No submit action defined');
-    }
-  }
-
-  callSubmitAction() {
-    if (this.submitActionObservable) {
-      this.submitActionObservable().subscribe(
-        null,
-        this.stopLoadingSpinner.bind(this),
-        this.hideModal.bind(this)
-      );
-    } else {
-      this.submitAction();
-    }
-  }
-
-  hideModal() {
-    this.modalRef.hide();
-  }
-
-  stopLoadingSpinner() {
-    this.deletionForm.setErrors({ cdSubmitButton: true });
-  }
-}