This is an automated email from the ASF dual-hosted git repository.

ababiichuk pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 3869055  AMBARI-22900 Log Search UI: implement 'History' functionality
3869055 is described below

commit 3869055ed2cf643271b103fe85c161940c7196a0
Author: aBabiichuk <ababiic...@hortonworks.com>
AuthorDate: Fri Feb 2 14:21:16 2018 +0200

    AMBARI-22900 Log Search UI: implement 'History' functionality
---
 .../ambari-logsearch-web/src/app/app.module.ts     |  13 +-
 .../classes/components/graph/graph.component.ts    |  18 +-
 .../src/app/classes/filtering.ts                   |   2 +-
 .../src/app/classes/list-item.ts                   |   5 +-
 .../src/app/classes/models/app-state.ts            |  13 +-
 .../action-menu/action-menu.component.html         |  15 +-
 .../action-menu/action-menu.component.spec.ts      |  69 ++++-
 .../action-menu/action-menu.component.ts           | 109 +++----
 .../audit-logs-entries.component.html              |   4 +-
 .../audit-logs-entries.component.spec.ts           |   2 +
 .../audit-logs-table.component.html                |   7 +-
 .../audit-logs-table/audit-logs-table.component.ts |   8 +-
 .../context-menu/context-menu.component.spec.ts    |   6 +-
 .../dropdown-button/dropdown-button.component.html |  20 +-
 .../dropdown-button/dropdown-button.component.less |   8 +-
 .../dropdown-button.component.spec.ts              |  12 +-
 .../dropdown-button/dropdown-button.component.ts   |  40 +--
 .../dropdown-list/dropdown-list.component.html     |  28 +-
 .../dropdown-list/dropdown-list.component.spec.ts  |   6 +-
 .../dropdown-list/dropdown-list.component.ts       |  39 ++-
 .../filter-button/filter-button.component.spec.ts  |  12 +-
 .../filter-dropdown.component.spec.ts              |  12 +-
 .../filter-dropdown/filter-dropdown.component.ts   |   5 -
 .../filters-panel/filters-panel.component.html     |  10 +-
 .../filters-panel/filters-panel.component.ts       |  17 ++
 .../history-item-controls.component.html}          |   8 +-
 .../history-item-controls.component.less}          |   9 +-
 .../history-item-controls.component.spec.ts}       |  16 +-
 .../history-item-controls.component.ts}            |  16 +-
 .../log-context/log-context.component.spec.ts      |   4 +-
 .../menu-button/menu-button.component.html         |   8 +-
 .../menu-button/menu-button.component.spec.ts      |  12 +-
 .../menu-button/menu-button.component.ts           |  28 +-
 .../pagination/pagination.component.html           |   3 +-
 .../components/search-box/search-box.component.ts  |   4 +-
 .../service-logs-table.component.html              |  13 +-
 .../service-logs-table.component.spec.ts           |   2 -
 .../service-logs-table.component.ts                |  99 +++++--
 .../time-range-picker.component.spec.ts            |   2 +
 .../time-range-picker.component.ts                 |   2 +-
 .../timezone-picker.component.spec.ts              |   2 -
 .../timezone-picker/timezone-picker.component.ts   |  17 +-
 .../components/top-menu/top-menu.component.html    |   8 +-
 .../components/top-menu/top-menu.component.spec.ts |   4 +
 .../app/components/top-menu/top-menu.component.ts  |  23 +-
 .../app/services/component-actions.service.spec.ts | 101 -------
 .../src/app/services/component-actions.service.ts  | 155 ----------
 .../services/component-generator.service.spec.ts   |   2 +
 .../app/services/component-generator.service.ts    |   6 +
 ...ice.spec.ts => history-manager.service.spec.ts} |  94 +++++-
 .../src/app/services/history-manager.service.ts    | 330 +++++++++++++++++++++
 .../app/services/logs-container.service.spec.ts    |   4 +-
 .../src/app/services/logs-container.service.ts     |  88 +++++-
 .../ambari-logsearch-web/src/assets/i18n/en.json   |   5 +
 54 files changed, 944 insertions(+), 601 deletions(-)

diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts 
b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
index c6c6922..0a42994 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
@@ -35,7 +35,6 @@ import {ServiceInjector} from '@app/classes/service-injector';
 
 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 {UtilsService} from '@app/services/utils.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {ComponentGeneratorService} from 
'@app/services/component-generator.service';
@@ -57,6 +56,7 @@ import {ServiceLogsFieldsService} from 
'@app/services/storage/service-logs-field
 import {AuditLogsFieldsService} from 
'@app/services/storage/audit-logs-fields.service';
 import {TabsService} from '@app/services/storage/tabs.service';
 import {AuthService} from '@app/services/auth.service';
+import {HistoryManagerService} from '@app/services/history-manager.service';
 import {reducer} from '@app/services/storage/reducers.service';
 
 import {AppComponent} from '@app/components/app.component';
@@ -96,6 +96,7 @@ import {GraphTooltipComponent} from 
'@app/components/graph-tooltip/graph-tooltip
 import {GraphLegendItemComponent} from 
'@app/components/graph-legend-item/graph-legend-item.component';
 import {TimeLineGraphComponent} from 
'@app/components/time-line-graph/time-line-graph.component';
 import {ContextMenuComponent} from 
'@app/components/context-menu/context-menu.component';
+import {HistoryItemControlsComponent} from 
'@app/components/history-item-controls/history-item-controls.component';
 
 import {TimeZoneAbbrPipe} from '@app/pipes/timezone-abbr.pipe';
 import {TimerSecondsPipe} from '@app/pipes/timer-seconds.pipe';
@@ -159,6 +160,7 @@ export function getXHRBackend(injector: Injector, browser: 
BrowserXhr, xsrf: XSR
     GraphLegendItemComponent,
     TimeLineGraphComponent,
     ContextMenuComponent,
+    HistoryItemControlsComponent,
     TimeZoneAbbrPipe,
     TimerSecondsPipe
   ],
@@ -183,7 +185,6 @@ export function getXHRBackend(injector: Injector, browser: 
BrowserXhr, xsrf: XSR
   ],
   providers: [
     HttpClientService,
-    ComponentActionsService,
     UtilsService,
     LogsContainerService,
     ComponentGeneratorService,
@@ -208,10 +209,14 @@ export function getXHRBackend(injector: Injector, 
browser: BrowserXhr, xsrf: XSR
       useFactory: getXHRBackend,
       deps: [Injector, BrowserXhr, XSRFStrategy, ResponseOptions]
     },
-    AuthService
+    AuthService,
+    HistoryManagerService
   ],
   bootstrap: [AppComponent],
-  entryComponents: [NodeBarComponent],
+  entryComponents: [
+    NodeBarComponent,
+    HistoryItemControlsComponent
+  ],
   schemas: [CUSTOM_ELEMENTS_SCHEMA]
 })
 export class AppModule {
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/graph/graph.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/graph/graph.component.ts
index 0cfe69a..b9140cd 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/graph/graph.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/graph/graph.component.ts
@@ -122,20 +122,6 @@ export class GraphComponent implements AfterViewInit, 
OnChanges {
   skipZeroValuesInTooltip: boolean = true;
 
   /**
-   * Indicates whether context menu for X axis ticks is available
-   * @type {boolean}
-   */
-  @Input()
-  hasXTickContextMenu: boolean = false;
-
-  /**
-   * Indicates whether context menu for Y axis ticks is available
-   * @type {boolean}
-   */
-  @Input()
-  hasYTickContextMenu: boolean = false;
-
-  /**
    * Indicates whether X axis event should be emitted with formatted string 
values that are displayed
    * (instead of raw values)
    * @type {boolean}
@@ -320,7 +306,7 @@ export class GraphComponent implements AfterViewInit, 
OnChanges {
     this.xAxis = axis;
     this.svg.append('g').attr('class', `axis 
${this.xAxisClassName}`).attr('transform', `translate(0,${this.height})`)
       .call(this.xAxis);
-    if (this.hasXTickContextMenu) {
+    if (this.xTickContextMenu.observers.length) {
       this.svg.selectAll(`.${this.xAxisClassName} .tick`).on('contextmenu', 
(tickValue: any, index: number): void => {
         const tick = this.emitFormattedXTick ? 
this.xAxisTickFormatter(tickValue, index) : tickValue,
           nativeEvent = d3.event;
@@ -342,7 +328,7 @@ export class GraphComponent implements AfterViewInit, 
OnChanges {
     }
     this.yAxis = axis;
     this.svg.append('g').attr('class', `axis 
${this.yAxisClassName}`).call(this.yAxis);
-    if (this.hasYTickContextMenu) {
+    if (this.yTickContextMenu.observers.length) {
       this.svg.selectAll(`.${this.yAxisClassName} .tick`).on('contextmenu', 
(tickValue: any, index: number): void => {
         const tick = this.emitFormattedYTick ? 
this.yAxisTickFormatter(tickValue, index): tickValue,
           nativeEvent = d3.event;
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts 
b/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts
index 3348969..bb75786 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts
@@ -48,7 +48,7 @@ export interface SortingListItem extends ListItem {
 export interface FilterCondition {
   label?: string;
   options?: (ListItem | TimeUnitListItem[])[];
-  defaultSelection?: ListItem | ListItem[] | number;
+  defaultSelection?: ListItem | ListItem[] | number | boolean;
   iconClass?: string;
   fieldName?: string;
 }
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts 
b/ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts
index 1aaaecc..8505373 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts
@@ -17,10 +17,11 @@
  */
 
 export interface ListItem {
-  id?: string;
+  id?: string | number;
   label?: string;
   value: any;
   iconClass?: string;
   isChecked?: boolean;
-  action?: string;
+  onSelect?: Function;
+  isDivider?: boolean;
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts 
b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts
index c3279ce..b187ca6 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts
@@ -17,8 +17,14 @@
  */
 
 import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry';
+import {ListItem} from '@app/classes/list-item';
 import {LogsType} from '@app/classes/string';
 
+export interface History {
+  items: ListItem[];
+  currentId: number;
+}
+
 export interface AppState {
   isAuthorized: boolean;
   isInitialLoading: boolean;
@@ -28,6 +34,7 @@ export interface AppState {
   isServiceLogContextView: boolean;
   activeLog: ActiveServiceLogEntry | null;
   activeFilters: object;
+  history: History;
 }
 
 export const initialState: AppState = {
@@ -38,5 +45,9 @@ export const initialState: AppState = {
   isServiceLogsFileView: false,
   isServiceLogContextView: false,
   activeLog: null,
-  activeFilters: null
+  activeFilters: null,
+  history: {
+    items: [],
+    currentId: -1
+  }
 };
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
index ab6326a..2e332d4 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
@@ -14,7 +14,14 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<menu-button *ngFor="let item of items" label="{{item.label | translate}}" 
[action]="item.action"
-             [iconClass]="item.iconClass" [labelClass]="item.labelClass" 
[subItems]="item.subItems"
-             [hideCaret]="item.hideCaret" [badge]="item.badge" 
[isRightAlign]="item.isRightAlign">
-</menu-button>
+
+<!-- TODO use listClass="history-dropdown" for custom styling -->
+<menu-button label="{{'topMenu.undo' | translate}}" [subItems]="undoItems" 
iconClass="fa fa-arrow-left"
+             listClass="history-dropdown" (buttonClick)="undoLatest()" 
(selectItem)="undo($event)"></menu-button>
+<menu-button label="{{'topMenu.redo' | translate}}" [subItems]="redoItems" 
iconClass="fa fa-arrow-right"
+             listClass="history-dropdown" (buttonClick)="redoLatest()" 
(selectItem)="redo($event)">></menu-button>
+<menu-button label="{{'topMenu.history' | translate}}" 
[subItems]="historyItems" iconClass="fa fa-history"
+             listClass="history-dropdown" [isRightAlign]="true"
+             
additionalLabelComponentSetter="getHistoryItemIcons"></menu-button>
+<menu-button label="{{'topMenu.refresh' | translate}}" iconClass="fa 
fa-refresh"
+             (buttonClick)="refresh()"></menu-button>
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
index 081304e..ba53ee1 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
@@ -19,6 +19,26 @@
 import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
 import {async, ComponentFixture, TestBed} from '@angular/core/testing';
 import {TranslationModules} from '@app/test-config.spec';
+import {StoreModule} from '@ngrx/store';
+import {AuditLogsService, auditLogs} from 
'@app/services/storage/audit-logs.service';
+import {ServiceLogsService, serviceLogs} from 
'@app/services/storage/service-logs.service';
+import {AuditLogsFieldsService, auditLogsFields} from 
'@app/services/storage/audit-logs-fields.service';
+import {AuditLogsGraphDataService, auditLogsGraphData} from 
'@app/services/storage/audit-logs-graph-data.service';
+import {ServiceLogsFieldsService, serviceLogsFields} from 
'@app/services/storage/service-logs-fields.service';
+import {
+  ServiceLogsHistogramDataService, serviceLogsHistogramData
+} from '@app/services/storage/service-logs-histogram-data.service';
+import {AppSettingsService, appSettings} from 
'@app/services/storage/app-settings.service';
+import {AppStateService, appState} from 
'@app/services/storage/app-state.service';
+import {ClustersService, clusters} from 
'@app/services/storage/clusters.service';
+import {ComponentsService, components} from 
'@app/services/storage/components.service';
+import {HostsService, hosts} from '@app/services/storage/hosts.service';
+import {ServiceLogsTruncatedService, serviceLogsTruncated} from 
'@app/services/storage/service-logs-truncated.service';
+import {TabsService, tabs} from '@app/services/storage/tabs.service';
+import {HistoryManagerService} from '@app/services/history-manager.service';
+import {HttpClientService} from '@app/services/http-client.service';
+import {LogsContainerService} from '@app/services/logs-container.service';
+import {UtilsService} from '@app/services/utils.service';
 
 import {ActionMenuComponent} from './action-menu.component';
 
@@ -27,9 +47,56 @@ describe('ActionMenuComponent', () => {
   let fixture: ComponentFixture<ActionMenuComponent>;
 
   beforeEach(async(() => {
+    const httpClient = {
+      get: () => {
+        return {
+          subscribe: () => {
+          }
+        }
+      }
+    };
     TestBed.configureTestingModule({
-      imports: TranslationModules,
+      imports: [
+        ...TranslationModules,
+        StoreModule.provideStore({
+          auditLogs,
+          serviceLogs,
+          auditLogsFields,
+          auditLogsGraphData,
+          serviceLogsFields,
+          serviceLogsHistogramData,
+          appSettings,
+          appState,
+          clusters,
+          components,
+          hosts,
+          serviceLogsTruncated,
+          tabs
+        })
+      ],
       declarations: [ActionMenuComponent],
+      providers: [
+        {
+          provide: HttpClientService,
+          useValue: httpClient
+        },
+        HistoryManagerService,
+        LogsContainerService,
+        UtilsService,
+        AuditLogsService,
+        ServiceLogsService,
+        AuditLogsFieldsService,
+        AuditLogsGraphDataService,
+        ServiceLogsFieldsService,
+        ServiceLogsHistogramDataService,
+        AppSettingsService,
+        AppStateService,
+        ClustersService,
+        ComponentsService,
+        HostsService,
+        ServiceLogsTruncatedService,
+        TabsService
+      ],
       schemas: [CUSTOM_ELEMENTS_SCHEMA]
     })
     .compileComponents();
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
index 72037f8..351268c 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
@@ -17,6 +17,9 @@
  */
 
 import {Component} from '@angular/core';
+import {LogsContainerService} from '@app/services/logs-container.service';
+import {HistoryManagerService} from '@app/services/history-manager.service';
+import {ListItem} from '@app/classes/list-item';
 
 @Component({
   selector: 'action-menu',
@@ -25,77 +28,39 @@ import {Component} from '@angular/core';
 })
 export class ActionMenuComponent {
 
-  //TODO implement loading of real data into subItems
-  readonly items = [
-    {
-      iconClass: 'fa fa-arrow-left',
-      label: 'topMenu.undo',
-      action: 'undo',
-      subItems: [
-        {
-          label: 'Apply \'Last week\' filter'
-        },
-        {
-          label: 'Clear all filters'
-        },
-        {
-          label: 'Apply \'HDFS\' filter'
-        },
-        {
-          label: 'Apply \'Errors\' filter'
-        }
-      ]
-    },
-    {
-      iconClass: 'fa fa-arrow-right',
-      label: 'topMenu.redo',
-      action: 'redo',
-      subItems: [
-        {
-          label: 'Apply \'Warnings\' filter'
-        },
-        {
-          label: 'Switch to graph mode'
-        },
-        {
-          label: 'Apply \'Custom Date\' filter'
-        }
-      ]
-    },
-    {
-      iconClass: 'fa fa-refresh',
-      label: 'topMenu.refresh',
-      action: 'refresh'
-    },
-    {
-      iconClass: 'fa fa-history',
-      label: 'topMenu.history',
-      action: 'openHistory',
-      isRightAlign: true,
-      subItems: [
-        {
-          label: 'Apply \'Custom Date\' filter'
-        },
-        {
-          label: 'Switch to graph mode'
-        },
-        {
-          label: 'Apply \'Warnings\' filter'
-        },
-        {
-          label: 'Apply \'Last week\' filter'
-        },
-        {
-          label: 'Clear all filters'
-        },
-        {
-          label: 'Apply \'HDFS\' filter'
-        },
-        {
-          label: 'Apply \'Errors\' filter'
-        }
-      ]
-    }
-  ];
+  constructor(private logsContainer: LogsContainerService, private 
historyManager: HistoryManagerService) {
+  }
+
+  get undoItems(): ListItem[] {
+    return this.historyManager.undoItems;
+  }
+
+  get redoItems(): ListItem[] {
+    return this.historyManager.redoItems;
+  }
+
+  get historyItems(): ListItem[] {
+    return this.historyManager.activeHistory;
+  }
+
+  undoLatest(): void {
+    this.historyManager.undo(this.undoItems[0]);
+  }
+
+  redoLatest(): void {
+    this.historyManager.redo(this.redoItems[0]);
+  }
+
+  undo(item: ListItem): void {
+    this.historyManager.undo(item);
+  }
+
+  redo(item: ListItem): void {
+    this.historyManager.redo(item);
+  }
+
+  refresh(): void {
+    this.logsContainer.loadLogs();
+  }
 
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-entries/audit-logs-entries.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-entries/audit-logs-entries.component.html
index 22deef1..6ebb92e 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-entries/audit-logs-entries.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-entries/audit-logs-entries.component.html
@@ -25,8 +25,8 @@
                             svgId="top-users-graph"></horizontal-histogram>
     </collapsible-panel>
     <collapsible-panel commonTitle="{{'logs.topResources' | translate: 
resourcesGraphTitleParams}}" class="col-md-6">
-      <horizontal-histogram [data]="topResourcesGraphData" 
[allowFractionalXTicks]="false" [hasYTickContextMenu]="true"
-                            [emitFormattedYTick]="true" 
(yTickContextMenu)="showContextMenu($event)"
+      <horizontal-histogram [data]="topResourcesGraphData" 
[allowFractionalXTicks]="false" [emitFormattedYTick]="true"
+                            (yTickContextMenu)="showContextMenu($event)"
                             svgId="top-resources-graph"></horizontal-histogram>
       <context-menu [isDisplayed]="isContextMenuDisplayed" 
[contextMenuItems]="contextMenuItems"
                     [leftPosition]="contextMenuLeft" 
[topPosition]="contextMenuTop" (itemSelect)="updateQuery($event)"
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-entries/audit-logs-entries.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-entries/audit-logs-entries.component.spec.ts
index f7d0cdc..51aaaa1 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-entries/audit-logs-entries.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-entries/audit-logs-entries.component.spec.ts
@@ -37,6 +37,7 @@ import {TabsService, tabs} from 
'@app/services/storage/tabs.service';
 import {TabsComponent} from '@app/components/tabs/tabs.component';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {HttpClientService} from '@app/services/http-client.service';
+import {UtilsService} from '@app/services/utils.service';
 
 import {AuditLogsEntriesComponent} from './audit-logs-entries.component';
 
@@ -82,6 +83,7 @@ describe('AuditLogsEntriesComponent', () => {
           provide: HttpClientService,
           useValue: httpClient
         },
+        UtilsService,
         AuditLogsService,
         ServiceLogsService,
         AuditLogsFieldsService,
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.html
index cad09bc..f970726 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.html
@@ -15,13 +15,12 @@
   limitations under the License.
 -->
 
-<dropdown-button class="pull-right" label="{{'logs.columns' | translate}}" 
[options]="columns" [isRightAlign]="true"
-                 [isMultipleChoice]="true" action="updateSelectedColumns"
-                 
[additionalArgs]="logsTypeMapObject.fieldsModel"></dropdown-button>
+<dropdown-button class="pull-right" label="{{'logs.columns' | translate}}" 
(selectItem)="updateSelectedColumns($event)"
+                 [options]="columns" [isRightAlign]="true" 
[isMultipleChoice]="true"></dropdown-button>
 <form *ngIf="logs && logs.length" [formGroup]="filtersForm" class="row 
pull-right">
   <filter-dropdown class="col-md-12" label="{{filters.auditLogsSorting.label | 
translate}}"
                    formControlName="auditLogsSorting" 
[options]="filters.auditLogsSorting.options"
-                   [isRightAlign]="true"></filter-dropdown>
+                   [isRightAlign]="true" 
[showCommonLabelWithSelection]="true"></filter-dropdown>
 </form>
 <div class="panel panel-default">
   <div class="panel-body">
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.ts
index deca936..fa5b1c5 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.ts
@@ -36,9 +36,7 @@ export class AuditLogsTableComponent extends 
LogsTableComponent {
 
   readonly timeFormat: string = 'YYYY-MM-DD HH:mm:ss,SSS';
 
-  get logsTypeMapObject(): object {
-    return this.logsContainer.logsTypeMap.auditLogs;
-  }
+  private readonly logsType: string = 'auditLogs';
 
   get filters(): any {
     return this.logsContainer.filters;
@@ -52,4 +50,8 @@ export class AuditLogsTableComponent extends 
LogsTableComponent {
     return this.columns.find((column: ListItem): boolean => column.value === 
name);
   }
 
+  updateSelectedColumns(columns: string[]): void {
+    this.logsContainer.updateSelectedColumns(columns, this.logsType);
+  }
+
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/context-menu/context-menu.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/context-menu/context-menu.component.spec.ts
index 1c5154b..1881f57 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/context-menu/context-menu.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/context-menu/context-menu.component.spec.ts
@@ -39,8 +39,8 @@ import {TabsService, tabs} from 
'@app/services/storage/tabs.service';
 import {ComponentGeneratorService} from 
'@app/services/component-generator.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {HttpClientService} from '@app/services/http-client.service';
-import {ComponentActionsService} from 
'@app/services/component-actions.service';
 import {AuthService} from '@app/services/auth.service';
+import {UtilsService} from '@app/services/utils.service';
 import {DropdownListComponent} from 
'@app/components/dropdown-list/dropdown-list.component';
 
 import {ContextMenuComponent} from './context-menu.component';
@@ -90,7 +90,6 @@ describe('ContextMenuComponent', () => {
           provide: HttpClientService,
           useValue: httpClient
         },
-        ComponentActionsService,
         HostsService,
         AuditLogsService,
         ServiceLogsService,
@@ -104,7 +103,8 @@ describe('ContextMenuComponent', () => {
         ComponentsService,
         ServiceLogsTruncatedService,
         TabsService,
-        AuthService
+        AuthService,
+        UtilsService
       ],
       schemas: [CUSTOM_ELEMENTS_SCHEMA]
     })
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.html
index d047d7a..396a277 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.html
@@ -16,16 +16,20 @@
 -->
 
 <div [ngClass]="{'dropup': isDropup}">
-  <button [ngClass]="['btn', 'btn-link', 'dropdown-toggle', buttonClass]" 
data-toggle="dropdown">
-    <span *ngIf="iconClass || label"
-          [ngClass]="{'filter-label': true, 'plain': !isMultipleChoice && 
!hideCaret && showSelectedValue}">
-      <span *ngIf="iconClass" [ngClass]="iconClass"></span>
-      <span *ngIf="label">{{label}}</span>
+  <button [ngClass]="['btn', 'dropdown-toggle', buttonClass]" 
data-toggle="dropdown">
+    <span class="filter-label">
+      <span *ngIf="iconClass || label" [class.plain]="!isMultipleChoice && 
!hideCaret && showSelectedValue">
+        <span *ngIf="iconClass" [ngClass]="iconClass"></span>
+        <span *ngIf="label && (!selection.length || isMultipleChoice || 
showCommonLabelWithSelection)"
+              [class.label-before-selection]="isSelectionDisplayable">
+          {{label}}
+        </span>
+      </span>
+      <span *ngIf="isSelectionDisplayable">{{selection[0].label | 
translate}}</span>
+      <span *ngIf="!hideCaret" class="caret"></span>
     </span>
-    <span *ngIf="showSelectedValue && !isMultipleChoice && 
selection.length">{{selection[0].label | translate}}</span>
-    <span *ngIf="!hideCaret" class="caret"></span>
   </button>
   <ul data-component="dropdown-list" [ngClass]="{'dropdown-menu': true, 
'dropdown-menu-right': isRightAlign}"
       [items]="options" [isMultipleChoice]="isMultipleChoice" 
(selectedItemChange)="updateSelection($event)"
-      [actionArguments]="additionalArgs"></ul>
+      [actionArguments]="listItemArguments"></ul>
 </div>
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.less
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.less
index 7b560c1..06861722 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.less
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.less
@@ -25,11 +25,13 @@
     text-transform: none;
 
     .filter-label {
-      padding: @input-group-addon-padding;
-
-      &.plain {
+      .plain {
         color: initial;
       }
+
+      .label-before-selection {
+        padding: @input-group-addon-padding;
+      }
     }
   }
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts
index b9f9540..8a72c38 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts
@@ -16,11 +16,10 @@
  * limitations under the License.
  */
 
-import {NO_ERRORS_SCHEMA, Injector} from '@angular/core';
-import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
+import {NO_ERRORS_SCHEMA} from '@angular/core';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
 import {TranslationModules} from '@app/test-config.spec';
 import {StoreModule} from '@ngrx/store';
-import {ServiceInjector} from '@app/classes/service-injector';
 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';
@@ -37,7 +36,6 @@ import {
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from 
'@app/services/storage/service-logs-truncated.service';
 import {TabsService, tabs} from '@app/services/storage/tabs.service';
 import {UtilsService} from '@app/services/utils.service';
-import {ComponentActionsService} from 
'@app/services/component-actions.service';
 import {HttpClientService} from '@app/services/http-client.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {AuthService} from '@app/services/auth.service';
@@ -92,7 +90,6 @@ describe('DropdownButtonComponent', () => {
         ServiceLogsTruncatedService,
         TabsService,
         UtilsService,
-        ComponentActionsService,
         {
           provide: HttpClientService,
           useValue: httpClient
@@ -105,12 +102,11 @@ describe('DropdownButtonComponent', () => {
     .compileComponents();
   }));
 
-  beforeEach(inject([Injector], (injector: Injector) => {
-    ServiceInjector.injector = injector;
+  beforeEach(() => {
     fixture = TestBed.createComponent(DropdownButtonComponent);
     component = fixture.componentInstance;
     fixture.detectChanges();
-  }));
+  });
 
   it('should create component', () => {
     expect(component).toBeTruthy();
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.ts
index ead9e1a..b642dd5 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.ts
@@ -16,10 +16,8 @@
  * limitations under the License.
  */
 
-import {Component, Input} from '@angular/core';
+import {Component, Input, Output, EventEmitter} from '@angular/core';
 import {ListItem} from '@app/classes/list-item';
-import {ServiceInjector} from '@app/classes/service-injector';
-import {ComponentActionsService} from 
'@app/services/component-actions.service';
 import {UtilsService} from '@app/services/utils.service';
 
 @Component({
@@ -30,14 +28,13 @@ import {UtilsService} from '@app/services/utils.service';
 export class DropdownButtonComponent {
 
   constructor(protected utils: UtilsService) {
-    this.actions = ServiceInjector.injector.get(ComponentActionsService);
   }
-  
+
   @Input()
   label?: string;
 
   @Input()
-  buttonClass: string = '';
+  buttonClass: string = 'btn-link';
 
   @Input()
   iconClass?: string;
@@ -52,10 +49,7 @@ export class DropdownButtonComponent {
   options: ListItem[] = [];
 
   @Input()
-  action?: string;
-
-  @Input()
-  additionalArgs: any[] = [];
+  listItemArguments: any[] = [];
 
   @Input()
   isMultipleChoice: boolean = false;
@@ -66,7 +60,11 @@ export class DropdownButtonComponent {
   @Input()
   isDropup: boolean = false;
 
-  private actions: ComponentActionsService;
+  @Input()
+  showCommonLabelWithSelection: boolean = false;
+
+  @Output()
+  selectItem: EventEmitter<any> = new EventEmitter();
 
   protected selectedItems?: ListItem[] = [];
 
@@ -78,22 +76,28 @@ export class DropdownButtonComponent {
     this.selectedItems = items;
   }
 
+  // TODO handle case of selections with multiple items
+  /**
+   * Indicates whether selection can be displayed at the moment, i.e. it's not 
empty, not multiple
+   * and set to be displayed by showSelectedValue flag
+   * @returns {boolean}
+   */
+  get isSelectionDisplayable():boolean {
+    return this.showSelectedValue && !this.isMultipleChoice && 
this.selection.length > 0;
+  }
+
   updateSelection(item: ListItem): void {
-    const action = this.action && this.actions[this.action];
+    const hasAction = this.selectItem.observers.length;
     if (this.isMultipleChoice) {
       this.options.find((option: ListItem): boolean => {
         return this.utils.isEqual(option.value, item.value);
       }).isChecked = item.isChecked;
       const checkedItems = this.options.filter((option: ListItem): boolean => 
option.isChecked);
       this.selection = checkedItems;
-      if (action) {
-        action(checkedItems.map((option: ListItem): any => option.value), 
...this.additionalArgs);
-      }
+      this.selectItem.emit(checkedItems.map((option: ListItem): any => 
option.value));
     } else if (!this.utils.isEqual(this.selection[0], item)) {
       this.selection = [item];
-      if (action) {
-        action(item.value, ...this.additionalArgs);
-      }
+      this.selectItem.emit(item.value);
     }
   }
 
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.html
index 64e4b8e..9861c24 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.html
@@ -15,19 +15,21 @@
   limitations under the License.
 -->
 
-<li *ngFor="let item of items">
-  <label class="list-item-label" *ngIf="isMultipleChoice">
-    <input type="checkbox" [attr.id]="item.id || item.value" 
[(ngModel)]="item.isChecked"
-           (change)="changeSelectedItem({value: item.value, isChecked: 
$event.currentTarget.checked})">
-    <label [attr.for]="item.id || item.value" class="label-container">
+<li *ngFor="let item of items" [class.divider]="item.isDivider" 
[attr.role]="item.isDivider ? 'separator' : null">
+  <ng-container *ngIf="!item.isDivider">
+    <label class="list-item-label" *ngIf="isMultipleChoice">
+      <input type="checkbox" [attr.id]="item.id || item.value" 
[(ngModel)]="item.isChecked"
+             (change)="changeSelectedItem({value: item.value, isChecked: 
$event.currentTarget.checked})">
+      <label [attr.for]="item.id || item.value" class="label-container">
+        <span *ngIf="item.iconClass" [ngClass]="item.iconClass"></span>
+        {{item.label | translate}}
+        <span #additionalComponent></span>
+      </label>
+    </label>
+    <span class="list-item-label label-container" *ngIf="!isMultipleChoice" 
(click)="changeSelectedItem(item)">
       <span *ngIf="item.iconClass" [ngClass]="item.iconClass"></span>
       {{item.label | translate}}
-      <div #additionalComponent></div>
-    </label>
-  </label>
-  <span class="list-item-label label-container" *ngIf="!isMultipleChoice" 
(click)="changeSelectedItem(item)">
-    <span *ngIf="item.iconClass" [ngClass]="item.iconClass"></span>
-    {{item.label | translate}}
-    <div #additionalComponent></div>
-  </span>
+      <span #additionalComponent></span>
+    </span>
+  </ng-container>
 </li>
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts
index dd602d7..6d88010 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts
@@ -38,8 +38,8 @@ import {TabsService, tabs} from 
'@app/services/storage/tabs.service';
 import {ComponentGeneratorService} from 
'@app/services/component-generator.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {HttpClientService} from '@app/services/http-client.service';
-import {ComponentActionsService} from 
'@app/services/component-actions.service';
 import {AuthService} from '@app/services/auth.service';
+import {UtilsService} from '@app/services/utils.service';
 
 import {DropdownListComponent} from './dropdown-list.component';
 
@@ -84,7 +84,6 @@ describe('DropdownListComponent', () => {
           provide: HttpClientService,
           useValue: httpClient
         },
-        ComponentActionsService,
         HostsService,
         AuditLogsService,
         ServiceLogsService,
@@ -98,7 +97,8 @@ describe('DropdownListComponent', () => {
         ComponentsService,
         ServiceLogsTruncatedService,
         TabsService,
-        AuthService
+        AuthService,
+        UtilsService
       ]
     })
     .compileComponents();
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.ts
index ef185d0..5d0ad4a 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.ts
@@ -16,33 +16,37 @@
  * limitations under the License.
  */
 
-import {Component, AfterViewInit, Input, Output, EventEmitter, ViewChildren, 
ViewContainerRef, QueryList} from '@angular/core';
+import {
+  Component, OnChanges, AfterViewChecked, SimpleChanges, Input, Output, 
EventEmitter, ViewChildren, ViewContainerRef,
+  QueryList
+} from '@angular/core';
 import {ListItem} from '@app/classes/list-item';
 import {ComponentGeneratorService} from 
'@app/services/component-generator.service';
-import {ComponentActionsService} from 
'@app/services/component-actions.service';
 
 @Component({
   selector: 'ul[data-component="dropdown-list"]',
   templateUrl: './dropdown-list.component.html',
   styleUrls: ['./dropdown-list.component.less']
 })
-export class DropdownListComponent implements AfterViewInit {
+export class DropdownListComponent implements OnChanges, AfterViewChecked {
 
-  constructor(private componentGenerator: ComponentGeneratorService, private 
actions: ComponentActionsService) {
+  constructor(private componentGenerator: ComponentGeneratorService) {
   }
 
-  ngAfterViewInit() {
-    const setter = this.additionalLabelComponentSetter;
-    if (setter) {
-      this.containers.forEach((container, index) => 
this.componentGenerator[setter](this.items[index].value, container));
+  private shouldRenderAdditionalComponents: boolean = false;
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes.hasOwnProperty('items')) {
+      this.shouldRenderAdditionalComponents = true;
     }
   }
 
-  @Input()
-  items: ListItem[];
+  ngAfterViewChecked() {
+    this.renderAdditionalComponents();
+  }
 
   @Input()
-  defaultAction: Function;
+  items: ListItem[];
 
   @Input()
   isMultipleChoice?: boolean = false;
@@ -61,9 +65,18 @@ export class DropdownListComponent implements AfterViewInit {
   })
   containers: QueryList<ViewContainerRef>;
 
+  private renderAdditionalComponents(): void {
+    const setter = this.additionalLabelComponentSetter,
+      containers = this.containers;
+    if (this.shouldRenderAdditionalComponents && setter && containers) {
+      containers.forEach((container, index) => 
this.componentGenerator[setter](this.items[index].value, container));
+      this.shouldRenderAdditionalComponents = false;
+    }
+  }
+
   changeSelectedItem(options: ListItem): void {
-    if (options.action) {
-      this.actions[options.action](...this.actionArguments);
+    if (options.onSelect) {
+      options.onSelect(...this.actionArguments);
     }
     this.selectedItemChange.emit(options);
   }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts
index 4c93cbe..87c490c 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts
@@ -16,11 +16,10 @@
  * limitations under the License.
  */
 
-import {NO_ERRORS_SCHEMA, Injector} from '@angular/core';
-import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
+import {NO_ERRORS_SCHEMA} from '@angular/core';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
 import {TranslationModules} from '@app/test-config.spec';
 import {StoreModule} from '@ngrx/store';
-import {ServiceInjector} from '@app/classes/service-injector';
 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';
@@ -36,7 +35,6 @@ import {
 } from '@app/services/storage/service-logs-histogram-data.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from 
'@app/services/storage/service-logs-truncated.service';
 import {TabsService, tabs} from '@app/services/storage/tabs.service';
-import {ComponentActionsService} from 
'@app/services/component-actions.service';
 import {UtilsService} from '@app/services/utils.service';
 import {HttpClientService} from '@app/services/http-client.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
@@ -91,7 +89,6 @@ describe('FilterButtonComponent', () => {
         ServiceLogsHistogramDataService,
         ServiceLogsTruncatedService,
         TabsService,
-        ComponentActionsService,
         UtilsService,
         {
           provide: HttpClientService,
@@ -105,12 +102,11 @@ describe('FilterButtonComponent', () => {
     .compileComponents();
   }));
 
-  beforeEach(inject([Injector], (injector: Injector) => {
-    ServiceInjector.injector = injector;
+  beforeEach(() => {
     fixture = TestBed.createComponent(FilterButtonComponent);
     component = fixture.componentInstance;
     fixture.detectChanges();
-  }));
+  });
 
   it('should create component', () => {
     expect(component).toBeTruthy();
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts
index 61d30d1..e3105c1 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts
@@ -15,11 +15,10 @@
  * limitations under the License.
  */
 
-import {NO_ERRORS_SCHEMA, Injector} from '@angular/core';
-import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
+import {NO_ERRORS_SCHEMA} from '@angular/core';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
 import {TranslationModules} from '@app/test-config.spec';
 import {StoreModule} from '@ngrx/store';
-import {ServiceInjector} from '@app/classes/service-injector';
 import {AppSettingsService, appSettings} from 
'@app/services/storage/app-settings.service';
 import {AppStateService, appState} from 
'@app/services/storage/app-state.service';
 import {AuditLogsService, auditLogs} from 
'@app/services/storage/audit-logs.service';
@@ -36,7 +35,6 @@ import {ClustersService, clusters} from 
'@app/services/storage/clusters.service'
 import {ComponentsService, components} from 
'@app/services/storage/components.service';
 import {HostsService, hosts} from '@app/services/storage/hosts.service';
 import {UtilsService} from '@app/services/utils.service';
-import {ComponentActionsService} from 
'@app/services/component-actions.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {HttpClientService} from '@app/services/http-client.service';
 import {AuthService} from '@app/services/auth.service';
@@ -111,7 +109,6 @@ describe('FilterDropdownComponent', () => {
           useValue: filtering
         },
         UtilsService,
-        ComponentActionsService,
         LogsContainerService,
         {
           provide: HttpClientService,
@@ -124,12 +121,11 @@ describe('FilterDropdownComponent', () => {
     .compileComponents();
   }));
 
-  beforeEach(inject([Injector], (injector: Injector) => {
-    ServiceInjector.injector = injector;
+  beforeEach(() => {
     fixture = TestBed.createComponent(FilterDropdownComponent);
     component = fixture.componentInstance;
     fixture.detectChanges();
-  }));
+  });
 
   it('should create component', () => {
     expect(component).toBeTruthy();
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.ts
index 665386b..b85c572 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.ts
@@ -17,7 +17,6 @@
 
 import {Component, forwardRef} from '@angular/core';
 import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
-import {UtilsService} from '@app/services/utils.service';
 import {DropdownButtonComponent} from 
'@app/components/dropdown-button/dropdown-button.component';
 import {ListItem} from '@app/classes/list-item';
 
@@ -35,10 +34,6 @@ import {ListItem} from '@app/classes/list-item';
 })
 export class FilterDropdownComponent extends DropdownButtonComponent 
implements ControlValueAccessor {
 
-  constructor(protected utils: UtilsService) {
-    super(utils);
-  }
-
   private onChange: (fn: any) => void;
 
   get selection(): ListItem[] {
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
index f0cf3f4..51233ff 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
@@ -29,9 +29,9 @@
     </button>
   </div>
   <div class="filter-buttons col-md-4">
-    <dropdown-button [options]="searchBoxItems | async" iconClass="fa 
fa-search-minus" action="proceedWithExclude"
-                     label="{{'filter.exclude' | translate}}" 
[hideCaret]="true"
-                     [showSelectedValue]="false"></dropdown-button>
+    <dropdown-button iconClass="fa fa-search-minus" label="{{'filter.exclude' 
| translate}}" [hideCaret]="true"
+                     [showSelectedValue]="false" 
[showCommonLabelWithSelection]="true"
+                     [options]="searchBoxItems | async" 
(selectItem)="proceedWithExclude($event)"></dropdown-button>
     <filter-button *ngIf="isFilterConditionDisplayed('hosts')" 
formControlName="hosts"
                    label="{{filters.hosts.label | translate}}" 
[iconClass]="filters.hosts.iconClass"
                    [subItems]="filters.hosts.options" 
[isMultipleChoice]="true" [isRightAlign]="true"
@@ -44,8 +44,8 @@
                    label="{{filters.levels.label | translate}}" 
[iconClass]="filters.levels.iconClass"
                    [subItems]="filters.levels.options" 
[isMultipleChoice]="true" [isRightAlign]="true"></filter-button>
     <menu-button *ngIf="!captureSeconds" label="{{'filter.capture' | 
translate}}" iconClass="fa fa-caret-right"
-                 action="startCapture"></menu-button>
+                 (buttonClick)="startCapture()"></menu-button>
     <menu-button *ngIf="captureSeconds" label="{{captureSeconds | 
timerSeconds}}" iconClass="fa fa-stop stop-icon"
-                 action="stopCapture"></menu-button>
+                 (buttonClick)="stopCapture()"></menu-button>
   </div>
 </form>
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts
index cd372ec..fe60671 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts
@@ -109,4 +109,21 @@ export class FiltersPanelComponent implements OnChanges {
     this.searchBoxValueUpdate.next();
   }
 
+  startCapture(): void {
+    this.logsContainer.startCaptureTimer();
+  }
+
+  stopCapture(): void {
+    this.logsContainer.stopCaptureTimer();
+  }
+
+  proceedWithExclude(item: string): void {
+    this.queryParameterNameChange.next({
+      item: {
+        value: item
+      },
+      isExclude: true
+    });
+  }
+
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/history-item-controls/history-item-controls.component.html
similarity index 71%
copy from 
ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
copy to 
ambari-logsearch/ambari-logsearch-web/src/app/components/history-item-controls/history-item-controls.component.html
index ab6326a..e6978f1 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/history-item-controls/history-item-controls.component.html
@@ -14,7 +14,7 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<menu-button *ngFor="let item of items" label="{{item.label | translate}}" 
[action]="item.action"
-             [iconClass]="item.iconClass" [labelClass]="item.labelClass" 
[subItems]="item.subItems"
-             [hideCaret]="item.hideCaret" [badge]="item.badge" 
[isRightAlign]="item.isRightAlign">
-</menu-button>
+
+<!-- TODO implement View details and Save filter actions -->
+<span class="fa fa-info-circle"></span>
+<span class="fa fa-floppy-o"></span>
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/history-item-controls/history-item-controls.component.less
similarity index 85%
copy from ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts
copy to 
ambari-logsearch/ambari-logsearch-web/src/app/components/history-item-controls/history-item-controls.component.less
index 1aaaecc..dfb9997 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/history-item-controls/history-item-controls.component.less
@@ -16,11 +16,6 @@
  * limitations under the License.
  */
 
-export interface ListItem {
-  id?: string;
-  label?: string;
-  value: any;
-  iconClass?: string;
-  isChecked?: boolean;
-  action?: string;
+:host {
+  float: right;
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/history-item-controls/history-item-controls.component.spec.ts
similarity index 70%
copy from 
ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
copy to 
ambari-logsearch/ambari-logsearch-web/src/app/components/history-item-controls/history-item-controls.component.spec.ts
index 081304e..4dbaa2d 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/history-item-controls/history-item-controls.component.spec.ts
@@ -16,27 +16,23 @@
  * limitations under the License.
  */
 
-import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
 import {async, ComponentFixture, TestBed} from '@angular/core/testing';
-import {TranslationModules} from '@app/test-config.spec';
 
-import {ActionMenuComponent} from './action-menu.component';
+import {HistoryItemControlsComponent} from './history-item-controls.component';
 
-describe('ActionMenuComponent', () => {
-  let component: ActionMenuComponent;
-  let fixture: ComponentFixture<ActionMenuComponent>;
+describe('HistoryItemControlsComponent', () => {
+  let component: HistoryItemControlsComponent;
+  let fixture: ComponentFixture<HistoryItemControlsComponent>;
 
   beforeEach(async(() => {
     TestBed.configureTestingModule({
-      imports: TranslationModules,
-      declarations: [ActionMenuComponent],
-      schemas: [CUSTOM_ELEMENTS_SCHEMA]
+      declarations: [HistoryItemControlsComponent]
     })
     .compileComponents();
   }));
 
   beforeEach(() => {
-    fixture = TestBed.createComponent(ActionMenuComponent);
+    fixture = TestBed.createComponent(HistoryItemControlsComponent);
     component = fixture.componentInstance;
     fixture.detectChanges();
   });
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/history-item-controls/history-item-controls.component.ts
similarity index 72%
copy from ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts
copy to 
ambari-logsearch/ambari-logsearch-web/src/app/components/history-item-controls/history-item-controls.component.ts
index 1aaaecc..1975d9a 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/history-item-controls/history-item-controls.component.ts
@@ -16,11 +16,13 @@
  * limitations under the License.
  */
 
-export interface ListItem {
-  id?: string;
-  label?: string;
-  value: any;
-  iconClass?: string;
-  isChecked?: boolean;
-  action?: string;
+import {Component} from '@angular/core';
+
+@Component({
+  selector: 'history-item-controls',
+  templateUrl: './history-item-controls.component.html',
+  styleUrls: ['./history-item-controls.component.less']
+})
+export class HistoryItemControlsComponent {
+  // TODO implement View details and Save filter actions
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts
index c346c9a..6e6a70e 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts
@@ -37,6 +37,7 @@ import {TranslationModules} from '@app/test-config.spec';
 import {ModalComponent} from '@app/components/modal/modal.component';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {HttpClientService} from '@app/services/http-client.service';
+import {UtilsService} from '@app/services/utils.service';
 
 import {LogContextComponent} from './log-context.component';
 
@@ -94,7 +95,8 @@ describe('LogContextComponent', () => {
         {
           provide: HttpClientService,
           useValue: httpClient
-        }
+        },
+        UtilsService
       ],
       schemas: [CUSTOM_ELEMENTS_SCHEMA]
     })
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html
index 5e2b15f..8e67b5d 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html
@@ -23,7 +23,9 @@
     <i *ngIf="hasCaret" [ngClass]="['fa ', caretClass ]"></i>
     <span *ngIf="label" class="menu-button-label">{{label}}</span>
   </a>
-  <ul data-component="dropdown-list" *ngIf="hasSubItems" [items]="subItems" 
(selectedItemChange)="onDropdownItemChange($event)"
-      [isMultipleChoice]="isMultipleChoice" 
[additionalLabelComponentSetter]="additionalLabelComponentSetter"
-      [ngClass]="{'dropdown-menu': true, 'dropdown-menu-right': 
isRightAlign}"></ul>
+  <ul data-component="dropdown-list" *ngIf="hasSubItems" [items]="subItems"
+      (selectedItemChange)="onDropdownItemChange($event)" 
[isMultipleChoice]="isMultipleChoice"
+      [additionalLabelComponentSetter]="additionalLabelComponentSetter"
+      [ngClass]="'dropdown-menu' + (isRightAlign ? ' dropdown-menu-right' : 
'') + (listClass ? ' ' + listClass : '')"
+  ></ul>
 </div>
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
index 3b210a3..4e77db5 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
@@ -16,10 +16,9 @@
  * limitations under the License.
  */
 
-import {NO_ERRORS_SCHEMA, Injector} from '@angular/core';
-import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
+import {NO_ERRORS_SCHEMA} from '@angular/core';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
 import {TranslationModules} from '@app/test-config.spec';
-import {ServiceInjector} from '@app/classes/service-injector';
 import {StoreModule} from '@ngrx/store';
 import {AppSettingsService, appSettings} from 
'@app/services/storage/app-settings.service';
 import {AppStateService, appState} from 
'@app/services/storage/app-state.service';
@@ -36,7 +35,6 @@ import {
 } from '@app/services/storage/service-logs-histogram-data.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from 
'@app/services/storage/service-logs-truncated.service';
 import {TabsService, tabs} from '@app/services/storage/tabs.service';
-import {ComponentActionsService} from 
'@app/services/component-actions.service';
 import {HttpClientService} from '@app/services/http-client.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {AuthService} from '@app/services/auth.service';
@@ -90,7 +88,6 @@ describe('MenuButtonComponent', () => {
         ServiceLogsHistogramDataService,
         ServiceLogsTruncatedService,
         TabsService,
-        ComponentActionsService,
         {
           provide: HttpClientService,
           useValue: httpClient
@@ -103,12 +100,11 @@ describe('MenuButtonComponent', () => {
     .compileComponents();
   }));
 
-  beforeEach(inject([Injector], (injector: Injector) => {
-    ServiceInjector.injector = injector;
+  beforeEach(() => {
     fixture = TestBed.createComponent(MenuButtonComponent);
     component = fixture.componentInstance;
     fixture.detectChanges();
-  }));
+  });
 
   it('should create component', () => {
     expect(component).toBeTruthy();
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
index 12da4ac..432b561 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
@@ -16,10 +16,8 @@
  * limitations under the License.
  */
 
-import {Component, Input, ViewChild, ElementRef} from '@angular/core';
+import {Component, Input, Output, ViewChild, ElementRef, EventEmitter} from 
'@angular/core';
 import {ListItem} from '@app/classes/list-item';
-import {ServiceInjector} from '@app/classes/service-injector';
-import {ComponentActionsService} from 
'@app/services/component-actions.service';
 
 @Component({
   selector: 'menu-button',
@@ -28,10 +26,6 @@ import {ComponentActionsService} from 
'@app/services/component-actions.service';
 })
 export class MenuButtonComponent {
 
-  constructor() {
-    this.actions = ServiceInjector.injector.get(ComponentActionsService);
-  }
-
   @ViewChild('dropdown')
   dropdown: ElementRef;
 
@@ -39,9 +33,6 @@ export class MenuButtonComponent {
   label?: string;
 
   @Input()
-  action: string;
-
-  @Input()
   iconClass: string;
 
   @Input()
@@ -84,7 +75,14 @@ export class MenuButtonComponent {
   @Input()
   maxLongClickDelay: number = 0;
 
-  private actions: ComponentActionsService;
+  @Input()
+  listClass: string = '';
+
+  @Output()
+  buttonClick: EventEmitter<void> = new EventEmitter();
+
+  @Output()
+  selectItem: EventEmitter<ListItem> = new EventEmitter();
 
   /**
    * This is a private property to indicate the mousedown timestamp, so that 
we can check it when teh click event
@@ -122,14 +120,14 @@ export class MenuButtonComponent {
       !this.maxLongClickDelay || mdt + this.maxLongClickDelay >= now
     );
     let openDropdown = this.hasSubItems && (
-      el.classList.contains(this.caretClass) || isLongClick || 
!this.actions[this.action]
+      el.classList.contains(this.caretClass) || isLongClick || 
!this.buttonClick.observers.length
     );
     if (openDropdown && this.dropdown) {
       if (this.toggleDropdown()) {
         this.listenToClickOut();
       }
-    } else if (this.action) {
-      this.actions[this.action]();
+    } else if (this.buttonClick.observers.length) {
+      this.buttonClick.emit();
     }
     this.mouseDownTimestamp = 0;
     event.preventDefault();
@@ -211,7 +209,7 @@ export class MenuButtonComponent {
   }
 
   updateSelection(options: ListItem) {
-    // TODO implement value change behaviour
+    this.selectItem.emit(options);
   }
 
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html
index 02ed84b..ecfa75d 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html
@@ -17,7 +17,8 @@
 
 <form class="pagination-form" [formGroup]="filtersForm">
   <filter-dropdown label="{{filterInstance.label | translate}}" 
formControlName="pageSize"
-                   [options]="filterInstance.options" [isRightAlign]="true" 
[isDropup]="true"></filter-dropdown>
+                   [options]="filterInstance.options" [isRightAlign]="true" 
[isDropup]="true"
+                   [showCommonLabelWithSelection]="true"></filter-dropdown>
   <span>{{'pagination.numbers' | translate: numbersTranslateParams}}</span>
   <pagination-controls formControlName="page" [totalCount]="totalCount" 
[pagesCount]="pagesCount"
                        
(currentPageChange)="setCurrentPage($event)"></pagination-controls>
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts
index 9dd8e26..ade57e5 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts
@@ -278,7 +278,7 @@ export class SearchBoxComponent implements OnInit, 
OnDestroy, ControlValueAccess
   updateValue = (): void => {
     this.currentValue = '';
     if (this.onChange) {
-      this.onChange(this.parameters);
+      this.onChange(this.parameters.slice());
     }
   };
 
@@ -299,7 +299,7 @@ export class SearchBoxComponent implements OnInit, 
OnDestroy, ControlValueAccess
   }
 
   writeValue(parameters: SearchBoxParameterProcessed[] = []): void {
-    this.parameters = parameters;
+    this.parameters = parameters.slice();
     this.updateValueSubject.next();
   }
 
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.html
index 3cd829e..3110d9a 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.html
@@ -23,12 +23,13 @@
         {{'logs.brokenListLayoutMessage' | translate}}
       </div>
       <form *ngIf="logs && logs.length" [formGroup]="filtersForm">
-        <filter-dropdown label="{{filters.serviceLogsSorting.label | 
translate}}"
-                         formControlName="serviceLogsSorting" 
[options]="filters.serviceLogsSorting.options" [isRightAlign]="true">
-        </filter-dropdown>
+        <filter-dropdown formControlName="serviceLogsSorting" 
label="{{filters.serviceLogsSorting.label | translate}}"
+                         [showCommonLabelWithSelection]="true" 
[options]="filters.serviceLogsSorting.options"
+                         [isRightAlign]="true"></filter-dropdown>
       </form>
       <dropdown-button label="{{'logs.columns' | translate}}" 
[options]="columns" [isRightAlign]="true"
-                       [isMultipleChoice]="true" 
action="updateSelectedColumns" [additionalArgs]="logsTypeMapObject.fieldsModel">
+                       [isMultipleChoice]="true" 
(selectItem)="updateSelectedColumns($event)"
+                       [listItemArguments]="logsTypeMapObject.fieldsModel">
       </dropdown-button>
       <div class="layout-btn-group">
         <a *ngIf="layout==='FLEX'" class="btn" (click)="toggleShowLabels()" 
tooltip="{{'logs.toggleLabels' | translate}}">
@@ -67,7 +68,7 @@
             <tr class="log-item-row">
               <td class="log-action">
                 <dropdown-button iconClass="fa fa-ellipsis-v action" 
[hideCaret]="true" [options]="logActions"
-                                 [additionalArgs]="[log]" 
[showSelectedValue]="false"></dropdown-button>
+                                 [listItemArguments]="[log]" 
[showSelectedValue]="false"></dropdown-button>
               </td>
               <td *ngIf="isColumnDisplayed('logtime')" class="log-time">
                 <time>
@@ -115,7 +116,7 @@
             <div class="log-header">
               <div class="log-action">
                 <dropdown-button iconClass="fa fa-ellipsis-v action" 
[hideCaret]="true" [options]="logActions"
-                                 [additionalArgs]="[log]" 
[showSelectedValue]="false"></dropdown-button>
+                                 [listItemArguments]="[log]" 
[showSelectedValue]="false"></dropdown-button>
               </div>
               <div *ngIf="isColumnDisplayed('level')" [ngClass]="'log-level ' 
+ log.level.toLowerCase()">
                 <log-level [logEntry]="log"></log-level>
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts
index e883c99..e2afbc8 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts
@@ -43,7 +43,6 @@ import {LogsContainerService} from 
'@app/services/logs-container.service';
 import {UtilsService} from '@app/services/utils.service';
 import {HttpClientService} from '@app/services/http-client.service';
 import {ComponentGeneratorService} from 
'@app/services/component-generator.service';
-import {ComponentActionsService} from 
'@app/services/component-actions.service';
 import {AuthService} from '@app/services/auth.service';
 import {PaginationComponent} from 
'@app/components/pagination/pagination.component';
 import {DropdownListComponent} from 
'@app/components/dropdown-list/dropdown-list.component';
@@ -113,7 +112,6 @@ describe('ServiceLogsTableComponent', () => {
         ComponentsService,
         HostsService,
         ComponentGeneratorService,
-        ComponentActionsService,
         AuthService
       ],
       schemas: [CUSTOM_ELEMENTS_SCHEMA]
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts
index 681149d..ee19e1c 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts
@@ -16,10 +16,11 @@
  * limitations under the License.
  */
 
-import {Component, AfterViewInit, AfterViewChecked, ViewChild, ElementRef, 
Input, ChangeDetectorRef} from '@angular/core';
+import {Component, AfterViewChecked, ViewChild, ElementRef, Input, 
ChangeDetectorRef} from '@angular/core';
 
 import {ListItem} from '@app/classes/list-item';
 import {LogsTableComponent} from 
'@app/classes/components/logs-table/logs-table-component';
+import {ServiceLog} from '@app/classes/models/service-log';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {UtilsService} from '@app/services/utils.service';
 
@@ -100,53 +101,71 @@ export class ServiceLogsTableComponent extends 
LogsTableComponent implements Aft
 
   readonly timeFormat: string = 'h:mm:ss A';
 
+  private copyLog = (log: ServiceLog): void => {
+    if (document.queryCommandSupported('copy')) {
+      const text = log.log_message,
+        node = document.createElement('textarea');
+      node.value = text;
+      Object.assign(node.style, {
+        position: 'fixed',
+        top: '0',
+        left: '0',
+        width: '1px',
+        height: '1px',
+        border: 'none',
+        outline: 'none',
+        boxShadow: 'none',
+        backgroundColor: 'transparent',
+        padding: '0'
+      });
+      document.body.appendChild(node);
+      node.select();
+      if (document.queryCommandEnabled('copy')) {
+        document.execCommand('copy');
+      } else {
+        // TODO open failed alert
+      }
+      // TODO success alert
+      document.body.removeChild(node);
+    } else {
+      // TODO failed alert
+    }
+  };
+
+  private openLog = (log: ServiceLog): void => {
+    this.logsContainer.openServiceLog(log);
+  };
+
+  private openContext = (log: ServiceLog): void => {
+    this.logsContainer.loadLogContext(log.id, log.host, log.type);
+  };
+
   readonly logActions = [
     {
       label: 'logs.copy',
       iconClass: 'fa fa-files-o',
-      action: 'copyLog'
+      onSelect: this.copyLog
     },
     {
       label: 'logs.open',
       iconClass: 'fa fa-external-link',
-      action: 'openLog'
+      onSelect: this.openLog
     },
     {
       label: 'logs.context',
       iconClass: 'fa fa-crosshairs',
-      action: 'openContext'
+      onSelect: this.openContext
     }
   ];
 
   readonly customStyledColumns: string[] = ['level', 'type', 'logtime', 
'log_message', 'path'];
 
-  get contextMenuItems(): ListItem[] {
-    return this.logsContainer.queryContextMenuItems;
-  }
-
   private readonly messageFilterParameterName: string = 'log_message';
 
-  /**
-   * The goal is to show or hide the context menu on right click.
-   * @type {boolean}
-   */
-  private isContextMenuDisplayed: boolean = false;
-
-  /**
-   * 'left' CSS property value for context menu dropdown
-   * @type {number}
-   */
-  private contextMenuLeft: number = 0;
-
-  /**
-   * 'top' CSS property value for context menu dropdown
-   * @type {number}
-   */
-  private contextMenuTop:number = 0;
+  private readonly logsType: string = 'serviceLogs';
 
   private selectedText: string = '';
 
-
   /**
    * This is a private flag to store the table layout check result. It is used 
to show user notifications about
    * non-visible information.
@@ -154,6 +173,10 @@ export class ServiceLogsTableComponent extends 
LogsTableComponent implements Aft
    */
   private tooManyColumnsSelected: boolean = false;
 
+  get contextMenuItems(): ListItem[] {
+    return this.logsContainer.queryContextMenuItems;
+  }
+
   get timeZone(): string {
     return this.logsContainer.timeZone;
   }
@@ -166,6 +189,22 @@ export class ServiceLogsTableComponent extends 
LogsTableComponent implements Aft
     return this.logsContainer.logsTypeMap.serviceLogs;
   }
 
+  get isContextMenuDisplayed(): boolean {
+    return Boolean(this.selectedText);
+  };
+
+  /**
+   * 'left' CSS property value for context menu dropdown
+   * @type {number}
+   */
+  contextMenuLeft: number = 0;
+
+  /**
+   * 'top' CSS property value for context menu dropdown
+   * @type {number}
+   */
+  contextMenuTop: number = 0;
+
   isDifferentDates(dateA, dateB): boolean {
     return this.utils.isDifferentDates(dateA, dateB, this.timeZone);
   }
@@ -173,7 +212,6 @@ export class ServiceLogsTableComponent extends 
LogsTableComponent implements Aft
   openMessageContextMenu(event: MouseEvent): void {
     const selectedText = getSelection().toString();
     if (selectedText) {
-      this.isContextMenuDisplayed = true;
       this.contextMenuLeft = event.clientX;
       this.contextMenuTop = event.clientY;
       this.selectedText = selectedText;
@@ -192,8 +230,7 @@ export class ServiceLogsTableComponent extends 
LogsTableComponent implements Aft
   /**
    * Handle the event when the contextual menu component hide itself.
    */
-  private onContextMenuDismiss = (): void => {
-    this.isContextMenuDisplayed = false;
+  onContextMenuDismiss(): void {
     this.selectedText = '';
   };
 
@@ -264,4 +301,8 @@ export class ServiceLogsTableComponent extends 
LogsTableComponent implements Aft
     this.showLabels = !this.showLabels;
   }
 
+  updateSelectedColumns(columns: string[]): void {
+    this.logsContainer.updateSelectedColumns(columns, this.logsType);
+  }
+
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts
index e3034b0..b41760f 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts
@@ -37,6 +37,7 @@ import {ServiceLogsTruncatedService, serviceLogsTruncated} 
from '@app/services/s
 import {TabsService, tabs} from '@app/services/storage/tabs.service';
 import {HttpClientService} from '@app/services/http-client.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
+import {UtilsService} from '@app/services/utils.service';
 
 import {TimeRangePickerComponent} from './time-range-picker.component';
 
@@ -79,6 +80,7 @@ describe('TimeRangePickerComponent', () => {
           useValue: httpClient
         },
         LogsContainerService,
+        UtilsService,
         AppSettingsService,
         AppStateService,
         ClustersService,
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts
index 74a2b2d..6c71a26 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts
@@ -79,7 +79,7 @@ export class TimeRangePickerComponent implements 
ControlValueAccessor {
 
   setCustomTimeRange(): void {
     this.selection = {
-      label: 'filter.timeRange.custom',
+      label: this.logsContainer.customTimeRangeKey,
       value: {
         type: 'CUSTOM',
         start: this.startTime,
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts
index 1772ec0..3d79b46 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts
@@ -34,7 +34,6 @@ import {
 } from '@app/services/storage/service-logs-histogram-data.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from 
'@app/services/storage/service-logs-truncated.service';
 import {TabsService, tabs} from '@app/services/storage/tabs.service';
-import {ComponentActionsService} from 
'@app/services/component-actions.service';
 import {HttpClientService} from '@app/services/http-client.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {AuthService} from '@app/services/auth.service';
@@ -94,7 +93,6 @@ describe('TimeZonePickerComponent', () => {
         ServiceLogsHistogramDataService,
         ServiceLogsTruncatedService,
         TabsService,
-        ComponentActionsService,
         {
           provide: HttpClientService,
           useValue: httpClient
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.ts
index 32f6474..98758ab 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.ts
@@ -16,21 +16,23 @@
  * limitations under the License.
  */
 
-import {Component} from '@angular/core';
+import {Component, OnInit} from '@angular/core';
 import * as $ from 'jquery';
 import '@vendor/js/WorldMapGenerator.min';
 import {AppSettingsService} from '@app/services/storage/app-settings.service';
-import {ComponentActionsService} from 
'@app/services/component-actions.service';
 
 @Component({
   selector: 'timezone-picker',
   templateUrl: './timezone-picker.component.html',
   styleUrls: ['./timezone-picker.component.less']
 })
-export class TimeZonePickerComponent {
+export class TimeZonePickerComponent implements OnInit {
 
-  constructor(private appSettings: AppSettingsService, private actions: 
ComponentActionsService) {
-    appSettings.getParameter('timeZone').subscribe(value => this.timeZone = 
value);
+  constructor(private appSettings: AppSettingsService) {
+  }
+
+  ngOnInit() {
+    this.appSettings.getParameter('timeZone').subscribe((value: string) => 
this.timeZone = value);
   }
 
   readonly mapElementId = 'timezone-map';
@@ -70,7 +72,10 @@ export class TimeZonePickerComponent {
 
   setTimeZone(): void {
     const timeZone = this.timeZoneSelect.val();
-    this.actions.setTimeZone(timeZone);
+
+    // TODO replace with setTimeZone() method call from settings service as 
soon as it's implemented
+    this.appSettings.setParameter('timeZone', timeZone);
+
     this.setTimeZonePickerDisplay(false);
   }
 
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.html
index 910e55f..71637bb 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.html
@@ -19,10 +19,10 @@
   <form [formGroup]="filtersForm" class="filters">
     <filter-dropdown *ngIf="isClustersFilterDisplayed" 
formControlName="clusters" [options]="filters.clusters.options"
                      [isMultipleChoice]="true" label="{{filters.clusters.label 
| translate}}" [isRightAlign]="true"
-                     buttonClass="inherited-color"></filter-dropdown>
+                     buttonClass="btn-link inherited-color"></filter-dropdown>
   </form>
-  <menu-button *ngFor="let item of items" label="{{item.label | translate}}" 
[action]="item.action"
-               [iconClass]="item.iconClass" [labelClass]="item.labelClass" 
[subItems]="item.subItems"
-               [hideCaret]="item.hideCaret" [badge]="item.badge" 
[isRightAlign]="item.isRightAlign">
+  <menu-button *ngFor="let item of items" label="{{item.label | translate}}" 
[iconClass]="item.iconClass"
+               [labelClass]="item.labelClass" [subItems]="item.subItems" 
[hideCaret]="item.hideCaret"
+               [badge]="item.badge" [isRightAlign]="item.isRightAlign">
   </menu-button>
 </div>
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts
index ce4fa1c..caeb197 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts
@@ -38,6 +38,8 @@ import {ComponentsService, components} from 
'@app/services/storage/components.se
 import {HostsService, hosts} from '@app/services/storage/hosts.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {HttpClientService} from '@app/services/http-client.service';
+import {AuthService} from '@app/services/auth.service';
+import {UtilsService} from '@app/services/utils.service';
 
 import {TopMenuComponent} from './top-menu.component';
 
@@ -81,6 +83,8 @@ describe('TopMenuComponent', () => {
           provide: HttpClientService,
           useValue: httpClient
         },
+        AuthService,
+        UtilsService,
         AuditLogsService,
         ServiceLogsService,
         AuditLogsFieldsService,
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
index 8d739ec..1d003ee 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
@@ -21,6 +21,7 @@ import {FormGroup} from '@angular/forms';
 import {FilterCondition, TimeUnitListItem} from '@app/classes/filtering';
 import {ListItem} from '@app/classes/list-item';
 import {HomogeneousObject} from '@app/classes/object';
+import {AuthService} from '@app/services/auth.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 
 @Component({
@@ -30,7 +31,7 @@ import {LogsContainerService} from 
'@app/services/logs-container.service';
 })
 export class TopMenuComponent {
 
-  constructor(private logsContainer: LogsContainerService) {
+  constructor(private authService: AuthService, private logsContainer: 
LogsContainerService) {
   }
 
   get filtersForm(): FormGroup {
@@ -41,7 +42,15 @@ export class TopMenuComponent {
     return this.logsContainer.filters;
   };
 
-  //TODO implement loading of real data into subItems
+  openSettings = (): void => {};
+
+  /**
+   * Request a logout action from AuthService
+   */
+  logout = (): void => {
+    this.authService.logout();
+  };
+
   readonly items = [
     {
       iconClass: 'fa fa-user grey',
@@ -49,11 +58,17 @@ export class TopMenuComponent {
       isRightAlign: true,
       subItems: [
         {
-          label: 'Options'
+          label: 'common.settings',
+          onSelect: this.openSettings,
+          iconClass: 'fa fa-cog'
+        },
+        {
+          isDivider: true
         },
         {
           label: 'authorization.logout',
-          action: 'logout'
+          onSelect: this.logout,
+          iconClass: 'fa fa-sign-out'
         }
       ]
     }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.spec.ts
deleted file mode 100644
index 952c542..0000000
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.spec.ts
+++ /dev/null
@@ -1,101 +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.
- */
-
-import {TestBed, inject} from '@angular/core/testing';
-import {TranslationModules} from '@app/test-config.spec';
-import {StoreModule} from '@ngrx/store';
-import {AppSettingsService, appSettings} from 
'@app/services/storage/app-settings.service';
-import {AppStateService, appState} from 
'@app/services/storage/app-state.service';
-import {ClustersService, clusters} from 
'@app/services/storage/clusters.service';
-import {ComponentsService, components} from 
'@app/services/storage/components.service';
-import {HostsService, hosts} from '@app/services/storage/hosts.service';
-import {AuditLogsService, auditLogs} from 
'@app/services/storage/audit-logs.service';
-import {ServiceLogsService, serviceLogs} from 
'@app/services/storage/service-logs.service';
-import {AuditLogsFieldsService, auditLogsFields} from 
'@app/services/storage/audit-logs-fields.service';
-import {AuditLogsGraphDataService, auditLogsGraphData} from 
'@app/services/storage/audit-logs-graph-data.service';
-import {ServiceLogsFieldsService, serviceLogsFields} from 
'@app/services/storage/service-logs-fields.service';
-import {
-  ServiceLogsHistogramDataService, serviceLogsHistogramData
-} from '@app/services/storage/service-logs-histogram-data.service';
-import {ServiceLogsTruncatedService, serviceLogsTruncated} from 
'@app/services/storage/service-logs-truncated.service';
-import {TabsService, tabs} from '@app/services/storage/tabs.service';
-import {HttpClientService} from '@app/services/http-client.service';
-import {LogsContainerService} from '@app/services/logs-container.service';
-import {AuthService} from '@app/services/auth.service';
-
-import {ComponentActionsService} from './component-actions.service';
-
-describe('ComponentActionsService', () => {
-  const httpClient = {
-    get: () => {
-      return {
-        subscribe: () => {
-        }
-      };
-    }
-  };
-
-  beforeEach(() => {
-    TestBed.configureTestingModule({
-      imports: [
-        StoreModule.provideStore({
-          appSettings,
-          appState,
-          clusters,
-          components,
-          hosts,
-          auditLogs,
-          serviceLogs,
-          auditLogsFields,
-          auditLogsGraphData,
-          serviceLogsFields,
-          serviceLogsHistogramData,
-          serviceLogsTruncated,
-          tabs
-        }),
-        ...TranslationModules
-      ],
-      providers: [
-        ComponentActionsService,
-        AppSettingsService,
-        AppStateService,
-        ClustersService,
-        ComponentsService,
-        HostsService,
-        AuditLogsService,
-        ServiceLogsService,
-        AuditLogsFieldsService,
-        AuditLogsGraphDataService,
-        ServiceLogsFieldsService,
-        ServiceLogsHistogramDataService,
-        ServiceLogsTruncatedService,
-        TabsService,
-        {
-          provide: HttpClientService,
-          useValue: httpClient
-        },
-        LogsContainerService,
-        AuthService
-      ]
-    });
-  });
-
-  it('should create service', inject([ComponentActionsService], (service: 
ComponentActionsService) => {
-    expect(service).toBeTruthy();
-  }));
-});
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts
deleted file mode 100644
index 36c4d8d..0000000
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts
+++ /dev/null
@@ -1,155 +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.
- */
-
-import {Injectable} from '@angular/core';
-import {AppSettingsService} from '@app/services/storage/app-settings.service';
-import {TabsService} from '@app/services/storage/tabs.service';
-import {CollectionModelService} from '@app/classes/models/store';
-import {LogsContainerService} from '@app/services/logs-container.service';
-import {AuthService} from '@app/services/auth.service';
-import {ServiceLog} from '@app/classes/models/service-log';
-import {ListItem} from '@app/classes/list-item';
-
-@Injectable()
-export class ComponentActionsService {
-
-  constructor(
-    private appSettings: AppSettingsService, private tabsStorage: TabsService, 
private authService: AuthService,
-    private logsContainer: LogsContainerService
-  ) {
-  }
-
-  //TODO implement actions
-
-  undo() {
-  }
-
-  redo() {
-  }
-
-  refresh(): void {
-    this.logsContainer.loadLogs();
-  }
-
-  openHistory() {
-  }
-
-  copyLog(log: ServiceLog): void {
-    if (document.queryCommandSupported('copy')) {
-      const text = log.log_message,
-        node = document.createElement('textarea');
-      node.value = text;
-      Object.assign(node.style, {
-        position: 'fixed',
-        top: '0',
-        left: '0',
-        width: '1px',
-        height: '1px',
-        border: 'none',
-        outline: 'none',
-        boxShadow: 'none',
-        backgroundColor: 'transparent',
-        padding: '0'
-      });
-      document.body.appendChild(node);
-      node.select();
-      if (document.queryCommandEnabled('copy')) {
-        document.execCommand('copy');
-      } else {
-        // TODO open failed alert
-      }
-      // TODO success alert
-      document.body.removeChild(node);
-    } else {
-      // TODO failed alert
-    }
-  }
-
-  openLog(log: ServiceLog): void {
-    const tab = {
-      id: log.id,
-      isCloseable: true,
-      label: `${log.host} >> ${log.type}`,
-      appState: {
-        activeLogsType: 'serviceLogs',
-        isServiceLogsFileView: true,
-        activeLog: {
-          id: log.id,
-          host_name: log.host,
-          component_name: log.type
-        },
-        activeFilters: 
Object.assign(this.logsContainer.getFiltersData('serviceLogs'), {
-          components: 
this.logsContainer.filters.components.options.find((option: ListItem): boolean 
=> {
-            return option.value === log.type;
-          }),
-          hosts: this.logsContainer.filters.hosts.options.find((option: 
ListItem): boolean => {
-            return option.value === log.host;
-          })
-        })
-      }
-    };
-    this.tabsStorage.addInstance(tab);
-    this.logsContainer.switchTab(tab);
-  }
-
-  openContext(log: ServiceLog): void {
-    this.logsContainer.loadLogContext(log.id, log.host, log.type);
-  }
-
-  startCapture(): void {
-    this.logsContainer.startCaptureTimer();
-  }
-
-  stopCapture(): void {
-    this.logsContainer.stopCaptureTimer();
-  }
-
-  setTimeZone(timeZone: string): void {
-    this.appSettings.setParameter('timeZone', timeZone);
-  }
-
-  updateSelectedColumns(columnNames: string[], model: CollectionModelService): 
void {
-    model.mapCollection(item => Object.assign({}, item, {
-      isDisplayed: columnNames.indexOf(item.name) > -1
-    }));
-  }
-
-  proceedWithExclude = (item: string): void => 
this.logsContainer.queryParameterNameChange.next({
-    item: {
-      value: item
-    },
-    isExclude: true
-  });
-
-  /**
-   * Request a login action from the AuthService
-   * @param {string} username
-   * @param {string} password
-   */
-  login(username: string, password: string): void {
-    this.authService.login(username, password);
-  }
-
-  /**
-   * Request a logout action from AuthService
-   */
-  logout(): void {
-    this.authService.logout();
-  }
-
-}
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts
index a8ab5e8..b87fa8c 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts
@@ -36,6 +36,7 @@ import {ServiceLogsTruncatedService, serviceLogsTruncated} 
from '@app/services/s
 import {TabsService, tabs} from '@app/services/storage/tabs.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {HttpClientService} from '@app/services/http-client.service';
+import {UtilsService} from '@app/services/utils.service';
 
 import {ComponentGeneratorService} from './component-generator.service';
 
@@ -75,6 +76,7 @@ describe('ComponentGeneratorService', () => {
           provide: HttpClientService,
           useValue: httpClient
         },
+        UtilsService,
         HostsService,
         AuditLogsService,
         ServiceLogsService,
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.ts
index 43755c0..1f82367 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.ts
@@ -21,6 +21,7 @@ import {HostsService} from 
'@app/services/storage/hosts.service';
 import {ComponentsService} from '@app/services/storage/components.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {NodeBarComponent} from '@app/components/node-bar/node-bar.component';
+import {HistoryItemControlsComponent} from 
'@app/components/history-item-controls/history-item-controls.component';
 
 @Injectable()
 export class ComponentGeneratorService {
@@ -75,4 +76,9 @@ export class ComponentGeneratorService {
     });
   }
 
+  getHistoryItemIcons(historyItem, container: ViewContainerRef): void {
+    // TODO implement View details and Save filter actions
+    this.createComponent(HistoryItemControlsComponent, container);
+  }
+
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/history-manager.service.spec.ts
similarity index 66%
copy from 
ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts
copy to 
ambari-logsearch/ambari-logsearch-web/src/app/services/history-manager.service.spec.ts
index a8ab5e8..68a0e99 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/history-manager.service.spec.ts
@@ -19,7 +19,6 @@
 import {TestBed, inject} from '@angular/core/testing';
 import {TranslationModules} from '@app/test-config.spec';
 import {StoreModule} from '@ngrx/store';
-import {HostsService, hosts} from '@app/services/storage/hosts.service';
 import {AuditLogsService, auditLogs} from 
'@app/services/storage/audit-logs.service';
 import {ServiceLogsService, serviceLogs} from 
'@app/services/storage/service-logs.service';
 import {AuditLogsFieldsService, auditLogsFields} from 
'@app/services/storage/audit-logs-fields.service';
@@ -32,14 +31,16 @@ import {AppSettingsService, appSettings} from 
'@app/services/storage/app-setting
 import {AppStateService, appState} from 
'@app/services/storage/app-state.service';
 import {ClustersService, clusters} from 
'@app/services/storage/clusters.service';
 import {ComponentsService, components} from 
'@app/services/storage/components.service';
+import {HostsService, hosts} from '@app/services/storage/hosts.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from 
'@app/services/storage/service-logs-truncated.service';
 import {TabsService, tabs} from '@app/services/storage/tabs.service';
-import {LogsContainerService} from '@app/services/logs-container.service';
 import {HttpClientService} from '@app/services/http-client.service';
+import {LogsContainerService} from '@app/services/logs-container.service';
+import {UtilsService} from '@app/services/utils.service';
 
-import {ComponentGeneratorService} from './component-generator.service';
+import {HistoryManagerService} from './history-manager.service';
 
-describe('ComponentGeneratorService', () => {
+describe('HistoryService', () => {
   beforeEach(() => {
     const httpClient = {
       get: () => {
@@ -51,8 +52,8 @@ describe('ComponentGeneratorService', () => {
     };
     TestBed.configureTestingModule({
       imports: [
+        ...TranslationModules,
         StoreModule.provideStore({
-          hosts,
           auditLogs,
           serviceLogs,
           auditLogsFields,
@@ -63,19 +64,19 @@ describe('ComponentGeneratorService', () => {
           appState,
           clusters,
           components,
+          hosts,
           serviceLogsTruncated,
           tabs
-        }),
-        ...TranslationModules
+        })
       ],
       providers: [
-        ComponentGeneratorService,
-        LogsContainerService,
+        HistoryManagerService,
         {
           provide: HttpClientService,
           useValue: httpClient
         },
-        HostsService,
+        LogsContainerService,
+        UtilsService,
         AuditLogsService,
         ServiceLogsService,
         AuditLogsFieldsService,
@@ -86,13 +87,84 @@ describe('ComponentGeneratorService', () => {
         AppStateService,
         ClustersService,
         ComponentsService,
+        HostsService,
         ServiceLogsTruncatedService,
         TabsService
       ]
     });
   });
 
-  it('should create service', inject([ComponentGeneratorService], (service: 
ComponentGeneratorService) => {
+  it('should be created', inject([HistoryManagerService], (service: 
HistoryManagerService) => {
     expect(service).toBeTruthy();
   }));
+
+  describe('#isHistoryUnchanged()', () => {
+    const cases = [
+      {
+        valueA: {
+          p0: 'v0',
+          p1: ['v1'],
+          p2: {
+            k2: 'v2'
+          }
+        },
+        valueB: {
+          p0: 'v0',
+          p1: ['v1'],
+          p2: {
+            k2: 'v2'
+          }
+        },
+        result: true,
+        title: 'no difference'
+      },
+      {
+        valueA: {
+          p0: 'v0',
+          p1: ['v1'],
+          p2: {
+            k2: 'v2'
+          },
+          page: 0
+        },
+        valueB: {
+          p0: 'v0',
+          p1: ['v1'],
+          p2: {
+            k2: 'v2'
+          },
+          page: 1
+        },
+        result: true,
+        title: 'difference in ignored parameters'
+      },
+      {
+        valueA: {
+          p0: 'v0',
+          p1: ['v1'],
+          p2: {
+            k2: 'v2'
+          },
+          page: 0
+        },
+        valueB: {
+          p0: 'v0',
+          p1: ['v3'],
+          p2: {
+            k2: 'v4'
+          },
+          page: 1
+        },
+        result: false,
+        title: 'difference in non-ignored parameters'
+      }
+    ];
+
+    cases.forEach(test => {
+      it(test.title, inject([HistoryManagerService], (service: 
HistoryManagerService) => {
+        const isHistoryUnchanged: (valueA: object, valueB: object) => boolean 
= service['isHistoryUnchanged'];
+        expect(isHistoryUnchanged(test.valueA, 
test.valueB)).toEqual(test.result);
+      }));
+    });
+  });
 });
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/history-manager.service.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/history-manager.service.ts
new file mode 100644
index 0000000..39cadc6
--- /dev/null
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/history-manager.service.ts
@@ -0,0 +1,330 @@
+/**
+ * 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 'rxjs/add/operator/distinctUntilChanged';
+import 'rxjs/add/operator/takeUntil';
+import {TranslateService} from '@ngx-translate/core';
+import {SearchBoxParameter, TimeUnitListItem} from '@app/classes/filtering';
+import {ListItem} from '@app/classes/list-item';
+import {HomogeneousObject} from '@app/classes/object';
+import {History} from '@app/classes/models/app-state';
+import {Tab} from '@app/classes/models/tab';
+import {LogsContainerService} from '@app/services/logs-container.service';
+import {UtilsService} from '@app/services/utils.service';
+import {AppStateService} from '@app/services/storage/app-state.service';
+import {TabsService} from '@app/services/storage/tabs.service';
+
+@Injectable()
+export class HistoryManagerService {
+
+  constructor(
+    private translate: TranslateService, private logsContainer: 
LogsContainerService, private utils: UtilsService,
+    private appState: AppStateService, private tabs: TabsService
+  ) {
+    // set labels for history list items
+    const filters = logsContainer.filters,
+      controlNames = Object.keys(filters).filter((name: string): boolean => {
+        const key = filters[name].label;
+        return key && this.ignoredParameters.indexOf(name) === -1;
+      }),
+      filterLabelKeys = controlNames.map((name: string): string => 
filters[name].label),
+      timeRangeLabels = filters.timeRange.options.reduce((
+          currentArray: string[], group: TimeUnitListItem[]
+        ): string[] => {
+          return [...currentArray, ...group.map((option: TimeUnitListItem): 
string => option.label)];
+        }, [logsContainer.customTimeRangeKey]);
+
+    translate.get([
+      'filter.include', 'filter.exclude', ...filterLabelKeys, 
...timeRangeLabels
+    ]).subscribe((translates: object): void => {
+      this.controlNameLabels = controlNames.reduce((
+        currentObject: HomogeneousObject<string>, name: string
+      ): HomogeneousObject<string> => {
+        return Object.assign({}, currentObject, {
+          [name]: translates[filters[name].label]
+        })
+      }, {
+        include: translates['filter.include'],
+        exclude: translates['filter.exclude']
+      });
+      this.timeRangeLabels = timeRangeLabels.reduce((
+        currentObject: HomogeneousObject<string>, key: string
+      ): HomogeneousObject<string> => {
+        return Object.assign({}, currentObject, {
+          [key]: translates[key]
+        });
+      }, {});
+    });
+
+    // set default history state for each tab
+    tabs.mapCollection((tab: Tab): Tab => {
+      let currentAppState = tab.appState || {};
+      const appState = Object.assign({}, currentAppState, {
+        history: {
+          items: [],
+          currentId: -1
+        }
+      });
+      return Object.assign({}, tab, {
+        appState
+      });
+    });
+
+    // set current history items after switching tabs
+    appState.getParameter('history').subscribe((history: History): void => {
+      const filtersForm = logsContainer.filtersForm;
+      let defaultState;
+      if (history.items.length === 0) {
+        defaultState = filtersForm.value
+      }
+      this.activeHistory = history.items.slice();
+      this.currentHistoryItemId = history.currentId;
+
+      // handle filtering values changes
+      filtersForm.valueChanges
+        .distinctUntilChanged(this.isHistoryUnchanged)
+        .takeUntil(this.logsContainer.filtersFormChange)
+        .subscribe((value): void => {
+          if (this.hasNoPendingUndoOrRedo) {
+            const currentHistory = this.activeHistory,
+              previousValue = this.activeHistory.length ? 
this.activeHistory[0].value.currentValue : defaultState,
+              isUndoOrRedo = value.isUndoOrRedo;
+            const previousChangeId = this.currentHistoryItemId;
+            if (isUndoOrRedo) {
+              this.hasNoPendingUndoOrRedo = false;
+              filtersForm.patchValue({
+                isUndoOrRedo: false
+              });
+              this.hasNoPendingUndoOrRedo = true;
+            } else {
+              this.currentHistoryItemId = currentHistory.length;
+            }
+            this.activeHistory = [
+              {
+                value: {
+                  currentValue: Object.assign({}, value),
+                  previousValue: Object.assign({}, previousValue),
+                  changeId: this.currentHistoryItemId,
+                  previousChangeId,
+                  isUndoOrRedo
+                },
+                label: this.getHistoryItemLabel(previousValue, value)
+              },
+              ...currentHistory
+            ].slice(0, this.maxHistoryItemsCount);
+
+            // update history for active tab
+            this.tabs.mapCollection((tab: Tab): Tab => {
+              const currentAppState = tab.appState || {},
+                appState = Object.assign({}, currentAppState, tab.isActive ? {
+                  history: {
+                    items: this.activeHistory.slice(),
+                    currentId: this.currentHistoryItemId
+                  }
+                } : null);
+              return Object.assign({}, tab, {
+                appState
+              });
+            });
+          }
+      });
+    });
+  }
+
+  /**
+   * List of filter parameters which shouldn't affect changes history (related 
to pagination and sorting)
+   * @type {string[]}
+   */
+  private readonly ignoredParameters: string[] = ['page', 'pageSize', 
'auditLogsSorting', 'serviceLogsSorting'];
+
+  /**
+   * Maximal number of displayed history items
+   * @type {number}
+   */
+  private readonly maxHistoryItemsCount: number = 25;
+
+  /**
+   * Indicates whether there is no changes being applied to filters that are 
triggered by undo or redo action.
+   * Since user can undo or redo several filters changes at once, and they are 
applied to form controls step-by-step,
+   * this flag is needed to avoid recording intermediate items to history.
+   * @type {boolean}
+   */
+  private hasNoPendingUndoOrRedo: boolean = true;
+
+  /**
+   * Id of currently active history item.
+   * Generally speaking, it isn't id of the latest one because it can be 
shifted by undo or redo action.
+   * @type {number}
+   */
+  private currentHistoryItemId: number = -1;
+
+  /**
+   * Contains i18n labels for filtering form control names
+   */
+  private controlNameLabels;
+
+  /**
+   * Contains i18n labels for time range options
+   */
+  private timeRangeLabels;
+
+  /**
+   * History items for current tab
+   * @type {Array}
+   */
+  activeHistory: ListItem[] = [];
+
+  /**
+   * List of filtering form control names for active tab
+   * @returns {Array}
+   */
+  private get filterParameters(): string[] {
+    return 
this.logsContainer.logsTypeMap[this.logsContainer.activeLogsType].listFilters;
+  }
+
+  /**
+   * List of changes that can be undone
+   * @returns {ListItem[]}
+   */
+  get undoItems(): ListItem[] {
+    const allItems = this.activeHistory;
+    let startIndex = allItems.findIndex((item: ListItem): boolean => {
+        return item.value.changeId === this.currentHistoryItemId && 
!item.value.isUndoOrRedo;
+      }),
+      endIndex = allItems.slice(startIndex + 1).findIndex((item: ListItem): 
boolean => item.value.isUndoOrRedo);
+    if (startIndex > -1) {
+      if (endIndex === -1) {
+        endIndex = allItems.length;
+        return allItems.slice(startIndex, startIndex + endIndex + 1);
+      }
+    } else {
+      return [];
+    }
+  }
+
+  /**
+   * List of changes that can be redone
+   * @returns {ListItem[]}
+   */
+  get redoItems(): ListItem[] {
+    const allItems = this.activeHistory.slice().reverse();
+    let startIndex = allItems.findIndex((item: ListItem): boolean => {
+        return item.value.previousChangeId === this.currentHistoryItemId && 
!item.value.isUndoOrRedo;
+      }),
+      endIndex = allItems.slice(startIndex + 1).findIndex((item: ListItem): 
boolean => item.value.isUndoOrRedo);
+    if (startIndex === -1) {
+      startIndex = allItems.length;
+    }
+    if (endIndex === -1) {
+      endIndex = allItems.length;
+    }
+    return allItems.slice(startIndex, endIndex + startIndex + 1);
+  }
+
+  /**
+   * Indicates whether there are no filtering form changes that should be 
tracked
+   * (all except the ones related to pagination and sorting)
+   * @param {object} valueA
+   * @param {object} valueB
+   * @returns {boolean}
+   */
+  private isHistoryUnchanged = (valueA: object, valueB: object): boolean => {
+    const objectA = Object.assign({}, valueA),
+      objectB = Object.assign({}, valueB);
+    this.ignoredParameters.forEach((controlName: string): void => {
+      delete objectA[controlName];
+      delete objectB[controlName];
+    });
+    return this.utils.isEqual(objectA, objectB);
+  };
+
+  /**
+   * Get label for certain form control change
+   * @param {string} controlName
+   * @param {any} selection
+   * @returns {string}
+   */
+  private getItemValueString(controlName: string, selection: any): string {
+    switch (controlName) {
+      case 'timeRange':
+        return `${this.controlNameLabels[controlName]}: 
${this.timeRangeLabels[selection.label]}`;
+      case 'query':
+        const includes = selection.filter((item: SearchBoxParameter): boolean 
=> {
+            return !item.isExclude;
+          }).map((item: SearchBoxParameter): string => `${item.name}: 
${item.value}`).join(', '),
+          excludes = selection.filter((item: SearchBoxParameter): boolean => {
+            return item.isExclude;
+          }).map((item: SearchBoxParameter): string => `${item.name}: 
${item.value}`).join(', '),
+          includesString = includes.length ? 
`${this.controlNameLabels.include}: ${includes}` : '',
+          excludesString = excludes.length ? 
`${this.controlNameLabels.exclude}: ${excludes}`: '';
+        return `${includesString} ${excludesString}`;
+      default:
+        const values = selection.map((option: ListItem) => 
option.value).join(', ');
+        return `${this.controlNameLabels[controlName]}: ${values}`;
+    }
+  }
+
+  /**
+   * Get label for history list item (i.e., difference with the previous one)
+   * @param {object} previousFormValue
+   * @param {object} currentFormValue
+   * @returns {string}
+   */
+  private getHistoryItemLabel(previousFormValue: object, currentFormValue: 
object): string {
+    return this.filterParameters.reduce((currentResult: string, currentName: 
string): string => {
+      const currentValue = currentFormValue[currentName];
+      if (this.ignoredParameters.indexOf(currentName) > -1
+        || this.utils.isEqual(previousFormValue[currentName], currentValue)) {
+        return currentResult;
+      } else {
+        const currentLabel = this.getItemValueString(currentName, 
currentValue);
+        return `${currentResult} ${currentLabel}`;
+      }
+    }, '');
+  }
+
+  /**
+   * Handle undo or redo action correctly
+   * @param {object} value
+   */
+  private handleUndoOrRedo(value: object): void {
+    const filtersForm = this.logsContainer.filtersForm;
+    this.hasNoPendingUndoOrRedo = false;
+    this.filterParameters.forEach((controlName: string): void => {
+      if (this.ignoredParameters.indexOf(controlName) === -1) {
+        filtersForm.controls[controlName].setValue(value[controlName]);
+      }
+    });
+    this.hasNoPendingUndoOrRedo = true;
+    filtersForm.controls.isUndoOrRedo.setValue(true);
+  }
+
+  undo(item: ListItem): void {
+    this.hasNoPendingUndoOrRedo = false;
+    this.currentHistoryItemId = item.value.previousChangeId;
+    this.handleUndoOrRedo(item.value.previousValue);
+  }
+
+  redo(item: ListItem): void {
+    this.hasNoPendingUndoOrRedo = false;
+    this.currentHistoryItemId = item.value.changeId;
+    this.handleUndoOrRedo(item.value.currentValue);
+  }
+
+}
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts
index 5961309..a8e7c3f 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts
@@ -35,6 +35,7 @@ import {HostsService, hosts} from 
'@app/services/storage/hosts.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from 
'@app/services/storage/service-logs-truncated.service';
 import {TabsService, tabs} from '@app/services/storage/tabs.service';
 import {HttpClientService} from '@app/services/http-client.service';
+import {UtilsService} from '@app/services/utils.service';
 import {ListItem} from '@app/classes/list-item';
 import {NodeItem} from '@app/classes/models/node-item';
 
@@ -87,7 +88,8 @@ describe('LogsContainerService', () => {
         {
           provide: HttpClientService,
           useValue: httpClient
-        }
+        },
+        UtilsService
       ]
     });
   });
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts
index baa3972..6a2108b 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts
@@ -23,11 +23,13 @@ import {Subject} from 'rxjs/Subject';
 import {Observable} from 'rxjs/Observable';
 import 'rxjs/add/observable/timer';
 import 'rxjs/add/observable/combineLatest';
+import 'rxjs/add/operator/distinctUntilChanged';
 import 'rxjs/add/operator/first';
 import 'rxjs/add/operator/map';
 import 'rxjs/add/operator/takeUntil';
 import * as moment from 'moment-timezone';
 import {HttpClientService} from '@app/services/http-client.service';
+import {UtilsService} from '@app/services/utils.service';
 import {AuditLogsService} from '@app/services/storage/audit-logs.service';
 import {AuditLogsFieldsService} from 
'@app/services/storage/audit-logs-fields.service';
 import {AuditLogsGraphDataService} from 
'@app/services/storage/audit-logs-graph-data.service';
@@ -62,13 +64,13 @@ import {CommonEntry} from 
'@app/classes/models/common-entry';
 export class LogsContainerService {
 
   constructor(
-    private httpClient: HttpClientService, private tabsStorage: TabsService, 
private componentsStorage: ComponentsService,
-    private hostsStorage: HostsService, private appState: AppStateService, 
private auditLogsStorage: AuditLogsService,
+    private httpClient: HttpClientService, private utils: UtilsService,
+    private tabsStorage: TabsService, private componentsStorage: 
ComponentsService, private hostsStorage: HostsService,
+    private appState: AppStateService, private auditLogsStorage: 
AuditLogsService,
     private auditLogsGraphStorage: AuditLogsGraphDataService, private 
auditLogsFieldsStorage: AuditLogsFieldsService,
     private serviceLogsStorage: ServiceLogsService, private 
serviceLogsFieldsStorage: ServiceLogsFieldsService,
     private serviceLogsHistogramStorage: ServiceLogsHistogramDataService, 
private clustersStorage: ClustersService,
     private serviceLogsTruncatedStorage: ServiceLogsTruncatedService, private 
appSettings: AppSettingsService
-
   ) {
     const formItems = Object.keys(this.filters).reduce((currentObject: any, 
key: string): HomogeneousObject<FormControl> => {
       let formControl = new FormControl(),
@@ -104,17 +106,20 @@ export class LogsContainerService {
         });
       }
       this.loadLogs();
-      
this.filtersForm.valueChanges.takeUntil(this.filtersFormChange).subscribe((value:
 object): void => {
-        this.tabsStorage.mapCollection((tab: Tab): Tab => {
-          const currentAppState = tab.appState || {},
-            appState = Object.assign({}, currentAppState, tab.isActive ? {
-              activeFilters: value
-            } : null);
-          return Object.assign({}, tab, {
-            appState
+      this.filtersForm.valueChanges
+        .distinctUntilChanged(this.isFormUnchanged)
+        .takeUntil(this.filtersFormChange)
+        .subscribe((value): void => {
+          this.tabsStorage.mapCollection((tab: Tab): Tab => {
+            const currentAppState = tab.appState || {},
+              appState = Object.assign({}, currentAppState, tab.isActive ? {
+                activeFilters: value
+              } : null);
+            return Object.assign({}, tab, {
+              appState
+            });
           });
-        });
-        this.loadLogs();
+          this.loadLogs();
       });
     });
   }
@@ -129,6 +134,7 @@ export class LogsContainerService {
       fieldName: 'cluster'
     },
     timeRange: {
+      label: 'filter.duration',
       options: [
         [
           {
@@ -473,7 +479,12 @@ export class LogsContainerService {
     page: {
       defaultSelection: 0
     },
-    query: {}
+    query: {
+      defaultSelection: []
+    },
+    isUndoOrRedo: {
+      defaultSelection: false
+    }
   };
 
   readonly colors = {
@@ -508,6 +519,8 @@ export class LogsContainerService {
     query: ['includeQuery', 'excludeQuery']
   };
 
+  readonly customTimeRangeKey: string = 'filter.timeRange.custom';
+
   readonly topResourcesCount: string = '10';
 
   readonly topUsersCount: string = '6';
@@ -569,7 +582,7 @@ export class LogsContainerService {
 
   activeLogsType: LogsType;
 
-  private filtersFormChange: Subject<void> = new Subject();
+  filtersFormChange: Subject<void> = new Subject();
 
   private columnsMapper<FieldT extends LogField>(fields: FieldT[]): ListItem[] 
{
     return fields.filter((field: FieldT): boolean => 
field.isAvailable).map((field: FieldT): ListItem => {
@@ -649,6 +662,16 @@ export class LogsContainerService {
 
   topResourcesGraphData: HomogeneousObject<HomogeneousObject<number>> = {};
 
+  private isFormUnchanged = (valueA: object, valueB: object): boolean => {
+    const trackedControlNames = 
this.logsTypeMap[this.activeLogsType].listFilters;
+    for (let name of trackedControlNames) {
+      if (!this.utils.isEqual(valueA[name], valueB[name])) {
+        return false;
+      }
+    }
+    return true;
+  };
+
   loadLogs = (logsType: LogsType = this.activeLogsType): void => {
     this.httpClient.get(logsType, 
this.getParams('listFilters')).subscribe((response: Response): void => {
       const jsonResponse = response.json(),
@@ -992,7 +1015,7 @@ export class LogsContainerService {
 
   setCustomTimeRange(startTime: number, endTime: number): void {
     this.filtersForm.controls.timeRange.setValue({
-      label: 'filter.timeRange.custom',
+      label: this.customTimeRangeKey,
       value: {
         type: 'CUSTOM',
         start: moment(startTime),
@@ -1016,4 +1039,37 @@ export class LogsContainerService {
       && Boolean(this.filtersForm.controls[key]);
   }
 
+  updateSelectedColumns(columnNames: string[], logsType: string): void {
+    this.logsTypeMap[logsType].fieldsModel.mapCollection(item => 
Object.assign({}, item, {
+      isDisplayed: columnNames.indexOf(item.name) > -1
+    }));
+  }
+
+  openServiceLog(log: ServiceLog): void {
+    const tab = {
+      id: log.id,
+      isCloseable: true,
+      label: `${log.host} >> ${log.type}`,
+      appState: {
+        activeLogsType: 'serviceLogs',
+        isServiceLogsFileView: true,
+        activeLog: {
+          id: log.id,
+          host_name: log.host,
+          component_name: log.type
+        },
+        activeFilters: Object.assign(this.getFiltersData('serviceLogs'), {
+          components: this.filters.components.options.find((option: ListItem): 
boolean => {
+            return option.value === log.type;
+          }),
+          hosts: this.filters.hosts.options.find((option: ListItem): boolean 
=> {
+            return option.value === log.host;
+          })
+        })
+      }
+    };
+    this.tabsStorage.addInstance(tab);
+    this.switchTab(tab);
+  }
+
 }
diff --git a/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json 
b/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
index 2b34b4d..1d8f6c4 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
+++ b/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
@@ -4,6 +4,9 @@
   "common.auditLogs": "Audit Logs",
   "common.summary": "Summary",
   "common.logs": "Logs",
+  "common.name": "Name",
+  "common.value": "Value",
+  "common.settings": "Settings",
 
   "modal.submit": "OK",
   "modal.cancel": "Cancel",
@@ -26,8 +29,10 @@
   "filter.clusters": "Clusters",
   "filter.components": "Components",
   "filter.levels": "Levels",
+  "filter.include": "Include",
   "filter.exclude": "Exclude",
   "filter.hosts": "Hosts",
+  "filter.duration": "Duration",
 
   "filter.capture": "Capture",
   "filter.capture.triggeringRefresh": "Triggering auto-refresh in 
{{remainingSeconds}} sec",

-- 
To stop receiving notification emails like this one, please contact
ababiic...@apache.org.

Reply via email to