Skip to content

Commit 2f41cce

Browse files
committed
mgr/dashboard: Add default state when gateway groups are empty
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 <[email protected]> (cherry picked from commit 9a7c907)
1 parent b74b612 commit 2f41cce

File tree

7 files changed

+98
-47
lines changed

7 files changed

+98
-47
lines changed

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
<cds-combo-box
77
type="single"
88
label="Selected Gateway Group"
9-
i18n-placeholder
10-
placeholder="Enter group"
9+
i18n-label
10+
[placeholder]="gwGroupPlaceholder"
1111
[items]="gwGroups"
1212
(selected)="onGroupSelection($event)"
13-
(clear)="onGroupClear()">
13+
(clear)="onGroupClear()"
14+
[disabled]="gwGroupsEmpty">
1415
<cds-dropdown-list></cds-dropdown-list>
1516
</cds-combo-box>
1617
</div>

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.spec.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { SharedModule } from '~/app/shared/shared.module';
77
import { ComboBoxModule, GridModule } from 'carbon-components-angular';
88
import { NvmeofTabsComponent } from '../nvmeof-tabs/nvmeof-tabs.component';
99
import { CephServiceService } from '~/app/shared/api/ceph-service.service';
10+
import { CephServiceSpec } from '~/app/shared/models/service.interface';
1011

1112
const mockServiceDaemons = [
1213
{
@@ -60,7 +61,7 @@ const mockGateways = [
6061
}
6162
];
6263

63-
const mockGwGroups = [
64+
const mockformattedGwGroups = [
6465
{
6566
content: 'default',
6667
serviceName: 'nvmeof.rbd.default'
@@ -96,6 +97,10 @@ class MockNvmeOfService {
9697
listGatewayGroups() {
9798
return of(mockServices);
9899
}
100+
101+
formatGwGroupsList(_data: CephServiceSpec[][]) {
102+
return mockformattedGwGroups;
103+
}
99104
}
100105

101106
class MockCephServiceService {
@@ -130,7 +135,7 @@ describe('NvmeofGatewayComponent', () => {
130135

131136
it('should load gateway groups correctly', () => {
132137
expect(component.gwGroups.length).toBe(2);
133-
expect(component.gwGroups).toStrictEqual(mockGwGroups);
138+
expect(component.gwGroups).toStrictEqual(mockformattedGwGroups);
134139
});
135140

136141
it('should set service name of gateway groups correctly', () => {

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,11 @@ import _ from 'lodash';
55
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
66
import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
77

8-
import { NvmeofService } from '../../../shared/api/nvmeof.service';
8+
import { GroupsComboboxItem, NvmeofService } from '../../../shared/api/nvmeof.service';
99
import { CephServiceSpec } from '~/app/shared/models/service.interface';
1010
import { CephServiceService } from '~/app/shared/api/ceph-service.service';
1111
import { Daemon } from '~/app/shared/models/daemon.interface';
1212

13-
type ComboBoxItem = {
14-
content: string;
15-
serviceName: string;
16-
selected?: boolean;
17-
};
18-
1913
type Gateway = {
2014
id: string;
2115
hostname: string;
@@ -28,14 +22,15 @@ enum TABS {
2822
'overview'
2923
}
3024

25+
const DEFAULT_PLACEHOLDER = $localize`Enter group name`;
26+
3127
@Component({
3228
selector: 'cd-nvmeof-gateway',
3329
templateUrl: './nvmeof-gateway.component.html',
3430
styleUrls: ['./nvmeof-gateway.component.scss']
3531
})
3632
export class NvmeofGatewayComponent implements OnInit {
3733
selectedTab: TABS;
38-
selectedGatewayGroup: string = null;
3934

4035
onSelected(tab: TABS) {
4136
this.selectedTab = tab;
@@ -51,8 +46,11 @@ export class NvmeofGatewayComponent implements OnInit {
5146
gateways: Gateway[] = [];
5247
gatewayColumns: any;
5348
selection = new CdTableSelection();
54-
gwGroups: ComboBoxItem[] = [];
49+
gwGroups: GroupsComboboxItem[] = [];
5550
groupService: string = null;
51+
selectedGatewayGroup: string = null;
52+
gwGroupsEmpty: boolean = false;
53+
gwGroupPlaceholder: string = DEFAULT_PLACEHOLDER;
5654

5755
constructor(
5856
private nvmeofService: NvmeofService,
@@ -61,7 +59,7 @@ export class NvmeofGatewayComponent implements OnInit {
6159
) {}
6260

6361
ngOnInit() {
64-
this.getGatewayGroups();
62+
this.setGatewayGroups();
6563
this.gatewayColumns = [
6664
{
6765
name: $localize`Gateway ID`,
@@ -109,7 +107,7 @@ export class NvmeofGatewayComponent implements OnInit {
109107
}
110108

111109
// Gateway groups
112-
onGroupSelection(selected: ComboBoxItem) {
110+
onGroupSelection(selected: GroupsComboboxItem) {
113111
selected.selected = true;
114112
this.groupService = selected.serviceName;
115113
this.selectedGatewayGroup = selected.content;
@@ -121,18 +119,20 @@ export class NvmeofGatewayComponent implements OnInit {
121119
this.getGateways();
122120
}
123121

124-
getGatewayGroups() {
122+
setGatewayGroups() {
125123
this.nvmeofService.listGatewayGroups().subscribe((response: CephServiceSpec[][]) => {
126-
this.gwGroups = response?.[0]?.length
127-
? response[0].map((group: CephServiceSpec) => {
128-
return {
129-
content: group?.spec?.group,
130-
serviceName: group?.service_name
131-
};
132-
})
133-
: [];
124+
if (response?.[0]?.length) {
125+
this.gwGroups = this.nvmeofService.formatGwGroupsList(response, true);
126+
} else this.gwGroups = [];
134127
// Select first group if no group is selected
135-
if (!this.groupService && this.gwGroups.length) this.onGroupSelection(this.gwGroups[0]);
128+
if (!this.groupService && this.gwGroups.length) {
129+
this.onGroupSelection(this.gwGroups[0]);
130+
this.gwGroupsEmpty = false;
131+
this.gwGroupPlaceholder = DEFAULT_PLACEHOLDER;
132+
} else {
133+
this.gwGroupsEmpty = true;
134+
this.gwGroupPlaceholder = $localize`No groups available`;
135+
}
136136
});
137137
}
138138
}

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
<cds-combo-box
77
type="single"
88
label="Selected Gateway Group"
9-
i18n-placeholder
10-
placeholder="Enter group"
9+
i18n-label
10+
[placeholder]="gwGroupPlaceholder"
1111
[items]="gwGroups"
1212
(selected)="onGroupSelection($event)"
13-
(clear)="onGroupClear()">
13+
(clear)="onGroupClear()"
14+
[disabled]="gwGroupsEmpty">
1415
<cds-dropdown-list></cds-dropdown-list>
1516
</cds-combo-box>
1617
</div>

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { NvmeofSubsystemsComponent } from './nvmeof-subsystems.component';
1212
import { NvmeofTabsComponent } from '../nvmeof-tabs/nvmeof-tabs.component';
1313
import { NvmeofSubsystemsDetailsComponent } from '../nvmeof-subsystems-details/nvmeof-subsystems-details.component';
1414
import { ComboBoxModule, GridModule } from 'carbon-components-angular';
15+
import { CephServiceSpec } from '~/app/shared/models/service.interface';
1516

1617
const mockSubsystems = [
1718
{
@@ -49,11 +50,24 @@ const mockGroups = [
4950
2
5051
];
5152

53+
const mockformattedGwGroups = [
54+
{
55+
content: 'default'
56+
},
57+
{
58+
content: 'foo'
59+
}
60+
];
61+
5262
class MockNvmeOfService {
5363
listSubsystems() {
5464
return of(mockSubsystems);
5565
}
5666

67+
formatGwGroupsList(_data: CephServiceSpec[][]) {
68+
return mockformattedGwGroups;
69+
}
70+
5771
listGatewayGroups() {
5872
return of(mockGroups);
5973
}

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,12 @@ import { Icons } from '~/app/shared/enum/icons.enum';
1212
import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component';
1313
import { FinishedTask } from '~/app/shared/models/finished-task';
1414
import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
15-
import { NvmeofService } from '~/app/shared/api/nvmeof.service';
15+
import { NvmeofService, GroupsComboboxItem } from '~/app/shared/api/nvmeof.service';
1616
import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
1717
import { CephServiceSpec } from '~/app/shared/models/service.interface';
1818

19-
type ComboBoxItem = {
20-
content: string;
21-
selected?: boolean;
22-
};
23-
2419
const BASE_URL = 'block/nvmeof/subsystems';
20+
const DEFAULT_PLACEHOLDER = $localize`Enter group name`;
2521

2622
@Component({
2723
selector: 'cd-nvmeof-subsystems',
@@ -35,8 +31,10 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
3531
selection = new CdTableSelection();
3632
tableActions: CdTableAction[];
3733
subsystemDetails: any[];
38-
gwGroups: ComboBoxItem[] = [];
34+
gwGroups: GroupsComboboxItem[] = [];
3935
group: string = null;
36+
gwGroupsEmpty: boolean = false;
37+
gwGroupPlaceholder: string = DEFAULT_PLACEHOLDER;
4038

4139
constructor(
4240
private nvmeofService: NvmeofService,
@@ -55,7 +53,7 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
5553
this.route.queryParams.subscribe((params) => {
5654
if (params?.['group']) this.onGroupSelection({ content: params?.['group'] });
5755
});
58-
this.getGatewayGroups();
56+
this.setGatewayGroups();
5957
this.subsystemsColumns = [
6058
{
6159
name: $localize`NQN`,
@@ -124,7 +122,7 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
124122
}
125123

126124
// Gateway groups
127-
onGroupSelection(selected: ComboBoxItem) {
125+
onGroupSelection(selected: GroupsComboboxItem) {
128126
selected.selected = true;
129127
this.group = selected.content;
130128
this.getSubsystems();
@@ -135,17 +133,20 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
135133
this.getSubsystems();
136134
}
137135

138-
getGatewayGroups() {
136+
setGatewayGroups() {
139137
this.nvmeofService.listGatewayGroups().subscribe((response: CephServiceSpec[][]) => {
140-
if (response?.[0].length) {
141-
this.gwGroups = response[0].map((group: CephServiceSpec) => {
142-
return {
143-
content: group?.spec?.group
144-
};
145-
});
146-
}
138+
if (response?.[0]?.length) {
139+
this.gwGroups = this.nvmeofService.formatGwGroupsList(response);
140+
} else this.gwGroups = [];
147141
// Select first group if no group is selected
148-
if (!this.group && this.gwGroups.length) this.onGroupSelection(this.gwGroups[0]);
142+
if (!this.group && this.gwGroups.length) {
143+
this.onGroupSelection(this.gwGroups[0]);
144+
this.gwGroupsEmpty = false;
145+
this.gwGroupPlaceholder = DEFAULT_PLACEHOLDER;
146+
} else {
147+
this.gwGroupsEmpty = true;
148+
this.gwGroupPlaceholder = $localize`No groups available`;
149+
}
149150
});
150151
}
151152
}

src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@ import { HttpClient } from '@angular/common/http';
44
import _ from 'lodash';
55
import { Observable, of as observableOf } from 'rxjs';
66
import { catchError, mapTo } from 'rxjs/operators';
7+
import { CephServiceSpec } from '../models/service.interface';
78

89
export const MAX_NAMESPACE = 1024;
910

11+
export type GroupsComboboxItem = {
12+
content: string;
13+
serviceName?: string;
14+
selected?: boolean;
15+
};
16+
1017
type NvmeofRequest = {
1118
gw_group: string;
1219
};
@@ -40,6 +47,28 @@ const UI_API_PATH = 'ui-api/nvmeof';
4047
export class NvmeofService {
4148
constructor(private http: HttpClient) {}
4249

50+
// formats the gateway groups to be consumed for combobox item
51+
formatGwGroupsList(
52+
data: CephServiceSpec[][],
53+
isGatewayList: boolean = false
54+
): GroupsComboboxItem[] {
55+
return data[0].reduce((gwGrpList: GroupsComboboxItem[], group: CephServiceSpec) => {
56+
if (isGatewayList && group?.spec?.group && group?.service_name) {
57+
gwGrpList.push({
58+
content: group.spec.group,
59+
serviceName: group.service_name
60+
});
61+
} else {
62+
if (group?.spec?.group) {
63+
gwGrpList.push({
64+
content: group.spec.group
65+
});
66+
}
67+
}
68+
return gwGrpList;
69+
}, []);
70+
}
71+
4372
// Gateway groups
4473
listGatewayGroups() {
4574
return this.http.get(`${API_PATH}/gateway/group`);

0 commit comments

Comments
 (0)