You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ab...@apache.org on 2017/07/10 18:02:40 UTC

ambari git commit: AMBARI-21438 Log Search UI: implement sorting of logs list. (ababiichuk)

Repository: ambari
Updated Branches:
  refs/heads/branch-feature-logsearch-ui e5ac65e04 -> 2d5b75618


AMBARI-21438 Log Search UI: implement sorting of logs list. (ababiichuk)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/2d5b7561
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/2d5b7561
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/2d5b7561

Branch: refs/heads/branch-feature-logsearch-ui
Commit: 2d5b75618c36a29792a312ba7c3ccdd01a454220
Parents: e5ac65e
Author: ababiichuk <ab...@hortonworks.com>
Authored: Mon Jul 10 20:58:26 2017 +0300
Committer: ababiichuk <ab...@hortonworks.com>
Committed: Mon Jul 10 20:58:26 2017 +0300

----------------------------------------------------------------------
 .../src/app/app.module.ts                       |   6 +-
 .../dropdown-button.component.html              |  23 ++++
 .../dropdown-button.component.less              |  30 +++++
 .../dropdown-button.component.spec.ts           |  79 +++++++++++
 .../dropdown-button.component.ts                |  75 +++++++++++
 .../dropdown-list.component.spec.ts             |  35 ++---
 .../dropdown-list/dropdown-list.component.ts    |   9 +-
 .../filter-button.component.spec.ts             |   4 +-
 .../filter-button/filter-button.component.ts    |  18 +--
 .../filter-dropdown.component.html              |  23 ----
 .../filter-dropdown.component.less              |   9 --
 .../filter-dropdown.component.spec.ts           |   2 +
 .../filter-dropdown.component.ts                |  48 ++++---
 .../filter-text-field.component.html            |   3 +-
 .../filter-text-field.component.spec.ts         |   2 +
 .../filter-text-field.component.ts              |  33 ++---
 .../filters-panel/filters-panel.component.html  |  25 ++--
 .../filters-panel/filters-panel.component.less  |   2 +-
 .../filters-panel.component.spec.ts             |   4 +-
 .../filters-panel/filters-panel.component.ts    |  16 +--
 .../logs-list/logs-list.component.html          |  10 ++
 .../logs-list/logs-list.component.less          |  14 ++
 .../logs-list/logs-list.component.spec.ts       |   8 +-
 .../components/logs-list/logs-list.component.ts |  18 ++-
 .../menu-button/menu-button.component.html      |   4 +-
 .../menu-button/menu-button.component.ts        |  13 +-
 .../src/app/components/variables.less           |   1 +
 .../src/app/services/filtering.service.spec.ts  |   4 +-
 .../src/app/services/filtering.service.ts       | 134 ++++++++++++++-----
 .../src/app/services/mock-api-data.service.ts   |  22 ++-
 .../src/app/services/utils.service.spec.ts      |  33 +++++
 .../src/app/services/utils.service.ts           |  40 ++++++
 .../src/assets/i18n/en.json                     |  13 +-
 33 files changed, 562 insertions(+), 198 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/app.module.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/app.module.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/app.module.ts
index 72b5ad1..580fffa 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/app.module.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/app.module.ts
@@ -32,6 +32,7 @@ import {mockApiDataService} from '@app/services/mock-api-data.service'
 import {HttpClientService} from '@app/services/http-client.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
 import {FilteringService} from '@app/services/filtering.service';
+import {UtilsService} from '@app/services/utils.service';
 
 import {AppSettingsService} from '@app/services/storage/app-settings.service';
 import {AppStateService} from '@app/services/storage/app-state.service';
@@ -58,6 +59,7 @@ import {FilterTextFieldComponent} from '@app/components/filter-text-field/filter
 import {FilterButtonComponent} from '@app/components/filter-button/filter-button.component';
 import {AccordionPanelComponent} from '@app/components/accordion-panel/accordion-panel.component';
 import {LogsListComponent} from '@app/components/logs-list/logs-list.component';
+import {DropdownButtonComponent} from '@app/components/dropdown-button/dropdown-button.component';
 
 export function HttpLoaderFactory(http: Http) {
   // adding 'static' parameter to step over mock data request
@@ -92,7 +94,8 @@ export function getXHRBackend(injector: Injector, browser: BrowserXhr, xsrf: XSR
     FilterTextFieldComponent,
     FilterButtonComponent,
     AccordionPanelComponent,
-    LogsListComponent
+    LogsListComponent,
+    DropdownButtonComponent
   ],
   imports: [
     BrowserModule,
@@ -115,6 +118,7 @@ export function getXHRBackend(injector: Injector, browser: BrowserXhr, xsrf: XSR
     HttpClientService,
     ComponentActionsService,
     FilteringService,
+    UtilsService,
     AppSettingsService,
     AppStateService,
     AuditLogsService,

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.html b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.html
new file mode 100644
index 0000000..8eb92f0
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.html
@@ -0,0 +1,23 @@
+<!--
+  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.
+-->
+
+<div class="filter-label" *ngIf="label">{{label | translate}}</div>
+<button class="btn btn-link dropdown-toggle" data-toggle="dropdown">
+  {{selectedLabel | translate}} <span class="caret"></span>
+</button>
+<ul data-component="dropdown-list" [ngClass]="{'dropdown-menu': true, 'dropdown-menu-right': isRightAlign}" [items]="options"
+    (selectedItemChange)="writeValue($event)"></ul>

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.less b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.less
new file mode 100644
index 0000000..a60246c
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.less
@@ -0,0 +1,30 @@
+/**
+ * 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 '../variables';
+
+:host {
+  position: relative;
+
+  .filter-label {
+    padding: @input-group-addon-padding;
+  }
+
+  .btn {
+    text-transform: none;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.spec.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.spec.ts
new file mode 100644
index 0000000..8efe320
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.spec.ts
@@ -0,0 +1,79 @@
+/**
+ * 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 {NO_ERRORS_SCHEMA} from '@angular/core';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+import {Http} from '@angular/http';
+import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
+import {TranslateHttpLoader} from '@ngx-translate/http-loader';
+import {StoreModule} from '@ngrx/store';
+import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service';
+import {ClustersService, clusters} from '@app/services/storage/clusters.service';
+import {ComponentsService, components} from '@app/services/storage/components.service';
+import {FilteringService} from '@app/services/filtering.service';
+import {UtilsService} from '@app/services/utils.service';
+import {ComponentActionsService} from '@app/services/component-actions.service';
+
+import {DropdownButtonComponent} from './dropdown-button.component';
+
+export function HttpLoaderFactory(http: Http) {
+  return new TranslateHttpLoader(http, 'assets/i18n/', '.json');
+}
+
+describe('DropdownButtonComponent', () => {
+  let component: DropdownButtonComponent;
+  let fixture: ComponentFixture<DropdownButtonComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [DropdownButtonComponent],
+      imports: [
+        StoreModule.provideStore({
+          appSettings,
+          clusters,
+          components
+        }),
+        TranslateModule.forRoot({
+          provide: TranslateLoader,
+          useFactory: HttpLoaderFactory,
+          deps: [Http]
+        })
+      ],
+      providers: [
+        AppSettingsService,
+        ClustersService,
+        ComponentsService,
+        FilteringService,
+        UtilsService,
+        ComponentActionsService
+      ],
+      schemas: [NO_ERRORS_SCHEMA]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(DropdownButtonComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create component', () => {
+    expect(component).toBeTruthy();
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.ts
new file mode 100644
index 0000000..821f137
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-button/dropdown-button.component.ts
@@ -0,0 +1,75 @@
+/**
+ * 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 {Component, OnInit, Input} from '@angular/core';
+import {FilteringService} from '@app/services/filtering.service';
+import {ComponentActionsService} from '@app/services/component-actions.service';
+import {UtilsService} from '@app/services/utils.service';
+
+@Component({
+  selector: 'dropdown-button',
+  templateUrl: './dropdown-button.component.html',
+  styleUrls: ['./dropdown-button.component.less']
+})
+export class DropdownButtonComponent implements OnInit {
+
+  constructor(protected filtering: FilteringService, protected actions: ComponentActionsService, protected utils: UtilsService) {
+  }
+
+  ngOnInit() {
+    this.selectedLabel = this.defaultLabel;
+  }
+  
+  @Input()
+  label?: string;
+
+  @Input()
+  options?: any[];
+
+  @Input()
+  defaultValue?: string;
+
+  @Input()
+  defaultLabel?: string;
+
+  @Input()
+  action?: string;
+
+  @Input()
+  isRightAlign?: boolean = false;
+
+  private selectedValue?: any;
+
+  selectedLabel: string;
+
+  get value(): any {
+    return this.selectedValue == null ? this.defaultValue : this.selectedValue;
+  }
+
+  writeValue(options: any) {
+    const value = options && options.value;
+    if (this.utils.valueHasChanged(this.value, value)) {
+      this.selectedValue = value;
+      this.selectedLabel = options.label;
+      if (this.action) {
+        this.actions[this.action](value);
+      }
+    }
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-list/dropdown-list.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-list/dropdown-list.component.spec.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-list/dropdown-list.component.spec.ts
index 445ee97..e16d93a 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-list/dropdown-list.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-list/dropdown-list.component.spec.ts
@@ -55,37 +55,22 @@ describe('DropdownListComponent', () => {
 
   describe('#changeSelectedItem()', () => {
 
+    const options = {
+      label: 'l',
+      value: 'v'
+    };
+
     beforeEach(() => {
       spyOn(component.selectedItemChange, 'emit').and.callFake(() => {});
+      component.changeSelectedItem(options);
     });
 
-    describe('not a filter list', () => {
-      it('event should not be emitted', () => {
-        component.isFilter = false;
-        component.changeSelectedItem({});
-        expect(component.selectedItemChange.emit).not.toHaveBeenCalled();
-      });
+    it('event should be emitted', () => {
+      expect(component.selectedItemChange.emit).toHaveBeenCalled();
     });
 
-    describe('filter list', () => {
-      const options = {
-        label: 'l',
-        value: 'v'
-      };
-
-      beforeEach(() => {
-        component.isFilter = true;
-        component.changeSelectedItem(options);
-      });
-
-      it('event should be emitted', () => {
-        expect(component.selectedItemChange.emit).toHaveBeenCalled();
-      });
-
-      it('event emitter should be called with correct arguments', () => {
-        expect(component.selectedItemChange.emit).toHaveBeenCalledWith(options);
-      });
-
+    it('event emitter should be called with correct arguments', () => {
+      expect(component.selectedItemChange.emit).toHaveBeenCalledWith(options);
     });
 
   });

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-list/dropdown-list.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-list/dropdown-list.component.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-list/dropdown-list.component.ts
index 6426f6a..3e9a445 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-list/dropdown-list.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/dropdown-list/dropdown-list.component.ts
@@ -19,7 +19,7 @@
 import {Component, Input, Output, EventEmitter} from '@angular/core';
 
 @Component({
-  selector: 'ul.dropdown-menu',
+  selector: 'ul[data-component="dropdown-list"]',
   templateUrl: './dropdown-list.component.html',
   styleUrls: ['./dropdown-list.component.less']
 })
@@ -31,16 +31,11 @@ export class DropdownListComponent {
   @Input()
   defaultAction: Function;
 
-  @Input()
-  isFilter: boolean;
-
   @Output()
   selectedItemChange: EventEmitter<any> = new EventEmitter();
 
   changeSelectedItem(options: any): void {
-    if (this.isFilter) {
-      this.selectedItemChange.emit(options);
-    }
+    this.selectedItemChange.emit(options);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-button/filter-button.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-button/filter-button.component.spec.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-button/filter-button.component.spec.ts
index dda81e6..370b46e 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-button/filter-button.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-button/filter-button.component.spec.ts
@@ -28,6 +28,7 @@ import {ClustersService, clusters} from '@app/services/storage/clusters.service'
 import {ComponentsService, components} from '@app/services/storage/components.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
 import {FilteringService} from '@app/services/filtering.service';
+import {UtilsService} from '@app/services/utils.service';
 
 import {FilterButtonComponent} from './filter-button.component';
 
@@ -58,7 +59,8 @@ describe('FilterButtonComponent', () => {
         ClustersService,
         ComponentsService,
         ComponentActionsService,
-        FilteringService
+        FilteringService,
+        UtilsService
       ],
       schemas: [NO_ERRORS_SCHEMA]
     })

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-button/filter-button.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-button/filter-button.component.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-button/filter-button.component.ts
index b529686..27456e6 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-button/filter-button.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-button/filter-button.component.ts
@@ -20,6 +20,7 @@ import {Component, Input, forwardRef} from '@angular/core';
 import {ControlValueAccessor, NG_VALUE_ACCESSOR, FormGroup} from '@angular/forms';
 import {ComponentActionsService} from '@app/services/component-actions.service';
 import {FilteringService} from '@app/services/filtering.service';
+import {UtilsService} from '@app/services/utils.service';
 import {MenuButtonComponent} from '@app/components/menu-button/menu-button.component';
 
 @Component({
@@ -36,29 +37,18 @@ import {MenuButtonComponent} from '@app/components/menu-button/menu-button.compo
 })
 export class FilterButtonComponent extends MenuButtonComponent implements ControlValueAccessor {
 
-  constructor(protected actions: ComponentActionsService, private filtering: FilteringService) {
+  constructor(protected actions: ComponentActionsService, private filtering: FilteringService, private utils: UtilsService) {
     super(actions);
   }
 
-  ngAfterViewInit() {
-    const callback = this.customOnChange ?
-      (value => this.actions[this.customOnChange](value)) : (() => this.filtering.filteringSubject.next(null));
-    this.form.controls[this.filterName].valueChanges.subscribe(callback);
-  }
-
   @Input()
   filterName: string;
 
   @Input()
-  customOnChange: string;
-
-  @Input()
   form: FormGroup;
 
   private onChange: (fn: any) => void;
 
-  readonly isFilter = true;
-
   get filterInstance(): any {
     return this.filtering.filters[this.filterName];
   }
@@ -68,7 +58,7 @@ export class FilterButtonComponent extends MenuButtonComponent implements Contro
   }
 
   set value(newValue: any) {
-    if (this.filtering.valueHasChanged(this.filterInstance.selectedValue, newValue)) {
+    if (this.utils.valueHasChanged(this.filterInstance.selectedValue, newValue)) {
       this.filterInstance.selectedValue = newValue;
       this.onChange(newValue);
     }
@@ -76,7 +66,7 @@ export class FilterButtonComponent extends MenuButtonComponent implements Contro
 
   writeValue(options: any) {
     const value = options && options.value;
-    if (this.filtering.valueHasChanged(this.filterInstance.selectedValue, value)) {
+    if (this.utils.valueHasChanged(this.filterInstance.selectedValue, value)) {
       this.filterInstance.selectedValue = value;
       this.filterInstance.selectedLabel = options.label;
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.html b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.html
deleted file mode 100644
index bb7a206..0000000
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
-  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.
--->
-
-<div class="filter-label" *ngIf="filterInstance.label">{{filterInstance.label | translate}}</div>
-<button class="btn btn-link dropdown-toggle" data-toggle="dropdown">
-  {{filterInstance.selectedLabel | translate}} <span class="caret"></span>
-</button>
-<ul class="dropdown-menu" [items]="filterInstance.options" [isFilter]="true"
-    (selectedItemChange)="writeValue($event)"></ul>

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.less b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.less
index ec8e84b..e5e85f4 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.less
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.less
@@ -18,14 +18,5 @@
 @import '../variables';
 
 :host {
-  position: relative;
   .default-flex;
-
-  .filter-label {
-    padding: @input-group-addon-padding;
-  }
-
-  .btn {
-    text-transform: none;
-  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts
index f6d1294..e05ef48 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts
@@ -24,6 +24,7 @@ import {TranslateHttpLoader} from '@ngx-translate/http-loader';
 import {StoreModule} from '@ngrx/store';
 import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service';
 import {FilteringService} from '@app/services/filtering.service';
+import {UtilsService} from '@app/services/utils.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
 
 import {FilterDropdownComponent} from './filter-dropdown.component';
@@ -71,6 +72,7 @@ describe('FilterDropdownComponent', () => {
           provide: FilteringService,
           useValue: filtering
         },
+        UtilsService,
         ComponentActionsService
       ],
       schemas: [NO_ERRORS_SCHEMA]

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.ts
index 1cf7d41..9ebd821 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-dropdown/filter-dropdown.component.ts
@@ -15,15 +15,17 @@
  * limitations under the License.
  */
 
-import {Component, AfterViewInit, Input, forwardRef} from '@angular/core';
+import {Component, Input, forwardRef} from '@angular/core';
 import {ControlValueAccessor, NG_VALUE_ACCESSOR, FormGroup} from '@angular/forms';
 import {FilteringService} from '@app/services/filtering.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
+import {UtilsService} from '@app/services/utils.service';
+import {DropdownButtonComponent} from '@app/components/dropdown-button/dropdown-button.component';
 
 @Component({
   selector: 'filter-dropdown',
-  templateUrl: './filter-dropdown.component.html',
-  styleUrls: ['./filter-dropdown.component.less'],
+  templateUrl: '../dropdown-button/dropdown-button.component.html',
+  styleUrls: ['../dropdown-button/dropdown-button.component.less', './filter-dropdown.component.less'],
   providers: [
     {
       provide: NG_VALUE_ACCESSOR,
@@ -32,24 +34,16 @@ import {ComponentActionsService} from '@app/services/component-actions.service';
     }
   ]
 })
-export class FilterDropdownComponent implements AfterViewInit, ControlValueAccessor {
+export class FilterDropdownComponent extends DropdownButtonComponent implements ControlValueAccessor {
 
-  constructor(private filtering: FilteringService, private actions: ComponentActionsService) {
+  constructor(protected filtering: FilteringService, protected actions: ComponentActionsService, protected utils: UtilsService) {
+    super(filtering, actions, utils);
   }
 
-  ngAfterViewInit() {
-    const callback = this.customOnChange ?
-      (value => this.actions[this.customOnChange](value)) : (() => this.filtering.filteringSubject.next(null));
-    this.form.controls[this.filterName].valueChanges.subscribe(callback);
+  ngOnInit() {
   }
 
   @Input()
-  options: any[];
-
-  @Input()
-  customOnChange: string;
-
-  @Input()
   form: FormGroup;
 
   @Input()
@@ -61,20 +55,36 @@ export class FilterDropdownComponent implements AfterViewInit, ControlValueAcces
     return this.filtering.filters[this.filterName];
   }
 
+  get label(): string {
+    return this.filterInstance.label;
+  }
+
+  get defaultValue(): any {
+    return this.filterInstance.defaultValue;
+  }
+
+  get defaultLabel(): any {
+    return this.filterInstance.defaultLabel;
+  }
+
   get value(): any {
-    return this.filterInstance.selectedValue;
+    return this.filterInstance.selectedValue == null ? this.defaultValue : this.filterInstance.selectedValue;
   }
 
   set value(newValue: any) {
-    if (this.filtering.valueHasChanged(this.filterInstance.selectedValue, newValue)) {
+    if (this.utils.valueHasChanged(this.filterInstance.selectedValue, newValue)) {
       this.filterInstance.selectedValue = newValue;
       this.onChange(newValue);
     }
   }
 
-  writeValue(options: any) {
+  get selectedLabel(): string {
+    return this.filterInstance.selectedLabel == null ? this.defaultLabel : this.filterInstance.selectedLabel;
+  }
+
+  writeValue(options: any): void {
     const value = options && options.value;
-    if (this.filtering.valueHasChanged(this.filterInstance.selectedValue, value)) {
+    if (this.utils.valueHasChanged(this.filterInstance.selectedValue, value)) {
       this.filterInstance.selectedValue = value;
       this.filterInstance.selectedLabel = options.label;
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.html b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.html
index 8fb7659..d135ba5 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.html
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.html
@@ -17,6 +17,5 @@
 
 <div class="input-group">
   <span class="input-group-addon">{{filterInstance.label | translate}}</span>
-  <input type="text" class="form-control" [(ngModel)]="filterInstance.selectedValue"
-         (ngModelChange)="writeValue($event)">
+  <input type="text" class="form-control" [(ngModel)]="instantValue" (ngModelChange)="updateValue($event)">
 </div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.spec.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.spec.ts
index 9123969..a30e12a 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.spec.ts
@@ -24,6 +24,7 @@ import {TranslateHttpLoader} from '@ngx-translate/http-loader';
 import {StoreModule} from '@ngrx/store';
 import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service';
 import {FilteringService} from '@app/services/filtering.service';
+import {UtilsService} from '@app/services/utils.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
 
 import {FilterTextFieldComponent} from './filter-text-field.component';
@@ -61,6 +62,7 @@ describe('FilterTextFieldComponent', () => {
           provide: FilteringService,
           useValue: filtering
         },
+        UtilsService,
         ComponentActionsService
       ],
       schemas: [CUSTOM_ELEMENTS_SCHEMA]

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.ts
index b05000b..fe6ea34 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filter-text-field/filter-text-field.component.ts
@@ -15,11 +15,12 @@
  * limitations under the License.
  */
 
-import {Component, AfterViewInit, Input, forwardRef} from '@angular/core';
+import {Component, Input, forwardRef} from '@angular/core';
 import {ControlValueAccessor, NG_VALUE_ACCESSOR, FormGroup} from '@angular/forms';
+import {Subject} from 'rxjs/Subject';
 import 'rxjs/add/operator/debounceTime';
 import {FilteringService} from '@app/services/filtering.service';
-import {ComponentActionsService} from '@app/services/component-actions.service';
+import {UtilsService} from '@app/services/utils.service';
 
 @Component({
   selector: 'filter-text-field',
@@ -33,30 +34,28 @@ import {ComponentActionsService} from '@app/services/component-actions.service';
     }
   ]
 })
-export class FilterTextFieldComponent implements AfterViewInit, ControlValueAccessor {
+export class FilterTextFieldComponent implements ControlValueAccessor {
 
-  constructor(private filtering: FilteringService, private actions: ComponentActionsService) {
-  }
-
-  ngAfterViewInit() {
-    const callback = this.customOnChange ?
-      (value => this.actions[this.customOnChange](value)) : (() => this.filtering.filteringSubject.next(null));
-    this.form.controls[this.filterName].valueChanges.debounceTime(this.debounceInterval).subscribe(callback);
+  constructor(private filtering: FilteringService, private utils: UtilsService) {
+    this.valueSubject.debounceTime(this.debounceInterval).subscribe(value => this.writeValue({
+      value
+    }));
   }
 
   @Input()
   filterName: string;
 
   @Input()
-  customOnChange: string;
-
-  @Input()
   form: FormGroup;
 
   private onChange: (fn: any) => void;
 
   private readonly debounceInterval = 1500;
 
+  instantValue: string;
+
+  private valueSubject = new Subject<string>();
+
   get filterInstance(): any {
     return this.filtering.filters[this.filterName];
   }
@@ -66,7 +65,7 @@ export class FilterTextFieldComponent implements AfterViewInit, ControlValueAcce
   }
 
   set value(newValue: any) {
-    if (this.filtering.valueHasChanged(this.filterInstance.selectedValue, newValue)) {
+    if (this.utils.valueHasChanged(this.filterInstance.selectedValue, newValue)) {
       this.filterInstance.selectedValue = newValue;
       this.onChange(newValue);
     }
@@ -74,7 +73,7 @@ export class FilterTextFieldComponent implements AfterViewInit, ControlValueAcce
 
   writeValue(options: any) {
     const value = options && options.value;
-    if (this.filtering.valueHasChanged(this.filterInstance.selectedValue, value)) {
+    if (this.utils.valueHasChanged(this.filterInstance.selectedValue, value)) {
       this.filterInstance.selectedValue = value;
     }
   }
@@ -86,4 +85,8 @@ export class FilterTextFieldComponent implements AfterViewInit, ControlValueAcce
   registerOnTouched() {
   }
 
+  updateValue(value: string): void {
+    this.valueSubject.next(value);
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.html b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.html
index 450ce5a..6547d88 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.html
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.html
@@ -17,15 +17,14 @@
 
 <form [formGroup]="filtersForm">
   <div class="form-inline filter-input-container col-md-8">
-    <filter-dropdown [(ngModel)]="filters.clusters.selectedValue" [filterName]="'clusters'"
-                     formControlName="clusters" [form]="filtersForm"></filter-dropdown>
-    <filter-text-field [(ngModel)]="filters.text.selectedValue" [filterName]="'text'" formControlName="text"
+    <filter-dropdown [(ngModel)]="filters.clusters.selectedValue" filterName="clusters" [form]="filtersForm"
+                     formControlName="clusters" [options]="filters.clusters.options"></filter-dropdown>
+    <filter-text-field [(ngModel)]="filters.text.selectedValue" filterName="text" formControlName="text"
                        [form]="filtersForm"></filter-text-field>
-    <filter-dropdown [(ngModel)]="filters.timeRange.selectedValue" [filterName]="'timeRange'"
-                     formControlName="timeRange" [form]="filtersForm"></filter-dropdown>
-    <filter-dropdown [(ngModel)]="filters.timeZone.selectedValue" [filterName]="'timeZone'"
-                     formControlName="timeZone" [form]="filtersForm"
-                     [customOnChange]="'setTimeZone'"></filter-dropdown>
+    <filter-dropdown [(ngModel)]="filters.timeRange.selectedValue" filterName="timeRange" [form]="filtersForm"
+                     formControlName="timeRange" [options]="filters.timeRange.options"></filter-dropdown>
+    <dropdown-button [options]="timeZoneSelection.options" [defaultValue]="timeZoneSelection.defaultValue"
+                     [defaultLabel]="timeZoneSelection.defaultLabel" action="setTimeZone"></dropdown-button>
     <!--button class="btn btn-success" type="button">
       <span class="fa fa-search"></span>
     </button-->
@@ -34,14 +33,12 @@
     <a href="#">
       <span class="fa fa-search-minus"></span> {{'filter.excluded' | translate}}
     </a>
-    <filter-button [(ngModel)]="filters.components.selectedValue" formControlName="components"
+    <filter-button [(ngModel)]="filters.components.selectedValue" [form]="filtersForm" formControlName="components"
                    [label]="filters.components.label" [iconClass]="filters.components.iconClass"
-                   [subItems]="filters.components.options" [filterName]="'components'"
-                   [form]="filtersForm"></filter-button>
-    <filter-button [(ngModel)]="filters.levels.selectedValue" formControlName="levels"
+                   [subItems]="filters.components.options" filterName="components"></filter-button>
+    <filter-button [(ngModel)]="filters.levels.selectedValue" [form]="filtersForm" formControlName="levels"
                    [label]="filters.levels.label" [iconClass]="filters.levels.iconClass"
-                   [subItems]="filters.levels.options" [filterName]="'levels'"
-                   [form]="filtersForm"></filter-button>
+                   [subItems]="filters.levels.options" filterName="levels"></filter-button>
     <menu-button label="filter.capture" iconClass="fa fa-caret-right"></menu-button>
   </div>
 </form>

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.less b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.less
index 46a157c..aebd385 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.less
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.less
@@ -27,7 +27,7 @@
     border-bottom-left-radius: 0;
   }
 
-  filter-dropdown {
+  filter-dropdown, dropdown-button {
     border: @input-border;
 
     &:not(:last-child) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.spec.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.spec.ts
index 1917f7f..c0234bb 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.spec.ts
@@ -27,6 +27,7 @@ import {ClustersService, clusters} from '@app/services/storage/clusters.service'
 import {ComponentsService, components} from '@app/services/storage/components.service';
 import {FilteringService} from '@app/services/filtering.service';
 import {HttpClientService} from '@app/services/http-client.service';
+import {UtilsService} from '@app/services/utils.service';
 
 import {FiltersPanelComponent} from './filters-panel.component';
 
@@ -69,7 +70,8 @@ describe('FiltersPanelComponent', () => {
         {
           provide: HttpClientService,
           useValue: httpClient
-        }
+        },
+        UtilsService
       ],
       schemas: [NO_ERRORS_SCHEMA]
     })

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.ts
index f42a2bb..9855bdd 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/filters-panel/filters-panel.component.ts
@@ -17,7 +17,6 @@
  */
 
 import {Component} from '@angular/core';
-import {FormControl, FormGroup} from '@angular/forms';
 import {FilteringService} from '@app/services/filtering.service';
 import {HttpClientService} from '@app/services/http-client.service';
 import {ClustersService} from '@app/services/storage/clusters.service';
@@ -35,16 +34,13 @@ export class FiltersPanelComponent {
     this.loadComponents();
   }
 
-  get filters() {
+  get filters(): any {
     return this.filtering.filters;
   }
 
-  private filtersFormItems = Object.keys(this.filters).reduce((currentObject, key) => {
-    const item = {
-      [key]: new FormControl()
-    };
-    return Object.assign(currentObject, item);
-  }, {});
+  get timeZoneSelection(): any {
+    return this.filtering.timeZoneSelection;
+  }
 
   private loadClusters(): void {
     this.httpClient.get('clusters').subscribe(response => {
@@ -66,6 +62,8 @@ export class FiltersPanelComponent {
     });
   }
 
-  filtersForm = new FormGroup(this.filtersFormItems);
+  get filtersForm() {
+    return this.filtering.filtersForm;
+  }
 
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.html b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.html
index 09b2e1e..5977ea2 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.html
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.html
@@ -15,6 +15,16 @@
   limitations under the License.
 -->
 
+<form *ngIf="(logs | async).length" [formGroup]="filtersForm">
+  <filter-dropdown [(ngModel)]="filters.sorting.selectedValue" filterName="sorting" [form]="filtersForm"
+                   formControlName="sorting" [options]="filters.sorting.options" isRightAlign="true"></filter-dropdown>
+</form>
+<div class="col-md-12 text-center" *ngIf="(logs | async).length">
+  <div class="logs-header">
+    <div class="col-md-1">{{'logs.status' | translate}}</div>
+    <div class="col-md-11">{{'logs.details' | translate}}</div>
+  </div>
+</div>
 <accordion-panel *ngFor="let log of logs | async; let i = index" [toggleId]="'details-' + i" class="col-md-12">
   <ng-template>
     <div [ngClass]="'hexagon ' + log.className"></div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.less b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.less
index 6ed0463..8ebe870 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.less
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.less
@@ -23,6 +23,19 @@
   padding-top: @block-margin-top;
   background-color: @main-background-color; // TODO implement actual color
 
+  .logs-header {
+    // TODO get rid of magic numbers, base on actual design
+    margin: 10px 0;
+    padding: 5px 0;
+    background-color: @list-header-background-color; // TODO implement actual color
+    overflow: hidden;
+    text-transform: uppercase;
+  }
+
+  filter-dropdown {
+    justify-content: flex-end;
+  }
+
   .hexagon {
     // TODO get rid of magic numbers, base on actual design
     left: -7.5px;
@@ -116,6 +129,7 @@
       &.collapsing + .log-content, &.collapse.in + .log-content {
         min-height: 6em;
         max-height: none;
+        overflow-x: auto;
       }
 
       .action-icon {

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.spec.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.spec.ts
index ffcfb83..2c4f372 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.spec.ts
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
+import {NO_ERRORS_SCHEMA} from '@angular/core';
 import {async, ComponentFixture, TestBed} from '@angular/core/testing';
 import {StoreModule} from '@ngrx/store';
 import {MomentModule} from 'angular2-moment';
@@ -27,6 +27,7 @@ import {ClustersService, clusters} from '@app/services/storage/clusters.service'
 import {ComponentsService, components} from '@app/services/storage/components.service';
 import {HttpClientService} from '@app/services/http-client.service';
 import {FilteringService} from '@app/services/filtering.service';
+import {UtilsService} from '@app/services/utils.service';
 
 import {LogsListComponent} from './logs-list.component';
 
@@ -66,9 +67,10 @@ describe('LogsListComponent', () => {
         AppSettingsService,
         ClustersService,
         ComponentsService,
-        FilteringService
+        FilteringService,
+        UtilsService
       ],
-      schemas: [CUSTOM_ELEMENTS_SCHEMA]
+      schemas: [NO_ERRORS_SCHEMA]
     })
     .compileComponents();
   }));

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.ts
index bf80445..a070814 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/logs-list/logs-list.component.ts
@@ -29,11 +29,13 @@ import {FilteringService} from '@app/services/filtering.service';
 export class LogsListComponent implements OnInit {
 
   constructor(private httpClient: HttpClientService, private serviceLogsStorage: ServiceLogsService, private filtering: FilteringService) {
-    this.filtering.filteringSubject.subscribe(this.loadLogs.bind(this));
   }
 
   ngOnInit() {
     this.loadLogs();
+    this.filtersForm.valueChanges.subscribe(() => {
+      this.loadLogs();
+    });
   }
 
   @Input()
@@ -46,7 +48,8 @@ export class LogsListComponent implements OnInit {
     text: ['iMessage'],
     timeRange: ['end_time', 'start_time'],
     components: ['component_name'],
-    levels: ['level']
+    levels: ['level'],
+    sorting: ['sortType', 'sortBy']
   };
 
   logs = this.serviceLogsStorage.getAll().map(logs => logs.map(log => {
@@ -63,6 +66,14 @@ export class LogsListComponent implements OnInit {
     return this.filtering.timeZone;
   }
 
+  get filters() {
+    return this.filtering.filters;
+  }
+  
+  get filtersForm() {
+    return this.filtering.filtersForm;
+  }
+
   private loadLogs(): void {
     this.httpClient.get(this.logsArrayId, this.getParams()).subscribe(response => {
       const jsonResponse = response.json(),
@@ -78,8 +89,7 @@ export class LogsListComponent implements OnInit {
   private getParams(): any {
     let params = {};
     Object.keys(this.usedFilters).forEach(key => {
-      const inputFilter = this.filtering.filters[key],
-        inputValue = inputFilter.selectedValue,
+      const inputValue = this.filtersForm.getRawValue()[key],
         paramNames = this.usedFilters[key];
       paramNames.forEach(paramName => {
         let value;

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/menu-button/menu-button.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/menu-button/menu-button.component.html b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/menu-button/menu-button.component.html
index 5111197..031dec1 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/menu-button/menu-button.component.html
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/menu-button/menu-button.component.html
@@ -22,6 +22,6 @@
   <br>
   <a *ngIf="label" (mousedown)="onMouseDown($event)" [ngClass]="labelClass" (mouseup)="onMouseUp($event)"
      (click)="$event.stopPropagation()">{{label | translate}}</a>
-  <ul class="dropdown-menu" *ngIf="hasSubItems" [items]="subItems" [isFilter]="isFilter"
-      (selectedItemChange)="isFilter && writeValue($event)"></ul>
+  <ul data-component="dropdown-list" class="dropdown-menu" *ngIf="hasSubItems" [items]="subItems"
+      (selectedItemChange)="writeValue($event)"></ul>
 </div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/menu-button/menu-button.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/menu-button/menu-button.component.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/menu-button/menu-button.component.ts
index 0c1f840..e245fb3 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/menu-button/menu-button.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/menu-button/menu-button.component.ts
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import {Component, AfterViewInit, Input, ViewChild, ElementRef} from '@angular/core';
+import {Component, Input, ViewChild, ElementRef} from '@angular/core';
 import {ComponentActionsService} from '@app/services/component-actions.service';
 import * as $ from 'jquery';
 
@@ -25,14 +25,11 @@ import * as $ from 'jquery';
   templateUrl: './menu-button.component.html',
   styleUrls: ['./menu-button.component.less']
 })
-export class MenuButtonComponent implements AfterViewInit {
+export class MenuButtonComponent {
 
   constructor(protected actions: ComponentActionsService) {
   }
 
-  ngAfterViewInit() {
-  }
-
   @ViewChild('dropdown')
   dropdown: ElementRef;
 
@@ -42,8 +39,6 @@ export class MenuButtonComponent implements AfterViewInit {
   @Input()
   action: string;
 
-  isFilter: boolean = false;
-
   @Input()
   iconClass: string;
 
@@ -85,5 +80,9 @@ export class MenuButtonComponent implements AfterViewInit {
       event.stopPropagation();
     }
   }
+  
+  writeValue(options: any) {
+    // TODO implement value change behaviour
+  }
 
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/components/variables.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/variables.less b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/variables.less
index 9a8ea09..c5f034c 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/components/variables.less
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/components/variables.less
@@ -29,6 +29,7 @@
 @grey-color: #666;
 @default-line-height: 1.42857143;
 @main-background-color: #ECECEC;
+@list-header-background-color: #F2F2F2;
 
 @fatal-color: #830A0A;
 @error-color: #E81D1D;

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/services/filtering.service.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/services/filtering.service.spec.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/services/filtering.service.spec.ts
index 0ad3795..4be15b4 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/services/filtering.service.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/services/filtering.service.spec.ts
@@ -21,6 +21,7 @@ import {StoreModule} from '@ngrx/store';
 import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service';
 import {ClustersService, clusters} from '@app/services/storage/clusters.service';
 import {ComponentsService, components} from '@app/services/storage/components.service';
+import {UtilsService} from '@app/services/utils.service';
 
 import {FilteringService} from './filtering.service';
 
@@ -38,7 +39,8 @@ describe('FilteringService', () => {
         FilteringService,
         AppSettingsService,
         ClustersService,
-        ComponentsService
+        ComponentsService,
+        UtilsService
       ]
     });
   });

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/services/filtering.service.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/services/filtering.service.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/services/filtering.service.ts
index 36bb756..e31202b 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/services/filtering.service.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/services/filtering.service.ts
@@ -17,16 +17,17 @@
  */
 
 import {Injectable} from '@angular/core';
-import {Subject} from 'rxjs/Subject';
+import {FormControl, FormGroup} from '@angular/forms';
 import * as moment from 'moment-timezone';
 import {AppSettingsService} from '@app/services/storage/app-settings.service';
 import {ClustersService} from '@app/services/storage/clusters.service';
 import {ComponentsService} from '@app/services/storage/components.service';
+import {UtilsService} from '@app/services/utils.service';
 
 @Injectable()
 export class FilteringService {
 
-  constructor(private appSettings: AppSettingsService, private clustersStorage: ClustersService, private componentsStorage: ComponentsService) {
+  constructor(private appSettings: AppSettingsService, private clustersStorage: ClustersService, private componentsStorage: ComponentsService, private utils: UtilsService) {
     this.appSettings.getParameter('timeZone').subscribe(value => this.timeZone = value || this.defaultTimeZone);
     this.clustersStorage.getAll().subscribe(clusters => this.filters.clusters.options = [...this.filters.clusters.options, ...clusters.map(this.getListItem)]);
     this.componentsStorage.getAll().subscribe(components => this.filters.components.options = [...this.filters.components.options, ...components.map(this.getListItem)]);
@@ -41,9 +42,13 @@ export class FilteringService {
 
   private readonly defaultTimeZone = moment.tz.guess();
 
+  private readonly sortMap = {
+    component_name: 'type',
+    start_time: 'logtime'
+  };
+
   timeZone: string = this.defaultTimeZone;
 
-  // TODO implement loading of real options data
   filters = {
     clusters: {
       label: 'filter.clusters',
@@ -54,12 +59,13 @@ export class FilteringService {
         }
       ],
       selectedValue: '',
-      selectedLabel: 'filter.all',
+      defaultValue: '',
+      defaultLabel: 'filter.all',
       paramName: 'clusters',
     },
     text: {
       label: 'filter.message',
-      selectedValue: ''
+      defaultValue: ''
     },
     timeRange: {
       options: [
@@ -135,18 +141,12 @@ export class FilteringService {
         unit: 'h',
         interval: 1
       },
-      selectedLabel: 'filter.timeRange.1hr'
-    },
-    timeZone: {
-      options: moment.tz.names().map(zone => {
-        // TODO map labels according to actual design requirements
-        return {
-          label: this.getTimeZoneLabel(zone),
-          value: zone
-        };
-      }),
-      selectedValue: this.defaultTimeZone,
-      selectedLabel: this.getTimeZoneLabel(this.defaultTimeZone)
+      defaultValue: {
+        type: 'LAST',
+        unit: 'h',
+        interval: 1
+      },
+      defaultLabel: 'filter.timeRange.1hr'
     },
     components: {
       label: 'filter.components',
@@ -158,7 +158,8 @@ export class FilteringService {
         }
       ],
       selectedValue: '',
-      selectedLabel: 'filter.all'
+      defaultValue: '',
+      defaultLabel: 'filter.all'
     },
     levels: {
       label: 'filter.levels',
@@ -198,10 +199,84 @@ export class FilteringService {
         }
       ],
       selectedValue: '',
-      selectedLabel: 'filter.all'
+      defaultValue: '',
+      defaultLabel: 'filter.all'
+    },
+    sorting: {
+      label: 'sorting.title',
+      options: [
+        {
+          label: 'sorting.level.asc',
+          value: {
+            key: 'level',
+            type: 'asc'
+          }
+        },
+        {
+          label: 'sorting.level.desc',
+          value: {
+            key: 'level',
+            type: 'desc'
+          }
+        },
+        {
+          label: 'sorting.component.asc',
+          value: {
+            key: 'component_name',
+            type: 'asc'
+          }
+        },
+        {
+          label: 'sorting.component.desc',
+          value: {
+            key: 'component_name',
+            type: 'desc'
+          }
+        },
+        {
+          label: 'sorting.time.asc',
+          value: {
+            key: 'start_time',
+            type: 'asc'
+          }
+        },
+        {
+          label: 'sorting.time.desc',
+          value: {
+            key: 'start_time',
+            type: 'desc'
+          }
+        }
+      ],
+      selectedValue: '',
+      defaultValue: '',
+      defaultLabel: ''
     }
   };
 
+  timeZoneSelection = {
+    options: moment.tz.names().map(zone => {
+      // TODO map labels according to actual design requirements
+      return {
+        label: this.utils.getTimeZoneLabel(zone),
+        value: zone
+      };
+    }),
+    defaultValue: this.defaultTimeZone,
+    defaultLabel: this.utils.getTimeZoneLabel(this.defaultTimeZone)
+  };
+
+  private filtersFormItems = Object.keys(this.filters).reduce((currentObject, key) => {
+    let formControl = new FormControl(),
+      item = {
+        [key]: new FormControl()
+      };
+    formControl.setValue(this.filters[key].defaultValue);
+    return Object.assign(currentObject, item);
+  }, {});
+
+  filtersForm = new FormGroup(this.filtersFormItems);
+
   readonly valueGetters = {
     end_time: value => {
       let time;
@@ -241,24 +316,9 @@ export class FilteringService {
         }
       }
       return time ? time.toISOString() : '';
-    }
+    },
+    sortType: value => value && value.type,
+    sortBy: value => value && (this.sortMap[value.key] || value.key)
   };
 
-  getTimeZoneLabel(timeZone) {
-    return `${timeZone} (${moment.tz(timeZone).format('Z')})`;
-  }
-
-  valueHasChanged(currentValue: any, newValue: any): boolean {
-    if (newValue == null) {
-      return false;
-    }
-    if (typeof newValue === 'object') {
-      return JSON.stringify(currentValue) !== JSON.stringify(newValue);
-    } else {
-      return currentValue !== newValue;
-    }
-  }
-
-  filteringSubject = new Subject();
-
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/services/mock-api-data.service.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/services/mock-api-data.service.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/services/mock-api-data.service.ts
index 40645f0..fdebb2f 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/app/services/mock-api-data.service.ts
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/services/mock-api-data.service.ts
@@ -104,7 +104,8 @@ export class mockApiDataService implements InMemoryDbService {
         const filterMapItem = this.filterMap[path];
         if (query && filterMapItem) {
           filteredData = {};
-          const collection = allData[filterMapItem.pathToCollection],
+          const pathToCollection = filterMapItem.pathToCollection,
+            collection = allData[pathToCollection],
             filteredCollection = collection.filter(item => {
             let result = true;
               query.paramsMap.forEach((value, key) => {
@@ -118,7 +119,24 @@ export class mockApiDataService implements InMemoryDbService {
             });
             return result;
           });
-          filteredData[filterMapItem.pathToCollection] = filteredCollection;
+          if (query.paramsMap.has('sortBy') && query.paramsMap.has('sortType')) {
+            const sortKey = query.paramsMap.get('sortBy')[0],
+              sortType = query.paramsMap.get('sortType')[0];
+            filteredCollection.sort((a, b) => {
+              const itemA = a[sortKey],
+                itemB = b[sortKey];
+              let ascResult;
+              if (itemA > itemB) {
+                ascResult = 1;
+              } else if (itemA < itemB) {
+                ascResult = -1;
+              } else {
+                ascResult = 0;
+              }
+              return ascResult * Math.pow(-1, Number(sortType === 'desc'));
+            });
+          }
+          filteredData[pathToCollection] = filteredCollection;
         } else {
           filteredData = allData;
         }

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/services/utils.service.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/services/utils.service.spec.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/services/utils.service.spec.ts
new file mode 100644
index 0000000..a93eee5
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/services/utils.service.spec.ts
@@ -0,0 +1,33 @@
+/**
+ * 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 {TestBed, inject} from '@angular/core/testing';
+
+import {UtilsService} from './utils.service';
+
+describe('UtilsService', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [UtilsService]
+    });
+  });
+
+  it('should create service', inject([UtilsService], (service: UtilsService) => {
+    expect(service).toBeTruthy();
+  }));
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/app/services/utils.service.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/app/services/utils.service.ts b/ambari-logsearch/ambari-logsearch-web-new/src/app/services/utils.service.ts
new file mode 100644
index 0000000..7180ebb
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/app/services/utils.service.ts
@@ -0,0 +1,40 @@
+/**
+ * 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 * as moment from 'moment-timezone';
+
+@Injectable()
+export class UtilsService {
+
+  valueHasChanged(currentValue: any, newValue: any): boolean {
+    if (newValue == null) {
+      return false;
+    }
+    if (typeof newValue === 'object') {
+      return JSON.stringify(currentValue) !== JSON.stringify(newValue);
+    } else {
+      return currentValue !== newValue;
+    }
+  }
+
+  getTimeZoneLabel(timeZone) {
+    return `${timeZone} (${moment.tz(timeZone).format('Z')})`;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/2d5b7561/ambari-logsearch/ambari-logsearch-web-new/src/assets/i18n/en.json
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web-new/src/assets/i18n/en.json b/ambari-logsearch/ambari-logsearch-web-new/src/assets/i18n/en.json
index 755f307..33d059e 100644
--- a/ambari-logsearch/ambari-logsearch-web-new/src/assets/i18n/en.json
+++ b/ambari-logsearch/ambari-logsearch-web-new/src/assets/i18n/en.json
@@ -36,5 +36,16 @@
   "levels.info": "Info",
   "levels.debug": "Debug",
   "levels.trace": "Trace",
-  "levels.unknown": "Unknown"
+  "levels.unknown": "Unknown",
+
+  "sorting.title": "Sort By",
+  "sorting.level.asc": "Ascending Level",
+  "sorting.level.desc": "Descending Level",
+  "sorting.component.asc": "Ascending Component",
+  "sorting.component.desc": "Descending Component",
+  "sorting.time.asc": "Ascending Time",
+  "sorting.time.desc": "Descending Time",
+
+  "logs.status": "Status",
+  "logs.details": "Details"
 }