]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Add default state when gateway groups are empty 63245/head
authorAfreen Misbah <afreen@ibm.com>
Thu, 8 May 2025 04:09:59 +0000 (09:39 +0530)
committerAfreen Misbah <afreen@ibm.com>
Mon, 12 May 2025 19:11:36 +0000 (00:41 +0530)
Fixes https://tracker.ceph.com/issues/71247

- after upgrades the nvmeof service spec does not contain `group` field
- this causes UI combobox internal errors
- checking for `group` in spec and disabling the selector

Signed-off-by: Afreen Misbah <afreen@ibm.com>
(cherry picked from commit 9a7c907bfc2345a79ecc1f850cc851f21f74fca5)

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts

index 5e3c6b2af107fb816de20d231b58e76555a5cad0..4778764d97636ddc6ee1c4acdcd313a739643f5e 100644 (file)
@@ -6,11 +6,12 @@
   <cds-combo-box
       type="single"
       label="Selected Gateway Group"
-      i18n-placeholder
-      placeholder="Enter group"
+      i18n-label
+      [placeholder]="gwGroupPlaceholder"
       [items]="gwGroups"
       (selected)="onGroupSelection($event)"
-      (clear)="onGroupClear()">
+      (clear)="onGroupClear()"
+      [disabled]="gwGroupsEmpty">
     <cds-dropdown-list></cds-dropdown-list>
   </cds-combo-box>
 </div>
index 1c8bf5485661be2f608681e302c51d61978e73b5..f4fb0c50d870c1a63a603394cb8285c90b5dad51 100644 (file)
@@ -7,6 +7,7 @@ import { SharedModule } from '~/app/shared/shared.module';
 import { ComboBoxModule, GridModule } from 'carbon-components-angular';
 import { NvmeofTabsComponent } from '../nvmeof-tabs/nvmeof-tabs.component';
 import { CephServiceService } from '~/app/shared/api/ceph-service.service';
+import { CephServiceSpec } from '~/app/shared/models/service.interface';
 
 const mockServiceDaemons = [
   {
@@ -60,7 +61,7 @@ const mockGateways = [
   }
 ];
 
-const mockGwGroups = [
+const mockformattedGwGroups = [
   {
     content: 'default',
     serviceName: 'nvmeof.rbd.default'
@@ -96,6 +97,10 @@ class MockNvmeOfService {
   listGatewayGroups() {
     return of(mockServices);
   }
+
+  formatGwGroupsList(_data: CephServiceSpec[][]) {
+    return mockformattedGwGroups;
+  }
 }
 
 class MockCephServiceService {
@@ -130,7 +135,7 @@ describe('NvmeofGatewayComponent', () => {
 
   it('should load gateway groups correctly', () => {
     expect(component.gwGroups.length).toBe(2);
-    expect(component.gwGroups).toStrictEqual(mockGwGroups);
+    expect(component.gwGroups).toStrictEqual(mockformattedGwGroups);
   });
 
   it('should set service name of gateway groups correctly', () => {
index 5364d7ea342bfe54875bf220e1ee4339b1fd9150..ea66250a348eb5bae0346ce6b4bfd64aff3c5b31 100644 (file)
@@ -5,17 +5,11 @@ import _ from 'lodash';
 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
 
-import { NvmeofService } from '../../../shared/api/nvmeof.service';
+import { GroupsComboboxItem, NvmeofService } from '../../../shared/api/nvmeof.service';
 import { CephServiceSpec } from '~/app/shared/models/service.interface';
 import { CephServiceService } from '~/app/shared/api/ceph-service.service';
 import { Daemon } from '~/app/shared/models/daemon.interface';
 
-type ComboBoxItem = {
-  content: string;
-  serviceName: string;
-  selected?: boolean;
-};
-
 type Gateway = {
   id: string;
   hostname: string;
@@ -28,6 +22,8 @@ enum TABS {
   'overview'
 }
 
+const DEFAULT_PLACEHOLDER = $localize`Enter group name`;
+
 @Component({
   selector: 'cd-nvmeof-gateway',
   templateUrl: './nvmeof-gateway.component.html',
@@ -35,7 +31,6 @@ enum TABS {
 })
 export class NvmeofGatewayComponent implements OnInit {
   selectedTab: TABS;
-  selectedGatewayGroup: string = null;
 
   onSelected(tab: TABS) {
     this.selectedTab = tab;
@@ -51,8 +46,11 @@ export class NvmeofGatewayComponent implements OnInit {
   gateways: Gateway[] = [];
   gatewayColumns: any;
   selection = new CdTableSelection();
-  gwGroups: ComboBoxItem[] = [];
+  gwGroups: GroupsComboboxItem[] = [];
   groupService: string = null;
+  selectedGatewayGroup: string = null;
+  gwGroupsEmpty: boolean = false;
+  gwGroupPlaceholder: string = DEFAULT_PLACEHOLDER;
 
   constructor(
     private nvmeofService: NvmeofService,
@@ -61,7 +59,7 @@ export class NvmeofGatewayComponent implements OnInit {
   ) {}
 
   ngOnInit() {
-    this.getGatewayGroups();
+    this.setGatewayGroups();
     this.gatewayColumns = [
       {
         name: $localize`Gateway ID`,
@@ -109,7 +107,7 @@ export class NvmeofGatewayComponent implements OnInit {
   }
 
   // Gateway groups
-  onGroupSelection(selected: ComboBoxItem) {
+  onGroupSelection(selected: GroupsComboboxItem) {
     selected.selected = true;
     this.groupService = selected.serviceName;
     this.selectedGatewayGroup = selected.content;
@@ -121,18 +119,20 @@ export class NvmeofGatewayComponent implements OnInit {
     this.getGateways();
   }
 
-  getGatewayGroups() {
+  setGatewayGroups() {
     this.nvmeofService.listGatewayGroups().subscribe((response: CephServiceSpec[][]) => {
-      this.gwGroups = response?.[0]?.length
-        ? response[0].map((group: CephServiceSpec) => {
-            return {
-              content: group?.spec?.group,
-              serviceName: group?.service_name
-            };
-          })
-        : [];
+      if (response?.[0]?.length) {
+        this.gwGroups = this.nvmeofService.formatGwGroupsList(response, true);
+      } else this.gwGroups = [];
       // Select first group if no group is selected
-      if (!this.groupService && this.gwGroups.length) this.onGroupSelection(this.gwGroups[0]);
+      if (!this.groupService && this.gwGroups.length) {
+        this.onGroupSelection(this.gwGroups[0]);
+        this.gwGroupsEmpty = false;
+        this.gwGroupPlaceholder = DEFAULT_PLACEHOLDER;
+      } else {
+        this.gwGroupsEmpty = true;
+        this.gwGroupPlaceholder = $localize`No groups available`;
+      }
     });
   }
 }
index 38456d06d03d87128fef308e3ff5e43cf64b8fe4..d3d0253db192c646d9c3cef9da3beb451b98cb5f 100644 (file)
@@ -6,11 +6,12 @@
   <cds-combo-box
       type="single"
       label="Selected Gateway Group"
-      i18n-placeholder
-      placeholder="Enter group"
+      i18n-label
+      [placeholder]="gwGroupPlaceholder"
       [items]="gwGroups"
       (selected)="onGroupSelection($event)"
-      (clear)="onGroupClear()">
+      (clear)="onGroupClear()"
+      [disabled]="gwGroupsEmpty">
     <cds-dropdown-list></cds-dropdown-list>
   </cds-combo-box>
 </div>
index c508cf74a778445fba41a7d34734d7a27f2840c9..3246f7286f51ddc418258f4f6f97503cf4a40632 100644 (file)
@@ -12,6 +12,7 @@ import { NvmeofSubsystemsComponent } from './nvmeof-subsystems.component';
 import { NvmeofTabsComponent } from '../nvmeof-tabs/nvmeof-tabs.component';
 import { NvmeofSubsystemsDetailsComponent } from '../nvmeof-subsystems-details/nvmeof-subsystems-details.component';
 import { ComboBoxModule, GridModule } from 'carbon-components-angular';
+import { CephServiceSpec } from '~/app/shared/models/service.interface';
 
 const mockSubsystems = [
   {
@@ -49,11 +50,24 @@ const mockGroups = [
   2
 ];
 
+const mockformattedGwGroups = [
+  {
+    content: 'default'
+  },
+  {
+    content: 'foo'
+  }
+];
+
 class MockNvmeOfService {
   listSubsystems() {
     return of(mockSubsystems);
   }
 
+  formatGwGroupsList(_data: CephServiceSpec[][]) {
+    return mockformattedGwGroups;
+  }
+
   listGatewayGroups() {
     return of(mockGroups);
   }
index b1f28d9cb6faf83cef5475918bf12ba55a556a25..f6f75f1356f1ac5c67ba98709b4b9ebc05f19c06 100644 (file)
@@ -12,16 +12,12 @@ import { Icons } from '~/app/shared/enum/icons.enum';
 import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component';
 import { FinishedTask } from '~/app/shared/models/finished-task';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
-import { NvmeofService } from '~/app/shared/api/nvmeof.service';
+import { NvmeofService, GroupsComboboxItem } from '~/app/shared/api/nvmeof.service';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { CephServiceSpec } from '~/app/shared/models/service.interface';
 
-type ComboBoxItem = {
-  content: string;
-  selected?: boolean;
-};
-
 const BASE_URL = 'block/nvmeof/subsystems';
+const DEFAULT_PLACEHOLDER = $localize`Enter group name`;
 
 @Component({
   selector: 'cd-nvmeof-subsystems',
@@ -35,8 +31,10 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
   selection = new CdTableSelection();
   tableActions: CdTableAction[];
   subsystemDetails: any[];
-  gwGroups: ComboBoxItem[] = [];
+  gwGroups: GroupsComboboxItem[] = [];
   group: string = null;
+  gwGroupsEmpty: boolean = false;
+  gwGroupPlaceholder: string = DEFAULT_PLACEHOLDER;
 
   constructor(
     private nvmeofService: NvmeofService,
@@ -55,7 +53,7 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
     this.route.queryParams.subscribe((params) => {
       if (params?.['group']) this.onGroupSelection({ content: params?.['group'] });
     });
-    this.getGatewayGroups();
+    this.setGatewayGroups();
     this.subsystemsColumns = [
       {
         name: $localize`NQN`,
@@ -124,7 +122,7 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
   }
 
   // Gateway groups
-  onGroupSelection(selected: ComboBoxItem) {
+  onGroupSelection(selected: GroupsComboboxItem) {
     selected.selected = true;
     this.group = selected.content;
     this.getSubsystems();
@@ -135,17 +133,20 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
     this.getSubsystems();
   }
 
-  getGatewayGroups() {
+  setGatewayGroups() {
     this.nvmeofService.listGatewayGroups().subscribe((response: CephServiceSpec[][]) => {
-      if (response?.[0].length) {
-        this.gwGroups = response[0].map((group: CephServiceSpec) => {
-          return {
-            content: group?.spec?.group
-          };
-        });
-      }
+      if (response?.[0]?.length) {
+        this.gwGroups = this.nvmeofService.formatGwGroupsList(response);
+      } else this.gwGroups = [];
       // Select first group if no group is selected
-      if (!this.group && this.gwGroups.length) this.onGroupSelection(this.gwGroups[0]);
+      if (!this.group && this.gwGroups.length) {
+        this.onGroupSelection(this.gwGroups[0]);
+        this.gwGroupsEmpty = false;
+        this.gwGroupPlaceholder = DEFAULT_PLACEHOLDER;
+      } else {
+        this.gwGroupsEmpty = true;
+        this.gwGroupPlaceholder = $localize`No groups available`;
+      }
     });
   }
 }
index 4a3148eb752d7c94b0aa74e29cb2d3627a349866..ff9647a3b53ea13a46cc046e0971d9254cc6a10a 100644 (file)
@@ -4,9 +4,16 @@ import { HttpClient } from '@angular/common/http';
 import _ from 'lodash';
 import { Observable, of as observableOf } from 'rxjs';
 import { catchError, mapTo } from 'rxjs/operators';
+import { CephServiceSpec } from '../models/service.interface';
 
 export const MAX_NAMESPACE = 1024;
 
+export type GroupsComboboxItem = {
+  content: string;
+  serviceName?: string;
+  selected?: boolean;
+};
+
 type NvmeofRequest = {
   gw_group: string;
 };
@@ -40,6 +47,28 @@ const UI_API_PATH = 'ui-api/nvmeof';
 export class NvmeofService {
   constructor(private http: HttpClient) {}
 
+  // formats the gateway groups to be consumed for combobox item
+  formatGwGroupsList(
+    data: CephServiceSpec[][],
+    isGatewayList: boolean = false
+  ): GroupsComboboxItem[] {
+    return data[0].reduce((gwGrpList: GroupsComboboxItem[], group: CephServiceSpec) => {
+      if (isGatewayList && group?.spec?.group && group?.service_name) {
+        gwGrpList.push({
+          content: group.spec.group,
+          serviceName: group.service_name
+        });
+      } else {
+        if (group?.spec?.group) {
+          gwGrpList.push({
+            content: group.spec.group
+          });
+        }
+      }
+      return gwGrpList;
+    }, []);
+  }
+
   // Gateway groups
   listGatewayGroups() {
     return this.http.get(`${API_PATH}/gateway/group`);