Skip to content

Commit af437b4

Browse files
author
Naman Munet
committed
mgr/dashboard: multisite sync policy improvements
https://tracker.ceph.com/issues/68097 Changes for this PR includes: 1) Populating the destination zones select option with a set of options to choose from, for flow and pipe so that user can't enter any invalid zone name 2) Provided zone option as 'All Zones (*)' in pipe, if user want to select all zones for source and destination zones 3) We are hiding the UniqueId column on sync policy table as we do not want to show it as this column is introduced just to uniquely identify a row in the table and should not be displayed to users as it is part of the internal logic to work. Signed-off-by: Naman Munet <[email protected]>
1 parent 6174c65 commit af437b4

File tree

7 files changed

+247
-102
lines changed

7 files changed

+247
-102
lines changed

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component.html

Lines changed: 90 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -2,109 +2,84 @@
22
<ng-container i18n="form title"
33
class="modal-title">{{ action | titlecase }} {{ groupType | upperFirst }} Flow</ng-container>
44

5-
<ng-container class="modal-content">
6-
<form name="flowForm"
7-
#frm="ngForm"
8-
[formGroup]="currentFormGroupContext"
9-
novalidate>
10-
<div class="modal-body">
5+
<ng-container class="modal-content">
6+
<form name="flowForm"
7+
#frm="ngForm"
8+
[formGroup]="currentFormGroupContext"
9+
novalidate>
10+
<div class="modal-body">
11+
<div class="form-group row">
12+
<label class="cd-col-form-label required"
13+
for="flow_id"
14+
i18n>Name</label>
15+
<div class="cd-col-form-input">
16+
<input class="form-control"
17+
type="text"
18+
placeholder="Flow Name..."
19+
id="flow_id"
20+
name="flow_id"
21+
formControlName="flow_id"/>
22+
</div>
23+
</div>
24+
<div class="form-group row">
25+
<label class="cd-col-form-label"
26+
for="bucket"
27+
i18n>Bucket Name</label>
28+
<div class="cd-col-form-input">
29+
<input id="bucket"
30+
name="bucket"
31+
class="form-control"
32+
type="text"
33+
i18n-placeholder
34+
placeholder="Bucket Name..."
35+
formControlName="bucket_name"/>
36+
<span class="invalid-feedback"
37+
*ngIf="currentFormGroupContext.showError('bucket_name', frm, 'bucketNameNotAllowed')"
38+
i18n>The bucket with chosen name does not exist.</span>
39+
</div>
40+
</div>
41+
<ng-container *ngIf="groupType == flowType.symmetrical; else directionalFlow">
1142
<div class="form-group row">
1243
<label class="cd-col-form-label required"
13-
for="flow_id"
14-
i18n>Name</label>
44+
for="zones">
45+
<ng-container i18n>Zones</ng-container>
46+
<cd-helper>
47+
<span i18n>Flow need to be associated with atleast one zone</span>
48+
</cd-helper>
49+
</label>
1550
<div class="cd-col-form-input">
16-
<input class="form-control"
17-
type="text"
18-
placeholder="Flow Name..."
19-
id="flow_id"
20-
name="flow_id"
21-
formControlName="flow_id"/>
51+
<ng-container *ngTemplateOutlet="zoneMultiSelect;context: { name: 'zones', zone: zones }"></ng-container>
2252
</div>
2353
</div>
54+
</ng-container>
55+
<ng-template #directionalFlow>
2456
<div class="form-group row">
25-
<label class="cd-col-form-label"
26-
for="bucket"
27-
i18n>Bucket Name</label>
57+
<label class="cd-col-form-label required"
58+
for="source_zone"
59+
i18n>Source Zone
60+
</label>
2861
<div class="cd-col-form-input">
29-
<input id="bucket"
30-
name="bucket"
31-
class="form-control"
32-
type="text"
33-
i18n-placeholder
34-
placeholder="Bucket Name..."
35-
formControlName="bucket_name"/>
36-
<span class="invalid-feedback"
37-
*ngIf="currentFormGroupContext.showError('bucket_name', frm, 'bucketNameNotAllowed')"
38-
i18n>The bucket with chosen name does not exist.</span>
62+
<ng-container *ngTemplateOutlet="sourceAndDestZone;context: { name: 'source_zone', zones: zones }"></ng-container>
3963
</div>
4064
</div>
41-
<ng-container *ngIf="groupType == flowType.symmetrical; else directionalFlow">
42-
<div class="form-group row">
43-
<label class="cd-col-form-label required"
44-
for="zones">
45-
<ng-container i18n>Zones</ng-container>
46-
<cd-helper>
47-
<span i18n>Flow need to be associated with atleast one zone</span>
48-
</cd-helper>
49-
</label>
50-
<div class="cd-col-form-input">
51-
<ng-container *ngTemplateOutlet="zoneMultiSelect;context: { name: 'zones', zone: zones }"></ng-container>
52-
</div>
53-
</div>
54-
</ng-container>
55-
<ng-template #directionalFlow>
56-
<div class="form-group row">
57-
<label class="cd-col-form-label required"
58-
for="source_zone"
59-
i18n>Source Zone
60-
</label>
61-
<div class="cd-col-form-input">
62-
<select id="sourceZone"
63-
name="sourceZone"
64-
class="form-select"
65-
formControlName="source_zone"
66-
[autofocus]="editing">
67-
<option i18n
68-
*ngIf="zones.data.available.length == 0"
69-
[ngValue]="null">Loading...</option>
70-
<option i18n
71-
*ngIf="zones.data.available.length > 0"
72-
[ngValue]="null">-- Select source zone --</option>
73-
<option *ngFor="let sourceZone of zones.data.available"
74-
[value]="sourceZone.name">{{ sourceZone.name }}</option>
75-
</select>
76-
<span class="invalid-feedback"
77-
*ngIf="currentFormGroupContext.showError('source_zone', frm, 'required')"
78-
i18n>This field is required.</span>
79-
</div>
80-
</div>
81-
<div class="form-group row">
82-
<label class="cd-col-form-label required"
83-
for="destination_zone"
84-
i18n>Destination Zone</label>
85-
<div class="cd-col-form-input">
86-
<input id="destination_zone"
87-
name="destination_zone"
88-
class="form-control"
89-
type="text"
90-
i18n-placeholder
91-
placeholder="Destination Zone..."
92-
formControlName="destination_zone"/>
93-
<span class="invalid-feedback"
94-
*ngIf="currentFormGroupContext.showError('destination_zone', frm, 'required')"
95-
i18n>This field is required.</span>
96-
</div>
65+
<div class="form-group row">
66+
<label class="cd-col-form-label required"
67+
for="destination_zone"
68+
i18n>Destination Zone</label>
69+
<div class="cd-col-form-input">
70+
<ng-container *ngTemplateOutlet="sourceAndDestZone;context: { name: 'destination_zone', zones: zones }"></ng-container>
9771
</div>
98-
</ng-template>
99-
</div>
100-
<div class="modal-footer">
101-
<cd-form-button-panel (submitActionEvent)="submit()"
102-
[form]="currentFormGroupContext"
103-
[submitText]="(action | titlecase) + ' ' + (groupType | upperFirst) + ' ' + 'Flow'"></cd-form-button-panel>
104-
</div>
105-
</form>
106-
</ng-container>
107-
</cd-modal>
72+
</div>
73+
</ng-template>
74+
</div>
75+
<div class="modal-footer">
76+
<cd-form-button-panel (submitActionEvent)="submit()"
77+
[form]="currentFormGroupContext"
78+
[submitText]="(action | titlecase) + ' ' + (groupType | upperFirst) + ' ' + 'Flow'"></cd-form-button-panel>
79+
</div>
80+
</form>
81+
</ng-container>
82+
</cd-modal>
10883

10984
<ng-template #zoneMultiSelect
11085
let-name="name"
@@ -128,3 +103,25 @@
128103
i18n>{{name?.split('_').join(' ')}} selection is required!
129104
</span>
130105
</ng-template>
106+
107+
<ng-template #sourceAndDestZone
108+
let-name="name"
109+
let-zones="zones">
110+
<select [id]="name"
111+
[name]="name"
112+
class="form-select"
113+
(change)="onChangeZoneDropdown(name, $event)"
114+
[autofocus]="editing">
115+
<option i18n
116+
*ngIf="zones.data.available.length == 0"
117+
[ngValue]="null">Loading...</option>
118+
<option i18n
119+
*ngIf="zones.data.available.length > 0"
120+
[ngValue]="null">-- Select {{name.split('_').join(' ')}} --</option>
121+
<option *ngFor="let destinationZone of zones.data.available"
122+
[value]="destinationZone.name">{{ destinationZone.name }}</option>
123+
</select>
124+
<span class="invalid-feedback"
125+
*ngIf="currentFormGroupContext.showError(name, frm, 'required')"
126+
i18n>This field is required.</span>
127+
</ng-template>

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component.spec.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@ import { PipesModule } from '~/app/shared/pipes/pipes.module';
66
import { HttpClientTestingModule } from '@angular/common/http/testing';
77
import { ReactiveFormsModule } from '@angular/forms';
88
import { CommonModule } from '@angular/common';
9+
import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
10+
import { of } from 'rxjs';
911

1012
enum FlowType {
1113
symmetrical = 'symmetrical',
1214
directional = 'directional'
1315
}
16+
17+
class MultisiteServiceMock {
18+
createEditSyncFlow = jest.fn().mockReturnValue(of(null));
19+
}
20+
1421
describe('RgwMultisiteSyncFlowModalComponent', () => {
1522
let component: RgwMultisiteSyncFlowModalComponent;
1623
let fixture: ComponentFixture<RgwMultisiteSyncFlowModalComponent>;
24+
let multisiteServiceMock: MultisiteServiceMock;
1725

1826
beforeEach(async () => {
1927
await TestBed.configureTestingModule({
@@ -25,10 +33,11 @@ describe('RgwMultisiteSyncFlowModalComponent', () => {
2533
ReactiveFormsModule,
2634
CommonModule
2735
],
28-
providers: [NgbActiveModal]
36+
providers: [NgbActiveModal, { provide: RgwMultisiteService, useClass: MultisiteServiceMock }]
2937
}).compileComponents();
3038

3139
fixture = TestBed.createComponent(RgwMultisiteSyncFlowModalComponent);
40+
multisiteServiceMock = (TestBed.inject(RgwMultisiteService) as unknown) as MultisiteServiceMock;
3241
component = fixture.componentInstance;
3342
component.groupType = FlowType.symmetrical;
3443
fixture.detectChanges();
@@ -37,4 +46,56 @@ describe('RgwMultisiteSyncFlowModalComponent', () => {
3746
it('should create', () => {
3847
expect(component).toBeTruthy();
3948
});
49+
50+
it('should assign zone value', () => {
51+
let zonesAdded: string[] = [];
52+
let selectedZone = ['zone2-zg1-realm1'];
53+
const spy = jest.spyOn(component, 'assignZoneValue').mockReturnValue(selectedZone);
54+
const res = component.assignZoneValue(zonesAdded, selectedZone);
55+
expect(spy).toHaveBeenCalled();
56+
expect(spy).toHaveBeenCalledWith(zonesAdded, selectedZone);
57+
expect(res).toEqual(selectedZone);
58+
});
59+
60+
it('should call createEditSyncFlow for creating/editing symmetrical sync flow', () => {
61+
component.editing = false;
62+
component.currentFormGroupContext.patchValue({
63+
flow_id: 'symmetrical',
64+
group_id: 'new',
65+
zones: { added: ['zone1-zg1-realm1'], removed: [] }
66+
});
67+
component.zones.data.selected = ['zone1-zg1-realm1'];
68+
const spy = jest.spyOn(component, 'submit');
69+
const putDataSpy = jest
70+
.spyOn(multisiteServiceMock, 'createEditSyncFlow')
71+
.mockReturnValue(of(null));
72+
component.submit();
73+
expect(spy).toHaveBeenCalled();
74+
expect(putDataSpy).toHaveBeenCalled();
75+
expect(putDataSpy).toHaveBeenCalledWith(component.currentFormGroupContext.getRawValue());
76+
});
77+
78+
it('should call createEditSyncFlow for creating/editing directional sync flow', () => {
79+
component.editing = false;
80+
component.groupType = FlowType.directional;
81+
component.ngOnInit();
82+
fixture.detectChanges();
83+
component.currentFormGroupContext.patchValue({
84+
flow_id: 'directional',
85+
group_id: 'new',
86+
source_zone: { added: ['zone1-zg1-realm1'], removed: [] },
87+
destination_zone: { added: ['zone2-zg1-realm1'], removed: [] }
88+
});
89+
const spy = jest.spyOn(component, 'submit');
90+
const putDataSpy = jest
91+
.spyOn(multisiteServiceMock, 'createEditSyncFlow')
92+
.mockReturnValue(of(null));
93+
component.submit();
94+
expect(spy).toHaveBeenCalled();
95+
expect(putDataSpy).toHaveBeenCalled();
96+
expect(putDataSpy).toHaveBeenCalledWith({
97+
...component.currentFormGroupContext.getRawValue(),
98+
zones: { added: [], removed: [] }
99+
});
100+
});
40101
});

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ export class RgwMultisiteSyncFlowModalComponent implements OnInit {
3535
flowType = FlowType;
3636
icons = Icons;
3737
zones = new ZoneData(false, 'Filter Zones');
38-
sourceZone: string;
39-
destinationZone: string;
4038

4139
constructor(
4240
public activeModal: NgbActiveModal,
@@ -122,6 +120,11 @@ export class RgwMultisiteSyncFlowModalComponent implements OnInit {
122120
});
123121
}
124122

123+
onChangeZoneDropdown(zoneType: string, event: Event) {
124+
const selectedVal = (event.target as HTMLSelectElement).value;
125+
this.currentFormGroupContext.get(zoneType).setValue(selectedVal);
126+
}
127+
125128
commonFormControls(flowType: FlowType) {
126129
return {
127130
bucket_name: new UntypedFormControl(this.groupExpandedRow?.bucket),

0 commit comments

Comments
 (0)