blob: d3d319cc2d366fb86a5c7077cc92892b1be3a73c [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges
} from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { Observer } from 'rxjs/Observer';
import 'rxjs/add/operator/startWith';
import { CanComponentDeactivate } from '@modules/shared/services/can-deactivate-guard.service';
import { ShipperCluster } from '../../models/shipper-cluster.type';
import { ShipperClusterService } from '../../models/shipper-cluster-service.type';
import { ShipperClusterServiceConfigurationInterface } from '../../models/shipper-cluster-service-configuration.interface';
import { ShipperConfigurationModel } from '../../models/shipper-configuration.model';
import * as formValidators from '../../directives/validator.directive';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subscription } from 'rxjs/Subscription';
import { ActivatedRoute } from '@angular/router';
import { ListItem } from '@app/classes/list-item';
@Component({
selector: 'shipper-configuration-form',
templateUrl: './shipper-service-configuration-form.component.html',
styleUrls: ['./shipper-service-configuration-form.component.less']
})
export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestroy, OnChanges, CanComponentDeactivate {
private configurationForm: FormGroup;
private validatorForm: FormGroup;
@Input()
clusterName: ShipperCluster;
@Input()
serviceName: ShipperClusterService;
@Input()
configuration: ShipperClusterServiceConfigurationInterface;
@Input()
existingServiceNames: Observable<ShipperClusterService[]> | ShipperClusterService[];
@Input()
validationResponse: { [key: string]: any };
@Input()
disabled = false;
@Output()
configurationSubmit: EventEmitter<FormGroup> = new EventEmitter<FormGroup>();
@Output()
validationSubmit: EventEmitter<FormGroup> = new EventEmitter<FormGroup>();
private configurationComponents$: Observable<string[]>;
private configurationComponentsList$: Observable<ListItem[]>;
private isLeavingDirtyForm = false;
private get clusterNameField(): AbstractControl {
return this.configurationForm.get('clusterName');
}
private get serviceNameField(): AbstractControl {
return this.configurationForm.get('serviceName');
}
private get configurationField(): AbstractControl {
return this.configurationForm.get('configuration');
}
private get componentNameField(): AbstractControl {
return this.validatorForm.get('componentName');
}
private get sampleDataField(): AbstractControl {
return this.validatorForm.get('sampleData');
}
private canDeactivateModalResult: Subject<boolean> = new Subject<boolean>();
private canDeactivateObservable$: Observable<boolean> = Observable.create((observer: Observer<boolean>) => {
this.canDeactivateModalResult.takeUntil(this.destroyed$).subscribe((result: boolean) => {
observer.next(result);
});
});
private serviceNamesListSubject: BehaviorSubject<ShipperClusterService[]> = new BehaviorSubject<
ShipperClusterService[]
>([]);
private subscriptions: Subscription[] = [];
private destroyed$ = new Subject();
constructor(
private formBuilder: FormBuilder,
private activatedRoute: ActivatedRoute,
private changeDetectionRef: ChangeDetectorRef
) {
// This is a fix to avoid the ExpressionChangedAfterItHasBeenCheckedError exception
// We create forms checking if there is serviceName set, so that is why we put this in the constructor.
this.createForms();
}
ngOnInit() {
this.subscriptions.push(
this.activatedRoute.params
.map(params => params.service)
.subscribe(service => {
this.serviceName = service;
})
);
if (!this.serviceName) {
this.configurationForm.controls.serviceName.setValidators([
Validators.required,
formValidators.uniqueServiceNameValidator(this.serviceNamesListSubject)
]);
}
this.configurationComponents$ = this.configurationForm.controls.configuration.valueChanges
.map(
(newValue: string): string[] => {
let components: string[];
try {
const inputs: { [key: string]: any }[] = (newValue ? JSON.parse(newValue) : {}).input;
components = inputs && inputs.length ? inputs.map(input => input.type) : [];
} catch (error) {
components = [];
}
return components || [];
}
)
.startWith([]);
this.configurationComponentsList$ = this.configurationComponents$
.map(
(components: string[]): ListItem[] =>
components
.filter((component: string) => component)
.map(
(component: string, index: number): ListItem => {
return {
value: component,
label: component,
isChecked: index === 0
};
}
)
)
.startWith([]);
if (this.existingServiceNames instanceof Observable) {
this.existingServiceNames.takeUntil(this.destroyed$).subscribe((serviceNames: ShipperClusterService[]) => {
this.serviceNamesListSubject.next(serviceNames);
});
} else {
this.serviceNamesListSubject.next(this.existingServiceNames);
}
}
ngOnChanges(changes: SimpleChanges): void {
if (this.configurationForm) {
Object.keys(changes).forEach((controlName: string) => {
if (this.configurationForm.controls[controlName]) {
let value: any = changes[controlName].currentValue;
if (controlName === 'configuration') {
value = value || new ShipperConfigurationModel();
if (!(value instanceof String)) {
value = this.getConfigurationAsString(value);
}
}
if (this.configurationForm.controls[controlName].value !== value) {
this.configurationForm.controls[controlName].setValue(value);
this.configurationForm.markAsPristine();
}
}
});
}
if (
this.validatorForm &&
changes.clusterName &&
this.validatorForm.controls.clusterName.value !== changes.clusterName.currentValue
) {
this.validatorForm.controls.clusterName.setValue(changes.clusterName.currentValue);
this.validatorForm.markAsPristine();
}
}
ngOnDestroy() {
if (this.subscriptions) {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
}
this.destroyed$.next(true);
}
leaveDirtyFormConfirmed = () => {
this.canDeactivateModalResult.next(true);
this.isLeavingDirtyForm = false;
};
leaveDirtyFormCancelled = () => {
this.canDeactivateModalResult.next(false);
this.isLeavingDirtyForm = false;
};
canDeactivate(): Observable<boolean> {
if (this.configurationForm.pristine) {
return Observable.of(true);
}
this.isLeavingDirtyForm = true;
return this.canDeactivateObservable$;
}
getConfigurationAsString(configuration: ShipperClusterServiceConfigurationInterface): string {
return configuration ? JSON.stringify(configuration, null, 4) : '';
}
createForms(): void {
const configuration: ShipperClusterServiceConfigurationInterface =
this.configuration || (this.serviceName ? this.configuration : new ShipperConfigurationModel());
this.configurationForm = this.formBuilder.group({
clusterName: this.formBuilder.control(this.clusterName, Validators.required),
serviceName: this.formBuilder.control(this.serviceName, [Validators.required]),
configuration: this.formBuilder.control(this.getConfigurationAsString(configuration), [
Validators.required,
formValidators.configurationValidator()
])
});
this.validatorForm = this.formBuilder.group({
clusterName: this.formBuilder.control(this.clusterName, [Validators.required]),
componentName: this.formBuilder.control('', [
Validators.required,
formValidators.getConfigurationServiceValidator(
this.configurationForm.controls.configuration,
listItem => listItem && listItem.length && listItem[0].value
)
]),
sampleData: this.formBuilder.control('', Validators.required),
configuration: this.formBuilder.control('', Validators.required)
});
this.configurationForm.valueChanges.takeUntil(this.destroyed$).subscribe(() => {
this.validatorForm.controls.componentName.updateValueAndValidity();
this.validatorForm.controls.configuration.setValue(this.configurationForm.controls.configuration.value);
});
}
onConfigurationSubmit(): void {
if (this.configurationForm.valid) {
this.configurationSubmit.emit(this.configurationForm);
}
}
onValidationSubmit(): void {
if (this.validatorForm.valid) {
this.validationSubmit.emit(this.validatorForm);
}
}
}