Add directive to handle the display of the form.
It will display the form only when loading is finished.
Otherwise it will show a loading message or error message.
Add a new class that should be extended by all form components.
For now it only has methods for dealing with loading,
but this could be improved later.
Fixes: https://tracker.ceph.com/issues/44912
Signed-off-by: Tiago Melo <tmelo@suse.com>
-<div class="cd-col-form">
+<div class="cd-col-form"
+ *cdFormLoading="loading">
<form name="targetForm"
#formDir="ngForm"
[formGroup]="targetForm"
- novalidate
- *ngIf="targetForm">
+ novalidate>
<div class="card">
<div i18n="form title|Example: Create Pool@@formTitle"
class="card-header">{{ action | titlecase }} {{ resource | upperFirst }}</div>
import { SelectOption } from '../../../shared/components/select/select-option.model';
import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
import { Icons } from '../../../shared/enum/icons.enum';
+import { CdForm } from '../../../shared/forms/cd-form';
import { CdFormGroup } from '../../../shared/forms/cd-form-group';
import { CdValidators } from '../../../shared/forms/cd-validators';
import { FinishedTask } from '../../../shared/models/finished-task';
templateUrl: './iscsi-target-form.component.html',
styleUrls: ['./iscsi-target-form.component.scss']
})
-export class IscsiTargetFormComponent implements OnInit {
+export class IscsiTargetFormComponent extends CdForm implements OnInit {
cephIscsiConfigVersion: number;
targetForm: CdFormGroup;
modalRef: BsModalRef;
private taskWrapper: TaskWrapperService,
public actionLabels: ActionLabelsI18n
) {
+ super();
this.resource = this.i18n('target');
}
if (data[5]) {
this.resolveModel(data[5]);
}
+
+ this.loadingReady();
});
}
-<div class="cd-col-form">
+<div class="cd-col-form"
+ *cdFormLoading="loading">
<form name="rbdForm"
#formDir="ngForm"
[formGroup]="rbdForm"
fixture = TestBed.createComponent(RbdFormComponent);
component = fixture.componentInstance;
activatedRoute = TestBed.get(ActivatedRoute);
+
+ component.loadingReady();
});
it('should create', () => {
import { RbdService } from '../../../shared/api/rbd.service';
import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
import { Icons } from '../../../shared/enum/icons.enum';
+import { CdForm } from '../../../shared/forms/cd-form';
import { CdFormGroup } from '../../../shared/forms/cd-form-group';
import {
RbdConfigurationEntry,
templateUrl: './rbd-form.component.html',
styleUrls: ['./rbd-form.component.scss']
})
-export class RbdFormComponent implements OnInit {
+export class RbdFormComponent extends CdForm implements OnInit {
poolPermission: Permission;
rbdForm: CdFormGroup;
getDirtyConfigurationValues: (
public actionLabels: ActionLabelsI18n,
public router: Router
) {
+ super();
this.poolPermission = this.authStorageService.getPermissions().pool;
this.resource = this.i18n('RBD');
this.features = {
this.setResponse(resp, this.snapName);
this.rbdImage.next(resp);
}
+
+ this.loadingReady();
});
_.each(this.features, (feature) => {
-<div class="cd-col-form">
+<div class="cd-col-form"
+ *cdFormLoading="loading">
<form name="configForm"
#formDir="ngForm"
[formGroup]="configForm"
import { ConfigFormModel } from '../../../../shared/components/config-option/config-option.model';
import { ConfigOptionTypes } from '../../../../shared/components/config-option/config-option.types';
import { NotificationType } from '../../../../shared/enum/notification-type.enum';
+import { CdForm } from '../../../../shared/forms/cd-form';
import { CdFormGroup } from '../../../../shared/forms/cd-form-group';
import { NotificationService } from '../../../../shared/services/notification.service';
import { ConfigFormCreateRequestModel } from './configuration-form-create-request.model';
templateUrl: './configuration-form.component.html',
styleUrls: ['./configuration-form.component.scss']
})
-export class ConfigurationFormComponent implements OnInit {
+export class ConfigurationFormComponent extends CdForm implements OnInit {
configForm: CdFormGroup;
response: ConfigFormModel;
type: string;
private notificationService: NotificationService,
private i18n: I18n
) {
+ super();
this.createForm();
}
const configName = params.name;
this.configService.get(configName).subscribe((resp: ConfigFormModel) => {
this.setResponse(resp);
+ this.loadingReady();
});
});
}
-<cd-loading-panel *ngIf="loading"
- i18n>Loading...</cd-loading-panel>
-
-<div class="cd-col-form">
+<div class="cd-col-form"
+ *cdFormLoading="loading">
<form name="hostForm"
- *ngIf="!loading"
#formDir="ngForm"
[formGroup]="hostForm"
novalidate>
import { I18n } from '@ngx-translate/i18n-polyfill';
import { HostService } from '../../../../shared/api/host.service';
import { ActionLabelsI18n, URLVerbs } from '../../../../shared/constants/app.constants';
+import { CdForm } from '../../../../shared/forms/cd-form';
import { CdFormGroup } from '../../../../shared/forms/cd-form-group';
import { CdValidators } from '../../../../shared/forms/cd-validators';
import { FinishedTask } from '../../../../shared/models/finished-task';
templateUrl: './host-form.component.html',
styleUrls: ['./host-form.component.scss']
})
-export class HostFormComponent implements OnInit {
+export class HostFormComponent extends CdForm implements OnInit {
hostForm: CdFormGroup;
action: string;
resource: string;
- loading = true;
hostnames: string[];
constructor(
private hostService: HostService,
private taskWrapper: TaskWrapperService
) {
+ super();
this.resource = this.i18n('host');
this.action = this.actionLabels.CREATE;
this.createForm();
this.hostnames = resp.map((host) => {
return host['hostname'];
});
- this.loading = false;
+ this.loadingReady();
});
}
-<cd-loading-panel *ngIf="loading && !error"
- i18n>Loading configuration...</cd-loading-panel>
-<cd-alert-panel type="error"
- *ngIf="loading && error"
- i18n>The configuration could not be loaded.</cd-alert-panel>
-
<div class="cd-col-form"
- *ngIf="!loading && !error">
+ *cdFormLoading="loading">
<form name="mgrModuleForm"
#frm="ngForm"
[formGroup]="mgrModuleForm"
import { MgrModuleService } from '../../../../shared/api/mgr-module.service';
import { NotificationType } from '../../../../shared/enum/notification-type.enum';
+import { CdForm } from '../../../../shared/forms/cd-form';
import { CdFormBuilder } from '../../../../shared/forms/cd-form-builder';
import { CdFormGroup } from '../../../../shared/forms/cd-form-group';
import { CdValidators } from '../../../../shared/forms/cd-validators';
templateUrl: './mgr-module-form.component.html',
styleUrls: ['./mgr-module-form.component.scss']
})
-export class MgrModuleFormComponent implements OnInit {
+export class MgrModuleFormComponent extends CdForm implements OnInit {
mgrModuleForm: CdFormGroup;
- error = false;
- loading = false;
moduleName = '';
moduleOptions: any[] = [];
private mgrModuleService: MgrModuleService,
private notificationService: NotificationService,
private i18n: I18n
- ) {}
+ ) {
+ super();
+ }
ngOnInit() {
this.route.params.subscribe((params: { name: string }) => {
this.moduleName = decodeURIComponent(params.name);
- this.loading = true;
const observables = [
this.mgrModuleService.getOptions(this.moduleName),
this.mgrModuleService.getConfig(this.moduleName)
];
observableForkJoin(observables).subscribe(
(resp: object) => {
- this.loading = false;
this.moduleOptions = resp[0];
// Create the form dynamically.
this.createForm();
// Set the form field values.
this.mgrModuleForm.setValue(resp[1]);
+ this.loadingReady();
},
(_error) => {
- this.error = true;
+ this.loadingError();
}
);
});
<cd-orchestrator-doc-panel *ngIf="!hasOrchestrator"></cd-orchestrator-doc-panel>
+
<div class="cd-col-form"
- *ngIf="!loading && hasOrchestrator">
+ *cdFormLoading="loading">
<form name="form"
#formDir="ngForm"
[formGroup]="form"
import { SubmitButtonComponent } from '../../../../shared/components/submit-button/submit-button.component';
import { ActionLabelsI18n } from '../../../../shared/constants/app.constants';
import { Icons } from '../../../../shared/enum/icons.enum';
+import { CdForm } from '../../../../shared/forms/cd-form';
import { CdFormGroup } from '../../../../shared/forms/cd-form-group';
import { CdTableColumn } from '../../../../shared/models/cd-table-column';
import { AuthStorageService } from '../../../../shared/services/auth-storage.service';
templateUrl: './osd-form.component.html',
styleUrls: ['./osd-form.component.scss']
})
-export class OsdFormComponent implements OnInit {
+export class OsdFormComponent extends CdForm implements OnInit {
@ViewChild('dataDeviceSelectionGroups', { static: false })
dataDeviceSelectionGroups: OsdDevicesSelectionGroupsComponent;
form: CdFormGroup;
columns: Array<CdTableColumn> = [];
- loading = false;
allDevices: InventoryDevice[] = [];
availDevices: InventoryDevice[] = [];
features: { [key: string]: OsdFeature };
featureList: OsdFeature[] = [];
- hasOrchestrator = false;
+ hasOrchestrator = true;
docsUrl: string;
constructor(
private router: Router,
private bsModalService: BsModalService
) {
+ super();
this.resource = this.i18n('OSDs');
this.action = this.actionLabels.CREATE;
this.features = {
ngOnInit() {
this.orchService.status().subscribe((status) => {
this.hasOrchestrator = status.available;
- if (this.hasOrchestrator) {
+ if (status.available) {
this.getDataDevices();
+ } else {
+ this.loadingNone();
}
});
}
getDataDevices() {
- if (this.loading) {
- return;
- }
- this.loading = true;
this.orchService.inventoryDeviceList().subscribe(
(devices: InventoryDevice[]) => {
this.allDevices = _.filter(devices, 'available');
this.availDevices = [...this.allDevices];
- this.loading = false;
+ this.loadingReady();
},
() => {
this.allDevices = [];
this.availDevices = [];
- this.loading = false;
+ this.loadingError();
}
);
}
-<cd-loading-panel *ngIf="loading && !error"
- i18n>Loading configuration...</cd-loading-panel>
-<cd-alert-panel type="error"
- *ngIf="loading && error"
- i18n>The configuration could not be loaded.</cd-alert-panel>
-
<div class="cd-col-form"
- *ngIf="!loading && !error">
+ *cdFormLoading="loading">
<ng-container [ngSwitch]="step">
<!-- Configuration step -->
<div *ngSwitchCase="1">
import { I18n } from '@ngx-translate/i18n-polyfill';
import * as _ from 'lodash';
-import { BlockUI, NgBlockUI } from 'ng-block-ui';
import { forkJoin as observableForkJoin } from 'rxjs';
import { MgrModuleService } from '../../../shared/api/mgr-module.service';
import { TelemetryService } from '../../../shared/api/telemetry.service';
import { NotificationType } from '../../../shared/enum/notification-type.enum';
+import { CdForm } from '../../../shared/forms/cd-form';
import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
import { CdFormGroup } from '../../../shared/forms/cd-form-group';
import { CdValidators } from '../../../shared/forms/cd-validators';
templateUrl: './telemetry.component.html',
styleUrls: ['./telemetry.component.scss']
})
-export class TelemetryComponent implements OnInit {
- @BlockUI()
- blockUI: NgBlockUI;
-
- error = false;
+export class TelemetryComponent extends CdForm implements OnInit {
configForm: CdFormGroup;
licenseAgrmt = false;
- loading = false;
moduleEnabled: boolean;
options: Object = {};
previewForm: CdFormGroup;
private telemetryService: TelemetryService,
private i18n: I18n,
private textToDownloadService: TextToDownloadService
- ) {}
+ ) {
+ super();
+ }
ngOnInit() {
- this.loading = true;
const observables = [
this.mgrModuleService.getOptions('telemetry'),
this.mgrModuleService.getConfig('telemetry')
const configs = _.pick(resp[1], this.requiredFields);
this.createConfigForm();
this.configForm.setValue(configs);
- this.loading = false;
+ this.loadingReady();
},
(_error) => {
- this.error = true;
+ this.loadingError();
}
);
}
}
private getReport() {
- this.loading = true;
+ this.loadingStart();
+
this.telemetryService.getReport().subscribe(
(resp: object) => {
this.report = resp;
this.reportId = resp['report']['report_id'];
this.createPreviewForm();
- this.loading = false;
+ this.loadingReady();
this.step++;
},
(_error) => {
- this.error = true;
+ this.loadingError();
}
);
}
-<div class="cd-col-form">
+<div class="cd-col-form"
+ *cdFormLoading="loading">
<form name="nfsForm"
#formDir="ngForm"
[formGroup]="nfsForm"
import { SelectOption } from '../../../shared/components/select/select-option.model';
import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
import { Icons } from '../../../shared/enum/icons.enum';
+import { CdForm } from '../../../shared/forms/cd-form';
import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
import { CdFormGroup } from '../../../shared/forms/cd-form-group';
import { CdValidators } from '../../../shared/forms/cd-validators';
templateUrl: './nfs-form.component.html',
styleUrls: ['./nfs-form.component.scss']
})
-export class NfsFormComponent implements OnInit {
+export class NfsFormComponent extends CdForm implements OnInit {
@ViewChild('nfsClients', { static: true })
nfsClients: NfsFormClientComponent;
private i18n: I18n,
public actionLabels: ActionLabelsI18n
) {
+ super();
this.permission = this.authStorageService.getPermissions().pool;
this.resource = this.i18n('NFS export');
this.createForm();
if (data[4]) {
this.resolveModel(data[4]);
}
+
+ this.loadingReady();
});
}
-<cd-loading-panel *ngIf="!(info && ecProfiles)"
- i18n>Loading...</cd-loading-panel>
-
-<div class="cd-col-form">
+<div class="cd-col-form"
+ *cdFormLoading="loading">
<form name="form"
- *ngIf="info && ecProfiles"
#formDir="ngForm"
[formGroup]="form"
novalidate>
navigationSpy = spyOn(router, 'navigate').and.stub();
setUpPoolComponent();
+
+ component.loadingReady();
});
it('should create', () => {
import { SelectOption } from '../../../shared/components/select/select-option.model';
import { ActionLabelsI18n, URLVerbs } from '../../../shared/constants/app.constants';
import { Icons } from '../../../shared/enum/icons.enum';
+import { CdForm } from '../../../shared/forms/cd-form';
import { CdFormGroup } from '../../../shared/forms/cd-form-group';
import { CdValidators } from '../../../shared/forms/cd-validators';
import {
templateUrl: './pool-form.component.html',
styleUrls: ['./pool-form.component.scss']
})
-export class PoolFormComponent implements OnInit {
+export class PoolFormComponent extends CdForm implements OnInit {
@ViewChild('crushInfoTabs', { static: false }) crushInfoTabs: TabsetComponent;
@ViewChild('crushDeletionBtn', { static: false }) crushDeletionBtn: TooltipDirective;
@ViewChild('ecpInfoTabs', { static: false }) ecpInfoTabs: TabsetComponent;
private i18n: I18n,
public actionLabels: ActionLabelsI18n
) {
+ super();
this.editing = this.router.url.startsWith(`/pool/${URLVerbs.EDIT}`);
this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE;
this.resource = this.i18n('pool');
this.initEditMode();
} else {
this.setAvailableApps();
+ this.loadingReady();
}
this.listenToChanges();
this.setComplexValidators();
this.poolService.get(param.name).subscribe((pool: Pool) => {
this.data.pool = pool;
this.initEditFormData(pool);
+ this.loadingReady();
})
);
}
-<cd-loading-panel *ngIf="editing && loading && !error"
- i18n>Loading bucket data...</cd-loading-panel>
-
<div class="cd-col-form"
- *ngIf="!loading && !error">
+ *cdFormLoading="loading">
<form name="bucketForm"
#frm="ngForm"
[formGroup]="bucketForm"
import { configureTestBed, FormHelper, i18nProviders } from '../../../../testing/unit-test-helper';
import { RgwBucketService } from '../../../shared/api/rgw-bucket.service';
import { RgwSiteService } from '../../../shared/api/rgw-site.service';
+import { RgwUserService } from '../../../shared/api/rgw-user.service';
import { NotificationType } from '../../../shared/enum/notification-type.enum';
import { NotificationService } from '../../../shared/services/notification.service';
import { SharedModule } from '../../../shared/shared.module';
let rgwBucketService: RgwBucketService;
let getPlacementTargetsSpy: jasmine.Spy;
let rgwBucketServiceGetSpy: jasmine.Spy;
+ let enumerateSpy: jasmine.Spy;
let formHelper: FormHelper;
configureTestBed({
rgwBucketService = TestBed.get(RgwBucketService);
rgwBucketServiceGetSpy = spyOn(rgwBucketService, 'get');
getPlacementTargetsSpy = spyOn(TestBed.get(RgwSiteService), 'getPlacementTargets');
+ enumerateSpy = spyOn(TestBed.get(RgwUserService), 'enumerate');
formHelper = new FormHelper(component.bucketForm);
});
]
};
getPlacementTargetsSpy.and.returnValue(observableOf(payload));
+ enumerateSpy.and.returnValue(observableOf([]));
fixture.detectChanges();
expect(component.zonegroup).toBe(payload.zonegroup);
component['route'].params = observableOf({ bid: 'bid' });
component.editing = true;
rgwBucketServiceGetSpy.and.returnValue(observableOf(fakeResponse));
+ enumerateSpy.and.returnValue(observableOf([]));
component.ngOnInit();
component.bucketForm.patchValue({
versioning: versioningChecked,
import { I18n } from '@ngx-translate/i18n-polyfill';
import * as _ from 'lodash';
+import { forkJoin } from 'rxjs';
import { RgwBucketService } from '../../../shared/api/rgw-bucket.service';
import { RgwSiteService } from '../../../shared/api/rgw-site.service';
import { ActionLabelsI18n, URLVerbs } from '../../../shared/constants/app.constants';
import { Icons } from '../../../shared/enum/icons.enum';
import { NotificationType } from '../../../shared/enum/notification-type.enum';
+import { CdForm } from '../../../shared/forms/cd-form';
import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
import { CdFormGroup } from '../../../shared/forms/cd-form-group';
import { CdValidators } from '../../../shared/forms/cd-validators';
templateUrl: './rgw-bucket-form.component.html',
styleUrls: ['./rgw-bucket-form.component.scss']
})
-export class RgwBucketFormComponent implements OnInit {
+export class RgwBucketFormComponent extends CdForm implements OnInit {
bucketForm: CdFormGroup;
editing = false;
- error = false;
- loading = false;
owners: string[] = null;
action: string;
resource: string;
private i18n: I18n,
public actionLabels: ActionLabelsI18n
) {
+ super();
this.editing = this.router.url.startsWith(`/rgw/bucket/${URLVerbs.EDIT}`);
this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE;
this.resource = this.i18n('bucket');
}
ngOnInit() {
- // Get the list of possible owners.
- this.rgwUserService.enumerate().subscribe((resp: string[]) => {
- this.owners = resp.sort();
- });
+ const promises = {
+ owners: this.rgwUserService.enumerate()
+ };
if (!this.editing) {
- // Get placement targets:
- this.rgwSiteService.getPlacementTargets().subscribe((placementTargets: any) => {
- this.zonegroup = placementTargets['zonegroup'];
- _.forEach(placementTargets['placement_targets'], (placementTarget) => {
- placementTarget['description'] = `${placementTarget['name']} (${this.i18n('pool')}: ${
- placementTarget['data_pool']
- })`;
- this.placementTargets.push(placementTarget);
- });
-
- // If there is only 1 placement target, select it by default:
- if (this.placementTargets.length === 1) {
- this.bucketForm.get('placement-target').setValue(this.placementTargets[0]['name']);
- }
- });
+ promises['getPlacementTargets'] = this.rgwSiteService.getPlacementTargets();
}
// Process route parameters.
this.route.params.subscribe((params: { bid: string }) => {
- if (!params.hasOwnProperty('bid')) {
- return;
+ if (params.hasOwnProperty('bid')) {
+ const bid = decodeURIComponent(params.bid);
+ promises['getBid'] = this.rgwBucketService.get(bid);
}
- const bid = decodeURIComponent(params.bid);
- this.loading = true;
- this.rgwBucketService.get(bid).subscribe((resp: object) => {
- this.loading = false;
+ forkJoin(promises).subscribe((data: any) => {
+ // Get the list of possible owners.
+ this.owners = (<string[]>data.owners).sort();
- // Get the default values (incl. the values from disabled fields).
- const defaults = _.clone(this.bucketForm.getRawValue());
+ // Get placement targets:
+ if (data['getPlacementTargets']) {
+ const placementTargets = data['getPlacementTargets'];
+ this.zonegroup = placementTargets['zonegroup'];
+ _.forEach(placementTargets['placement_targets'], (placementTarget) => {
+ placementTarget['description'] = `${placementTarget['name']} (${this.i18n('pool')}: ${
+ placementTarget['data_pool']
+ })`;
+ this.placementTargets.push(placementTarget);
+ });
- // Get the values displayed in the form. We need to do that to
- // extract those key/value pairs from the response data, otherwise
- // the Angular react framework will throw an error if there is no
- // field for a given key.
- let value: object = _.pick(resp, _.keys(defaults));
- value['placement-target'] = resp['placement_rule'];
- value['versioning'] = resp['versioning'] === RgwBucketVersioning.ENABLED;
- value['mfa-delete'] = resp['mfa_delete'] === RgwBucketMfaDelete.ENABLED;
+ // If there is only 1 placement target, select it by default:
+ if (this.placementTargets.length === 1) {
+ this.bucketForm.get('placement-target').setValue(this.placementTargets[0]['name']);
+ }
+ }
+
+ if (data['getBid']) {
+ const bidResp = data['getBid'];
+ // Get the default values (incl. the values from disabled fields).
+ const defaults = _.clone(this.bucketForm.getRawValue());
- // Append default values.
- value = _.merge(defaults, value);
+ // Get the values displayed in the form. We need to do that to
+ // extract those key/value pairs from the response data, otherwise
+ // the Angular react framework will throw an error if there is no
+ // field for a given key.
+ let value: object = _.pick(bidResp, _.keys(defaults));
+ value['placement-target'] = bidResp['placement_rule'];
+ value['versioning'] = bidResp['versioning'] === RgwBucketVersioning.ENABLED;
+ value['mfa-delete'] = bidResp['mfa_delete'] === RgwBucketMfaDelete.ENABLED;
- // Update the form.
- this.bucketForm.setValue(value);
- if (this.editing) {
- this.isVersioningAlreadyEnabled = this.isVersioningEnabled;
- this.isMfaDeleteAlreadyEnabled = this.isMfaDeleteEnabled;
- this.setMfaDeleteValidators();
+ // Append default values.
+ value = _.merge(defaults, value);
+
+ // Update the form.
+ this.bucketForm.setValue(value);
+ if (this.editing) {
+ this.isVersioningAlreadyEnabled = this.isVersioningEnabled;
+ this.isMfaDeleteAlreadyEnabled = this.isMfaDeleteEnabled;
+ this.setMfaDeleteValidators();
+ }
}
+
+ this.loadingReady();
});
});
}
-<cd-loading-panel *ngIf="editing && loading && !error"
- i18n>Loading user data...</cd-loading-panel>
-<cd-alert-panel type="error"
- *ngIf="editing && error"
- (backAction)="goToListView()"
- i18n>The user data could not be loaded.</cd-alert-panel>
-
<div class="cd-col-form"
- *ngIf="!loading && !error">
+ *cdFormLoading="loading">
<form #frm="ngForm"
[formGroup]="userForm"
novalidate>
import { ActionLabelsI18n, URLVerbs } from '../../../shared/constants/app.constants';
import { Icons } from '../../../shared/enum/icons.enum';
import { NotificationType } from '../../../shared/enum/notification-type.enum';
+import { CdForm } from '../../../shared/forms/cd-form';
import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
import { CdFormGroup } from '../../../shared/forms/cd-form-group';
import { CdValidators, isEmptyInputValue } from '../../../shared/forms/cd-validators';
templateUrl: './rgw-user-form.component.html',
styleUrls: ['./rgw-user-form.component.scss']
})
-export class RgwUserFormComponent implements OnInit {
+export class RgwUserFormComponent extends CdForm implements OnInit {
userForm: CdFormGroup;
editing = false;
- error = false;
- loading = false;
submitObservables: Observable<Object>[] = [];
icons = Icons;
subusers: RgwUserSubuser[] = [];
private i18n: I18n,
public actionLabels: ActionLabelsI18n
) {
+ super();
this.resource = this.i18n('user');
this.subuserLabel = this.i18n('subuser');
this.s3keyLabel = this.i18n('S3 Key');
// Process route parameters.
this.route.params.subscribe((params: { uid: string }) => {
if (!params.hasOwnProperty('uid')) {
+ this.loadingReady();
return;
}
const uid = decodeURIComponent(params.uid);
- this.loading = true;
// Load the user and quota information.
const observables = [];
observables.push(this.rgwUserService.get(uid));
observables.push(this.rgwUserService.getQuota(uid));
observableForkJoin(observables).subscribe(
(resp: any[]) => {
- this.loading = false;
// Get the default values.
const defaults = _.clone(this.userForm.value);
// Extract the values displayed in the form.
}
});
this.capabilities = resp[0].caps;
+
+ this.loadingReady();
},
- (error) => {
- this.error = error;
+ () => {
+ this.loadingError();
}
);
});
-<div class="cd-col-form">
+<div class="cd-col-form"
+ *cdFormLoading="loading">
<form name="roleForm"
#formDir="ngForm"
[formGroup]="roleForm"
import { ScopeService } from '../../../shared/api/scope.service';
import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
import { NotificationType } from '../../../shared/enum/notification-type.enum';
+import { CdForm } from '../../../shared/forms/cd-form';
import { CdFormGroup } from '../../../shared/forms/cd-form-group';
import { CdValidators } from '../../../shared/forms/cd-validators';
import { CdTableColumn } from '../../../shared/models/cd-table-column';
templateUrl: './role-form.component.html',
styleUrls: ['./role-form.component.scss']
})
-export class RoleFormComponent implements OnInit {
+export class RoleFormComponent extends CdForm implements OnInit {
@ViewChild('headerPermissionCheckboxTpl', { static: true })
headerPermissionCheckboxTpl: TemplateRef<any>;
@ViewChild('cellScopeCheckboxTpl', { static: true })
private i18n: I18n,
public actionLabels: ActionLabelsI18n
) {
+ super();
this.resource = this.i18n('role');
this.createForm();
this.listenToChanges();
this.scopeService.list().subscribe((scopes: Array<string>) => {
this.scopes = scopes;
this.roleForm.get('scopes_permissions').setValue({});
+
+ this.loadingReady();
});
}
['name', 'description', 'scopes_permissions'].forEach((key) =>
this.roleForm.get(key).setValue(resp[1][key])
);
+
+ this.loadingReady();
});
});
}
-<cd-loading-panel *ngIf="!pwdExpirationSettings"
- i18n>Loading...</cd-loading-panel>
-
-<div class="cd-col-form">
+<div class="cd-col-form"
+ *cdFormLoading="loading">
<form name="userForm"
#formDir="ngForm"
[formGroup]="userForm"
- *ngIf="pwdExpirationSettings"
novalidate>
<div class="card">
<div i18n="form title|Example: Create Pool@@formTitle"
import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
import { Icons } from '../../../shared/enum/icons.enum';
import { NotificationType } from '../../../shared/enum/notification-type.enum';
+import { CdForm } from '../../../shared/forms/cd-form';
import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
import { CdFormGroup } from '../../../shared/forms/cd-form-group';
import { CdValidators } from '../../../shared/forms/cd-validators';
templateUrl: './user-form.component.html',
styleUrls: ['./user-form.component.scss']
})
-export class UserFormComponent implements OnInit {
+export class UserFormComponent extends CdForm implements OnInit {
@ViewChild('removeSelfUserReadUpdatePermissionTpl', { static: true })
removeSelfUserReadUpdatePermissionTpl: TemplateRef<any>;
private formBuilder: CdFormBuilder,
private settingsService: SettingsService
) {
+ super();
this.resource = this.i18n('user');
this.createForm();
this.messages = new SelectMessages({ empty: this.i18n('There are no roles.') }, this.i18n);
pwdExpirationDateField.setValue(expirationDate);
pwdExpirationDateField.setValidators([Validators.required]);
}
+
+ this.loadingReady();
}
}
);
this.userService.get(username).subscribe((userFormModel: UserFormModel) => {
this.response = _.cloneDeep(userFormModel);
this.setResponse(userFormModel);
+
+ this.loadingReady();
});
});
}
<ng-template #content>
<ng-content></ng-content>
</ng-template>
-
-<div class="text-right"
- *ngIf="backAction.observers.length > 0">
- <button class="btn btn-light tc_backButton"
- type="button"
- (click)="backAction.emit()"
- autofocus
- i18n>Back</button>
-</div>
-import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
import { I18n } from '@ngx-translate/i18n-polyfill';
import { Icons } from '../../enum/icons.enum';
title = '';
@Input()
bootstrapClass = '';
- @Output()
- backAction = new EventEmitter();
@Input()
type: 'warning' | 'error' | 'info' | 'success';
@Input()
import { configureTestBed, modalServiceShow } from '../../../../testing/unit-test-helper';
import { DirectivesModule } from '../../directives/directives.module';
+import { AlertPanelComponent } from '../alert-panel/alert-panel.component';
+import { LoadingPanelComponent } from '../loading-panel/loading-panel.component';
import { CriticalConfirmationModalComponent } from './critical-confirmation-modal.component';
@NgModule({
let fixture: ComponentFixture<CriticalConfirmationModalComponent>;
configureTestBed({
- declarations: [MockComponent, CriticalConfirmationModalComponent],
+ declarations: [
+ MockComponent,
+ CriticalConfirmationModalComponent,
+ LoadingPanelComponent,
+ AlertPanelComponent
+ ],
schemas: [NO_ERRORS_SCHEMA],
imports: [ModalModule.forRoot(), ReactiveFormsModule, MockModule, DirectivesModule],
providers: [BsModalRef]
<alert type="info">
<strong>
<i [ngClass]="[icons.spinner, icons.spin]"
- aria-hidden="true"></i>
+ aria-hidden="true"
+ class="mr-2"></i>
</strong>
<ng-content></ng-content>
</alert>
import { NgModule } from '@angular/core';
+import { AlertPanelComponent } from '../components/alert-panel/alert-panel.component';
+import { LoadingPanelComponent } from '../components/loading-panel/loading-panel.component';
import { AutofocusDirective } from './autofocus.directive';
import { Copy2ClipboardButtonDirective } from './copy2clipboard-button.directive';
import { DimlessBinaryPerSecondDirective } from './dimless-binary-per-second.directive';
import { DimlessBinaryDirective } from './dimless-binary.directive';
+import { FormLoadingDirective } from './form-loading.directive';
import { IopsDirective } from './iops.directive';
import { MillisecondsDirective } from './milliseconds.directive';
import { PasswordButtonDirective } from './password-button.directive';
PasswordButtonDirective,
TrimDirective,
MillisecondsDirective,
- IopsDirective
+ IopsDirective,
+ FormLoadingDirective
],
exports: [
AutofocusDirective,
PasswordButtonDirective,
TrimDirective,
MillisecondsDirective,
- IopsDirective
+ IopsDirective,
+ FormLoadingDirective
],
- providers: []
+ providers: [],
+ entryComponents: [LoadingPanelComponent, AlertPanelComponent]
})
export class DirectivesModule {}
--- /dev/null
+import { Component } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+
+import { AlertModule } from 'ngx-bootstrap/alert';
+
+import { configureTestBed, i18nProviders } from '../../../testing/unit-test-helper';
+import { CdForm } from '../forms/cd-form';
+import { SharedModule } from '../shared.module';
+import { FormLoadingDirective } from './form-loading.directive';
+
+@Component({ selector: 'cd-test-cmp', template: '<span *cdFormLoading="loading">foo</span>' })
+class TestComponent extends CdForm {
+ constructor() {
+ super();
+ }
+}
+
+describe('FormLoadingDirective', () => {
+ let component: TestComponent;
+ let fixture: ComponentFixture<any>;
+
+ const expectShown = (elem: number, error: number, loading: number) => {
+ expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(elem);
+ expect(fixture.debugElement.queryAll(By.css('cd-alert-panel')).length).toEqual(error);
+ expect(fixture.debugElement.queryAll(By.css('cd-loading-panel')).length).toEqual(loading);
+ };
+
+ configureTestBed({
+ declarations: [TestComponent],
+ imports: [AlertModule.forRoot(), SharedModule],
+ providers: [i18nProviders]
+ });
+
+ afterEach(() => {
+ fixture = null;
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TestComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create an instance', () => {
+ const directive = new FormLoadingDirective(null, null, null, null);
+ expect(directive).toBeTruthy();
+ });
+
+ it('should show loading component by default', () => {
+ expectShown(0, 0, 1);
+
+ const alert = fixture.debugElement.nativeElement.querySelector('cd-loading-panel alert');
+ expect(alert.textContent).toBe('Loading form data...');
+ });
+
+ it('should show error component when calling loadingError()', () => {
+ component.loadingError();
+ fixture.detectChanges();
+
+ expectShown(0, 1, 0);
+
+ const alert = fixture.debugElement.nativeElement.querySelector(
+ 'cd-alert-panel .alert-panel-text'
+ );
+ expect(alert.textContent).toBe('Form data could not be loaded.');
+ });
+
+ it('should show original component when calling loadingReady()', () => {
+ component.loadingReady();
+ fixture.detectChanges();
+
+ expectShown(1, 0, 0);
+
+ const alert = fixture.debugElement.nativeElement.querySelector('span');
+ expect(alert.textContent).toBe('foo');
+ });
+
+ it('should show nothing when calling loadingNone()', () => {
+ component.loadingNone();
+ fixture.detectChanges();
+
+ expectShown(0, 0, 0);
+ });
+});
--- /dev/null
+import {
+ ComponentFactoryResolver,
+ Directive,
+ Input,
+ TemplateRef,
+ ViewContainerRef
+} from '@angular/core';
+
+import { I18n } from '@ngx-translate/i18n-polyfill';
+
+import { AlertPanelComponent } from '../components/alert-panel/alert-panel.component';
+import { LoadingPanelComponent } from '../components/loading-panel/loading-panel.component';
+import { LoadingStatus } from '../forms/cd-form';
+
+@Directive({
+ selector: '[cdFormLoading]'
+})
+export class FormLoadingDirective {
+ constructor(
+ private templateRef: TemplateRef<any>,
+ private viewContainer: ViewContainerRef,
+ private componentFactoryResolver: ComponentFactoryResolver,
+ private i18n: I18n
+ ) {}
+
+ @Input('cdFormLoading') set cdFormLoading(condition: LoadingStatus) {
+ let factory: any;
+ let content: any;
+
+ this.viewContainer.clear();
+
+ switch (condition) {
+ case LoadingStatus.Loading:
+ factory = this.componentFactoryResolver.resolveComponentFactory(LoadingPanelComponent);
+ content = this.resolveNgContent(this.i18n(`Loading form data...`));
+ this.viewContainer.createComponent(factory, null, null, content);
+ break;
+ case LoadingStatus.Ready:
+ this.viewContainer.createEmbeddedView(this.templateRef);
+ break;
+ case LoadingStatus.Error:
+ factory = this.componentFactoryResolver.resolveComponentFactory(AlertPanelComponent);
+ content = this.resolveNgContent(this.i18n(`Form data could not be loaded.`));
+ const componentRef = this.viewContainer.createComponent(factory, null, null, content);
+ (<AlertPanelComponent>componentRef.instance).type = 'error';
+ break;
+ }
+ }
+
+ resolveNgContent(content: string) {
+ const element = document.createTextNode(content);
+ return [[element]];
+ }
+}
--- /dev/null
+import { CdForm, LoadingStatus } from './cd-form';
+
+describe('CdForm', () => {
+ let form: CdForm;
+
+ beforeEach(() => {
+ form = new CdForm();
+ });
+
+ describe('loading', () => {
+ it('should start in loading state', () => {
+ expect(form.loading).toBe(LoadingStatus.Loading);
+ });
+
+ it('should change to ready when calling loadingReady', () => {
+ form.loadingReady();
+ expect(form.loading).toBe(LoadingStatus.Ready);
+ });
+
+ it('should change to error state calling loadingError', () => {
+ form.loadingError();
+ expect(form.loading).toBe(LoadingStatus.Error);
+ });
+
+ it('should change to loading state calling loadingStart', () => {
+ form.loadingError();
+ expect(form.loading).toBe(LoadingStatus.Error);
+ form.loadingStart();
+ expect(form.loading).toBe(LoadingStatus.Loading);
+ });
+ });
+});
--- /dev/null
+export enum LoadingStatus {
+ Loading,
+ Ready,
+ Error,
+ None
+}
+
+export class CdForm {
+ loading = LoadingStatus.Loading;
+
+ loadingStart() {
+ this.loading = LoadingStatus.Loading;
+ }
+
+ loadingReady() {
+ this.loading = LoadingStatus.Ready;
+ }
+
+ loadingError() {
+ this.loading = LoadingStatus.Error;
+ }
+
+ loadingNone() {
+ this.loading = LoadingStatus.None;
+ }
+}