Skip to content

Commit fb68c68

Browse files
authored
Merge pull request ceph#66105 from rhcs-dashboard/carbonize-sync-policy-form
mgr/dashboard: Carbonize multisite sync policy forms Reviewed-by: Pedro Gonzalez Gomez <[email protected]>
2 parents d3199cd + 495f9b7 commit fb68c68

12 files changed

+493
-475
lines changed

src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@ export class MultisitePageHelper extends PageHelper {
3838
@PageHelper.restrictTo(pages.create.url)
3939
create(group_id: string, status: string, bucket_name: string) {
4040
// Enter in group_id
41-
cy.get('#group_id').type(group_id);
41+
cy.get('#group_id').type(group_id, { force: true });
4242
// Show Status
4343
this.selectOption('status', status);
4444
cy.get('#status').should('have.class', 'ng-valid');
4545
// Enter the bucket_name
4646
cy.get('#bucket_name').type(bucket_name);
47+
cy.get('#bucket_name').should('have.class', 'ng-valid');
4748
// Click the create button and wait for policy to be made
48-
cy.contains('button', 'Create Sync Policy Group').wait(WAIT_TIMER).click();
49+
cy.contains('cd-submit-button button', 'Create').click();
4950
this.getFirstTableCell(group_id).should('exist');
5051
}
5152

@@ -55,7 +56,7 @@ export class MultisitePageHelper extends PageHelper {
5556

5657
// Change the status field
5758
this.selectOption('status', status);
58-
cy.contains('button', 'Edit Sync Policy Group').click();
59+
cy.contains('cd-submit-button button', 'Edit').click();
5960

6061
this.searchTable(group_id);
6162
cy.get(`[cdstabledata]:nth-child(${this.columnIndex.status})`)
Lines changed: 106 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,113 @@
1-
<cd-modal [modalRef]="activeModal">
2-
<ng-container i18n="form title"
3-
class="modal-title">{{ action | titlecase }} {{ groupType | upperFirst }} Flow</ng-container>
4-
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">
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-
<ng-container *ngTemplateOutlet="sourceAndDestZone;context: { name: 'source_zone', zones: zones }"></ng-container>
63-
</div>
64-
</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>
71-
</div>
72-
</div>
1+
<cds-modal size="sm"
2+
[open]="open">
3+
<cds-modal-header (closeSelect)="closeModal()">
4+
<h3 cdsModalHeaderHeading
5+
i18n>{{ action | titlecase }} {{ groupType }} flow</h3>
6+
<cd-help-text [formAllFieldsRequired]="true"></cd-help-text>
7+
</cds-modal-header>
8+
<form name="flowForm"
9+
#frm="ngForm"
10+
[formGroup]="currentFormGroupContext"
11+
novalidate>
12+
<div cdsModalContent>
13+
<!-- Flow Name-->
14+
<div class="form-item">
15+
<cds-text-label for="flow_id"
16+
[invalid]="!currentFormGroupContext.controls.flow_id.valid && currentFormGroupContext.controls.flow_id.dirty"
17+
[invalidText]="flowIdError">
18+
<ng-container i18n>Name</ng-container>
19+
<input cdsText
20+
type="text"
21+
id="flow_id"
22+
formControlName="flow_id"
23+
autofocus
24+
[invalid]="!currentFormGroupContext.controls.flow_id.valid && currentFormGroupContext.controls.flow_id.dirty">
25+
</cds-text-label>
26+
<ng-template #flowIdError>
27+
@if (currentFormGroupContext.showError('flow_id', formDir, 'required')) {
28+
<span class="invalid-feedback">
29+
<ng-container i18n> This field is required. </ng-container>
30+
</span>
31+
}
7332
</ng-template>
7433
</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>
34+
<!-- Bucket Name-->
35+
@if (currentFormGroupContext.controls.bucket_name.value) {
36+
<div class="form-item">
37+
<cds-text-label for="bucket_name"
38+
cdOptionalField="Bucket name">
39+
<ng-container i18n>Bucket name</ng-container>
40+
<input cdsText
41+
type="text"
42+
id="bucket_name"
43+
formControlName="bucket_name"
44+
[readOnly]="true">
45+
</cds-text-label>
7946
</div>
80-
</form>
81-
</ng-container>
82-
</cd-modal>
83-
84-
<ng-template #zoneMultiSelect
85-
let-name="name"
86-
let-zone="zone">
87-
<cd-select-badges [id]="name"
88-
[name]="name"
89-
[customBadges]="zone.customBadges"
90-
[customBadgeValidators]="zone.data.validators"
91-
[messages]="zone.data.messages"
92-
[data]="zone.data.selected"
93-
[options]="zone.data.available"
94-
(selection)="zoneSelection()">
95-
</cd-select-badges>
96-
97-
<svg *ngIf="zone.data.selected.length <= 0"
98-
[cdsIcon]="icons.warning"
99-
[size]="icons.size16"
100-
title="Flow should be associated with {{name?.split('_').join(' ')}}"
101-
class="cds-warning-color"
102-
i18n-title></svg>
103-
<span class="invalid-feedback"
104-
*ngIf="currentFormGroupContext.showError(name, frm, 'required')"
105-
i18n>{{name?.split('_').join(' ')}} selection is required!
106-
</span>
107-
</ng-template>
47+
}
48+
<!-- Symmetrical flow zones -->
49+
@if (groupType == flowType.symmetrical) {
50+
<div class="form-item">
51+
<cds-combo-box label="Zones"
52+
type="multi"
53+
selectionFeedback="top-after-reopen"
54+
for="zones"
55+
formControlName="zones"
56+
[helperText]="'Flow need to be associated with atleast one zone'"
57+
i18n-helperText
58+
[appendInline]="true"
59+
[items]="zones"
60+
itemValueKey="content"
61+
id="zones"
62+
cdDynamicInputCombobox
63+
[invalid]="currentFormGroupContext.controls?.zones?.invalid && currentFormGroupContext.controls?.zones?.dirty"
64+
[invalidText]="'Zone selection is required!'"
65+
i18n>
66+
<cds-dropdown-list></cds-dropdown-list>
67+
</cds-combo-box>
68+
</div>
69+
} @else {
70+
<div class="form-item">
71+
<ng-container *ngTemplateOutlet="sourceAndDestZone;context: { formControl: 'source_zone', zones: zones, name: 'Source zone' }"></ng-container>
72+
</div>
73+
<div class="form-item">
74+
<ng-container *ngTemplateOutlet="sourceAndDestZone;context: { formControl: 'destination_zone', zones: zones, name: 'Destination zone' }"></ng-container>
75+
</div>
76+
}
77+
<!-- Directional flow zones -->
78+
</div>
79+
<cd-form-button-panel (submitActionEvent)="submit()"
80+
[form]="currentFormGroupContext"
81+
[modalForm]="true"
82+
[submitText]="(action | titlecase)"></cd-form-button-panel>
83+
</form>
84+
</cds-modal>
10885

10986
<ng-template #sourceAndDestZone
11087
let-name="name"
111-
let-zones="zones">
112-
<select [id]="name"
113-
[name]="name"
114-
class="form-select"
115-
(change)="onChangeZoneDropdown(name, $event)"
116-
[autofocus]="editing">
117-
<option i18n
118-
*ngIf="zones.data.available.length == 0"
119-
[ngValue]="null">Loading...</option>
120-
<option i18n
121-
*ngIf="zones.data.available.length > 0"
122-
[ngValue]="null">-- Select {{name.split('_').join(' ')}} --</option>
123-
<option *ngFor="let destinationZone of zones.data.available"
124-
[value]="destinationZone.name">{{ destinationZone.name }}</option>
125-
</select>
126-
<span class="invalid-feedback"
127-
*ngIf="currentFormGroupContext.showError(name, frm, 'required')"
128-
i18n>This field is required.</span>
88+
let-zones="zones"
89+
let-formControl="formControl"
90+
[formGroup]="currentFormGroupContext">
91+
<cds-select [label]="name"
92+
[formControlName]="formControl"
93+
[id]="formControl"
94+
[invalid]="currentFormGroupContext.controls[formControl].invalid && (currentFormGroupContext.controls[formControl].dirty)"
95+
[invalidText]="zoneError"
96+
i18n>
97+
@if (zones.length == 0) {
98+
<option [ngValue]="null">Loading...</option>
99+
}
100+
@if (zones.length > 0) {
101+
<option [ngValue]="null">-- Select {{name}} --</option>
102+
}
103+
@for (zone of zones; track zone) {
104+
<option [value]="zone.name">{{ zone.name }}</option>
105+
}
106+
</cds-select>
107+
<ng-template #zoneError>
108+
@if (currentFormGroupContext.showError(formControl, frm, 'required')) {
109+
<span class="invalid-feedback"
110+
i18n>This field is required.</span>
111+
}
112+
</ng-template>
129113
</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: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
22
import { RgwMultisiteSyncFlowModalComponent } from './rgw-multisite-sync-flow-modal.component';
3-
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
43
import { ToastrModule } from 'ngx-toastr';
54
import { PipesModule } from '~/app/shared/pipes/pipes.module';
65
import { HttpClientTestingModule } from '@angular/common/http/testing';
@@ -9,6 +8,7 @@ import { CommonModule } from '@angular/common';
98
import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
109
import { of } from 'rxjs';
1110
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
11+
import { ComboBoxModule, ModalModule, SelectModule } from 'carbon-components-angular';
1212

1313
enum FlowType {
1414
symmetrical = 'symmetrical',
@@ -32,16 +32,31 @@ describe('RgwMultisiteSyncFlowModalComponent', () => {
3232
ToastrModule.forRoot(),
3333
PipesModule,
3434
ReactiveFormsModule,
35-
CommonModule
35+
CommonModule,
36+
SelectModule,
37+
ModalModule,
38+
ComboBoxModule
3639
],
3740
schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA],
38-
providers: [NgbActiveModal, { provide: RgwMultisiteService, useClass: MultisiteServiceMock }]
41+
providers: [
42+
{ provide: RgwMultisiteService, useClass: MultisiteServiceMock },
43+
{ provide: 'groupType', useValue: FlowType.symmetrical },
44+
{ provide: 'groupExpandedRow', useValue: { groupName: 'new', bucket: 'bucket1' } },
45+
{
46+
provide: 'flowSelectedRow',
47+
useValue: { id: 'symmetrical', zones: ['zone1-zg1-realm1'] }
48+
},
49+
{ provide: 'action', useValue: 'create' }
50+
]
3951
}).compileComponents();
4052

4153
fixture = TestBed.createComponent(RgwMultisiteSyncFlowModalComponent);
4254
multisiteServiceMock = (TestBed.inject(RgwMultisiteService) as unknown) as MultisiteServiceMock;
4355
component = fixture.componentInstance;
4456
component.groupType = FlowType.symmetrical;
57+
component.groupExpandedRow = { groupName: 'new', bucket: 'bucket1' };
58+
component.flowSelectedRow = { id: 'symmetrical', zones: ['zone1-zg1-realm1'] };
59+
component.action = 'create';
4560
fixture.detectChanges();
4661
});
4762

@@ -61,32 +76,34 @@ describe('RgwMultisiteSyncFlowModalComponent', () => {
6176

6277
it('should call createEditSyncFlow for creating/editing symmetrical sync flow', () => {
6378
component.editing = false;
79+
component.ngOnInit();
6480
component.currentFormGroupContext.patchValue({
6581
flow_id: 'symmetrical',
6682
group_id: 'new',
67-
zones: { added: ['zone1-zg1-realm1'], removed: [] }
83+
zones: ['zone1-zg1-realm1']
6884
});
69-
component.zones.data.selected = ['zone1-zg1-realm1'];
7085
const spy = jest.spyOn(component, 'submit');
7186
const putDataSpy = jest
7287
.spyOn(multisiteServiceMock, 'createEditSyncFlow')
7388
.mockReturnValue(of(null));
7489
component.submit();
7590
expect(spy).toHaveBeenCalled();
7691
expect(putDataSpy).toHaveBeenCalled();
77-
expect(putDataSpy).toHaveBeenCalledWith(component.currentFormGroupContext.getRawValue());
92+
expect(putDataSpy).toHaveBeenCalledWith({
93+
...component.currentFormGroupContext.getRawValue(),
94+
zones: { added: ['zone1-zg1-realm1'], removed: [] }
95+
});
7896
});
7997

8098
it('should call createEditSyncFlow for creating/editing directional sync flow', () => {
8199
component.editing = false;
82100
component.groupType = FlowType.directional;
83101
component.ngOnInit();
84-
fixture.detectChanges();
85102
component.currentFormGroupContext.patchValue({
86103
flow_id: 'directional',
87104
group_id: 'new',
88-
source_zone: { added: ['zone1-zg1-realm1'], removed: [] },
89-
destination_zone: { added: ['zone2-zg1-realm1'], removed: [] }
105+
source_zone: ['zone1-zg1-realm1'],
106+
destination_zone: ['zone2-zg1-realm1']
90107
});
91108
const spy = jest.spyOn(component, 'submit');
92109
const putDataSpy = jest

0 commit comments

Comments
 (0)