tobias-istvan closed pull request #56: [AMBARI-24896] [Log Search UI] Turn off 
features in the client when it is not available on backend
URL: https://github.com/apache/ambari-logsearch/pull/56
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/ambari-logsearch-web/src/app/app.module.ts 
b/ambari-logsearch-web/src/app/app.module.ts
index c38924d494..78e3a02e41 100644
--- a/ambari-logsearch-web/src/app/app.module.ts
+++ b/ambari-logsearch-web/src/app/app.module.ts
@@ -116,12 +116,14 @@ import { LogsFilteringUtilsService } from 
'@app/services/logs-filtering-utils.se
 import { LogsStateService } from '@app/services/storage/logs-state.service';
 import { LoginScreenGuardService } from 
'@app/services/login-screen-guard.service';
 import { UserSettingsService } from '@app/services/user-settings.service';
+import { MetaDataApiFeatureGuard } from 
'@app/services/meta-data-api-feature.guard';
 
 import { AuthEffects } from '@app/store/effects/auth.effects';
 import { NotificationEffects } from '@app/store/effects/notification.effects';
 import { UserSettingsEffects } from '@app/store/effects/user-settings.effects';
 import { FilterHistoryManagerComponent } from 
'./components/filter-history-manager/filter-history-manager.component';
 import { AuditLogReposEffects } from './store/effects/audit-log-repos.effects';
+import { ApiFeaturesEffects } from './store/effects/api-features.effects';
 
 import { HostNamePipe } from '@app/pipes/host-name.pipe';
 
@@ -205,7 +207,8 @@ import { HostNamePipe } from '@app/pipes/host-name.pipe';
     EffectsModule.run(AuthEffects),
     EffectsModule.run(AuditLogReposEffects),
     EffectsModule.run(UserSettingsEffects),
-    EffectsModule.run(NotificationEffects)
+    EffectsModule.run(NotificationEffects),
+    EffectsModule.run(ApiFeaturesEffects)
 
   ],
   providers: [
@@ -239,7 +242,8 @@ import { HostNamePipe } from '@app/pipes/host-name.pipe';
     LogsFilteringUtilsService,
     LogsStateService,
     LoginScreenGuardService,
-    UserSettingsService
+    UserSettingsService,
+    MetaDataApiFeatureGuard
   ],
   bootstrap: [AppComponent],
   entryComponents: [
diff --git a/ambari-logsearch-web/src/app/classes/list-item.ts 
b/ambari-logsearch-web/src/app/classes/list-item.ts
index 3a9b72c92d..5a1a6c0a3f 100644
--- a/ambari-logsearch-web/src/app/classes/list-item.ts
+++ b/ambari-logsearch-web/src/app/classes/list-item.ts
@@ -19,10 +19,12 @@
 export interface ListItem {
   id?: string | number;
   label?: string;
+  secondaryLabel?: string;
   value: any;
   iconClass?: string;
   cssClass?: string;
   isChecked?: boolean;
   onSelect?: Function;
   isDivider?: boolean;
+  disabled?: boolean;
 }
diff --git a/ambari-logsearch-web/src/app/classes/models/store.ts 
b/ambari-logsearch-web/src/app/classes/models/store.ts
index b1d1bd9164..980944c92a 100644
--- a/ambari-logsearch-web/src/app/classes/models/store.ts
+++ b/ambari-logsearch-web/src/app/classes/models/store.ts
@@ -38,6 +38,7 @@ import * as auth from '@app/store/reducers/auth.reducers';
 import * as filterHistory from '@app/store/reducers/filter-history.reducers';
 import * as auditLogRepos from '@app/store/reducers/audit-log-repos.reducers';
 import * as userSettings from '@app/store/reducers/user-settings.reducers';
+import * as apiFeatures from '@app/store/reducers/api-features.reducers';
 
 const storeActions = {
     'ARRAY.ADD': 'ADD',
@@ -77,6 +78,7 @@ export interface AppStore {
   auth: auth.State;
   filterHistory: filterHistory.FilterHistoryState;
   auditLogRepos: auditLogRepos.AuditLogRepo[];
+  apiFeatures: apiFeatures.ApiFeatureSet;
 }
 
 export class ModelService {
diff --git 
a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
 
b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
index f8c65de82c..cc8600c44d 100644
--- 
a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
+++ 
b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
@@ -16,13 +16,15 @@
 -->
 <filter-history-manager></filter-history-manager>
 <menu-button label="{{'topMenu.filter' | translate}}" iconClass="fa fa-filter"
-             (buttonClick)="openLogIndexFilter()"></menu-button>
+  (buttonClick)="openLogIndexFilter()" 
+  [disabled]="!(logLevelFiltersFeatureState$ | async)" 
[class.disabled]="!(logLevelFiltersFeatureState$ | async)"
+  [tooltip]="(logLevelFiltersFeatureTooltip$ | async) | 
translate"></menu-button>
 <menu-button *ngIf="!captureSeconds" label="{{'filter.capture' | translate}}" 
iconClass="fa fa-caret-right"
-             (buttonClick)="startCapture()"></menu-button>
+  (buttonClick)="startCapture()"></menu-button>
 <menu-button *ngIf="captureSeconds" label="{{captureSeconds | timerSeconds}}" 
iconClass="fa fa-stop stop-icon"
-             (buttonClick)="stopCapture()"></menu-button>
+  (buttonClick)="stopCapture()"></menu-button>
 <menu-button label="{{'topMenu.refresh' | translate}}" iconClass="fa 
fa-refresh"
-             (buttonClick)="refresh()"></menu-button>
+  (buttonClick)="refresh()"></menu-button>
 <modal-dialog *ngIf="isLogIndexFilterDisplayed$ | async"
   class="log-index-filter"
   [visible]="isLogIndexFilterDisplayed$ | async"
diff --git 
a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
 
b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
index 69df733dab..e9a01012ed 100644
--- 
a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
+++ 
b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
@@ -57,6 +57,7 @@ import {NotificationService} from 
'@modules/shared/services/notification.service
 import { DataAvailabilityStatesStore, dataAvailabilityStates } from 
'@app/modules/app-load/stores/data-availability-state.store';
 
 import * as auth from '@app/store/reducers/auth.reducers';
+import * as apiFeatures from '@app/store/reducers/api-features.reducers';
 import { AuthService } from '@app/services/auth.service';
 import { EffectsModule } from '@ngrx/effects';
 import { AuthEffects } from '@app/store/effects/auth.effects';
@@ -90,7 +91,8 @@ describe('ActionMenuComponent', () => {
           tabs,
           dataAvailabilityStates,
           auth: auth.reducer,
-          userSettings
+          userSettings,
+          apiFeatures: apiFeatures.reducer
         }),
         EffectsModule.run(AuthEffects),
         EffectsModule.run(NotificationEffects)
diff --git 
a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts 
b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
index cbc35f1dc4..9b448b645b 100644
--- 
a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
+++ 
b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
@@ -22,6 +22,7 @@ import { ActivatedRoute, Router } from '@angular/router';
 
 import { Observable } from 'rxjs/Observable';
 import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import 'rxjs/operator/do';
 
 import { LogsContainerService } from '@app/services/logs-container.service';
 import { ServerSettingsService } from '@app/services/server-settings.service';
@@ -30,6 +31,13 @@ import { ClustersService } from 
'@app/services/storage/clusters.service';
 import { UtilsService } from '@app/services/utils.service';
 import { Subject } from 'rxjs/Subject';
 
+import { Store } from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+import { selectLogLevelFiltersFeatureState } from 
'@app/store/selectors/api-features.selectors';
+
+import { AddNotificationAction, NotificationActions } from 
'@app/store/actions/notification.actions';
+import { NotificationType } from 
'@modules/shared/services/notification.service';
+
 @Component({
   selector: 'action-menu',
   templateUrl: './action-menu.component.html',
@@ -37,12 +45,26 @@ import { Subject } from 'rxjs/Subject';
 })
 export class ActionMenuComponent  implements OnInit, OnDestroy {
 
-  isLogIndexFilterDisplayed$: Observable<boolean> = this.route.queryParams
-    .map((params) => {
-      return params;
-    })
-    .map((params): boolean => 
/^(show|yes|true|1)$/.test(params.logIndexFilterSettings))
-    .distinctUntilChanged();
+  logLevelFiltersFeatureState$: Observable<any> = 
this.store.select(selectLogLevelFiltersFeatureState);
+  logLevelFiltersFeatureTooltip$: Observable<string> = 
this.logLevelFiltersFeatureState$.map((state: boolean) => (
+    state ? '' : 'apiFeatures.disabled'
+  ));
+
+  isLogIndexFilterDisplayed$: Observable<boolean> = Observable.combineLatest(
+    this.route.queryParams
+      .map((params) => {
+        return params;
+      })
+      .map((params): boolean => 
/^(show|yes|true|1)$/.test(params.logIndexFilterSettings)),
+    this.logLevelFiltersFeatureState$
+  ).do(([show, enabled]) => {
+    if (show && !enabled) {
+      this.store.dispatch(new AddNotificationAction({
+        type: NotificationType.ERROR,
+        message: 'apiFeatures.disabled'
+      }))
+    }
+  }).map(([show, enabled]) => show && enabled).distinctUntilChanged();
 
   settingsForm: FormGroup = this.settings.settingsFormGroup;
 
@@ -67,7 +89,8 @@ export class ActionMenuComponent  implements OnInit, 
OnDestroy {
     private route: ActivatedRoute,
     private router: Router,
     private clustersService: ClustersService,
-    private utilsService: UtilsService
+    private utilsService: UtilsService,
+    private store: Store<AppStore>
   ) {
   }
 
diff --git 
a/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.html
 
b/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.html
index c34a46e5e2..3dd50b320a 100644
--- 
a/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.html
+++ 
b/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.html
@@ -15,15 +15,15 @@
   limitations under the License.
 -->
 <menu-button label="{{'topMenu.undo' | translate}}" 
[subItems]="activeUndoHistoryListItems$ | async" iconClass="fa fa-arrow-left"
-  class="history-menu" [class.disabled]="!(hasActiveUndoHistoryItems$ | 
async)" [isDisabled]="!(hasActiveUndoHistoryItems$ | async)"
+  class="history-menu" [class.disabled]="!(hasActiveUndoHistoryItems$ | 
async)" [disabled]="!(hasActiveUndoHistoryItems$ | async)"
   listClass="history-dropdown" [isRightAlign]="true" (buttonClick)="undo()" 
(selectItem)="onListItemClick($event)">
 </menu-button>
 
 <menu-button label="{{'topMenu.redo' | translate}}" 
[subItems]="activeRedoHistoryListItems$ | async" iconClass="fa fa-arrow-right"
-  class="history-menu" [class.disabled]="!(hasActiveRedoHistoryItems$ | 
async)" [isDisabled]="!(hasActiveRedoHistoryItems$ | async)"
+  class="history-menu" [class.disabled]="!(hasActiveRedoHistoryItems$ | 
async)" [disabled]="!(hasActiveRedoHistoryItems$ | async)"
   listClass="history-dropdown" [isRightAlign]="true" (buttonClick)="redo()" 
(selectItem)="onListItemClick($event)">
 </menu-button>
 
 <menu-button label="{{'topMenu.history' | translate}}" 
[subItems]="activeHistoryListItems$ | async" iconClass="fa fa-history"
-  class="history-menu" [class.disabled]="!(hasActiveHistoryItems$ | async)" 
[isDisabled]="!(hasActiveHistoryItems$ | async)"
+  class="history-menu" [class.disabled]="!(hasActiveHistoryItems$ | async)" 
[disabled]="!(hasActiveHistoryItems$ | async)"
   listClass="history-dropdown" [isRightAlign]="true" 
(selectItem)="onListItemClick($event)"></menu-button>
diff --git 
a/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
 
b/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
index 016ac02306..9550c735b3 100644
--- 
a/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
+++ 
b/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
@@ -44,7 +44,7 @@
       [iconClass]="filters.hosts.iconClass"
       [isRightAlign]="true"
       [class.disabled]="isServiceLogsFileView$ | async"
-      [isDisabled]="isServiceLogsFileView$ | async"
+      [disabled]="isServiceLogsFileView$ | async"
       additionalLabelComponentSetter="getDataForHostsNodeBar"></filter-button>
 
     <filter-button *ngIf="isFilterConditionDisplayed('users')"
@@ -65,7 +65,7 @@
       [iconClass]="filters.components.iconClass"
       [isRightAlign]="true"
       [class.disabled]="isServiceLogsFileView$ | async"
-      [isDisabled]="isServiceLogsFileView$ | async"
+      [disabled]="isServiceLogsFileView$ | async"
       
additionalLabelComponentSetter="getDataForComponentsNodeBar"></filter-button>
 
     <filter-button *ngIf="isFilterConditionDisplayed('repos')"
@@ -77,7 +77,7 @@
       [iconClass]="filters.repos.iconClass"
       [isRightAlign]="true"
       [class.disabled]="isServiceLogsFileView$ | async"
-      [isDisabled]="isServiceLogsFileView$ | async"></filter-button>
+      [disabled]="isServiceLogsFileView$ | async"></filter-button>
 
     <filter-button *ngIf="isFilterConditionDisplayed('levels')"
       formControlName="levels"
diff --git 
a/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html
 
b/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html
index cb63d440a1..d2d2f6ab46 100644
--- 
a/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html
+++ 
b/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html
@@ -15,7 +15,7 @@
   limitations under the License.
 -->
 
-<div #dropdown [ngClass]="{'dropdown': hasSubItems, 'text-center': true, 
'open': dropdownIsOpen, 'disabled': isDisabled, 'has-selection': hasSelection}">
+<div #dropdown [ngClass]="{'dropdown': hasSubItems, 'text-center': true, 
'open': dropdownIsOpen, 'disabled': disabled, 'has-selection': hasSelection}">
   <a class="dropdown-toggle" [ngClass]="(labelClass || '') + (hasCaret ? ' 
has-caret' : '')"
     (click)="onMouseClick($event)"
     (mousedown)="onMouseDown($event)">
diff --git 
a/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts 
b/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
index 3d510fc9a1..79ca5e7dfa 100644
--- 
a/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
+++ 
b/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
@@ -87,7 +87,7 @@ export class MenuButtonComponent {
   maxLongClickDelay = 0;
 
   @Input()
-  isDisabled = false;
+  disabled = false;
 
   @Input()
   listClass = '';
@@ -155,7 +155,7 @@ export class MenuButtonComponent {
    * @param {MouseEvent} event
    */
   onMouseClick(event: MouseEvent): void {
-    if (!this.isDisabled) {
+    if (!this.disabled) {
       const el = <HTMLElement>event.target;
       const now = Date.now();
       const mdt = this.mouseDownTimestamp; // mousedown time
diff --git 
a/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts 
b/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts
index a340dd669f..3d35ba9628 100644
--- 
a/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts
+++ 
b/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts
@@ -51,6 +51,7 @@ 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 * as apiFeatures from '@app/store/reducers/api-features.reducers';
 import { EffectsModule } from '@ngrx/effects';
 import { AuthEffects } from '@app/store/effects/auth.effects';
 import { NotificationEffects } from '@app/store/effects/notification.effects';
@@ -80,7 +81,8 @@ describe('TopMenuComponent', () => {
           components,
           hosts,
           auth: auth.reducer,
-          userSettings: userSettings.reducer
+          userSettings: userSettings.reducer,
+          apiFeatures: apiFeatures.reducer
         }),
         EffectsModule.run(AuthEffects),
         EffectsModule.run(NotificationEffects),
diff --git 
a/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts 
b/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
index bff40da388..d80fbc6146 100644
--- a/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
+++ b/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { Component } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
 import { FormGroup } from '@angular/forms';
 import { FilterCondition, TimeUnitListItem } from '@app/classes/filtering';
 import { ListItem } from '@app/classes/list-item';
@@ -25,15 +25,26 @@ import { LogsContainerService } from 
'@app/services/logs-container.service';
 import { Router, ActivatedRoute } from '@angular/router';
 
 import { Store } from '@ngrx/store';
+import { Subject } from 'rxjs/Subject';
+import { Observable } from 'rxjs/Observable';
+
 import { AppStore } from '@app/classes/models/store';
 import { LogOutAction } from '@app/store/actions/auth.actions';
 
+import { selectMetadataPatternsFeatureState } from 
'@app/store/selectors/api-features.selectors';
+
 @Component({
   selector: 'top-menu',
   templateUrl: './top-menu.component.html',
   styleUrls: ['./top-menu.component.less']
 })
-export class TopMenuComponent {
+export class TopMenuComponent implements OnInit, OnDestroy {
+
+  private items;
+  
+  metadataPatternsFeatureState$ = 
this.store.select(selectMetadataPatternsFeatureState).startWith(true);
+  
+  destroyed$: Subject<boolean> = new Subject();
 
   constructor(
     private logsContainer: LogsContainerService,
@@ -42,6 +53,15 @@ export class TopMenuComponent {
     private store: Store<AppStore>
   ) {}
 
+  ngOnInit() {
+    
this.metadataPatternsFeatureState$.takeUntil(this.destroyed$).subscribe(this.onMetadataFeatureStateChange);
+  }
+
+  ngOnDestroy() {
+    this.destroyed$.next(true);
+    this.destroyed$.complete();
+  }
+
   get filtersForm(): FormGroup {
     return this.logsContainer.filtersForm;
   };
@@ -57,20 +77,9 @@ export class TopMenuComponent {
       relativeTo: this.route.root.firstChild
     });
   }
-  
-  /**
-   * Dispatch the LogOutAction.
-   */
-  logout = (): void => {
-    this.store.dispatch(new LogOutAction());
-  }
 
-  navigateToShipperConfig = (): void => {
-    this.router.navigate(['/shipper']);
-  }
-
-  readonly items = [
-    {
+  onMetadataFeatureStateChange = (state: boolean) => {
+    this.items = [{
       iconClass: 'fa fa-user grey',
       hideCaret: true,
       isRightAlign: true,
@@ -83,8 +92,10 @@ export class TopMenuComponent {
 
         {
           label: 'topMenu.shipperConfiguration',
+          secondaryLabel: state ? '' : 'apiFeatures.disabled',
           onSelect: this.navigateToShipperConfig,
-          iconClass: 'fa fa-file-code-o'
+          iconClass: 'fa fa-file-code-o',
+          disabled: !state
         },
         {
           isDivider: true
@@ -95,8 +106,21 @@ export class TopMenuComponent {
           iconClass: 'fa fa-sign-out'
         }
       ]
-    }
-  ];
+    }];
+  }
+
+  openSettings = (): void => {};
+
+  /**
+   * Dispatch the LogOutAction.
+   */
+  logout = (): void => {
+    this.store.dispatch(new LogOutAction());
+  }
+
+  navigateToShipperConfig = (): void => {
+    this.router.navigate(['/shipper']);
+  }
 
   get clusters(): (ListItem | TimeUnitListItem[])[] {
     return this.filters.clusters.options;
diff --git 
a/ambari-logsearch-web/src/app/modules/app-load/services/app-load.service.ts 
b/ambari-logsearch-web/src/app/modules/app-load/services/app-load.service.ts
index 44d22be673..cf1a365279 100644
--- a/ambari-logsearch-web/src/app/modules/app-load/services/app-load.service.ts
+++ b/ambari-logsearch-web/src/app/modules/app-load/services/app-load.service.ts
@@ -39,6 +39,7 @@ import { Store } from '@ngrx/store';
 import { AppStore } from '@app/classes/models/store';
 import { isAuthorizedSelector } from '@app/store/selectors/auth.selectors';
 import { LoadAuditLogsReposAction } from 
'@app/store/actions/audit-log-repos.actions';
+import { LoadApiFeaturesAction } from 
'@app/store/actions/api-features.actions';
 
 // @ToDo create a separate data state enrty in the store with keys of the 
model names
 export enum DataStateStoreKeys {
@@ -104,6 +105,7 @@ export class AppLoadService {
 
       });
       
this.baseDataAvailibilityState$.subscribe(this.onBaseDataAvailabilityChange);
+      this.loadApiFeaturesInfo();
   }
 
   onDataAvailibilityChange = (dataAvailabilityStates: 
DataAvailabilityValues[]): void => {
@@ -238,6 +240,10 @@ export class AppLoadService {
     return responses$;
   }
 
+  loadApiFeaturesInfo() {
+    this.store.dispatch( new LoadApiFeaturesAction() );
+  }
+
   initOnAuthorization = (isAuthorized): void => {
     if (isAuthorized) {
         this.loadClusters();
diff --git 
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
 
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
index cc790aba03..ae5c748029 100644
--- 
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
+++ 
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
@@ -16,7 +16,8 @@
 -->
 <ng-template #listItem let-item let-isMultipleChoice="isMultipleChoice">
   <li [class.divider]="item.isDivider" [class.filtered]="isFiltered(item)"
-      [attr.role]="item.isDivider ? 'separator' : null" 
[ngClass]="(item.cssClass || '')" (click)="onItemClick($event)">
+    [attr.role]="item.isDivider ? 'separator' : null" 
[ngClass]="(item.cssClass || '')" (click)="onItemClick($event)"
+    [class.disabled]="item.disabled">
     <ng-container *ngIf="!item.isDivider">
       <span class="list-item-label" *ngIf="isMultipleChoice">
         <input type="checkbox" [attr.id]="(instanceId) + '-' + (item.id || 
item.value)" [ngModel]="item.isChecked"
@@ -24,12 +25,14 @@
         <label [attr.for]="(instanceId) + '-' + (item.id || item.value)" 
class="label-container">
           <span *ngIf="item.iconClass" [ngClass]="item.iconClass"></span>
           <span class="item-label-text">{{item.label | translate}}</span>
+          <span *ngIf="item.secondaryLabel" 
class="item-secondary-label-text">{{item.secondaryLabel | translate}}</span>
           <span #additionalComponent></span>
         </label>
       </span>
       <span class="list-item-label label-container" *ngIf="!isMultipleChoice" 
(click)="changeSelectedItem(item, $event)">
         <span *ngIf="item.iconClass" [ngClass]="item.iconClass"></span>
         <span class="item-label-text">{{item.label | translate}}</span>
+        <span *ngIf="item.secondaryLabel" 
class="item-secondary-label-text">{{item.secondaryLabel | translate}}</span>
         <span #additionalComponent></span>
       </span>
     </ng-container>
diff --git 
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.less
 
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.less
index 415a8fd149..c432a0d6bd 100644
--- 
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.less
+++ 
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.less
@@ -33,6 +33,10 @@
       opacity: 0;
       height: 0;
     }
+    &.disabled {
+      color: @fluid-gray-2;
+      cursor: default;
+    }
     .list-item-label {
       .dropdown-item-child-default;
 
@@ -49,6 +53,11 @@
         width: 100%;
       }
     }
+    .item-secondary-label-text {
+      color: @fluid-gray-2;
+      display: block;
+      font-size: .8em;
+    }
     &.filter {
       padding: 0 .25em;
       position: relative;
diff --git 
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
 
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
index 4db1bc58e1..fb937f0ade 100644
--- 
a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
+++ 
b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
@@ -222,7 +222,7 @@ export class DropdownListComponent implements OnInit, 
OnChanges, AfterViewChecke
 
   changeSelectedItem(item: ListItem | ListItem[], event?: MouseEvent): void {
     (Array.isArray(item) ? item : [item]).forEach((currentItem: ListItem) => {
-      if (currentItem.onSelect) {
+      if (currentItem.onSelect && !currentItem.disabled) {
         currentItem.onSelect(...this.actionArguments)
       }
     });
diff --git 
a/ambari-logsearch-web/src/app/modules/shipper/shipper-routing.module.ts 
b/ambari-logsearch-web/src/app/modules/shipper/shipper-routing.module.ts
index 5c92de0672..784bd030e5 100644
--- a/ambari-logsearch-web/src/app/modules/shipper/shipper-routing.module.ts
+++ b/ambari-logsearch-web/src/app/modules/shipper/shipper-routing.module.ts
@@ -16,14 +16,16 @@
  * limitations under the License.
  */
 
-import {NgModule} from '@angular/core';
-import {RouterModule, Routes} from '@angular/router';
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
 
-import {AuthGuardService} from '@app/services/auth-guard.service';
-import {CanDeactivateGuardService} from 
'@modules/shared/services/can-deactivate-guard.service';
+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';
+import { ShipperConfigurationComponent } from 
'./components/shipper-configuration/shipper-configuration.component';
+import { ShipperGuard } from '@modules/shipper/services/shipper.guard';
+
+import { MetaDataApiFeatureGuard } from 
'@app/services/meta-data-api-feature.guard';
 
 const shipperRoutes: Routes = [{
   path: 'shipper/:cluster/add',
@@ -32,7 +34,7 @@ const shipperRoutes: Routes = [{
     breadcrumbs: ['shipperConfiguration.breadcrumbs.title', 
'shipperConfiguration.breadcrumbs.add'],
     multiClusterFilter: false
   },
-  canActivate: [AuthGuardService],
+  canActivate: [AuthGuardService, MetaDataApiFeatureGuard],
   canDeactivate: [CanDeactivateGuardService]
 }, {
   path: 'shipper/:cluster/:service',
@@ -41,7 +43,7 @@ const shipperRoutes: Routes = [{
     breadcrumbs: ['shipperConfiguration.breadcrumbs.title', 
'shipperConfiguration.breadcrumbs.update'],
     multiClusterFilter: false
   },
-  canActivate: [AuthGuardService, ShipperGuard],
+  canActivate: [AuthGuardService, ShipperGuard, MetaDataApiFeatureGuard],
   canDeactivate: [CanDeactivateGuardService]
 }, {
   path: 'shipper/:cluster',
@@ -50,7 +52,7 @@ const shipperRoutes: Routes = [{
     breadcrumbs: 'shipperConfiguration.breadcrumbs.title',
     multiClusterFilter: false
   },
-  canActivate: [AuthGuardService, ShipperGuard]
+  canActivate: [AuthGuardService, ShipperGuard, MetaDataApiFeatureGuard]
 }, {
   path: 'shipper',
   component: ShipperConfigurationComponent,
@@ -58,7 +60,7 @@ const shipperRoutes: Routes = [{
     breadcrumbs: 'shipperConfiguration.breadcrumbs.title',
     multiClusterFilter: false
   },
-  canActivate: [AuthGuardService, ShipperGuard]
+  canActivate: [AuthGuardService, ShipperGuard, MetaDataApiFeatureGuard]
 }];
 
 @NgModule({
diff --git a/ambari-logsearch-web/src/app/services/api-feature.guard.ts 
b/ambari-logsearch-web/src/app/services/api-feature.guard.ts
new file mode 100644
index 0000000000..85c172b5c1
--- /dev/null
+++ b/ambari-logsearch-web/src/app/services/api-feature.guard.ts
@@ -0,0 +1,48 @@
+/**
+ * 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 } from '@angular/router';
+
+import { Observable } from 'rxjs/Observable';
+import 'rxjs/operator/do';
+import { Store } from '@ngrx/store';
+
+import { AppStore } from '@app/classes/models/store';
+import { AddNotificationAction, NotificationActions } from 
'@app/store/actions/notification.actions';
+import { NotificationType } from 
'@modules/shared/services/notification.service';
+
+@Injectable()
+export class ApiFeatureGuard implements CanActivate {
+  
+  protected selector;
+
+  constructor(private store: Store<AppStore>){}
+
+  canActivate(): Observable<boolean> {
+    const canActivate$: Observable<boolean> = (this.selector ? 
this.store.select(this.selector) : Observable.of(true));
+    return canActivate$.do((state) => {
+      if (!state) {
+        this.store.dispatch(new AddNotificationAction({
+          type: NotificationType.ERROR,
+          message: 'apiFeatures.disabled'
+        }));
+      }
+    });
+  }
+}
\ No newline at end of file
diff --git a/ambari-logsearch-web/src/app/services/http-client.service.ts 
b/ambari-logsearch-web/src/app/services/http-client.service.ts
index abdf9ccf74..e06ee1e38a 100644
--- a/ambari-logsearch-web/src/app/services/http-client.service.ts
+++ b/ambari-logsearch-web/src/app/services/http-client.service.ts
@@ -36,6 +36,7 @@ import { Store } from '@ngrx/store';
 import { AppStore } from '@app/classes/models/store';
 import { HttpAuthorizationErrorResponseAction } from 
'@app/store/actions/auth.actions';
 import { isAuthorizedSelector } from '@app/store/selectors/auth.selectors';
+import { selectLogLevelFiltersFeatureState } from 
'@app/store/selectors/api-features.selectors';
 
 import { BehaviorSubject } from 'rxjs/BehaviorSubject';
 
@@ -110,6 +111,9 @@ export class HttpClientService extends Http {
     userSettings: {
       url: 'metadata/list',
       params: () => ({type: 'user_settings'})
+    },
+    apiFeatures: {
+      url: 'info/features'
     }
   };
 
diff --git 
a/ambari-logsearch-web/src/app/services/meta-data-api-feature.guard.ts 
b/ambari-logsearch-web/src/app/services/meta-data-api-feature.guard.ts
new file mode 100644
index 0000000000..c7536b28bd
--- /dev/null
+++ b/ambari-logsearch-web/src/app/services/meta-data-api-feature.guard.ts
@@ -0,0 +1,31 @@
+/**
+ * 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 } from '@angular/router';
+
+import { ApiFeatureGuard } from '@app/services/api-feature.guard';
+
+import { selectMetadataPatternsFeatureState } from 
'@app/store/selectors/api-features.selectors';
+
+@Injectable()
+export class MetaDataApiFeatureGuard extends ApiFeatureGuard {
+  
+  protected selector = selectMetadataPatternsFeatureState;
+
+}
\ No newline at end of file
diff --git a/ambari-logsearch-web/src/app/services/storage/reducers.service.ts 
b/ambari-logsearch-web/src/app/services/storage/reducers.service.ts
index 68dc186cc6..0d6c03c566 100644
--- a/ambari-logsearch-web/src/app/services/storage/reducers.service.ts
+++ b/ambari-logsearch-web/src/app/services/storage/reducers.service.ts
@@ -40,6 +40,7 @@ import * as auth from '@app/store/reducers/auth.reducers';
 import * as filterHistory from '@app/store/reducers/filter-history.reducers';
 import * as auditLogRepos from '@app/store/reducers/audit-log-repos.reducers';
 import * as userSettings from '@app/store/reducers/user-settings.reducers';
+import * as apiFeatures from '@app/store/reducers/api-features.reducers';
 
 export const reducers = {
   appSettings,
@@ -63,7 +64,8 @@ export const reducers = {
   auth: auth.reducer,
   filterHistory: filterHistory.reducer,
   auditLogRepos: auditLogRepos.reducer,
-  userSettings: userSettings.reducer
+  userSettings: userSettings.reducer,
+  apiFeatures: apiFeatures.reducer
 };
 
 export function reducer(state: any, action: any) {
diff --git a/ambari-logsearch-web/src/app/store/actions/api-features.actions.ts 
b/ambari-logsearch-web/src/app/store/actions/api-features.actions.ts
new file mode 100644
index 0000000000..8452174a6b
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/actions/api-features.actions.ts
@@ -0,0 +1,53 @@
+/**
+ * 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 { Action } from '@ngrx/store';
+
+import { ApiFeatureSet } from '../reducers/api-features.reducers';
+
+export enum ApiFeaturesActionTypes {
+  SET = '[Api Features] Set',
+  LOAD = '[Api Features] Load',
+  LOAD_SUCCESS = '[Api Features] Load success',
+  LOAD_FAILED = '[Api Features] Load failed'
+}
+
+export class LoadApiFeaturesAction implements Action {
+  readonly type = ApiFeaturesActionTypes.LOAD;
+  constructor() {}
+}
+
+export class LoadSuccessApiFeaturesAction implements Action {
+  readonly type = ApiFeaturesActionTypes.LOAD_SUCCESS;
+  constructor(public payload: ApiFeatureSet) {}
+}
+
+export class LoadFailedApiFeaturesAction implements Action {
+  readonly type = ApiFeaturesActionTypes.LOAD_FAILED;
+  constructor(public payload: any) {}
+}
+
+export class SetApiFeaturesAction implements Action {
+  readonly type = ApiFeaturesActionTypes.SET;
+  constructor(public payload: ApiFeatureSet) {}
+}
+
+export type ApiFeaturesActions = LoadApiFeaturesAction
+  | LoadSuccessApiFeaturesAction
+  | LoadFailedApiFeaturesAction
+  | SetApiFeaturesAction;
\ No newline at end of file
diff --git a/ambari-logsearch-web/src/app/store/effects/api-features.effects.ts 
b/ambari-logsearch-web/src/app/store/effects/api-features.effects.ts
new file mode 100644
index 0000000000..8644e3d68d
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/effects/api-features.effects.ts
@@ -0,0 +1,72 @@
+/**
+ * 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 { Actions, Effect } from '@ngrx/effects';
+import { Observable } from 'rxjs/Observable';
+import { Store } from '@ngrx/store';
+import 'rxjs/add/operator/mapTo';
+
+import { AppStore } from '@app/classes/models/store';
+import { HttpClientService } from '@app/services/http-client.service';
+
+import { AddNotificationAction, NotificationActions } from 
'@app/store/actions/notification.actions';
+import { NotificationType } from 
'@modules/shared/services/notification.service';
+
+import { 
+  ApiFeaturesActions,
+  LoadSuccessApiFeaturesAction,
+  LoadFailedApiFeaturesAction,
+  SetApiFeaturesAction,
+  ApiFeaturesActionTypes
+} from '@app/store/actions/api-features.actions';
+import { ApiFeatureSet } from '@app/store/reducers/api-features.reducers';
+
+@Injectable()
+export class ApiFeaturesEffects {
+  constructor(
+    private actions$: Actions,
+    private store: Store<AppStore>,
+    private httpClient: HttpClientService
+  ) {}
+
+  @Effect()
+  loadAction: Observable<ApiFeaturesActions> = this.actions$
+    .ofType(ApiFeaturesActionTypes.LOAD)
+    .switchMap((action) => {
+      return this.httpClient.get('apiFeatures').map((response) => {
+        const responseBody = response.json();
+        return response.ok ? new LoadSuccessApiFeaturesAction(responseBody) : 
new LoadFailedApiFeaturesAction(responseBody);
+      });
+    });
+
+  @Effect()
+  loadSuccessAction: Observable<ApiFeaturesActions> = this.actions$
+    .ofType(ApiFeaturesActionTypes.LOAD_SUCCESS)
+    .map((action: LoadSuccessApiFeaturesAction): ApiFeatureSet => 
action.payload)
+    .map((apiFeatures: ApiFeatureSet) => new 
SetApiFeaturesAction(apiFeatures));
+    
+  @Effect()
+  loadFailedAction: Observable<NotificationActions> = this.actions$
+    .ofType(ApiFeaturesActionTypes.LOAD_FAILED)
+    .mapTo(new AddNotificationAction({
+      type: NotificationType.ERROR,
+      message: 'apiFeatures.loadFailed'
+    }));
+
+}
\ No newline at end of file
diff --git 
a/ambari-logsearch-web/src/app/store/reducers/api-features.reducers.ts 
b/ambari-logsearch-web/src/app/store/reducers/api-features.reducers.ts
new file mode 100644
index 0000000000..6c89e62c51
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/reducers/api-features.reducers.ts
@@ -0,0 +1,39 @@
+/**
+ * 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 { ApiFeaturesActions, ApiFeaturesActionTypes } from 
'../actions/api-features.actions';
+
+export interface ApiFeatureSet {
+  [key: string]: boolean | {[key: string]: boolean};
+};
+
+export const initialState = {
+  'metadata_patterns': true,
+  'log_level_filters': true
+};
+
+export function reducer(state = initialState, action: ApiFeaturesActions) {
+  switch (action.type) {
+    case ApiFeaturesActionTypes.SET: {
+      return action.payload;
+    }
+    default: {
+      return state;
+    }
+  }
+}
diff --git 
a/ambari-logsearch-web/src/app/store/reducers/audit-log-repos.reducers.ts 
b/ambari-logsearch-web/src/app/store/reducers/audit-log-repos.reducers.ts
index c04ff3b94e..ddeb9a6c45 100644
--- a/ambari-logsearch-web/src/app/store/reducers/audit-log-repos.reducers.ts
+++ b/ambari-logsearch-web/src/app/store/reducers/audit-log-repos.reducers.ts
@@ -1,5 +1,3 @@
-import { AuditLogReposActions, AuditLogReposActionTypes } from 
"../actions/audit-log-repos.actions";
-
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -18,6 +16,8 @@ import { AuditLogReposActions, AuditLogReposActionTypes } 
from "../actions/audit
  * limitations under the License.
  */
 
+import { AuditLogReposActions, AuditLogReposActionTypes } from 
"../actions/audit-log-repos.actions";
+
 export interface AuditLogRepo {
   name: string;
   label: string;
diff --git 
a/ambari-logsearch-web/src/app/store/selectors/api-features.selectors.ts 
b/ambari-logsearch-web/src/app/store/selectors/api-features.selectors.ts
new file mode 100644
index 0000000000..2d75be16c4
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/selectors/api-features.selectors.ts
@@ -0,0 +1,34 @@
+/**
+ * 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 { createSelector, Selector } from 'reselect';
+
+import { AppStore } from '@app/classes/models/store';
+import { ApiFeatureSet } from '@app/store/reducers/api-features.reducers';
+
+export const selectApiFeaturesState = (state: AppStore): ApiFeatureSet => 
state.apiFeatures;
+
+export const selectMetadataPatternsFeatureState = createSelector(
+  selectApiFeaturesState,
+  (state: ApiFeatureSet) => state.metadata_patterns
+);
+
+export const selectLogLevelFiltersFeatureState = createSelector(
+  selectApiFeaturesState,
+  (state: ApiFeatureSet) => state.log_level_filters
+);
\ No newline at end of file
diff --git a/ambari-logsearch-web/src/assets/i18n/en.json 
b/ambari-logsearch-web/src/assets/i18n/en.json
index 6ae7a447f9..9d21f8db77 100644
--- a/ambari-logsearch-web/src/assets/i18n/en.json
+++ b/ambari-logsearch-web/src/assets/i18n/en.json
@@ -393,6 +393,10 @@
       "success": "The user settings has been saved.",
       "error": "Some error happened saving the user settings."
     }
+  },
+  "apiFeatures": {
+    "loadFailed": "We were not able to explore the available features from the 
server. Some of the features maybe not disabled in the client but disabled on 
the server.",
+    "disabled": "This feature is disabled on the server."
   }
 
 }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


With regards,
Apache Git Services

Reply via email to