]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Merge disable and disableDesc 37763/head
authorTiago Melo <tmelo@suse.com>
Fri, 24 Jul 2020 15:59:35 +0000 (15:59 +0000)
committerKiefer Chang <kiefer.chang@suse.com>
Fri, 20 Nov 2020 05:16:00 +0000 (13:16 +0800)
Fixes: https://tracker.ceph.com/issues/46750
Signed-off-by: Tiago Melo <tmelo@suse.com>
(cherry picked from commit 2f1c977ec6d8a25b4e5cf7ce7f3155eac69761a6)

 Conflicts:
     src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts
     src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-namespace-list/rbd-namespace-list.component.ts
     src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-actions.model.ts
     src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts
     src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.ts
     src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts
     - `$localize` calls are not available in Angular 8. They are replaced with i18n.
     - Optional chaining syntax is not supported in typescript 3.5.3. Statements with optional chaining are re-coded.

14 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.spec.ts
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-namespace-list/rbd-namespace-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-actions.model.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-action.ts

index 16ff57abd2319d381a0a06540ee5bb33b9e3cceb..a9b4a8f07b911106338017ff093884fea43b8632 100644 (file)
@@ -195,8 +195,7 @@ describe('IscsiTargetListComponent', () => {
             id: '-1'
           }
         ];
-        expect(action.disable(undefined)).toBeTruthy();
-        expect(action.disableDesc(undefined)).toBe('Unavailable gateway(s)');
+        expect(action.disable(undefined)).toBe('Unavailable gateway(s)');
       });
 
       it('should be enabled if active sessions', () => {
@@ -209,7 +208,6 @@ describe('IscsiTargetListComponent', () => {
           }
         ];
         expect(action.disable(undefined)).toBeFalsy();
-        expect(action.disableDesc(undefined)).toBeUndefined();
       });
 
       it('should be enabled if no active sessions', () => {
@@ -222,7 +220,6 @@ describe('IscsiTargetListComponent', () => {
           }
         ];
         expect(action.disable(undefined)).toBeFalsy();
-        expect(action.disableDesc(undefined)).toBeUndefined();
       });
     });
 
@@ -237,8 +234,7 @@ describe('IscsiTargetListComponent', () => {
             id: '-1'
           }
         ];
-        expect(action.disable(undefined)).toBeTruthy();
-        expect(action.disableDesc(undefined)).toBe('Unavailable gateway(s)');
+        expect(action.disable(undefined)).toBe('Unavailable gateway(s)');
       });
 
       it('should be disabled if active sessions', () => {
@@ -250,8 +246,7 @@ describe('IscsiTargetListComponent', () => {
             }
           }
         ];
-        expect(action.disable(undefined)).toBeTruthy();
-        expect(action.disableDesc(undefined)).toBe('Target has active sessions');
+        expect(action.disable(undefined)).toBe('Target has active sessions');
       });
 
       it('should be enabled if no active sessions', () => {
@@ -264,7 +259,6 @@ describe('IscsiTargetListComponent', () => {
           }
         ];
         expect(action.disable(undefined)).toBeFalsy();
-        expect(action.disableDesc(undefined)).toBeUndefined();
       });
     });
   });
index 15e4341e81e8f31dd4a9aeac662200f6325d8e1d..09bc4d288d1040b46d1d9ff263ce9e894f3e215d 100644 (file)
@@ -82,16 +82,14 @@ export class IscsiTargetListComponent extends ListWithDetails implements OnInit,
         icon: Icons.edit,
         routerLink: () => `/block/iscsi/targets/edit/${this.selection.first().target_iqn}`,
         name: this.actionLabels.EDIT,
-        disable: () => !this.selection.first() || !_.isUndefined(this.getEditDisableDesc()),
-        disableDesc: () => this.getEditDisableDesc()
+        disable: () => this.getEditDisableDesc()
       },
       {
         permission: 'delete',
         icon: Icons.destroy,
         click: () => this.deleteIscsiTargetModal(),
         name: this.actionLabels.DELETE,
-        disable: () => !this.selection.first() || !_.isUndefined(this.getDeleteDisableDesc()),
-        disableDesc: () => this.getDeleteDisableDesc()
+        disable: () => this.getDeleteDisableDesc()
       }
     ];
   }
@@ -156,8 +154,9 @@ export class IscsiTargetListComponent extends ListWithDetails implements OnInit,
     }
   }
 
-  getEditDisableDesc(): string | undefined {
+  getEditDisableDesc(): string | boolean {
     const first = this.selection.first();
+
     if (first && first.cdExecuting) {
       return first.cdExecuting;
     }
@@ -165,11 +164,12 @@ export class IscsiTargetListComponent extends ListWithDetails implements OnInit,
       return this.i18n('Unavailable gateway(s)');
     }
 
-    return undefined;
+    return !first;
   }
 
-  getDeleteDisableDesc(): string | undefined {
+  getDeleteDisableDesc(): string | boolean {
     const first = this.selection.first();
+
     if (first && first.cdExecuting) {
       return first.cdExecuting;
     }
@@ -180,7 +180,7 @@ export class IscsiTargetListComponent extends ListWithDetails implements OnInit,
       return this.i18n('Target has active sessions');
     }
 
-    return undefined;
+    return !first;
   }
 
   prepareResponse(resp: any): any[] {
index f9ad4cf7cb74b797f63258510bd7114717de232d..87f8e78f994fec3048ae86a8f8cc6903e3e0be4a 100644 (file)
@@ -138,7 +138,7 @@ describe('RbdListComponent', () => {
           }
         ]
       });
-      expect(component.getDeleteDisableDesc()).toBe(
+      expect(component.getDeleteDisableDesc(component.selection)).toBe(
         'This RBD has cloned snapshots. Please delete related RBDs before deleting this RBD.'
       );
     });
index 18c95ec1e236975a547c0b5b4d9efc0969c682df..da53bb3fd549a201beb2787176dc7fdbfc3c659c 100644 (file)
@@ -138,11 +138,7 @@ export class RbdListComponent extends ListWithDetails implements OnInit {
       icon: Icons.destroy,
       click: () => this.deleteRbdModal(),
       name: this.actionLabels.DELETE,
-      disable: (selection: CdTableSelection) =>
-        !this.selection.first() ||
-        !this.selection.hasSingleSelection ||
-        this.hasClonedSnapshots(selection.first()),
-      disableDesc: () => this.getDeleteDisableDesc()
+      disable: (selection: CdTableSelection) => this.getDeleteDisableDesc(selection)
     };
     const copyAction: CdTableAction = {
       permission: 'create',
@@ -430,14 +426,19 @@ export class RbdListComponent extends ListWithDetails implements OnInit {
     }, []);
   }
 
-  getDeleteDisableDesc(): string {
-    const first = this.selection.first();
+  getDeleteDisableDesc(selection: CdTableSelection): string | boolean {
+    const first = selection.first();
+
     if (first && this.hasClonedSnapshots(first)) {
       return this.i18n(
         'This RBD has cloned snapshots. Please delete related RBDs before deleting this RBD.'
       );
     }
 
-    return '';
+    return (
+      !selection.first() ||
+      !selection.hasSingleSelection ||
+      this.hasClonedSnapshots(selection.first())
+    );
   }
 }
index 80b0945f65d58a402671534267f749fddcdaa9de..82038162c78e413c102f83b5b7ca04d1873fb103 100644 (file)
@@ -55,8 +55,7 @@ export class RbdNamespaceListComponent implements OnInit {
       icon: Icons.destroy,
       click: () => this.deleteModal(),
       name: this.actionLabels.DELETE,
-      disable: () => !this.selection.first() || !_.isUndefined(this.getDeleteDisableDesc()),
-      disableDesc: () => this.getDeleteDisableDesc()
+      disable: () => this.getDeleteDisableDesc()
     };
     this.tableActions = [createAction, deleteAction];
   }
@@ -153,14 +152,14 @@ export class RbdNamespaceListComponent implements OnInit {
     });
   }
 
-  getDeleteDisableDesc(): string | undefined {
+  getDeleteDisableDesc(): string | boolean {
     const first = this.selection.first();
     if (first) {
       if (first.num_images > 0) {
         return this.i18n('Namespace contains images');
       }
+      return false;
     }
-
-    return undefined;
+    return true;
   }
 }
index 3086a1a5a2acfd72da79b1ea180d737e93b8cfd4..f2ac418ac85a93d1186fa378f92df585ef80fb0c 100644 (file)
@@ -49,11 +49,7 @@ export class RbdSnapshotActionsModel {
     this.clone = {
       permission: 'create',
       canBePrimary: (selection: CdTableSelection) => selection.hasSingleSelection,
-      disable: (selection: CdTableSelection) =>
-        !selection.hasSingleSelection ||
-        selection.first().cdExecuting ||
-        !_.isUndefined(this.getCloneDisableDesc(featuresName)),
-      disableDesc: () => this.getCloneDisableDesc(featuresName),
+      disable: (selection: CdTableSelection) => this.getCloneDisableDesc(selection, featuresName),
       icon: Icons.clone,
       name: actionLabels.CLONE
     };
@@ -92,11 +88,15 @@ export class RbdSnapshotActionsModel {
     ];
   }
 
-  getCloneDisableDesc(featuresName: string[]): string | undefined {
-    if (!featuresName.includes('layering')) {
-      return this.i18n('Parent image must support Layering');
+  getCloneDisableDesc(selection: CdTableSelection, featuresName: string[]): boolean | string {
+    if (selection && selection.hasSingleSelection && !selection.first().cdExecuting) {
+      if (!_.includes(featuresName, 'layering')) {
+        return this.i18n('Parent image must support Layering');
+      }
+
+      return false;
     }
 
-    return undefined;
+    return true;
   }
 }
index 695898c25ae50e1e567e8d30f79f2914973e4e79..7efb4ed9bb95d374c23dfe71dfe019c5315831e8 100644 (file)
@@ -116,12 +116,12 @@ describe('HostsComponent', () => {
       );
     });
 
-    it('should disable button and return undefined (no selection)', () => {
+    it('should disable button and return true (no selection)', () => {
       expect(tableAction.disable(component.selection)).toBeTruthy();
-      expect(component.getEditDisableDesc(component.selection)).toBeUndefined();
+      expect(component.getEditDisableDesc(component.selection)).toBeTruthy();
     });
 
-    it('should enable button and return undefined (managed by Orchestrator)', () => {
+    it('should enable button and return false (managed by Orchestrator)', () => {
       component.selection.add({
         sources: {
           ceph: false,
@@ -129,7 +129,7 @@ describe('HostsComponent', () => {
         }
       });
       expect(tableAction.disable(component.selection)).toBeFalsy();
-      expect(component.getEditDisableDesc(component.selection)).toBeUndefined();
+      expect(component.getEditDisableDesc(component.selection)).toBeFalsy();
     });
   });
 
@@ -152,11 +152,11 @@ describe('HostsComponent', () => {
       );
     });
 
-    it('should return undefined (no selection)', () => {
-      expect(component.getDeleteDisableDesc(component.selection)).toBeUndefined();
+    it('should return true (no selection)', () => {
+      expect(component.getDeleteDisableDesc(component.selection)).toBeTruthy();
     });
 
-    it('should return undefined (managed by Orchestrator)', () => {
+    it('should return false (managed by Orchestrator)', () => {
       component.selection.add({
         sources: {
           ceph: false,
@@ -169,7 +169,7 @@ describe('HostsComponent', () => {
           orchestrator: true
         }
       });
-      expect(component.getDeleteDisableDesc(component.selection)).toBeUndefined();
+      expect(component.getDeleteDisableDesc(component.selection)).toBeFalsy();
     });
   });
 });
index 52414bdf3ef0ceed7ce0a709cf5d8ed4ca578116..ff97846943c29e6e721e21daac033caac3fe6ad4 100644 (file)
@@ -92,9 +92,7 @@ export class HostsComponent extends ListWithDetails implements OnInit {
             () => this.editAction()
           );
         },
-        disable: (selection: CdTableSelection) =>
-          !selection.hasSingleSelection || !selection.first().sources.orchestrator,
-        disableDesc: this.getEditDisableDesc.bind(this)
+        disable: this.getEditDisableDesc.bind(this)
       },
       {
         name: this.actionLabels.DELETE,
@@ -107,10 +105,7 @@ export class HostsComponent extends ListWithDetails implements OnInit {
             () => this.deleteAction()
           );
         },
-        disable: (selection: CdTableSelection) =>
-          !selection.hasSelection ||
-          !selection.selected.every((selected) => selected.sources.orchestrator),
-        disableDesc: this.getDeleteDisableDesc.bind(this)
+        disable: this.getDeleteDisableDesc.bind(this)
       }
     ];
   }
@@ -192,13 +187,18 @@ export class HostsComponent extends ListWithDetails implements OnInit {
     });
   }
 
-  getEditDisableDesc(selection: CdTableSelection): string | undefined {
-    if (selection && selection.hasSingleSelection && !selection.first().sources.orchestrator) {
-      return this.i18n(
-        'Host editing is disabled because the selected host is not managed by Orchestrator.'
-      );
+  getEditDisableDesc(selection: CdTableSelection): boolean | string {
+    if (selection && selection.hasSingleSelection) {
+      if (!selection.first().sources.orchestrator) {
+        return this.i18n(
+          'Host editing is disabled because the selected host is not managed by Orchestrator.'
+        );
+      }
+
+      return false;
     }
-    return undefined;
+
+    return true;
   }
 
   deleteAction() {
@@ -217,17 +217,18 @@ export class HostsComponent extends ListWithDetails implements OnInit {
     });
   }
 
-  getDeleteDisableDesc(selection: CdTableSelection): string | undefined {
-    if (
-      selection &&
-      selection.hasSelection &&
-      !selection.selected.every((selected) => selected.sources.orchestrator)
-    ) {
-      return this.i18n(
-        'Host deletion is disabled because a selected host is not managed by Orchestrator.'
-      );
+  getDeleteDisableDesc(selection: CdTableSelection): boolean | string {
+    if (selection && selection.hasSelection) {
+      if (!selection.selected.every((selected) => selected.sources.orchestrator)) {
+        return this.i18n(
+          'Host deletion is disabled because a selected host is not managed by Orchestrator.'
+        );
+      }
+
+      return false;
     }
-    return undefined;
+
+    return true;
   }
 
   getHosts(context: CdTableFetchDataContext) {
index ea9214a6deb71d295223462c26242263557cbe8f..9c8fcf09e5eeaa1364e806ccee7252287f08e09b 100644 (file)
@@ -90,8 +90,7 @@ export class MgrModuleListComponent extends ListWithDetails {
         name: this.i18n('Disable'),
         permission: 'update',
         click: () => this.updateModuleState(),
-        disable: () => this.isTableActionDisabled('disabled'),
-        disableDesc: () => this.getTableActionDisabledDesc(),
+        disable: () => this.getTableActionDisabledDesc(),
         icon: Icons.stop
       }
     ];
@@ -141,15 +140,12 @@ export class MgrModuleListComponent extends ListWithDetails {
     }
   }
 
-  getTableActionDisabledDesc(): string | undefined {
-    if (this.selection.hasSelection) {
-      const selected = this.selection.first();
-      if (selected.always_on) {
-        return this.i18n('This Manager module is always on.');
-      }
+  getTableActionDisabledDesc(): string | boolean {
+    if (this.selection && this.selection.first().always_on) {
+      return this.i18n('This Manager module is always on.');
     }
 
-    return undefined;
+    return this.isTableActionDisabled('disabled');
   }
 
   /**
index 165ed7f34e83e9d0b852e84f4d649bf824d35c65..31e5ca8cbc76e93728539cd73b78bea5676627a9 100644 (file)
@@ -479,6 +479,10 @@ describe('PoolListComponent', () => {
   });
 
   describe('getDisableDesc', () => {
+    beforeEach(() => {
+      component.selection.selected = [{ pool_name: 'foo' }];
+    });
+
     it('should return message if mon_allow_pool_delete flag is set to false', () => {
       component.monAllowPoolDelete = false;
       expect(component.getDisableDesc()).toBe(
@@ -486,9 +490,9 @@ describe('PoolListComponent', () => {
       );
     });
 
-    it('should return undefined if mon_allow_pool_delete flag is set to true', () => {
+    it('should return false if mon_allow_pool_delete flag is set to true', () => {
       component.monAllowPoolDelete = true;
-      expect(component.getDisableDesc()).toBeUndefined();
+      expect(component.getDisableDesc()).toBeFalsy();
     });
   });
 });
index 93958f1b5339eb61f79e04cacb3afe8dee07404e..2f35cf8a97f8c89d0136372a7c4314c97931e269 100644 (file)
@@ -93,8 +93,7 @@ export class PoolListComponent extends ListWithDetails implements OnInit {
         icon: Icons.destroy,
         click: () => this.deletePoolModal(),
         name: this.actionLabels.DELETE,
-        disable: () => !this.selection.first() || !this.monAllowPoolDelete,
-        disableDesc: () => this.getDisableDesc()
+        disable: this.getDisableDesc.bind(this)
       }
     ];
 
@@ -294,14 +293,18 @@ export class PoolListComponent extends ListWithDetails implements OnInit {
     }
   }
 
-  getDisableDesc(): string | undefined {
-    if (!this.monAllowPoolDelete) {
-      return this.i18n(
-        'Pool deletion is disabled by the mon_allow_pool_delete configuration setting.'
-      );
+  getDisableDesc(): boolean | string {
+    if (this.selection && this.selection.hasSelection) {
+      if (!this.monAllowPoolDelete) {
+        return this.i18n(
+          'Pool deletion is disabled by the mon_allow_pool_delete configuration setting.'
+        );
+      }
+
+      return false;
     }
 
-    return undefined;
+    return true;
   }
 
   setExpandedRow(expandedRow: any) {
index 2593a59b2bea74643c518cd4f4bad704c0445cce..e7b2b73ee6a5c9a79e28e07f1c231d2d5e24aced 100644 (file)
@@ -163,12 +163,12 @@ describe('TableActionsComponent', () => {
   });
 
   describe('useDisableDesc', () => {
-    it('should return a description if disableDesc is set for action', () => {
+    it('should return a description if disable method returns a string', () => {
       const deleteWithDescAction: CdTableAction = {
         permission: 'delete',
         icon: 'fa-times',
         canBePrimary: (selection: CdTableSelection) => selection.hasSelection,
-        disableDesc: () => {
+        disable: () => {
           return 'Delete action disabled description';
         },
         name: 'DeleteDesc'
@@ -179,7 +179,7 @@ describe('TableActionsComponent', () => {
       );
     });
 
-    it('should return no description if disableDesc is not set for action', () => {
+    it('should return no description if disable does not return string', () => {
       expect(component.useDisableDesc(deleteAction)).toBeUndefined();
     });
   });
index 334f5563eeb5f770e8f5f0fe0e9b895970544f00..baf5761510fc7b5cb366d01a7d00b033c7efb9b6 100644 (file)
@@ -148,6 +148,11 @@ export class TableActionsComponent implements OnInit {
   }
 
   useDisableDesc(action: CdTableAction) {
-    return action.disableDesc && action.disableDesc(this.selection);
+    if (action.disable) {
+      const result = action.disable(this.selection);
+      return _.isString(result) ? result : undefined;
+    }
+
+    return undefined;
   }
 }
index 9af10625ae65bd3226dfeec3ff4cf6b5cee5660e..70f06e506c365637d645eb8a7f6e3c835ff8520c 100644 (file)
@@ -19,17 +19,15 @@ export class CdTableAction {
   // The font awesome icon that will be used
   icon: string;
 
-  // You can define the condition to disable the action.
-  // By default all 'update' and 'delete' actions will only be enabled
-  // if one selection is made and no task is running on the selected item.
-  disable?: (_: CdTableSelection) => boolean;
-
   /**
+   * You can define the condition to disable the action.
+   * By default all 'update' and 'delete' actions will only be enabled
+   * if one selection is made and no task is running on the selected item.`
+   *
    * In some cases you might want to give the user a hint why a button is
-   * disabled. The specified message will be shown to the user as a button
-   * tooltip.
-   */
-  disableDesc?: (_: CdTableSelection) => string | undefined;
+   * disabled. This is achieved by returning a string.
+   * */
+  disable?: (_: CdTableSelection) => boolean | string;
 
   /**
    * Defines if the button can become 'primary' (displayed as button and not