This is an automated email from the ASF dual-hosted git repository. tobiasistvan pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/ambari-logsearch.git
The following commit(s) were added to refs/heads/master by this push: new b89f41f [AMBARI-25028] [Log Search UI] Populate `Component Name` in validator (#67) b89f41f is described below commit b89f41f357d77d6e8e95c7a0aebc43f0f17563b2 Author: Istvan Tobias <tobias.ist...@gmail.com> AuthorDate: Sat Jan 5 23:20:12 2019 +0100 [AMBARI-25028] [Log Search UI] Populate `Component Name` in validator (#67) * MBARI-23456. Add cloud mode documentation (#66) * [AMBARI-25028] [Log Search UI] Populate `Component Name` in validator --- .../dropdown-button/dropdown-button.component.html | 29 ++-- .../dropdown-button/dropdown-button.component.ts | 58 +++++-- .../filter-dropdown.component.spec.ts | 69 ++++---- .../filter-dropdown/filter-dropdown.component.ts | 23 +-- .../shipper-configuration.component.spec.ts | 78 ++++----- .../shipper-configuration.component.ts | 112 +++++++------ ...ipper-service-configuration-form.component.html | 174 +++++++++++++-------- ...ipper-service-configuration-form.component.less | 72 +++++---- ...shipper-service-configuration-form.component.ts | 164 +++++++++++-------- .../shipper/directives/validator.directive.ts | 37 +++-- 10 files changed, 488 insertions(+), 328 deletions(-) diff --git a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.html b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.html index 52f97be..b24d15a 100644 --- a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.html +++ b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.html @@ -15,16 +15,18 @@ limitations under the License. --> -<div [ngClass]="{'dropup': isDropup, 'has-selection': hasSelection}"> - <button [ngClass]="['btn', 'dropdown-toggle', buttonClass]" data-toggle="dropdown"> +<div [ngClass]="{ dropup: isDropup, 'has-selection': hasSelection }"> + <button [ngClass]="['btn', 'dropdown-toggle', buttonClass]" [class.disabled]="disabled" data-toggle="dropdown"> <span class="filter-label"> <span *ngIf="iconClass || label" [class.plain]="!isMultipleChoice && !hideCaret && showSelectedValue"> <span *ngIf="iconClass" [ngClass]="iconClass"></span> - <span *ngIf="label && (!hasSelection || isMultipleChoice || showCommonLabelWithSelection)" - [class.label-before-selection]="isSelectionDisplayable"> - {{label}} + <span + *ngIf="label && (!hasSelection || isMultipleChoice || showCommonLabelWithSelection)" + [class.label-before-selection]="isSelectionDisplayable" + > + {{ label }} </span> - <span *ngIf="showTotalSelection && totalSelection" class="total-selection badge">{{totalSelection}}</span> + <span *ngIf="showTotalSelection && totalSelection" class="total-selection badge">{{ totalSelection }}</span> </span> <span *ngIf="isSelectionDisplayable"> <span class="selected-item-label" *ngFor="let item of selectedItems">{{ item.label | translate }}</span> @@ -32,8 +34,15 @@ <span *ngIf="!hideCaret" class="caret"></span> </span> </button> - <ul data-component="dropdown-list" (selectedItemChange)="updateSelection($event)" - [ngClass]="{'dropdown-menu': true, 'dropdown-menu-right': isRightAlign}" [closeOnSelection]="closeOnSelection" - [items]="options" [actionArguments]="listItemArguments" [isMultipleChoice]="isMultipleChoice" - [useClearToDefaultSelection]="useClearToDefaultSelection" [useLocalFilter]="useDropDownLocalFilter"></ul> + <ul + data-component="dropdown-list" + (selectedItemChange)="updateSelection($event)" + [ngClass]="{ 'dropdown-menu': true, 'dropdown-menu-right': isRightAlign }" + [closeOnSelection]="closeOnSelection" + [items]="options" + [actionArguments]="listItemArguments" + [isMultipleChoice]="isMultipleChoice" + [useClearToDefaultSelection]="useClearToDefaultSelection" + [useLocalFilter]="useDropDownLocalFilter" + ></ul> </div> diff --git a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts index 77ea8e1..fb8a981 100644 --- a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts +++ b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts @@ -16,7 +16,7 @@ * limitations under the License. */ -import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; import { ListItem } from '@app/classes/list-item'; import { UtilsService } from '@app/services/utils.service'; @@ -25,8 +25,7 @@ import { UtilsService } from '@app/services/utils.service'; templateUrl: './dropdown-button.component.html', styleUrls: ['./dropdown-button.component.less'] }) -export class DropdownButtonComponent { - +export class DropdownButtonComponent implements OnChanges { @Input() label?: string; @@ -76,6 +75,9 @@ export class DropdownButtonComponent { @Input() closeOnSelection = true; + @Input() + disabled = false; + protected selectedItems: ListItem[] = []; get selection(): ListItem[] { @@ -83,15 +85,19 @@ export class DropdownButtonComponent { } set selection(items: ListItem[]) { - this.selectedItems = <ListItem[]>(Array.isArray(items) ? items : (items || [])); + this.selectedItems = <ListItem[]>(Array.isArray(items) ? items : items || []); if (this.selectedItems.length > 1 && !this.isMultipleChoice) { this.selectedItems = this.selectedItems.slice(0, 1); } if (this.isMultipleChoice && this.options) { - this.options.forEach((option: ListItem): void => { - const selectionItem = this.selectedItems.find((item: ListItem): boolean => this.utils.isEqual(item.value, option.value)); - option.isChecked = !!selectionItem; - }); + this.options.forEach( + (option: ListItem): void => { + const selectionItem = this.selectedItems.find( + (item: ListItem): boolean => this.utils.isEqual(item.value, option.value) + ); + option.isChecked = !!selectionItem; + } + ); } } @@ -113,9 +119,22 @@ export class DropdownButtonComponent { return this.showSelectedValue && !this.isMultipleChoice && this.hasSelection; } - constructor( - protected utils: UtilsService - ) {} + get value(): any { + const values = this.selectedItems && this.selectedItems.length && this.selectedItems.map(item => item.value); + return this.isMultipleChoice ? values : values[0]; + } + + constructor(protected utils: UtilsService) {} + + ngOnChanges(changes: SimpleChanges) { + if (changes.options) { + this.hanldeOptionsChange(); + } + } + + hanldeOptionsChange() { + this.filterAndSetSelection(); + } clearSelection(silent: boolean = false) { let hasChange = false; @@ -128,14 +147,16 @@ export class DropdownButtonComponent { } } - updateSelection(updates: ListItem | ListItem[], callOnChange: boolean = true): boolean { + updateSelection(updates: ListItem | ListItem[]): boolean { let hasChange = false; if (updates && (!Array.isArray(updates) || updates.length)) { const items: ListItem[] = Array.isArray(updates) ? updates : [updates]; if (this.isMultipleChoice) { items.forEach((item: ListItem) => { if (this.options && this.options.length) { - const itemToUpdate: ListItem = this.options.find((option: ListItem) => this.utils.isEqual(option.value, item.value)); + const itemToUpdate: ListItem = this.options.find((option: ListItem) => + this.utils.isEqual(option.value, item.value) + ); if (itemToUpdate) { hasChange = hasChange || itemToUpdate.isChecked !== item.isChecked; itemToUpdate.isChecked = item.isChecked; @@ -151,15 +172,18 @@ export class DropdownButtonComponent { }); } } else { - this.options.forEach((item: ListItem) => item.isChecked = false); + this.options.forEach((item: ListItem) => (item.isChecked = false)); } - const checkedItems = this.options.filter((option: ListItem): boolean => option.isChecked); - this.selection = checkedItems; + this.filterAndSetSelection(); if (hasChange) { - const selectedValues = checkedItems.map((option: ListItem): any => option.value); + const selectedValues = this.selection.map((option: ListItem): any => option.value); this.selectItem.emit(this.isMultipleChoice ? selectedValues : selectedValues.shift()); } return hasChange; } + protected filterAndSetSelection() { + const checkedItems = this.options.filter((option: ListItem): boolean => option.isChecked); + this.selection = checkedItems; + } } diff --git a/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.spec.ts b/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.spec.ts index 4157333..a8aff3f 100644 --- a/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.spec.ts +++ b/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.spec.ts @@ -15,37 +15,41 @@ * limitations under the License. */ -import {NO_ERRORS_SCHEMA} from '@angular/core'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import {MockHttpRequestModules, TranslationModules} from '@app/test-config.spec'; -import {StoreModule} from '@ngrx/store'; -import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service'; -import {AppStateService, appState} from '@app/services/storage/app-state.service'; -import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.service'; -import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; -import {AuditLogsGraphDataService, auditLogsGraphData} from '@app/services/storage/audit-logs-graph-data.service'; -import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.service'; -import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; +import { NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MockHttpRequestModules, TranslationModules } from '@app/test-config.spec'; +import { StoreModule } from '@ngrx/store'; +import { AppSettingsService, appSettings } from '@app/services/storage/app-settings.service'; +import { AppStateService, appState } from '@app/services/storage/app-state.service'; +import { AuditLogsService, auditLogs } from '@app/services/storage/audit-logs.service'; +import { AuditLogsFieldsService, auditLogsFields } from '@app/services/storage/audit-logs-fields.service'; +import { AuditLogsGraphDataService, auditLogsGraphData } from '@app/services/storage/audit-logs-graph-data.service'; +import { ServiceLogsService, serviceLogs } from '@app/services/storage/service-logs.service'; +import { ServiceLogsFieldsService, serviceLogsFields } from '@app/services/storage/service-logs-fields.service'; import { - ServiceLogsHistogramDataService, serviceLogsHistogramData + ServiceLogsHistogramDataService, + serviceLogsHistogramData } from '@app/services/storage/service-logs-histogram-data.service'; -import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; -import {TabsService, tabs} from '@app/services/storage/tabs.service'; -import {ClustersService, clusters} from '@app/services/storage/clusters.service'; -import {ComponentsService, components} from '@app/services/storage/components.service'; -import {HostsService, hosts} from '@app/services/storage/hosts.service'; -import {UtilsService} from '@app/services/utils.service'; -import {LogsContainerService} from '@app/services/logs-container.service'; -import {AuthService} from '@app/services/auth.service'; +import { + ServiceLogsTruncatedService, + serviceLogsTruncated +} from '@app/services/storage/service-logs-truncated.service'; +import { TabsService, tabs } from '@app/services/storage/tabs.service'; +import { ClustersService, clusters } from '@app/services/storage/clusters.service'; +import { ComponentsService, components } from '@app/services/storage/components.service'; +import { HostsService, hosts } from '@app/services/storage/hosts.service'; +import { UtilsService } from '@app/services/utils.service'; +import { LogsContainerService } from '@app/services/logs-container.service'; +import { AuthService } from '@app/services/auth.service'; -import {FilterDropdownComponent} from './filter-dropdown.component'; -import {ClusterSelectionService} from '@app/services/storage/cluster-selection.service'; -import {LogsStateService} from '@app/services/storage/logs-state.service'; -import {RoutingUtilsService} from '@app/services/routing-utils.service'; -import {LogsFilteringUtilsService} from '@app/services/logs-filtering-utils.service'; -import {RouterTestingModule} from '@angular/router/testing'; -import {NotificationService} from '@modules/shared/services/notification.service'; -import {NotificationsService} from 'angular2-notifications/src/notifications.service'; +import { FilterDropdownComponent } from './filter-dropdown.component'; +import { ClusterSelectionService } from '@app/services/storage/cluster-selection.service'; +import { LogsStateService } from '@app/services/storage/logs-state.service'; +import { RoutingUtilsService } from '@app/services/routing-utils.service'; +import { LogsFilteringUtilsService } from '@app/services/logs-filtering-utils.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NotificationService } from '@modules/shared/services/notification.service'; +import { NotificationsService } from 'angular2-notifications/src/notifications.service'; import * as auth from '@app/store/reducers/auth.reducers'; import { EffectsModule } from '@ngrx/effects'; @@ -77,8 +81,7 @@ describe('FilterDropdownComponent', () => { const httpClient = { get: () => { return { - subscribe: () => { - } + subscribe: () => {} }; } }; @@ -136,9 +139,8 @@ describe('FilterDropdownComponent', () => { NotificationsService, NotificationService ], - schemas: [NO_ERRORS_SCHEMA] - }) - .compileComponents(); + schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA] + }).compileComponents(); })); beforeEach(() => { @@ -150,5 +152,4 @@ describe('FilterDropdownComponent', () => { it('should create component', () => { expect(component).toBeTruthy(); }); - }); diff --git a/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.ts b/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.ts index b0c766e..2bcf4a2 100644 --- a/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.ts +++ b/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.ts @@ -15,10 +15,10 @@ * limitations under the License. */ -import {Component, forwardRef} from '@angular/core'; -import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; -import {DropdownButtonComponent} from '@modules/shared/components/dropdown-button/dropdown-button.component'; -import {ListItem} from '@app/classes/list-item'; +import { Component, forwardRef, Input } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { DropdownButtonComponent } from '@modules/shared/components/dropdown-button/dropdown-button.component'; +import { ListItem } from '@app/classes/list-item'; @Component({ selector: 'filter-dropdown', @@ -33,7 +33,6 @@ import {ListItem} from '@app/classes/list-item'; ] }) export class FilterDropdownComponent extends DropdownButtonComponent implements ControlValueAccessor { - private onChange; private _onChange(value) { @@ -42,6 +41,9 @@ export class FilterDropdownComponent extends DropdownButtonComponent implements } } + @Input() + options: ListItem[] = []; + updateSelection(updates: ListItem | ListItem[], callOnChange: boolean = true): boolean { const hasChange = super.updateSelection(updates); if (hasChange && callOnChange) { @@ -50,15 +52,18 @@ export class FilterDropdownComponent extends DropdownButtonComponent implements return hasChange; } + hanldeOptionsChange() { + super.hanldeOptionsChange(); + this._onChange(this.selection); + } + writeValue(items: ListItem[]) { - this.selection = items ? (Array.isArray(items) ? items : [items] ) : []; + this.selection = items ? (Array.isArray(items) ? items : [items]) : []; } registerOnChange(callback: any): void { this.onChange = callback; } - registerOnTouched() { - } - + registerOnTouched() {} } diff --git a/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.spec.ts b/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.spec.ts index 0ec4f56..a8e2b1f 100644 --- a/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.spec.ts +++ b/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.spec.ts @@ -16,39 +16,45 @@ * limitations under the License. */ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import {ShipperConfigurationComponent} from './shipper-configuration.component'; -import {StoreModule} from '@ngrx/store'; -import {auditLogs, AuditLogsService} from '@app/services/storage/audit-logs.service'; -import {serviceLogsTruncated, ServiceLogsTruncatedService} from '@app/services/storage/service-logs-truncated.service'; -import {components, ComponentsService} from '@app/services/storage/components.service'; -import {UtilsService} from '@app/services/utils.service'; -import {tabs, TabsService} from '@app/services/storage/tabs.service'; -import {serviceLogs, ServiceLogsService} from '@app/services/storage/service-logs.service'; -import {hosts, HostsService} from '@app/services/storage/hosts.service'; -import {MockHttpRequestModules, TranslationModules} from '@app/test-config.spec'; -import {ComponentGeneratorService} from '@app/services/component-generator.service'; -import {auditLogsGraphData, AuditLogsGraphDataService} from '@app/services/storage/audit-logs-graph-data.service'; -import {serviceLogsHistogramData, ServiceLogsHistogramDataService} from '@app/services/storage/service-logs-histogram-data.service'; -import {clusters, ClustersService} from '@app/services/storage/clusters.service'; -import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; -import {appSettings, AppSettingsService} from '@app/services/storage/app-settings.service'; -import {appState, AppStateService} from '@app/services/storage/app-state.service'; -import {ClusterSelectionService} from '@app/services/storage/cluster-selection.service'; -import {serviceLogsFields, ServiceLogsFieldsService} from '@app/services/storage/service-logs-fields.service'; -import {LogsContainerService} from '@app/services/logs-container.service'; -import {ShipperRoutingModule} from '@modules/shipper/shipper-routing.module'; -import {ShipperClusterServiceListComponent} from '@modules/shipper/components/shipper-cluster-service-list/shipper-cluster-service-list.component'; -import {ShipperServiceConfigurationFormComponent} from '@modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {TypeaheadModule} from 'ngx-bootstrap'; -import {DisableControlDirective} from '@modules/shared/directives/disable-control.directive'; -import {ModalComponent} from '@modules/shared/components/modal/modal.component'; -import {RouterTestingModule} from '@angular/router/testing'; -import {ShipperClusterServiceListService} from '@modules/shipper/services/shipper-cluster-service-list.service'; -import {ShipperConfigurationService} from '@modules/shipper/services/shipper-configuration.service'; -import {NotificationService} from '@modules/shared/services/notification.service'; -import {NotificationsService} from 'angular2-notifications/src/notifications.service'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { ShipperConfigurationComponent } from './shipper-configuration.component'; +import { StoreModule } from '@ngrx/store'; +import { auditLogs, AuditLogsService } from '@app/services/storage/audit-logs.service'; +import { + serviceLogsTruncated, + ServiceLogsTruncatedService +} from '@app/services/storage/service-logs-truncated.service'; +import { components, ComponentsService } from '@app/services/storage/components.service'; +import { UtilsService } from '@app/services/utils.service'; +import { tabs, TabsService } from '@app/services/storage/tabs.service'; +import { serviceLogs, ServiceLogsService } from '@app/services/storage/service-logs.service'; +import { hosts, HostsService } from '@app/services/storage/hosts.service'; +import { MockHttpRequestModules, TranslationModules } from '@app/test-config.spec'; +import { ComponentGeneratorService } from '@app/services/component-generator.service'; +import { auditLogsGraphData, AuditLogsGraphDataService } from '@app/services/storage/audit-logs-graph-data.service'; +import { + serviceLogsHistogramData, + ServiceLogsHistogramDataService +} from '@app/services/storage/service-logs-histogram-data.service'; +import { clusters, ClustersService } from '@app/services/storage/clusters.service'; +import { AuditLogsFieldsService, auditLogsFields } from '@app/services/storage/audit-logs-fields.service'; +import { appSettings, AppSettingsService } from '@app/services/storage/app-settings.service'; +import { appState, AppStateService } from '@app/services/storage/app-state.service'; +import { ClusterSelectionService } from '@app/services/storage/cluster-selection.service'; +import { serviceLogsFields, ServiceLogsFieldsService } from '@app/services/storage/service-logs-fields.service'; +import { LogsContainerService } from '@app/services/logs-container.service'; +import { ShipperRoutingModule } from '@modules/shipper/shipper-routing.module'; +import { ShipperClusterServiceListComponent } from '@modules/shipper/components/shipper-cluster-service-list/shipper-cluster-service-list.component'; +import { ShipperServiceConfigurationFormComponent } from '@modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TypeaheadModule } from 'ngx-bootstrap'; +import { DisableControlDirective } from '@modules/shared/directives/disable-control.directive'; +import { ModalComponent } from '@modules/shared/components/modal/modal.component'; +import { RouterTestingModule } from '@angular/router/testing'; +import { ShipperClusterServiceListService } from '@modules/shipper/services/shipper-cluster-service-list.service'; +import { ShipperConfigurationService } from '@modules/shipper/services/shipper-configuration.service'; +import { NotificationService } from '@modules/shared/services/notification.service'; +import { NotificationsService } from 'angular2-notifications/src/notifications.service'; describe('ShipperConfigurationComponent', () => { let component: ShipperConfigurationComponent; @@ -110,9 +116,9 @@ describe('ShipperConfigurationComponent', () => { ShipperServiceConfigurationFormComponent, DisableControlDirective, ModalComponent - ] - }) - .compileComponents(); + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }).compileComponents(); })); beforeEach(() => { diff --git a/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts b/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts index d8fccbd..82ae869 100644 --- a/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts +++ b/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts @@ -15,25 +15,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core'; -import {ActivatedRoute, Router} from '@angular/router'; -import {Response} from '@angular/http'; -import {Observable} from 'rxjs/Observable'; +import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Response } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/skipWhile'; -import {NotificationService, NotificationType} from '@modules/shared/services/notification.service'; -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 {ShipperConfigurationService} from '../../services/shipper-configuration.service'; -import {ShipperClusterServiceListService} from '../../services/shipper-cluster-service-list.service'; -import { - ShipperServiceConfigurationFormComponent -} from '@modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component'; -import {TranslateService} from '@ngx-translate/core'; -import {ClusterSelectionService} from '@app/services/storage/cluster-selection.service'; -import {Subscription} from 'rxjs/Subscription'; +import { NotificationService, NotificationType } from '@modules/shared/services/notification.service'; +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 { ShipperConfigurationService } from '../../services/shipper-configuration.service'; +import { ShipperClusterServiceListService } from '../../services/shipper-cluster-service-list.service'; +import { ShipperServiceConfigurationFormComponent } from '@modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component'; +import { TranslateService } from '@ngx-translate/core'; +import { ClusterSelectionService } from '@app/services/storage/cluster-selection.service'; +import { Subscription } from 'rxjs/Subscription'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { FormGroup } from '@angular/forms'; @@ -43,7 +41,6 @@ import { FormGroup } from '@angular/forms'; styleUrls: ['./shipper-configuration.component.less'] }) export class ShipperConfigurationComponent implements CanComponentDeactivate, OnInit, OnDestroy { - static clusterSelectionStoreKey = 'shipper'; @Input() @@ -57,21 +54,25 @@ export class ShipperConfigurationComponent implements CanComponentDeactivate, On private clusterName$: Observable<ShipperClusterService> = this.activatedRoute.params.map(params => params.cluster); private serviceName$: Observable<ShipperClusterService> = this.activatedRoute.params.map(params => params.service); - private serviceNamesList$: Observable<ShipperClusterService[]> = this.clusterName$.switchMap((cluster: ShipperCluster) => { - return cluster ? this.shipperClusterServiceListService.getServicesForCluster(cluster) : Observable.of(undefined); - }); - - private configuration$: Observable<{[key: string]: any}> = Observable.combineLatest( - this.clusterName$, - this.serviceName$ - ).switchMap(([clusterName, serviceName]: [ShipperCluster, ShipperClusterService]) => { - return clusterName && serviceName ? - this.shipperConfigurationService.loadConfiguration(clusterName, serviceName) : Observable.of(undefined); - }); + private serviceNamesList$: Observable<ShipperClusterService[]> = this.clusterName$.switchMap( + (cluster: ShipperCluster) => { + return cluster ? this.shipperClusterServiceListService.getServicesForCluster(cluster) : Observable.of(undefined); + } + ); + + private configuration$: Observable<{ + [key: string]: any; + }> = Observable.combineLatest(this.clusterName$, this.serviceName$).switchMap( + ([clusterName, serviceName]: [ShipperCluster, ShipperClusterService]) => { + return clusterName && serviceName + ? this.shipperConfigurationService.loadConfiguration(clusterName, serviceName) + : Observable.of(undefined); + } + ); private subscriptions: Subscription[] = []; - validationResponse: {[key: string]: any}; + validationResponse: { [key: string]: any }; constructor( private router: Router, @@ -81,7 +82,7 @@ export class ShipperConfigurationComponent implements CanComponentDeactivate, On private notificationService: NotificationService, private translate: TranslateService, private clusterSelectionStoreService: ClusterSelectionService - ) { } + ) {} ngOnInit() { this.subscriptions.push( @@ -98,7 +99,8 @@ export class ShipperConfigurationComponent implements CanComponentDeactivate, On } private getPathMapForClusterFirstService(cluster: ShipperCluster): Observable<string[]> { - return this.shipperClusterServiceListService.getServicesForCluster(cluster) + return this.shipperClusterServiceListService + .getServicesForCluster(cluster) .switchMap((serviceNamesList: ShipperClusterService[]) => { return Observable.of(this.getRouterLink([cluster, serviceNamesList[0]])); }); @@ -112,26 +114,42 @@ export class ShipperConfigurationComponent implements CanComponentDeactivate, On if (clusterName) { this.clusterName$.first().subscribe((currentClusterName: ShipperCluster) => { if (currentClusterName !== clusterName) { - this.getPathMapForClusterFirstService(clusterName).first().subscribe((path: string[]) => this.router.navigate(path)); + this.getPathMapForClusterFirstService(clusterName) + .first() + .subscribe((path: string[]) => this.router.navigate(path)); } }); } - } + }; private getRouterLink(path: string | string[]): string[] { return [...this.routerPath, ...(Array.isArray(path) ? path : [path])]; } - getResponseHandler(cmd: string, type: string, form?: FormGroup) { - const msgVariables = form.getRawValue(); + getResponseHandler(cmd: string, type: string, msgVariables: any = {}, form?: FormGroup) { return (response: Response) => { const result = response.json(); // @ToDo change the backend response status to some error code if the configuration is not valid and don't use the .message prop - const resultType = response ? (response.ok && !result.errorMessage ? NotificationType.SUCCESS : NotificationType.ERROR) : type; - const translateParams = {errorMessage: (result && result.message) || '', ...msgVariables, ...result}; + const resultType = response + ? response.ok && !result.errorMessage + ? NotificationType.SUCCESS + : NotificationType.ERROR + : type; + const translateParams = { + errorMessage: (result && result.message) || '', + ...msgVariables, + ...result + }; const title = this.translate.instant(`shipperConfiguration.action.${cmd}.title`, translateParams); - const message = this.translate.instant(`shipperConfiguration.action.${cmd}.${resultType}.message`, translateParams); - this.notificationService.addNotification({type: resultType, title, message}); + const message = this.translate.instant( + `shipperConfiguration.action.${cmd}.${resultType}.message`, + translateParams + ); + this.notificationService.addNotification({ + type: resultType, + title, + message + }); if (resultType !== NotificationType.ERROR) { form.markAsPristine(); } @@ -149,23 +167,24 @@ export class ShipperConfigurationComponent implements CanComponentDeactivate, On service: rawValue.serviceName, configuration: JSON.parse(rawValue.configuration) }).subscribe( - this.getResponseHandler(cmd, NotificationType.SUCCESS, configurationForm), - this.getResponseHandler(cmd, NotificationType.ERROR, configurationForm) + this.getResponseHandler(cmd, NotificationType.SUCCESS, rawValue, configurationForm), + this.getResponseHandler(cmd, NotificationType.ERROR, rawValue, configurationForm) ); }); } - private setValidationResult = (result: {[key: string]: any}) => { + private setValidationResult = (result: { [key: string]: any }) => { this.validationResponse = result; - } + }; onValidationFormSubmit(validationForm: FormGroup): void { this.validationResponse = null; const rawValue = validationForm.getRawValue(); + rawValue.componentName = rawValue.componentName[0].value; const request$: Observable<Response> = this.shipperConfigurationService.testConfiguration(rawValue); request$.subscribe( - this.getResponseHandler('validate', NotificationType.SUCCESS, validationForm), - this.getResponseHandler('validate', NotificationType.ERROR, validationForm) + this.getResponseHandler('validate', NotificationType.SUCCESS, rawValue, validationForm), + this.getResponseHandler('validate', NotificationType.ERROR, rawValue, validationForm) ); request$ .filter((response: Response): boolean => response.ok) @@ -178,5 +197,4 @@ export class ShipperConfigurationComponent implements CanComponentDeactivate, On canDeactivate() { return this.configurationFormRef.canDeactivate(); } - } diff --git a/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.html b/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.html index 7e91137..742eb1d 100644 --- a/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.html +++ b/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.html @@ -14,52 +14,78 @@ See the License for the specific language governing permissions and limitations under the License. --> -<ng-template #typeAheadTpl let-item="item"> - {{item | translate}} -</ng-template> +<ng-template #typeAheadTpl let-item="item"> {{ item | translate }} </ng-template> <div class="container-fluid"> <div class="row"> <div class="shipper-form-configuration col-md-6"> <form [formGroup]="configurationForm" (ngSubmit)="onConfigurationSubmit($event)"> <fieldset [disabled]="disabled"> - <h2>{{(serviceName ? 'shipperConfiguration.form.titleEdit' : 'shipperConfiguration.form.titleAdd') | translate}}</h2> - <div [ngClass]="{'has-error': serviceNameField.invalid, 'form-group': true}"> + <h2> + {{ + (serviceName ? 'shipperConfiguration.form.titleEdit' : 'shipperConfiguration.form.titleAdd') | translate + }} + </h2> + <div + [ngClass]="{ + 'has-error': serviceNameField.invalid && configurationForm.touched, + 'form-group': true + }" + > <label> - {{'shipperConfiguration.form.serviceLabel' | translate}} - <span *ngIf="serviceNameField.errors && serviceNameField.errors.serviceNameExists" - class="help-block validation-block pull-right"> - {{'shipperConfiguration.form.errors.serviceName.exists' | translate}} + {{ 'shipperConfiguration.form.serviceLabel' | translate }} + <span + *ngIf="serviceNameField.errors && serviceNameField.errors.serviceNameExists" + class="help-block validation-block pull-right" + > + {{ 'shipperConfiguration.form.errors.serviceName.exists' | translate }} </span> - <span *ngIf="serviceNameField.errors && serviceNameField.errors.required" - class="help-block validation-block pull-right"> - {{'common.form.errors.required' | translate}} + <span + *ngIf="serviceNameField.errors && serviceNameField.errors.required" + class="help-block validation-block pull-right" + > + {{ 'common.form.errors.required' | translate }} </span> </label> - <input *ngIf="!serviceName" formControlName="serviceName" class="form-control"> + <input *ngIf="!serviceName" formControlName="serviceName" class="form-control" /> <ng-container *ngIf="serviceName"> - <div class="shipper-configuration-service-name">{{serviceName}}</div> - <input type="hidden" name="serviceName" formControlName="serviceName"> + <div class="shipper-configuration-service-name">{{ serviceName }}</div> + <input type="hidden" name="serviceName" formControlName="serviceName" /> </ng-container> </div> - <input type="hidden" name="clusterName" formControlName="clusterName"> - <div [ngClass]="{'has-error': configurationField.invalid, 'form-group': true}"> + <input type="hidden" name="clusterName" formControlName="clusterName" /> + <div + [ngClass]="{ + 'has-error': configurationField.invalid, + 'form-group': true + }" + > <label> - {{'shipperConfiguration.form.configurationJSONLabel' | translate}} - <span *ngIf="configurationField.errors && configurationField.errors.invalidJSON" - class="help-block validation-block pull-right"> - {{'shipperConfiguration.form.errors.configuration.invalidJSON' | translate}} + {{ 'shipperConfiguration.form.configurationJSONLabel' | translate }} + <span + *ngIf="configurationField.errors && configurationField.errors.invalidJSON" + class="help-block validation-block pull-right" + > + {{ 'shipperConfiguration.form.errors.configuration.invalidJSON' | translate }} </span> - <span *ngIf="configurationField.errors && configurationField.errors.required" - class="help-block validation-block pull-right"> - {{'common.form.errors.required' | translate}} + <span + *ngIf="configurationField.errors && configurationField.errors.required" + class="help-block validation-block pull-right" + > + {{ 'common.form.errors.required' | translate }} </span> </label> - <textarea class="form-control configuration" name="configuration" - formControlName="configuration"></textarea> + <textarea + class="form-control configuration" + name="configuration" + formControlName="configuration" + ></textarea> </div> - <button class="btn btn-primary pull-right" type="submit" - [disabled]="(!configurationForm.valid || configurationForm.pristine)"> - {{'shipperConfiguration.form.saveBtn.label' | translate}} + <button + class="btn btn-primary pull-right" + type="submit" + [disabled]="!configurationForm.valid || configurationForm.pristine" + > + {{ 'shipperConfiguration.form.saveBtn.label' | translate }} </button> </fieldset> </form> @@ -68,47 +94,67 @@ <div class="container-fluid"> <div class="row"> <form [formGroup]="validatorForm" (ngSubmit)="onValidationSubmit($event)"> - <h2>{{'shipperConfiguration.validator.title' | translate}}</h2> - <input type="hidden" name="clusterName" formControlName="clusterName"> + <h2>{{ 'shipperConfiguration.validator.title' | translate }}</h2> + <input type="hidden" name="clusterName" formControlName="clusterName" /> <div class="form-group" [class.has-warning]="componentNameField.invalid"> <label> - {{'shipperConfiguration.validator.componentNameLabel' | translate}} - <span [class.hide]="!componentNameField.errors || !componentNameField.errors.required" - class="help-block validation-block pull-right"> - {{'common.form.errors.required' | translate}} + <span + [class.hide]=" + !componentNameField.errors || !componentNameField.errors.required || configurationForm.invalid + " + class="help-block validation-block pull-right" + > + {{ 'common.form.errors.required' | translate }} </span> - <span [class.hide]="!componentNameField.value || !componentNameField.errors || !componentNameField.errors.serviceNameDoesNotExistInConfiguration" - class="help-block validation-block pull-right"> - {{'shipperConfiguration.form.errors.componentNameField.serviceNameDoesNotExistInConfiguration' | translate}} + <span + [class.hide]=" + !componentNameField.value || + !componentNameField.errors || + !componentNameField.errors.serviceNameDoesNotExistInConfiguration + " + class="help-block validation-block pull-right" + > + {{ + 'shipperConfiguration.form.errors.componentNameField.serviceNameDoesNotExistInConfiguration' + | translate + }} </span> </label> - <input class="form-control component-name" name="componentName" formControlName="componentName" - [typeahead]="configurationComponents$ | async" - [typeaheadItemTemplate]="typeAheadTpl" - [typeaheadMinLength]="0" - [disableControl]="configurationForm.invalid" - autocomplete="off"> + <filter-dropdown + [options]="configurationComponentsList$ | async" + formControlName="componentName" + [label]="'shipperConfiguration.validator.componentNameLabel' | translate" + [showCommonLabelWithSelection]="true" + [disabled]="configurationForm.invalid || !(configurationComponentsList$ | async).length" + ></filter-dropdown> </div> <div class="form-group" [class.has-warning]="sampleDataField.invalid"> <label> - {{'shipperConfiguration.validator.sampleDataLabel' | translate}} - <span [class.hide]="!sampleDataField.errors || !sampleDataField.errors.required" - class="help-block validation-block pull-right"> - {{'common.form.errors.required' | translate}} + {{ 'shipperConfiguration.validator.sampleDataLabel' | translate }} + <span + [class.hide]="!sampleDataField.errors || !sampleDataField.errors.required" + class="help-block validation-block pull-right" + > + {{ 'common.form.errors.required' | translate }} </span> </label> - <textarea class="form-control sample-data" name="sampleData" formControlName="sampleData" - [disableControl]="configurationForm.invalid"></textarea> + <textarea + class="form-control sample-data" + name="sampleData" + formControlName="sampleData" + [disableControl]="configurationForm.invalid" + ></textarea> </div> <div *ngIf="validationResponse" class="form-group"> - <label> - {{'shipperConfiguration.validator.result' | translate}} - </label> - <pre>{{validationResponse | json}}</pre> + <label> {{ 'shipperConfiguration.validator.result' | translate }} </label> + <pre>{{ validationResponse | json }}</pre> </div> - <button class="btn btn-default pull-right" type="submit" - [disabled]="(!validatorForm.valid)"> - <i class="fa fa-table" aria-hidden="true"></i> {{'shipperConfiguration.form.testBtn.label' | translate}} + <button + class="btn btn-default pull-right" + type="submit" + [disabled]="validatorForm.invalid || configurationForm.invalid" + > + <i class="fa fa-table" aria-hidden="true"></i> {{ 'shipperConfiguration.form.testBtn.label' | translate }} </button> </form> </div> @@ -116,8 +162,14 @@ </div> </div> </div> -<modal *ngIf="isLeavingDirtyForm" (submit)="leaveDirtyFormConfirmed()" (cancel)="leaveDirtyFormCancelled()" - (close)="leaveDirtyFormCancelled()" showCloseButton="false" isSmallModal="true" - title="{{'shipperConfiguration.form.leavingDirty.title' | translate}}" - bodyText="{{'shipperConfiguration.form.leavingDirty.message' | translate}}"> +<modal + *ngIf="isLeavingDirtyForm" + (submit)="leaveDirtyFormConfirmed()" + (cancel)="leaveDirtyFormCancelled()" + (close)="leaveDirtyFormCancelled()" + showCloseButton="false" + isSmallModal="true" + title="{{ 'shipperConfiguration.form.leavingDirty.title' | translate }}" + bodyText="{{ 'shipperConfiguration.form.leavingDirty.message' | translate }}" +> </modal> diff --git a/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.less b/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.less index f6f3713..0fa195c 100644 --- a/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.less +++ b/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.less @@ -16,39 +16,51 @@ * limitations under the License. */ -@import "../../../shared/forms"; - -textarea { - @extend input.form-control - min-height: 5em; - resize: vertical; - width: 100%; - &.validation-result, - &.configuration { - min-height: 30em; +@import '../../../shared/forms'; +:host { + textarea { + &:extend(input.form-control); + min-height: 5em; + resize: vertical; + width: 100%; + &.validation-result, + &.configuration { + min-height: 30em; + } + &.sample-data { + min-height: 8em; + } } - &.sample-data { - min-height: 8em; + label { + width: 100%; } -} -label { - width: 100%; -} -.help-block.validation-block { - display: none; - font-size: .9em; - margin: 0; - opacity: 0; - transition: all 1s ease-in; -} -.has-error, .has-warning { .help-block.validation-block { - display: inline-block; - opacity: 1; + display: none; + font-size: 0.9em; + margin: 0; + opacity: 0; + transition: all 1s ease-in; + } + .has-error, + .has-warning { + .help-block.validation-block { + display: inline-block; + opacity: 1; + } + } + + .shipper-form-configuration { + background-color: @main-background-color; + padding-bottom: 4em; } -} -.shipper-form-configuration { - background-color: @main-background-color; - padding-bottom: 4em; + /deep/ filter-dropdown { + button, + button:focus { + padding: 0; + .label-before-selection { + font-weight: bold; + } + } + } } diff --git a/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.ts b/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.ts index b41b941..d3d319c 100644 --- a/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.ts +++ b/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.ts @@ -16,23 +16,34 @@ * 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 { + 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 { 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 { 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 { 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', @@ -40,7 +51,6 @@ import {ActivatedRoute} from '@angular/router'; styleUrls: ['./shipper-service-configuration-form.component.less'] }) export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestroy, OnChanges, CanComponentDeactivate { - private configurationForm: FormGroup; private validatorForm: FormGroup; @@ -57,7 +67,7 @@ export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestr existingServiceNames: Observable<ShipperClusterService[]> | ShipperClusterService[]; @Input() - validationResponse: {[key: string]: any}; + validationResponse: { [key: string]: any }; @Input() disabled = false; @@ -69,6 +79,7 @@ export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestr validationSubmit: EventEmitter<FormGroup> = new EventEmitter<FormGroup>(); private configurationComponents$: Observable<string[]>; + private configurationComponentsList$: Observable<ListItem[]>; private isLeavingDirtyForm = false; @@ -94,18 +105,20 @@ export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestr private canDeactivateModalResult: Subject<boolean> = new Subject<boolean>(); - private canDeactivateObservable$: Observable<boolean> = Observable.create((observer: Observer<boolean>) => { - this.subscriptions.push( - this.canDeactivateModalResult.subscribe((result: boolean) => { - observer.next(result); - }) - ); + 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 serviceNamesListSubject: BehaviorSubject<ShipperClusterService[]> = new BehaviorSubject< + ShipperClusterService[] + >([]); private subscriptions: Subscription[] = []; + private destroyed$ = new Subject(); + constructor( private formBuilder: FormBuilder, private activatedRoute: ActivatedRoute, @@ -118,33 +131,52 @@ export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestr ngOnInit() { this.subscriptions.push( - this.activatedRoute.params.map(params => params.service).subscribe((service) => { - this.serviceName = service; - }) + 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.changeDetectionRef.detectChanges(); } - 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.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.subscriptions.push( - this.existingServiceNames.subscribe((serviceNames: ShipperClusterService[]) => { - this.serviceNamesListSubject.next(serviceNames); - }) - ); + this.existingServiceNames.takeUntil(this.destroyed$).subscribe((serviceNames: ShipperClusterService[]) => { + this.serviceNamesListSubject.next(serviceNames); + }); } else { this.serviceNamesListSubject.next(this.existingServiceNames); } @@ -168,7 +200,11 @@ export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestr } }); } - if (this.validatorForm && changes.clusterName && this.validatorForm.controls.clusterName.value !== changes.clusterName.currentValue) { + if ( + this.validatorForm && + changes.clusterName && + this.validatorForm.controls.clusterName.value !== changes.clusterName.currentValue + ) { this.validatorForm.controls.clusterName.setValue(changes.clusterName.currentValue); this.validatorForm.markAsPristine(); } @@ -178,17 +214,18 @@ export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestr 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) { @@ -203,39 +240,33 @@ export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestr } createForms(): void { - const configuration: ShipperClusterServiceConfigurationInterface = this.configuration || ( - this.serviceName ? this.configuration : new ShipperConfigurationModel() - ); + 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()] - ) + 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] - ), + clusterName: this.formBuilder.control(this.clusterName, [Validators.required]), componentName: this.formBuilder.control('', [ Validators.required, - formValidators.getConfigurationServiceValidator(this.configurationForm.controls.configuration) + 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.subscriptions.push( - this.configurationForm.valueChanges.subscribe(() => { - this.validatorForm.controls.componentName.updateValueAndValidity(); - this.validatorForm.controls.configuration.setValue(this.configurationForm.controls.configuration.value); - }) - ); + this.configurationForm.valueChanges.takeUntil(this.destroyed$).subscribe(() => { + this.validatorForm.controls.componentName.updateValueAndValidity(); + this.validatorForm.controls.configuration.setValue(this.configurationForm.controls.configuration.value); + }); } onConfigurationSubmit(): void { @@ -249,5 +280,4 @@ export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestr this.validationSubmit.emit(this.validatorForm); } } - } diff --git a/ambari-logsearch-web/src/app/modules/shipper/directives/validator.directive.ts b/ambari-logsearch-web/src/app/modules/shipper/directives/validator.directive.ts index 50c1237..a590125 100644 --- a/ambari-logsearch-web/src/app/modules/shipper/directives/validator.directive.ts +++ b/ambari-logsearch-web/src/app/modules/shipper/directives/validator.directive.ts @@ -16,46 +16,49 @@ * limitations under the License. */ -import {AbstractControl, ValidatorFn} from '@angular/forms'; -import {ShipperClusterService} from '@modules/shipper/models/shipper-cluster-service.type'; -import {ValidationErrors} from '@angular/forms/src/directives/validators'; -import {BehaviorSubject} from 'rxjs/BehaviorSubject'; +import { AbstractControl, ValidatorFn } from '@angular/forms'; +import { ShipperClusterService } from '@modules/shipper/models/shipper-cluster-service.type'; +import { ValidationErrors } from '@angular/forms/src/directives/validators'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; export function configurationValidator(): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { try { - const json: {[key: string]: any} = JSON.parse(control.value); + const json: { [key: string]: any } = JSON.parse(control.value); return null; } catch (error) { return { - invalidJSON: {value: control.value} + invalidJSON: { value: control.value } }; } }; } -export function uniqueServiceNameValidator( - serviceNames: BehaviorSubject<ShipperClusterService[]> -): ValidatorFn { +export function uniqueServiceNameValidator(serviceNames: BehaviorSubject<ShipperClusterService[]>): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const services: ShipperClusterService[] = serviceNames.getValue(); - return services && services.indexOf(control.value) > -1 ? { - serviceNameExists: {value: control.value} - } : null; + return services && services.indexOf(control.value) > -1 + ? { + serviceNameExists: { value: control.value } + } + : null; }; } -export function getConfigurationServiceValidator(configControl: AbstractControl): ValidatorFn { +export function getConfigurationServiceValidator(configControl: AbstractControl, valueMapper?: Function): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { let components: string[]; try { - const inputs: {[key: string]: any}[] = (configControl.value ? JSON.parse(configControl.value) : {}).input; + const inputs: { [key: string]: any }[] = (configControl.value ? JSON.parse(configControl.value) : {}).input; components = inputs && inputs.length ? inputs.map(input => input.type) : []; } catch (error) { components = []; } - return components.length && components.indexOf(control.value) === -1 ? { - serviceNameDoesNotExistInConfiguration: {value: control.value} - } : null; + const value = valueMapper ? valueMapper(control.value) : control.value; + return components.length && components.indexOf(value) === -1 + ? { + serviceNameDoesNotExistInConfiguration: { value: value } + } + : null; }; }