This is an automated email from the ASF dual-hosted git repository. ababiichuk pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/trunk by this push: new f9429ae [AMBARI-23648] - Log Search UI: Various fixes for Shipper Configuration f9429ae is described below commit f9429ae6868beb3cd51659cda4ce5c1f5741fea4 Author: Istvan Tobias <tobias.ist...@gmail.com> AuthorDate: Sat Apr 21 13:37:22 2018 +0200 [AMBARI-23648] - Log Search UI: Various fixes for Shipper Configuration --- .../ambari-logsearch-web/src/app/app.module.ts | 4 +- .../log-index-filter.component.html | 4 -- .../src/app/modules/shared/notifications.less | 1 - .../shared/services/notification.service.ts | 19 ++++++-- .../shipper-configuration.component.less | 4 ++ .../shipper-configuration.component.ts | 8 ++- ...ipper-service-configuration-form.component.html | 7 ++- ...shipper-service-configuration-form.component.ts | 28 ++++++++--- .../shipper/directives/validator.directive.ts | 17 ++++++- .../app/modules/shipper/services/shipper.guard.ts | 57 ++++++++++++++++++++++ .../app/modules/shipper/shipper-routing.module.ts | 5 +- .../src/app/modules/shipper/shipper.module.ts | 4 +- .../ambari-logsearch-web/src/assets/i18n/en.json | 13 ++--- 13 files changed, 138 insertions(+), 33 deletions(-) diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts index b0612fe..8db8dea 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts @@ -24,11 +24,11 @@ import {InMemoryBackendService} from 'angular-in-memory-web-api'; import {TypeaheadModule, TooltipModule} from 'ngx-bootstrap'; import {TranslateModule, TranslateLoader} from '@ngx-translate/core'; import {StoreModule} from '@ngrx/store'; -import { StoreDevtoolsModule } from '@ngrx/store-devtools'; +import {StoreDevtoolsModule} from '@ngrx/store-devtools'; import {MomentModule} from 'angular2-moment'; import {MomentTimezoneModule} from 'angular-moment-timezone'; import {NgStringPipesModule} from 'angular-pipes'; -import { SimpleNotificationsModule } from 'angular2-notifications'; +import {SimpleNotificationsModule} from 'angular2-notifications'; import {environment} from '@envs/environment'; diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.html index 50d7742..5137275 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.html @@ -69,10 +69,6 @@ <div class="col-md-6"> <div class="text-uppercase">{{'logIndexFilter.hostname' | translate}}</div> <input type="text" class="form-control" [(ngModel)]="component.hosts" (change)="updateValue()"> - <!-- TODO implement typeahead similar to search box - (some functionality and appearance most likely will be shared) --> - <!-- <input type="text" class="form-control" [(ngModel)]="component.hosts" (change)="updateValue()" - [typeahead]="hosts | async" typeaheadOptionField="name"> --> </div> <div class=" col-md-6"> <div class="text-uppercase">{{'logIndexFilter.expiryDate' | translate}}</div> diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/notifications.less b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/notifications.less index e9f4fd9..7a0a045 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/notifications.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/notifications.less @@ -56,7 +56,6 @@ .sn-content { font-size: @notification-content-font-size; padding: 0; - white-space: pre-wrap; } .sn-progress-loader { height: 2px; diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/services/notification.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/services/notification.service.ts index b149e38..df6ca2a 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/services/notification.service.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/services/notification.service.ts @@ -40,6 +40,14 @@ export const notificationIcons: Icons = { }; Object.assign(defaultIcons, notificationIcons); +export const messageTemplate = ` +<div class="notification-wrapper"> + <div class="notification-header sn-title">{{title}}</div> + <div class="notification-body sn-content">{{message}}</div> + {{icon}} +</div> +`; + @Injectable() export class NotificationService { @@ -60,11 +68,12 @@ export class NotificationService { ...config }); } - return this.notificationService[method]( - this.translateService.instant(title), - this.translateService.instant(message), - {...config, icon: notificationIcons[method] || notificationIcons['info']} - ); + const icon = notificationIcons[method] || notificationIcons['info']; + const htmlMsg = messageTemplate + .replace(/{{title}}/gi, this.translateService.instant(title)) + .replace(/{{message}}/gi, this.translateService.instant(message)) + .replace(/{{icon}}/gi, icon); + return this.notificationService.html(htmlMsg, method, {icon, ...config}); } } diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.less index a08f650..f416b07 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.less @@ -27,3 +27,7 @@ a.btn, a.btn:focus, a.btn:visited { color: #fff; } + +/deep/ .navigation-bar-container { + padding-bottom: 1em; +} diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts index 761dc3b..33466a1 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts @@ -142,6 +142,7 @@ export class ShipperConfigurationComponent implements CanComponentDeactivate, On getResponseHandler(cmd: string, type: string, msgVariables?: {[key: string]: any}) { 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 .errorMessage prop const resultType = response ? (response.ok && !result.errorMessage ? NotificationType.SUCCESS : NotificationType.ERROR) : type; const translateParams = {errorMessage: '', ...msgVariables, ...result}; const title = this.translate.instant(`shipperConfiguration.action.${cmd}.title`, translateParams); @@ -175,7 +176,12 @@ export class ShipperConfigurationComponent implements CanComponentDeactivate, On this.getResponseHandler('validate', NotificationType.SUCCESS, rawValue), this.getResponseHandler('validate', NotificationType.ERROR, rawValue) ); - request$.map((response: Response) => response.json()).subscribe(this.setValidationResult); + request$ + .filter((response: Response): boolean => response.ok) + .map((response: Response) => response.json()) + // @ToDo change the backend response status to some error code if the configuration is not valid and don't use the .errorMessage prop + .filter(result => result.errorMessage === undefined) + .subscribe(this.setValidationResult); } canDeactivate() { diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.html index 42d364b..765aa50 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.html @@ -75,10 +75,15 @@ class="help-block validation-block pull-right"> {{'common.form.errors.required' | translate}} </span> + <span *ngIf="validatorForm.controls.componentName.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"> </div> <div [ngClass]="{'has-warning': sampleDataField.invalid, 'form-group': true}"> @@ -96,8 +101,6 @@ <label> {{'shipperConfiguration.validator.result' | translate}} </label> - <!--textarea class="form-control validation-result" name="validationResult" - [disableControl]="configurationForm.invalid" [value]="validationResponse | json"></textarea--> <pre>{{validationResponse | json}}</pre> </div> <button class="btn btn-default pull-right" type="submit" diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.ts index 23ee07c..b28c0d9 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component.ts @@ -16,7 +16,7 @@ * limitations under the License. */ -import {ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core'; +import {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'; @@ -94,9 +94,11 @@ export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestr private canDeactivateModalResult: Subject<boolean> = new Subject<boolean>(); private canDeactivateObservable$: Observable<boolean> = Observable.create((observer: Observer<boolean>) => { - this.canDeactivateModalResult.subscribe((result: boolean) => { - observer.next(result); - }); + this.subscriptions.push( + this.canDeactivateModalResult.subscribe((result: boolean) => { + observer.next(result); + }) + ); }); private serviceNamesListSubject: BehaviorSubject<ShipperClusterService[]> = new BehaviorSubject<ShipperClusterService[]>([]); @@ -105,8 +107,7 @@ export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestr constructor( private utilsService: UtilsService, - private formBuilder: FormBuilder, - private changeDetectorRef: ChangeDetectorRef + private formBuilder: FormBuilder ) {} ngOnInit() { @@ -205,10 +206,21 @@ export class ShipperServiceConfigurationFormComponent implements OnInit, OnDestr }); this.validatorForm = this.formBuilder.group({ - clusterName: this.formBuilder.control(this.clusterName, Validators.required), - componentName: this.formBuilder.control('', Validators.required), + clusterName: this.formBuilder.control( + this.clusterName, + [Validators.required] + ), + componentName: this.formBuilder.control('', [ + Validators.required, + formValidators.getConfigurationServiceValidator(this.configurationForm.controls.configuration) + ]), sampleData: this.formBuilder.control('', Validators.required) }); + this.subscriptions.push( + this.configurationForm.valueChanges.subscribe(() => { + this.validatorForm.controls.componentName.updateValueAndValidity(); + }) + ); } onConfigurationSubmit(configurationForm: FormGroup): void { diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/directives/validator.directive.ts b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/directives/validator.directive.ts index 649acc0..ad6f009 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/directives/validator.directive.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/directives/validator.directive.ts @@ -16,10 +16,11 @@ * limitations under the License. */ -import {AbstractControl, ValidatorFn} from '@angular/forms'; +import {AbstractControl, AsyncValidatorFn, 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 {Observable} from 'rxjs/Observable'; export function configurationValidator(): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { @@ -45,3 +46,17 @@ export function uniqueServiceNameValidator( }; } +export function getConfigurationServiceValidator(configControl: AbstractControl): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + let components: string[]; + try { + 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; + }; +} diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/services/shipper.guard.ts b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/services/shipper.guard.ts new file mode 100644 index 0000000..f57a6c7 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/services/shipper.guard.ts @@ -0,0 +1,57 @@ +/** + * 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 {Injectable} from '@angular/core'; +import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router} from '@angular/router'; +import {Observable} from 'rxjs/Observable'; +import {RoutingUtilsService} from '@app/services/routing-utils.service'; +import {ClustersService} from '@app/services/storage/clusters.service'; +import {ShipperClusterServiceListService} from '@modules/shipper/services/shipper-cluster-service-list.service'; + +@Injectable() +export class ShipperGuard implements CanActivate { + + constructor ( + private routingUtilsService: RoutingUtilsService, + private router: Router, + private clustersStoreService: ClustersService, + private shipperClusterServiceListService: ShipperClusterServiceListService + ) {} + + getFirstCluster(): Observable<string> { + return this.clustersStoreService.getAll().map((clusters: string[]) => Array.isArray(clusters) ? clusters[0] : clusters); + } + + getFirstServiceForCluster(cluster: string): Observable<string> { + return this.shipperClusterServiceListService.getServicesForCluster(cluster) + .map((services: string[]) => Array.isArray(services) ? services[0] : services); + } + + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable<boolean> | Promise<boolean> | boolean { + const cluster: string = this.routingUtilsService.getParamFromActivatedRouteSnapshot(next, 'cluster'); + const service: string = this.routingUtilsService.getParamFromActivatedRouteSnapshot(next, 'service'); + (cluster ? Observable.of(cluster) : this.getFirstCluster()).first().subscribe((firstCluster: string) => { + this.getFirstServiceForCluster(firstCluster).first().subscribe((firstService: string) => { + this.router.navigate(['/shipper', firstCluster, firstService]); + }); + }); + return !!(cluster && service); + } +} diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper-routing.module.ts b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper-routing.module.ts index 35aed14..85d0bb9 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper-routing.module.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper-routing.module.ts @@ -23,6 +23,7 @@ import {AuthGuardService} from '@app/services/auth-guard.service'; import {CanDeactivateGuardService} from '@modules/shared/services/can-deactivate-guard.service'; import {ShipperConfigurationComponent} from './components/shipper-configuration/shipper-configuration.component'; +import {ShipperGuard} from '@modules/shipper/services/shipper.guard'; const shipperRoutes: Routes = [{ path: 'shipper/:cluster/add', @@ -49,7 +50,7 @@ const shipperRoutes: Routes = [{ breadcrumbs: 'shipperConfiguration.breadcrumbs.title', multiClusterFilter: false }, - canActivate: [AuthGuardService] + canActivate: [AuthGuardService, ShipperGuard] }, { path: 'shipper', component: ShipperConfigurationComponent, @@ -57,7 +58,7 @@ const shipperRoutes: Routes = [{ breadcrumbs: 'shipperConfiguration.breadcrumbs.title', multiClusterFilter: false }, - canActivate: [AuthGuardService] + canActivate: [AuthGuardService, ShipperGuard] }]; @NgModule({ diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper.module.ts b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper.module.ts index 682b739..1f6eddf 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper.module.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/shipper.module.ts @@ -35,6 +35,7 @@ import {ShipperConfigurationStore} from './stores/shipper-configuration.store'; import {ShipperConfigurationComponent} from './components/shipper-configuration/shipper-configuration.component'; import {ShipperClusterServiceListService} from './services/shipper-cluster-service-list.service'; import {ShipperConfigurationService} from './services/shipper-configuration.service'; +import {ShipperGuard} from '@modules/shipper/services/shipper.guard'; @NgModule({ imports: [ @@ -59,7 +60,8 @@ import {ShipperConfigurationService} from './services/shipper-configuration.serv providers: [ ShipperConfigurationStore, ShipperConfigurationService, - ShipperClusterServiceListService + ShipperClusterServiceListService, + ShipperGuard ] }) export class ShipperModule {} diff --git a/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json b/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json index bc96827..e70ab9b 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json +++ b/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json @@ -213,8 +213,8 @@ "logIndexFilter.hostname": "Hostname", "logIndexFilter.expiryDate": "Expiry Date", "logIndexFilter.update.title": "Log Index Filter Update", - "logIndexFilter.update.success": "Log Index Filter for cluster {{cluster}} has been successfully updated.", - "logIndexFilter.update.error": "Error at updating Log Index Filter for cluster {{cluster}}. {{message}}", + "logIndexFilter.update.success": "Log Index Filter for cluster <span class='cluster-name'>{{cluster}}</span> has been successfully updated.", + "logIndexFilter.update.error": "Error at updating Log Index Filter for cluster <span class='cluster-name'>{{cluster}}</span>. {{message}}", "login.title": "Login", @@ -235,6 +235,7 @@ "shipperConfiguration.form.testBtn.label": "Test", "shipperConfiguration.form.errors.configuration.invalidJSON": "Invalid JSON!", "shipperConfiguration.form.errors.serviceName.exists": "This service name already exists.", + "shipperConfiguration.form.errors.componentNameField.serviceNameDoesNotExistInConfiguration": "This component is not in the configuration.", "shipperConfiguration.form.leavingDirty.title": "You have unsaved changes", "shipperConfiguration.form.leavingDirty.message": "Are you sure that you cancel the changes?", @@ -246,12 +247,12 @@ "shipperConfiguration.action.add.title": "New Configuration", "shipperConfiguration.action.add.success.message": "New configuration has been added successfully.", - "shipperConfiguration.action.add.error.message": "Error at adding new configuration.\nCluster: {{clusterName}}\nService: {{componentName}}", + "shipperConfiguration.action.add.error.message": "Error at adding new configuration.<div class='cluster-name'>Cluster: {{clusterName}}</div><div class='service-name'>Service: {{componentName}}</div>", "shipperConfiguration.action.update.title": "Update Configuration", - "shipperConfiguration.action.update.success.message": "The configuration has been updated successfully.\nCluster: {{clusterName}}\nService: {{componentName}}", - "shipperConfiguration.action.update.error.message": "Error at updating the configuration.\nCluster: {{clusterName}}\nService: {{componentName}}", + "shipperConfiguration.action.update.success.message": "The configuration has been updated successfully.<div class='cluster-name'>Cluster: {{clusterName}}</div><div class='service-name'>Service: {{componentName}}</div>", + "shipperConfiguration.action.update.error.message": "Error at updating the configuration.<div class='cluster-name'>Cluster: {{clusterName}}</div><div class='service-name'>Service: {{componentName}}</div>", "shipperConfiguration.action.validate.title": "Validate Configuration", "shipperConfiguration.action.validate.success.message": "The configuration is valid.", - "shipperConfiguration.action.validate.error.message": "The configuration is not valid.\nCluster: {{clusterName}}\nService: {{componentName}}\n{{errorMessage}}" + "shipperConfiguration.action.validate.error.message": "The configuration is not valid.<div class='cluster-name'>Cluster: {{clusterName}}</div><div class='service-name'>Service: {{componentName}}</div><div class='error-message'>{{errorMessage}}</div>" } -- To stop receiving notification emails like this one, please contact ababiic...@apache.org.