METRON-2190 [UI] Alerts UI: Indicating loading and preventing parallel requests (tiborm via sardell) closes apache/metron#1514
diff --git a/metron-interface/metron-alerts/cypress/fixtures/search-1.1.json b/metron-interface/metron-alerts/cypress/fixtures/search-1.1.json
new file mode 100644
index 0000000..529f4bf
--- /dev/null
+++ b/metron-interface/metron-alerts/cypress/fixtures/search-1.1.json
@@ -0,0 +1,102 @@
+{
+  "total":1,
+  "results":[
+   {
+      "id":"test-alert-entry-id-1",
+      "source":{
+         "enrichments:geo:ip_dst_addr:locID":"5368361",
+         "bro_timestamp":"1537304979.801853",
+         "status_code":200,
+         "enrichments:geo:ip_dst_addr:location_point":"34.0494,-118.2641",
+         "ip_dst_port":80,
+         "threatinteljoinbolt:joiner:ts":"1537304981038",
+         "enrichments:geo:ip_dst_addr:dmaCode":"803",
+         "enrichmentsplitterbolt:splitter:begin:ts":"1537304981020",
+         "enrichmentjoinbolt:joiner:ts":"1537304981027",
+         "adapter:geoadapter:begin:ts":"1537304981022",
+         "enrichments:geo:ip_dst_addr:latitude":"34.0494",
+         "uid":"C6NKjA4tt5Xc1a6uzd",
+         "resp_mime_types":[
+            "text/plain"
+         ],
+         "trans_depth":1,
+         "protocol":"http",
+         "source:type":"bro",
+         "adapter:threatinteladapter:end:ts":"1537304981036",
+         "original_string":"HTTP | id.orig_p:49204 status_code:200 method:POST request_body_len:110 id.resp_p:80 orig_mime_types:[\"text\\/plain\"] uri:/wp-content/themes/grizzly/img5.php?u=ka6nnuvccqlw9 tags:[] uid:C6NKjA4tt5Xc1a6uzd resp_mime_types:[\"text\\/plain\"] trans_depth:1 orig_fuids:[\"Fr5Cg02TcSAxFeYoBh\"] host:comarksecurity.com status_msg:OK id.orig_h:192.168.138.158 response_body_len:14 user_agent:Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0) ts:1537304979.801853 id.resp_h:72.34.49.86 resp_fuids:[\"FQcLCtotjacEmeBEf\"]",
+         "ip_dst_addr":"72.34.49.86",
+         "adapter:hostfromjsonlistadapter:end:ts":"1537304981022",
+         "host":"comarksecurity.com",
+         "adapter:geoadapter:end:ts":"1537304981022",
+         "ip_src_addr":"192.168.138.158",
+         "threatintelsplitterbolt:splitter:end:ts":"1537304981029",
+         "enrichments:geo:ip_dst_addr:longitude":"-118.2641",
+         "user_agent":"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)",
+         "resp_fuids":[
+            "FQcLCtotjacEmeBEf"
+         ],
+         "timestamp":1537304979801,
+         "method":"POST",
+         "enrichmentsplitterbolt:splitter:end:ts":"1537304981020",
+         "request_body_len":110,
+         "enrichments:geo:ip_dst_addr:city":"Los Angeles",
+         "enrichments:geo:ip_dst_addr:postalCode":"90014",
+         "adapter:hostfromjsonlistadapter:begin:ts":"1537304981022",
+         "orig_mime_types":[
+            "text/plain"
+         ],
+         "uri":"/wp-content/themes/grizzly/img5.php?u=ka6nnuvccqlw9",
+         "tags":[
+
+         ],
+         "alert_status":"OPEN",
+         "orig_fuids":[
+            "Fr5Cg02TcSAxFeYoBh"
+         ],
+         "ip_src_port":49204,
+         "threatintelsplitterbolt:splitter:begin:ts":"1537304981029",
+         "adapter:threatinteladapter:begin:ts":"1537304981033",
+         "status_msg":"OK",
+         "guid":"test-id-1.1",
+         "enrichments:geo:ip_dst_addr:country":"US",
+         "response_body_len":14
+      },
+      "score":1.0,
+      "index":"bro_index_2018.09.18.21"
+   }
+  ],
+  "facetCounts":{
+     "source:type":{
+        "metaalert":1,
+        "bro":52319,
+        "snort":52273
+     },
+     "ip_dst_addr":{
+        "95.163.121.204":15832,
+        "72.34.49.86":5079,
+        "192.168.138.158":17989,
+        "188.165.164.184":995,
+        "192.168.138.2":6396,
+        "192.168.66.1":4226,
+        "62.75.195.236":15813,
+        "224.0.0.251":4979,
+        "192.168.66.121":28822,
+        "204.152.254.221":4461
+     },
+     "enrichments:geo:ip_dst_addr:country":{
+        "RU":15832,
+        "FR":16808,
+        "US":9540
+     },
+     "ip_src_addr":{
+        "95.163.121.204":2106,
+        "72.34.49.86":2284,
+        "192.168.138.158":48576,
+        "192.168.138.2":118,
+        "192.168.66.1":33801,
+        "62.75.195.236":12552,
+        "192.168.66.121":4226,
+        "204.152.254.221":929
+     }
+  }
+}
\ No newline at end of file
diff --git a/metron-interface/metron-alerts/cypress/fixtures/search-1.2.json b/metron-interface/metron-alerts/cypress/fixtures/search-1.2.json
new file mode 100644
index 0000000..13c7434
--- /dev/null
+++ b/metron-interface/metron-alerts/cypress/fixtures/search-1.2.json
@@ -0,0 +1,102 @@
+{
+  "total":1,
+  "results":[
+   {
+      "id":"test-alert-entry-id-2",
+      "source":{
+         "enrichments:geo:ip_dst_addr:locID":"5368361",
+         "bro_timestamp":"1537304979.801853",
+         "status_code":200,
+         "enrichments:geo:ip_dst_addr:location_point":"34.0494,-118.2641",
+         "ip_dst_port":80,
+         "threatinteljoinbolt:joiner:ts":"1537304981038",
+         "enrichments:geo:ip_dst_addr:dmaCode":"803",
+         "enrichmentsplitterbolt:splitter:begin:ts":"1537304981020",
+         "enrichmentjoinbolt:joiner:ts":"1537304981027",
+         "adapter:geoadapter:begin:ts":"1537304981022",
+         "enrichments:geo:ip_dst_addr:latitude":"34.0494",
+         "uid":"C6NKjA4tt5Xc1a6uzd",
+         "resp_mime_types":[
+            "text/plain"
+         ],
+         "trans_depth":1,
+         "protocol":"http",
+         "source:type":"bro",
+         "adapter:threatinteladapter:end:ts":"1537304981036",
+         "original_string":"HTTP | id.orig_p:49204 status_code:200 method:POST request_body_len:110 id.resp_p:80 orig_mime_types:[\"text\\/plain\"] uri:/wp-content/themes/grizzly/img5.php?u=ka6nnuvccqlw9 tags:[] uid:C6NKjA4tt5Xc1a6uzd resp_mime_types:[\"text\\/plain\"] trans_depth:1 orig_fuids:[\"Fr5Cg02TcSAxFeYoBh\"] host:comarksecurity.com status_msg:OK id.orig_h:192.168.138.158 response_body_len:14 user_agent:Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0) ts:1537304979.801853 id.resp_h:72.34.49.86 resp_fuids:[\"FQcLCtotjacEmeBEf\"]",
+         "ip_dst_addr":"72.34.49.86",
+         "adapter:hostfromjsonlistadapter:end:ts":"1537304981022",
+         "host":"comarksecurity.com",
+         "adapter:geoadapter:end:ts":"1537304981022",
+         "ip_src_addr":"192.168.138.158",
+         "threatintelsplitterbolt:splitter:end:ts":"1537304981029",
+         "enrichments:geo:ip_dst_addr:longitude":"-118.2641",
+         "user_agent":"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)",
+         "resp_fuids":[
+            "FQcLCtotjacEmeBEf"
+         ],
+         "timestamp":1537304979801,
+         "method":"POST",
+         "enrichmentsplitterbolt:splitter:end:ts":"1537304981020",
+         "request_body_len":110,
+         "enrichments:geo:ip_dst_addr:city":"Los Angeles",
+         "enrichments:geo:ip_dst_addr:postalCode":"90014",
+         "adapter:hostfromjsonlistadapter:begin:ts":"1537304981022",
+         "orig_mime_types":[
+            "text/plain"
+         ],
+         "uri":"/wp-content/themes/grizzly/img5.php?u=ka6nnuvccqlw9",
+         "tags":[
+
+         ],
+         "alert_status":"OPEN",
+         "orig_fuids":[
+            "Fr5Cg02TcSAxFeYoBh"
+         ],
+         "ip_src_port":49204,
+         "threatintelsplitterbolt:splitter:begin:ts":"1537304981029",
+         "adapter:threatinteladapter:begin:ts":"1537304981033",
+         "status_msg":"OK",
+         "guid":"test-id-2.1",
+         "enrichments:geo:ip_dst_addr:country":"US",
+         "response_body_len":14
+      },
+      "score":1.0,
+      "index":"bro_index_2018.09.18.21"
+   }
+  ],
+  "facetCounts":{
+     "source:type":{
+        "metaalert":1,
+        "bro":52319,
+        "snort":52273
+     },
+     "ip_dst_addr":{
+        "95.163.121.204":15832,
+        "72.34.49.86":5079,
+        "192.168.138.158":17989,
+        "188.165.164.184":995,
+        "192.168.138.2":6396,
+        "192.168.66.1":4226,
+        "62.75.195.236":15813,
+        "224.0.0.251":4979,
+        "192.168.66.121":28822,
+        "204.152.254.221":4461
+     },
+     "enrichments:geo:ip_dst_addr:country":{
+        "RU":15832,
+        "FR":16808,
+        "US":9540
+     },
+     "ip_src_addr":{
+        "95.163.121.204":2106,
+        "72.34.49.86":2284,
+        "192.168.138.158":48576,
+        "192.168.138.2":118,
+        "192.168.66.1":33801,
+        "62.75.195.236":12552,
+        "192.168.66.121":4226,
+        "204.152.254.221":929
+     }
+  }
+}
\ No newline at end of file
diff --git a/metron-interface/metron-alerts/cypress/integration/search/auto-polling.feature.spec.js b/metron-interface/metron-alerts/cypress/integration/search/auto-polling.feature.spec.js
new file mode 100644
index 0000000..c99f700
--- /dev/null
+++ b/metron-interface/metron-alerts/cypress/integration/search/auto-polling.feature.spec.js
@@ -0,0 +1,98 @@
+/// <reference types="Cypress" />
+/**
+ * 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 * as appConfigJSON from '../../../src/assets/app-config.json';
+
+describe('Automatic data polling on Alerts View', () => {
+
+  const configuringDefaultStubs = () => {
+    cy.route({
+      method: 'GET',
+      url: '/api/v1/user',
+      response: 'user'
+    });
+
+    cy.route('GET', '/api/v1/global/config', 'fixture:config.json');
+    cy.route('GET', appConfigJSON.contextMenuConfigURL, 'fixture:context-menu.conf.json');
+  };
+
+  beforeEach(() => {
+    cy.server();
+    configuringDefaultStubs();
+  });
+
+  it('auto polling should keep polling after start depending on polling interval', () => {
+    cy.visit('login');
+    cy.get('[name="user"]').type('user');
+    cy.get('[name="password"]').type('password');
+    cy.contains('LOG IN').click();
+
+    // defining response for initial poll request
+    cy.route({
+      url: '/api/v1/search/search',
+      method: 'POST',
+      response: 'fixture:search.json',
+    }).as('initReq');
+
+    cy.log('Turning polling on');
+    cy.get('app-auto-polling > .btn').click();
+
+    cy.log('changing interval to 5 sec');
+    cy.get('.settings').click();
+    cy.get('[value="5"]').click();
+    cy.get('.settings').click();
+
+    // defining respons for the first scheduled poll
+    cy.route({
+      url: '/api/v1/search/search',
+      method: 'POST',
+      response: 'fixture:search-1.1.json',
+    }).as('1stPoll');
+
+    // Waiting 5.5 sec for the request
+    cy.wait('@1stPoll', { timeout: 5500 });
+    // Validating dom change
+    cy.contains('test-id-1.1').should('be.visible');
+
+    // defining respons for the second scheduled poll
+    cy.route({
+      url: '/api/v1/search/search',
+      method: 'POST',
+      response: 'fixture:search-1.2.json',
+    }).as('2ndPoll');;
+
+    // Waiting 5.5 sec for the request
+    cy.wait('@2ndPoll', { timeout: 5500 });
+    // Validating dom change
+    cy.contains('test-id-2.1').should('be.visible');
+
+    // turning off polling
+    cy.get('app-auto-polling > .btn').click();
+
+    cy.route({
+      url: '/api/v1/search/search',
+      method: 'POST',
+      response: 'fixture:search.json',
+    });
+
+    cy.wait(5500).then(() => {
+      // same element should be visible bc the polling is turned off
+    cy.contains('test-id-2.1').should('be.visible');
+    })
+  });
+});
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html
old mode 100644
new mode 100755
index e56fb1b..ea09288
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html
@@ -12,6 +12,7 @@
   the specific language governing permissions and limitations under the License.
   -->
 <div class="container-fluid px-0">
+    <app-modal-loading-indicator [show]="!!pendingSearch"></app-modal-loading-indicator>
     <div class="mrow">
         <div class="col-md-12 px-0">
             <div >
@@ -19,26 +20,29 @@
                     <span class="input-group-prepend">
                         <button class="btn btn-secondary btn-saved-searches" type="button" (click)="showSavedSearches()">Searches</button>
                     </span>
-                    <div appAceEditor *ngIf="!hideQueryBuilder" class="flex-fill" placeholder="Search Alerts" [text]="queryBuilder.displayQuery" (textChanged)="onSearch($event)"> </div>
-                    <div class="flex-fill" [class.d-none]="!hideQueryBuilder">
-                        <input class="manual-query-input" data-qe-id="manual-query-input" type="text" #manualQuery >
+                    <div appAceEditor *ngIf="!isQueryBuilderModeManual()" class="flex-fill" placeholder="Search Alerts" [text]="queryBuilder.displayQuery" (textChanged)="onSearch($event)"> </div>
+                    <div class="flex-fill" *ngIf="isQueryBuilderModeManual()">
+                        <input #manualQuery type="text"
+                            class="manual-query-input"
+                            [value]="queryBuilder.getManualQuery()"
+                            data-qe-id="manual-query-input">
                     </div>
                     <span class="input-group-append">
-                        <button class="btn btn-secondary btn-options" (click)="toggleQueryBuilder()">
-                            <span *ngIf="hideQueryBuilder">Use Query Builder</span>
-                            <span *ngIf="!hideQueryBuilder">Use Manual Query</span>
+                        <button class="btn btn-secondary btn-options" (click)="toggleQueryBuilderMode()">
+                            <span *ngIf="isQueryBuilderModeManual()">Use Query Builder</span>
+                            <span *ngIf="!isQueryBuilderModeManual()">Use Manual Query</span>
                         </button>
                     </span>
                     <span class="input-group-append">
                         <button class="btn btn-secondary btn-search-clear" type="button" (click)="onClear()"></button>
                     </span>
-                    <span class="input-group-append" style="white-space: nowrap;" [class.d-none]="hideQueryBuilder">
+                    <span class="input-group-append" style="white-space: nowrap;" [class.d-none]="isQueryBuilderModeManual()">
                         <app-time-range class="d-flex position-relative" (timeRangeChange)="onTimeRangeChange($event)" [disabled]="timeStampFilterPresent" [selectedTimeRange]="selectedTimeRange"> </app-time-range>
                     </span>
-                    <span class="input-group-append" [class.d-none]="hideQueryBuilder">
+                    <span class="input-group-append" [class.d-none]="isQueryBuilderModeManual()">
                         <button data-qe-id="alert-search-btn" class="btn btn-secondary btn-search rounded-right" type="button" data-name="search" (click)="onSearch(alertSearchDirective.getSeacrhText())"></button>
                     </span>
-                    <span class="input-group-append" [class.d-none]="!hideQueryBuilder">
+                    <span class="input-group-append" [class.d-none]="!isQueryBuilderModeManual()">
                         <button class="btn btn-secondary btn-search rounded-right" type="button" data-name="search" (click)="search(false, null)"></button>
                     </span>
                     <div class="input-group-append">
@@ -62,11 +66,15 @@
                 <div #settingsIcon class="btn settings">
                     <i class="fa fa-sliders" aria-hidden="true"></i>
                 </div>
-                <app-configure-rows [srcElement]="settingsIcon" [tableMetaData]="tableMetaData" [(interval)]="refreshInterval" [(size)]="tableMetaData.size" (configRowsChange)="onConfigRowsChange()" > </app-configure-rows>
-                <div class="btn  pause-play" (click)="onPausePlay()">
-                    <i *ngIf="!isRefreshPaused" class="fa fa-pause" aria-hidden="true"></i>
-                    <i *ngIf="isRefreshPaused" class="fa fa-play" aria-hidden="true"></i>
-                </div>
+                <app-configure-rows
+                    [srcElement]="settingsIcon"
+                    [refreshInterval]="autoPollingSvc.getInterval()"
+                    [pageSize]="tableMetaData.size"
+                    (configRowsChange)="onConfigRowsChange($event)"
+                    ></app-configure-rows>
+
+                <app-auto-polling #autoPolling></app-auto-polling>
+
                 <div id="table-actions" class="dropdown d-inline-block">
                     <button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">ACTIONS</button>
                     <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
@@ -84,14 +92,20 @@
 
 <div class="container-fluid no-gutters">
     <div class="row">
-      <div class="px-0" style="width: 200px;max-width: 200px;" [class.d-none]="hideQueryBuilder">
+      <div class="px-0" style="width: 200px;max-width: 200px;" [class.d-none]="isQueryBuilderModeManual()">
         <app-alert-filters [facets]="searchResponse.facetCounts" (facetFilterChange)="onAddFacetFilter($event)"> </app-alert-filters>
       </div>
       <div class="col px-0 pl-4" style="overflow: auto;">
-        <div class="alert alert-warning" role="alert" *ngIf="staleDataState" data-qe-id="staleDataWarning">
-            <i class="fa fa-warning" aria-hidden="true"></i> Data is in a stale state! Click
-            <i class="fa fa-search" aria-hidden="true"></i> to update your view based on your current filter and time-range configuration!
-        </div>
+        <div class="alert alert-warning" role="alert"
+            *ngIf="staleDataState"
+            [innerHTML]="getStaleDataWarning()"
+            data-qe-id="staleDataWarning"
+            ></div>
+        <div class="alert alert-warning" role="alert"
+            *ngIf="autoPollingSvc.getIsCongestion()"
+            [innerHTML]="getPollingCongestionWarning()"
+            data-qe-id="pollingCongestionWarning"
+            ></div>
         <div class="col-xs-12 pl-0 pb-3">
           <app-group-by [facets]="groupFacets" (groupsChange)="onGroupsChange($event)"> </app-group-by>
         </div>
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss
index c39887d..4d27e4e 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss
@@ -197,27 +197,6 @@
   cursor: pointer;
 }
 
-.pause-play {
-  height: 38px;
-  padding: 0px;
-  border: 1px solid #0F6F9E;
-  width: 38px;
-  line-height: 39px;
-  border-radius: 12px;
-  margin-left: 15px;
-  background: $mine-shaft-2;
-  cursor: pointer;
-
-  i {
-    font-size: 17px;
-    color: $piction-blue;
-  }
-
-  .fa-play {
-    padding-left: 3px;
-  }
-}
-
 .settings, .cog {
   height: 38px;
   padding: 0px;
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.spec.ts
index 8cbff8f..74d0f8c 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.spec.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.spec.ts
@@ -16,8 +16,8 @@
  * limitations under the License.
  */
 import { AlertsListComponent } from './alerts-list.component';
-import { ComponentFixture, async, TestBed } from '@angular/core/testing';
-import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { ComponentFixture, async, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { Component, Input, Directive } from '@angular/core';
 import { RouterTestingModule } from '@angular/router/testing';
 import { SearchService } from 'app/service/search.service';
 import { UpdateService } from 'app/service/update.service';
@@ -28,53 +28,136 @@
 import { MetaAlertService } from 'app/service/meta-alert.service';
 import { GlobalConfigService } from 'app/service/global-config.service';
 import { DialogService } from 'app/service/dialog.service';
-import { SearchRequest } from 'app/model/search-request';
-import { Observable, of, Subject } from 'rxjs';
-import { Filter } from 'app/model/filter';
-import { QueryBuilder } from './query-builder';
 import { TIMESTAMP_FIELD_NAME } from 'app/utils/constants';
-import { SearchResponse } from 'app/model/search-response';
 import { By } from '@angular/platform-browser';
+import { Observable, Subject, of, throwError } from 'rxjs';
+import { Filter } from 'app/model/filter';
+import { QueryBuilder, FilteringMode } from './query-builder';
+import { SearchResponse } from 'app/model/search-response';
+import { AutoPollingService } from './auto-polling/auto-polling.service';
+import { Router } from '@angular/router';
+import { Alert } from 'app/model/alert';
+import { AlertSource } from 'app/model/alert-source';
+import { SearchRequest } from 'app/model/search-request';
+import { query } from '@angular/core/src/render3';
+import { RestError } from 'app/model/rest-error';
+import { DialogType } from 'app/shared/metron-dialog/metron-dialog.component';
+
+@Component({
+  selector: 'app-auto-polling',
+  template: '<div></div>',
+})
+class MockAutoPollingComponent {}
+
+@Component({
+  selector: 'app-configure-rows',
+  template: '<div></div>',
+})
+class MockConfigureRowsComponent {
+  @Input() refreshInterval = 0;
+  @Input() srcElement = {};
+  @Input() pageSize = 0;
+}
+
+@Component({
+  selector: 'app-modal-loading-indicator',
+  template: '<div></div>',
+})
+class MockModalLoadingIndicatorComponent {
+  @Input() show = false;
+}
+
+@Component({
+  selector: 'app-time-range',
+  template: '<div></div>',
+})
+class MockTimeRangeComponent {
+  @Input() disabled = false;
+  @Input() selectedTimeRange = {};
+}
+
+@Directive({
+  selector: '[appAceEditor]',
+})
+class MockAceEditorDirective {
+  @Input() text = '';
+}
+
+@Component({
+  selector: 'app-alert-filters',
+  template: '<div></div>',
+})
+class MockAlertFilterComponent {
+  @Input() facets = [];
+}
+
+@Component({
+  selector: 'app-group-by',
+  template: '<div></div>',
+})
+class MockGroupByComponent {
+  @Input() facets = [];
+}
+
+@Component({
+  selector: 'app-table-view',
+  template: '<div></div>',
+})
+class MockTableViewComponent {
+  @Input() alerts = [];
+  @Input() pagination = {};
+  @Input() alertsColumnsToDisplay = [];
+  @Input() selectedAlerts = [];
+}
+
+@Component({
+  selector: 'app-tree-view',
+  template: '<div></div>',
+})
+class MockTreeViewComponent {
+  @Input() alerts = [];
+  @Input() pagination = {};
+  @Input() alertsColumnsToDisplay = [];
+  @Input() selectedAlerts = [];
+  @Input() globalConfig = {};
+  @Input() query = '';
+  @Input() groups = [];
+}
+
 
 describe('AlertsListComponent', () => {
 
   let component: AlertsListComponent;
   let fixture: ComponentFixture<AlertsListComponent>;
-  let searchServiceStub = {
-    search() { return of({
-      total: 0,
-      groupedBy: '',
-      results: [],
-      facetCounts: [],
-      groups: []
-    }) },
-    pollSearch() { return of({}) }
-  }
-  let queryBuilderStub = {
-    addOrUpdateFilter() { return {} },
-    clearSearch() { return {} },
-    generateSelect() { return '*' },
-    isTimeStampFieldPresent() { return {} },
-    filters: [{}],
-    searchRequest: {
-      from: 0
-    }
-  }
 
   let queryBuilder: QueryBuilder;
   let searchService: SearchService;
 
   beforeEach(async(() => {
+
+    const searchResponseFake = new SearchResponse();
+    searchResponseFake.facetCounts = {};
+
     TestBed.configureTestingModule({
-      schemas: [ NO_ERRORS_SCHEMA ],
       imports: [
-        RouterTestingModule.withRoutes([]),
+        RouterTestingModule.withRoutes([{path: 'alerts-list', component: AlertsListComponent}]),
       ],
       declarations: [
         AlertsListComponent,
+        MockAutoPollingComponent,
+        MockModalLoadingIndicatorComponent,
+        MockTimeRangeComponent,
+        MockAceEditorDirective,
+        MockConfigureRowsComponent,
+        MockAlertFilterComponent,
+        MockGroupByComponent,
+        MockTableViewComponent,
+        MockTreeViewComponent,
       ],
       providers: [
-        { provide: SearchService, useValue: searchServiceStub },
+        { provide: SearchService, useClass: () => { return {
+          search: () => of(searchResponseFake),
+        } } },
         { provide: UpdateService, useClass: () => { return {
           alertChanged$: new Observable(),
         } } },
@@ -88,6 +171,7 @@
         } } },
         { provide: SaveSearchService, useClass: () => { return {
           loadSavedSearch$: new Observable(),
+          setCurrentQueryBuilderAndTableColumns: () => {},
         } } },
         { provide: MetaAlertService, useClass: () => { return {
           alertChanged$: new Observable(),
@@ -96,7 +180,29 @@
           get: () => new Observable(),
         } } },
         { provide: DialogService, useClass: () => { return {} } },
-        { provide: QueryBuilder, useValue: queryBuilderStub },
+        { provide: QueryBuilder, useClass: () => { return {
+          filters: [],
+          query: '*',
+          get searchRequest() {
+            return new SearchResponse();
+          },
+          addOrUpdateFilter: () => {},
+          clearSearch: () => {},
+          isTimeStampFieldPresent: () => {},
+          getManualQuery: () => {},
+          setManualQuery: () => {},
+          getFilteringMode: () => {},
+          setFilteringMode: () => {},
+        } } },
+        { provide: AutoPollingService, useClass: () => { return {
+          data: new Subject<SearchResponse>(),
+          getIsCongestion: () => {},
+          getInterval: () => {},
+          getIsPollingActive: () => {},
+          dropNextAndContinue: () => {},
+          onDestroy: () => {},
+          setSuppression: () => {},
+        } } },
       ]
     })
     .compileComponents();
@@ -139,64 +245,110 @@
     expect(fixture.nativeElement.querySelector('[data-qe-id="alert-subgroup-total"]')).toBeNull();
   });
 
-  it('should toggle the query builder with toggleQueryBuilder', () => {
-    component.toggleQueryBuilder();
-    fixture.detectChanges();
-    expect(component.hideQueryBuilder).toBe(true);
+  describe('filtering by query builder or manual query', () => {
+    it('should be able to toggle the query builder mode', () => {
+      spyOn(component, 'setSearchRequestSize');
+      spyOn(queryBuilder, 'setFilteringMode');
 
-    component.hideQueryBuilder = true;
-    component.pagination.from = 0;
-    component.pagination.size = 25;
+      queryBuilder.getFilteringMode = () => FilteringMode.BUILDER;
 
-    fixture.detectChanges();
-    component.toggleQueryBuilder();
-    expect(component.hideQueryBuilder).toBe(false);
+      component.toggleQueryBuilderMode();
+      expect(queryBuilder.setFilteringMode).toHaveBeenCalledWith(FilteringMode.MANUAL);
+
+      queryBuilder.getFilteringMode = () => FilteringMode.MANUAL;
+
+      component.toggleQueryBuilderMode();
+      expect(queryBuilder.setFilteringMode).toHaveBeenCalledWith(FilteringMode.BUILDER);
+    });
+
+    it('isQueryBuilderModeManual should return true if queryBuilder is in manual mode', () => {
+      queryBuilder.getFilteringMode = () => FilteringMode.MANUAL;
+      expect(component.isQueryBuilderModeManual()).toBe(true);
+
+      queryBuilder.getFilteringMode = () => FilteringMode.BUILDER;
+      expect(component.isQueryBuilderModeManual()).toBe(false);
+    });
+
+    it('should show manual input dom element depending on mode', () => {
+      let input = fixture.debugElement.query(By.css('[data-qe-id="manual-query-input"]'));
+
+      expect(input).toBeFalsy();
+
+      queryBuilder.getFilteringMode = () => FilteringMode.MANUAL;
+      fixture.detectChanges();
+      input = fixture.debugElement.query(By.css('[data-qe-id="manual-query-input"]'));
+
+      expect(input).toBeTruthy();
+
+      queryBuilder.getFilteringMode = () => FilteringMode.BUILDER;
+      fixture.detectChanges();
+      input = fixture.debugElement.query(By.css('[data-qe-id="manual-query-input"]'));
+
+      expect(input).toBeFalsy();
+    });
+
+    it('should bind default manual query from query builder', () => {
+      spyOn(queryBuilder, 'getManualQuery').and.returnValue('test manual query string')
+
+      queryBuilder.getFilteringMode = () => FilteringMode.MANUAL;
+      fixture.detectChanges();
+      let input: HTMLInputElement = fixture.debugElement.query(By.css('[data-qe-id="manual-query-input"]')).nativeElement;
+
+      expect(input.value).toBe('test manual query string');
+    });
+
+    it('should pass the manual query value to the query builder when editing mode is manual', fakeAsync(() => {
+      spyOn(queryBuilder, 'setManualQuery');
+
+      queryBuilder.getFilteringMode = () => FilteringMode.MANUAL;
+      fixture.detectChanges();
+
+      const input = fixture.debugElement.query(By.css('[data-qe-id="manual-query-input"]'));
+      const el = input.nativeElement;
+
+      el.value = 'test';
+      (el as HTMLElement).dispatchEvent(new Event('keyup'));
+      fixture.detectChanges();
+      tick(300);
+
+      expect(queryBuilder.setManualQuery).toHaveBeenCalledWith('test');
+    }));
   });
 
-  it('should pass the manual query value when hideQueryBuilder is true', () => {
-    const input = fixture.debugElement.query(By.css('[data-qe-id="manual-query-input"]'));
-    const el = input.nativeElement;
+  describe('handling pending search requests', () => {
+    it('should set pendingSearch on search', () => {
+      spyOn(searchService, 'search').and.returnValue(of(new SearchResponse()));
+      spyOn(component, 'saveCurrentSearch');
+      spyOn(component, 'setSearchRequestSize');
+      spyOn(component, 'setSelectedTimeRange');
+      spyOn(component, 'createGroupFacets');
 
-    expect(component.queryForTreeView()).toBe('*');
+      component.search();
+      expect(component.pendingSearch).toBeTruthy();
+    });
 
-    component.toggleQueryBuilder();
-    fixture.detectChanges();
-    expect(component.hideQueryBuilder).toBe(true);
+    it('should clear pendingSearch on search success', (done) => {
+      const fakeObservable = new Subject();
+      spyOn(searchService, 'search').and.returnValue(fakeObservable);
+      spyOn(component, 'saveCurrentSearch');
+      spyOn(component, 'setSearchRequestSize');
+      spyOn(component, 'setSelectedTimeRange');
+      spyOn(component, 'createGroupFacets');
 
-    el.value = 'test';
-    expect(component.queryForTreeView()).toBe('test');
-  });
+      component.search();
 
-  it('should build a new search request if hideQueryBuilder is true', () => {
-    const input = fixture.debugElement.query(By.css('[data-qe-id="manual-query-input"]'));
-    const el = input.nativeElement;
-    const searchServiceSpy = spyOn(searchService, 'search').and.returnValue(of());
-    const newSearch = new SearchRequest();
+      setTimeout(() => {
+        fakeObservable.next(new SearchResponse());
+      }, 0);
 
-    el.value = 'test';
-    component.hideQueryBuilder = true;
-    component.pagination.size = 25;
-    newSearch.query = 'test'
-    newSearch.size = 25
-    newSearch.from = 0;
-
-    fixture.detectChanges();
-    component.search();
-    expect(searchServiceSpy).toHaveBeenCalledWith(newSearch);
-  });
-
-  it('should poll with new search request if isRefreshPaused is true and manualSearch is present', () => {
-    const searchServiceSpy = spyOn(searchService, 'pollSearch').and.returnValue(of());
-    const newSearch = new SearchRequest();
-
-    component.isRefreshPaused = false;
-    fixture.detectChanges();
-    component.tryStartPolling(newSearch);
-    expect(searchServiceSpy).toHaveBeenCalledWith(newSearch);
+      fakeObservable.subscribe(() => {
+        expect(component.pendingSearch).toBe(null);
+        done();
+      })
+    });
   });
 
   describe('stale data state', () => {
-
     it('should set staleDataState flag to true on filter change', () => {
       expect(component.staleDataState).toBe(false);
       component.onAddFilter(new Filter('ip_src_addr', '0.0.0.0'));
@@ -204,7 +356,7 @@
     });
 
     it('should set staleDataState flag to true on filter clearing', () => {
-      queryBuilder.clearSearch = jasmine.createSpy('clearSearch');
+      spyOn(component, 'setSearchRequestSize');
 
       expect(component.staleDataState).toBe(false);
       component.onClear();
@@ -229,6 +381,21 @@
       expect(component.staleDataState).toBe(false);
     });
 
+    it('should set stale date true when query changes in manual mode', fakeAsync(() => {
+      queryBuilder.getFilteringMode = () => FilteringMode.MANUAL;
+
+      fixture.detectChanges();
+      const input = fixture.debugElement.query(By.css('[data-qe-id="manual-query-input"]'));
+      const el = input.nativeElement;
+
+      el.value = 'test';
+      (el as HTMLElement).dispatchEvent(new Event('keyup'));
+      fixture.detectChanges();
+      tick(300);
+
+      expect(component.staleDataState).toBe(true);
+    }));
+
     it('should show warning if data is in a stale state', () => {
       expect(fixture.debugElement.query(By.css('[data-qe-id="staleDataWarning"]'))).toBe(null);
 
@@ -238,6 +405,200 @@
       expect(fixture.debugElement.query(By.css('[data-qe-id="staleDataWarning"]'))).toBeTruthy();
     });
 
-  })
+  });
 
+  describe('auto polling', () => {
+    it('should refresh view on data emit', () => {
+      const fakeResponse = new SearchResponse();
+      spyOn(component, 'setData');
+
+      TestBed.get(AutoPollingService).data.next(fakeResponse);
+
+      expect(component.setData).toHaveBeenCalledWith(fakeResponse);
+    });
+
+    it('should set staleDataState false on auto polling refresh', () => {
+      spyOn(component, 'setData');
+      component.staleDataState = true;
+
+      TestBed.get(AutoPollingService).data.next(new SearchResponse());
+
+      expect(component.staleDataState).toBe(false);
+    });
+
+    it('should show warning on auto polling congestion', () => {
+      expect(fixture.debugElement.query(By.css('[data-qe-id="pollingCongestionWarning"]'))).toBeFalsy();
+
+      TestBed.get(AutoPollingService).getIsCongestion = () => true;
+      fixture.detectChanges();
+
+      expect(fixture.debugElement.query(By.css('[data-qe-id="pollingCongestionWarning"]'))).toBeTruthy();
+
+      TestBed.get(AutoPollingService).getIsCongestion = () => false;
+      fixture.detectChanges();
+
+      expect(fixture.debugElement.query(By.css('[data-qe-id="pollingCongestionWarning"]'))).toBeFalsy();
+    });
+
+    it('should pass refresh interval to row config component', () => {
+      TestBed.get(AutoPollingService).getInterval = () => 44;
+      fixture.detectChanges();
+
+      expect(fixture.debugElement.query(By.directive(MockConfigureRowsComponent)).componentInstance.refreshInterval).toBe(44);
+    });
+
+    it('should drop pending auto polling result if user trigger search request manually', () => {
+      const autoPollingSvc = TestBed.get(AutoPollingService);
+      spyOn(autoPollingSvc, 'dropNextAndContinue');
+      spyOn(component, 'setSearchRequestSize');
+
+      autoPollingSvc.getIsPollingActive = () => false;
+      component.search()
+
+      expect(autoPollingSvc.dropNextAndContinue).not.toHaveBeenCalled();
+
+      autoPollingSvc.getIsPollingActive = () => true;
+      component.search()
+
+      expect(autoPollingSvc.dropNextAndContinue).toHaveBeenCalled();
+    });
+
+    it('should show different stale data warning when polling is active', () => {
+      const autoPollingSvc = TestBed.get(AutoPollingService);
+
+      autoPollingSvc.getIsPollingActive = () => false;
+      const warning = component.getStaleDataWarning();
+
+      autoPollingSvc.getIsPollingActive = () => true;
+      const warningWhenPolling = component.getStaleDataWarning();
+
+      expect(warning).not.toEqual(warningWhenPolling);
+    });
+
+    it('should show getIsCongestion scennarios', () => {
+      const autoPollingSvc = TestBed.get(AutoPollingService);
+
+      autoPollingSvc.getIsCongestion = () => false;
+      fixture.detectChanges();
+      expect(fixture.debugElement.query(By.css('[data-qe-id="pollingCongestionWarning"]'))).toBeFalsy();
+
+      autoPollingSvc.getIsCongestion = () => true;
+      fixture.detectChanges();
+      expect(fixture.debugElement.query(By.css('[data-qe-id="pollingCongestionWarning"]'))).toBeTruthy();
+
+    });
+
+    it('should suppress polling when user select alerts', () => {
+      const autoPollingSvc = TestBed.get(AutoPollingService);
+      spyOn(autoPollingSvc, 'setSuppression');
+
+      component.onSelectedAlertsChange([{ source: { metron_alert: [] } }]);
+
+      expect(autoPollingSvc.setSuppression).toHaveBeenCalledWith(true);
+    });
+
+    it('should restore polling from suppression when user deselect alerts', () => {
+      const autoPollingSvc = TestBed.get(AutoPollingService);
+      spyOn(autoPollingSvc, 'setSuppression');
+
+      component.onSelectedAlertsChange([]);
+
+      expect(autoPollingSvc.setSuppression).toHaveBeenCalledWith(false);
+    });
+
+    it('should suppress polling when open details pane', () => {
+      const autoPollingSvc = TestBed.get(AutoPollingService);
+      const router = TestBed.get(Router);
+      spyOn(router, 'navigate').and.returnValue(true);
+      spyOn(router, 'navigateByUrl').and.returnValue(true);
+      spyOn(autoPollingSvc, 'setSuppression');
+
+      component.showConfigureTable();
+
+      expect(autoPollingSvc.setSuppression).toHaveBeenCalledWith(true);
+    });
+
+    it('should suppress polling when open column config pane', () => {
+      const router = TestBed.get(Router);
+      const autoPollingSvc = TestBed.get(AutoPollingService);
+      spyOn(router, 'navigate');
+      spyOn(router, 'navigateByUrl');
+      spyOn(autoPollingSvc, 'setSuppression');
+
+      const fakeAlert = new Alert();
+      fakeAlert.source = new AlertSource();
+
+      component.showDetails(fakeAlert);
+
+      expect(autoPollingSvc.setSuppression).toHaveBeenCalledWith(true);
+    });
+
+    it('should suppress polling when open Saved Searches pane', () => {
+      const router = TestBed.get(Router);
+      const autoPollingSvc = TestBed.get(AutoPollingService);
+      spyOn(router, 'navigate');
+      spyOn(router, 'navigateByUrl');
+      spyOn(autoPollingSvc, 'setSuppression');
+
+      component.showSavedSearches();
+
+      expect(autoPollingSvc.setSuppression).toHaveBeenCalledWith(true);
+    });
+
+    it('should suppress polling when open Save Search dialogue pane', () => {
+      const router = TestBed.get(Router);
+      const autoPollingSvc = TestBed.get(AutoPollingService);
+      const saveSearchSvc = TestBed.get(SaveSearchService);
+      spyOn(router, 'navigate');
+      spyOn(router, 'navigateByUrl');
+      spyOn(autoPollingSvc, 'setSuppression');
+      spyOn(saveSearchSvc, 'setCurrentQueryBuilderAndTableColumns');
+
+      component.showSaveSearch();
+
+      expect(autoPollingSvc.setSuppression).toHaveBeenCalledWith(true);
+    });
+
+    it('should restore the polling supression on bulk status update (other scenario of deselecting alerts)', () => {
+      const autoPollingSvc = TestBed.get(AutoPollingService);
+      spyOn(autoPollingSvc, 'setSuppression');
+
+      component.updateSelectedAlertStatus('fakeState');
+
+      expect(autoPollingSvc.setSuppression).toHaveBeenCalledWith(false);
+    });
+
+    it('should restore the polling supression when returning from a subroute', fakeAsync(() => {
+      const autoPollingSvc = TestBed.get(AutoPollingService);
+      spyOn(autoPollingSvc, 'setSuppression');
+
+      autoPollingSvc.getIsPollingActive = () => false;
+      fixture.ngZone.run(() => {
+        TestBed.get(Router).navigate(['/alerts-list']);
+      });
+
+      expect(autoPollingSvc.setSuppression).not.toHaveBeenCalled();
+
+      autoPollingSvc.getIsPollingActive = () => true;
+      fixture.ngZone.run(() => {
+        TestBed.get(Router).navigate(['/alerts-list']);
+      });
+
+      expect(autoPollingSvc.setSuppression).toHaveBeenCalledWith(false);
+    }));
+  });
+
+  describe('search', () => {
+    it('should show notification on http error', fakeAsync(() => {
+      const fakeDialogService = TestBed.get(DialogService);
+
+      spyOn(searchService, 'search').and.returnValue(throwError(new RestError()));
+      fakeDialogService.launchDialog = () => {};
+      spyOn(fakeDialogService, 'launchDialog');
+
+      component.search();
+
+      expect(fakeDialogService.launchDialog).toHaveBeenCalledWith('Server were unable to apply query string.', DialogType.Error);
+    }));
+  });
 });
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts
old mode 100644
new mode 100755
index 2cd34a5..a644b76
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts
@@ -15,28 +15,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {forkJoin as observableForkJoin} from 'rxjs';
-import {Component, OnInit, ViewChild, ElementRef, OnDestroy, ChangeDetectorRef} from '@angular/core';
+import {forkJoin, noop, fromEvent} from 'rxjs';
+import {Component, OnInit, ViewChild, ElementRef, OnDestroy, ChangeDetectorRef, AfterViewInit} from '@angular/core';
 import {Router, NavigationStart} from '@angular/router';
 import {Subscription} from 'rxjs';
 
 import {Alert} from '../../model/alert';
 import {SearchService} from '../../service/search.service';
 import {UpdateService} from '../../service/update.service';
-import {QueryBuilder} from './query-builder';
+import {QueryBuilder, FilteringMode} from './query-builder';
 import {ConfigureTableService} from '../../service/configure-table.service';
 import {AlertsService} from '../../service/alerts.service';
 import {ClusterMetaDataService} from '../../service/cluster-metadata.service';
 import {ColumnMetadata} from '../../model/column-metadata';
 import {SaveSearchService} from '../../service/save-search.service';
-import {RefreshInterval} from '../configure-rows/configure-rows-enums';
 import {SaveSearch} from '../../model/save-search';
 import {TableMetadata} from '../../model/table-metadata';
 import {AlertSearchDirective} from '../../shared/directives/alert-search.directive';
 import {SearchResponse} from '../../model/search-response';
 import {ElasticsearchUtils} from '../../utils/elasticsearch-utils';
 import {Filter} from '../../model/filter';
-import { TIMESTAMP_FIELD_NAME, ALL_TIME, POLLING_DEFAULT_STATE } from '../../utils/constants';
+import { TIMESTAMP_FIELD_NAME, ALL_TIME } from '../../utils/constants';
 import {TableViewComponent, PageChangedEvent, SortChangedEvent} from './table-view/table-view.component';
 import {Pagination} from '../../model/pagination';
 import {MetaAlertService} from '../../service/meta-alert.service';
@@ -46,14 +45,16 @@
 import { DialogType } from 'app/model/dialog-type';
 import { Utils } from 'app/utils/utils';
 import { AlertSource } from '../../model/alert-source';
+import { AutoPollingService } from './auto-polling/auto-polling.service';
+import { ConfigureRowsModel } from '../configure-rows/configure-rows.component';
 import { SearchRequest } from 'app/model/search-request';
+import { switchMap, map, debounceTime } from 'rxjs/operators';
 
 @Component({
   selector: 'app-alerts-list',
   templateUrl: './alerts-list.component.html',
   styleUrls: ['./alerts-list.component.scss']
 })
-
 export class AlertsListComponent implements OnInit, OnDestroy {
 
   alertsColumns: ColumnMetadata[] = [];
@@ -62,10 +63,7 @@
   alerts: Alert[] = [];
   searchResponse: SearchResponse = new SearchResponse();
   colNumberTimerId: number;
-  refreshInterval = RefreshInterval.TEN_MIN;
-  refreshTimer: Subscription;
-  isRefreshPaused = POLLING_DEFAULT_STATE;
-  lastIsRefreshPausedValue = false;
+
   isMetaAlertPresentInSelectedAlerts = false;
   timeStampFilterPresent = false;
 
@@ -75,7 +73,18 @@
   @ViewChild('table') table: ElementRef;
   @ViewChild('dataViewComponent') dataViewComponent: TableViewComponent;
   @ViewChild(AlertSearchDirective) alertSearchDirective: AlertSearchDirective;
-  @ViewChild('manualQuery') manualQuery: ElementRef;
+
+  private manualQueryFieldChangeSubs: Subscription;
+  private manualQueryInputEl: ElementRef;
+  @ViewChild('manualQuery') set manualQuery(el: ElementRef) {
+    if (el && !this.manualQueryInputEl) {
+      this.manualQueryInputEl = el;
+      this.manualQueryFieldChangeSubs = this.addManualQueryFieldChangeStream(el.nativeElement);
+    }
+  };
+  get manualQuery(): ElementRef {
+    return this.manualQueryInputEl;
+  }
 
   tableMetaData = new TableMetadata();
   pagination: Pagination = new Pagination();
@@ -85,8 +94,8 @@
   configSubscription: Subscription;
   groups = [];
   subgroupTotal = 0;
-  hideQueryBuilder = false;
 
+  pendingSearch: Subscription;
   staleDataState = false;
 
   constructor(private router: Router,
@@ -99,17 +108,23 @@
               private metaAlertsService: MetaAlertService,
               private globalConfigService: GlobalConfigService,
               private dialogService: DialogService,
+              private cdRef: ChangeDetectorRef,
               public queryBuilder: QueryBuilder,
-              private cdRef: ChangeDetectorRef) {
+              public autoPollingSvc: AutoPollingService) {
     router.events.subscribe(event => {
       if (event instanceof NavigationStart && event.url === '/alerts-list') {
         this.selectedAlerts = [];
-        this.restoreRefreshState();
+        this.restoreAutoPollingState();
       }
     });
+
+    autoPollingSvc.data.subscribe((result: SearchResponse) => {
+      this.setData(result);
+      this.staleDataState = false;
+    })
   }
 
-  addAlertChangedListner() {
+  addAlertChangedListener() {
     this.metaAlertsService.alertChanged$.subscribe(alertSource => {
       if (alertSource['status'] === 'inactive') {
         this.removeAlert(alertSource)
@@ -122,7 +137,7 @@
     });
   }
 
-  addAlertColChangedListner() {
+  addAlertColChangedListener() {
     this.configureTableService.tableChanged$.subscribe(colChanged => {
       if (colChanged) {
         this.getAlertColumnNames(false);
@@ -130,7 +145,7 @@
     });
   }
 
-  addLoadSavedSearchListner() {
+  addLoadSavedSearchListener() {
     this.saveSearchService.loadSavedSearch$.subscribe((savedSearch: SaveSearch) => {
       this.queryBuilder.searchRequest = savedSearch.searchRequest;
       this.queryBuilder.filters = savedSearch.filters;
@@ -170,7 +185,7 @@
   }
 
   getAlertColumnNames(resetPaginationForSearch: boolean) {
-    observableForkJoin(
+    forkJoin(
         this.configureTableService.getTableMetadata(),
         this.clusterMetaDataService.getDefaultColumns()
     ).subscribe((response: any) => {
@@ -193,9 +208,13 @@
   }
 
   ngOnDestroy() {
-    this.tryStopPolling();
+    this.autoPollingSvc.onDestroy();
     this.removeAlertChangedListner();
     this.configSubscription.unsubscribe();
+
+    if (this.manualQueryFieldChangeSubs) {
+      this.manualQueryFieldChangeSubs.unsubscribe();
+    }
   }
 
   ngOnInit() {
@@ -212,9 +231,23 @@
 
     this.setDefaultTimeRange(this.DEFAULT_TIME_RANGE);
     this.getAlertColumnNames(true);
-    this.addAlertColChangedListner();
-    this.addLoadSavedSearchListner();
-    this.addAlertChangedListner();
+    this.addAlertColChangedListener();
+    this.addLoadSavedSearchListener();
+    this.addAlertChangedListener();
+  }
+
+  private addManualQueryFieldChangeStream(inputDomEl: HTMLInputElement) {
+    return fromEvent<KeyboardEvent>(inputDomEl, 'keyup').pipe(
+      map(event => (event.target as HTMLInputElement).value),
+      debounceTime(300),
+    ).subscribe((manualQuery) => {
+      this.onManualQueryInputChange(manualQuery);
+    });
+  }
+
+  private onManualQueryInputChange(value: string) {
+    this.queryBuilder.setManualQuery(value);
+    this.staleDataState = true;
   }
 
   private setDefaultTimeRange(timeRangeId: string) {
@@ -226,8 +259,6 @@
   onClear() {
     this.timeStampFilterPresent = false;
     this.queryBuilder.clearSearch();
-    if (this.hideQueryBuilder) { this.manualQuery.nativeElement.value = '*'; }
-    this.search();
     this.staleDataState = true;
   }
 
@@ -258,11 +289,7 @@
       alert => (alert.source.metron_alert && alert.source.metron_alert.length > 0)
     );
 
-    if (selectedAlerts.length > 0) {
-      this.pause();
-    } else {
-      this.resume();
-    }
+    this.autoPollingSvc.setSuppression(!!selectedAlerts.length);
   }
 
   onAddFilter(filter: Filter) {
@@ -271,9 +298,24 @@
     this.staleDataState = true;
   }
 
-  onConfigRowsChange() {
-    this.searchService.interval = this.refreshInterval;
-    this.search();
+  onConfigRowsChange(config: ConfigureRowsModel) {
+    const { values, triggerQuery } = config;
+
+    this.tableMetaData.size = values.pageSize;
+    this.updatePollingInterval(values.refreshInterval);
+    this.saveSaveRowsConfig();
+
+    if (triggerQuery) {
+      this.search();
+    }
+  }
+
+  private saveSaveRowsConfig() {
+    this.configureTableService
+      .saveTableMetaData(this.tableMetaData).subscribe(
+        noop,
+        () => console.log('Unable to save settings ....')
+      );
   }
 
   onGroupsChange(groups) {
@@ -282,15 +324,6 @@
     this.search();
   }
 
-  onPausePlay() {
-    this.isRefreshPaused = !this.isRefreshPaused;
-    if (this.isRefreshPaused) {
-      this.tryStopPolling();
-    } else {
-      this.search(false);
-    }
-  }
-
   onResize() {
     clearTimeout(this.colNumberTimerId);
     this.colNumberTimerId = window.setTimeout(() => { this.calcColumnsToDisplay(); }, 500);
@@ -311,16 +344,12 @@
 
   prepareColumnData(configuredColumns: ColumnMetadata[], defaultColumns: ColumnMetadata[]) {
     this.alertsColumns = (configuredColumns && configuredColumns.length > 0) ? configuredColumns : defaultColumns;
-    this.queryBuilder.setFields(this.getColumnNamesForQuery());
     this.calcColumnsToDisplay();
   }
 
   prepareData(tableMetaData: TableMetadata, defaultColumns: ColumnMetadata[]) {
-    this.tableMetaData = tableMetaData;
-    this.refreshInterval = this.tableMetaData.refreshInterval;
-
-    this.updateConfigRowsSettings();
     this.prepareColumnData(tableMetaData.tableColumns, defaultColumns);
+    this.tableMetaData = tableMetaData;
   }
 
   preventDropdownOptionIfDisabled(event: Event): boolean {
@@ -373,11 +402,6 @@
     }
   }
 
-  restoreRefreshState() {
-    this.isRefreshPaused = this.lastIsRefreshPausedValue;
-    this.tryStartPolling();
-  }
-
   search(resetPaginationParams = true, savedSearch?: SaveSearch) {
     if (savedSearch) { this.saveCurrentSearch(savedSearch); }
     if (resetPaginationParams) {
@@ -386,31 +410,19 @@
 
     this.setSearchRequestSize();
 
-    if (this.hideQueryBuilder) {
-      const newSearch = new SearchRequest();
-      newSearch.query = this.manualQuery.nativeElement.value;
-      newSearch.size = this.pagination.size;
-      newSearch.from = 0;
-
-      this.searchService.search(newSearch).subscribe(results => {
+    this.pendingSearch = this.searchService.search(this.queryBuilder.searchRequest).subscribe(
+      results => {
         this.setData(results);
+        this.pendingSearch = null;
         this.staleDataState = false;
       }, error => {
         this.setData(new SearchResponse());
-        this.dialogService.launchDialog(ElasticsearchUtils.extractESErrorMessage(error), DialogType.Error);
+        this.pendingSearch = null;
+        this.dialogService.launchDialog('Server were unable to apply query string.', DialogType.Error);
       });
 
-      this.tryStartPolling(newSearch);
-    } else {
-        this.searchService.search(this.queryBuilder.searchRequest).subscribe(results => {
-        this.setData(results);
-        this.staleDataState = false;
-      }, error => {
-        this.setData(new SearchResponse());
-        this.dialogService.launchDialog(ElasticsearchUtils.extractESErrorMessage(error), DialogType.Error);
-      });
-
-      this.tryStartPolling();
+    if (this.autoPollingSvc.getIsPollingActive()) {
+      this.autoPollingSvc.dropNextAndContinue();
     }
   }
 
@@ -460,69 +472,30 @@
   }
 
   showConfigureTable() {
-    this.saveRefreshState();
+    this.autoPollingSvc.setSuppression(true);
     this.router.navigateByUrl('/alerts-list(dialog:configure-table)');
   }
 
   showDetails(alert: Alert) {
     this.selectedAlerts = [];
     this.selectedAlerts = [alert];
-    this.saveRefreshState();
+    this.autoPollingSvc.setSuppression(true);
     let sourceType = alert.source[this.globalConfig['source.type.field']];
     let url = '/alerts-list(dialog:details/' + sourceType + '/' + alert.source.guid + '/' + alert.index + ')';
     this.router.navigateByUrl(url);
   }
 
-  saveRefreshState() {
-    this.lastIsRefreshPausedValue = this.isRefreshPaused;
-    this.tryStopPolling();
-  }
-
-  pause() {
-    this.isRefreshPaused = true;
-    this.tryStopPolling();
-  }
-
-  resume() {
-    this.isRefreshPaused = false;
-    this.tryStartPolling();
-  }
-
   showSavedSearches() {
-    this.saveRefreshState();
+    this.autoPollingSvc.setSuppression(true);
     this.router.navigateByUrl('/alerts-list(dialog:saved-searches)');
   }
 
   showSaveSearch() {
-    this.saveRefreshState();
+    this.autoPollingSvc.setSuppression(true);
     this.saveSearchService.setCurrentQueryBuilderAndTableColumns(this.queryBuilder, this.alertsColumns);
     this.router.navigateByUrl('/alerts-list(dialog:save-search)');
   }
 
-  tryStartPolling(manualSearch?: SearchRequest) {
-    if (!this.isRefreshPaused && !manualSearch) {
-      this.tryStopPolling();
-      this.refreshTimer = this.searchService.pollSearch(this.queryBuilder.searchRequest).subscribe(results => {
-        this.setData(results);
-      });
-    } else if (!this.isRefreshPaused && manualSearch) {
-      this.tryStopPolling();
-      this.refreshTimer = this.searchService.pollSearch(manualSearch).subscribe(results => {
-        this.setData(results);
-      });
-    }
-  }
-
-  tryStopPolling() {
-    if (this.refreshTimer && !this.refreshTimer.closed) {
-      this.refreshTimer.unsubscribe();
-    }
-  }
-
-  updateConfigRowsSettings() {
-    this.searchService.interval = this.refreshInterval;
-  }
-
   updateAlert(alertSource: AlertSource) {
     this.alerts.filter(alert => alert.source.guid === alertSource.guid)
             .map(alert => alert.source = alertSource);
@@ -537,7 +510,7 @@
       selectedAlert.source['alert_status'] = status;
     }
     this.selectedAlerts = [];
-    this.resume();
+    this.autoPollingSvc.setSuppression(false);
   }
 
   removeAlertChangedListner() {
@@ -549,24 +522,52 @@
     this.cdRef.detectChanges();
   }
 
-  toggleQueryBuilder() {
-    this.setSelectedTimeRange([this.selectedTimeRange]);
-    if (!this.hideQueryBuilder) {
-      this.hideQueryBuilder = true;
-      this.manualQuery.nativeElement.value = this.queryBuilder.query;
+  getStaleDataWarning() {
+    if (this.autoPollingSvc.getIsPollingActive()) {
+      return `<i class="fa fa-warning" aria-hidden="true"></i> Data is in a stale state!
+        Click <i class="fa fa-search" aria-hidden="true"></i> to update your view based
+        on your current filter and time-range configuration!`;
     } else {
-      this.hideQueryBuilder = false;
+      return `<i class="fa fa-warning" aria-hidden="true"></i> Data is in a stale state!
+        Automatic refresh is turned on. Your filter and/or time-range changes will apply automatically on next refresh.`;
+    }
+  }
+
+  getPollingCongestionWarning() {
+    return `<i class="fa fa-warning" aria-hidden="true"></i> Refresh interval is shorter than the response time.
+      Please increase the refresh interval in the <i class="fa fa-sliders" aria-hidden="true"></i> menu above,
+      or try to simplify your query filter.`;
+  }
+
+  private updatePollingInterval(refreshInterval: number): void {
+    this.autoPollingSvc.setInterval(refreshInterval);
+  }
+
+  private restoreAutoPollingState() {
+    if (this.autoPollingSvc.getIsPollingActive()) {
+      this.autoPollingSvc.setSuppression(false);
+    }
+  }
+
+  isQueryBuilderModeManual() {
+    return this.queryBuilder.getFilteringMode() === FilteringMode.MANUAL;
+  }
+
+  toggleQueryBuilderMode() {
+    // FIXME setting timerange on toggle feels like a hack
+    this.setSelectedTimeRange([this.selectedTimeRange]);
+    if (this.queryBuilder.getFilteringMode() === FilteringMode.BUILDER) {
+      this.queryBuilder.setFilteringMode(FilteringMode.MANUAL);
+    } else {
+      this.queryBuilder.setFilteringMode(FilteringMode.BUILDER);
+      // FIXME: this could lead to a large blocking load depending on the response time
       this.queryBuilder.clearSearch();
       this.search();
     }
   }
 
   queryForTreeView() {
-    if (!this.hideQueryBuilder) {
-      return this.queryBuilder.generateSelect();
-    } else {
-      return this.manualQuery.nativeElement.value;
-    }
+    return this.queryBuilder.query;
   }
 
 }
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts
old mode 100644
new mode 100755
index 1126f14..2adcb90
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts
@@ -31,6 +31,9 @@
 import { AlertFiltersComponent } from './alert-filters/alert-filters.component';
 import { TableViewComponent } from './table-view/table-view.component';
 import { TreeViewComponent } from './tree-view/tree-view.component';
+import { ModalLoadingIndicatorComponent } from 'app/shared/modal-loading-indicator/modal-loading-indicator.component';
+import { AutoPollingComponent } from './auto-polling/auto-polling.component';
+import { AutoPollingService } from './auto-polling/auto-polling.service';
 
 @NgModule({
   imports: [
@@ -49,8 +52,13 @@
     AlertsListComponent,
     TableViewComponent,
     TreeViewComponent,
-    AlertFiltersComponent
+    AlertFiltersComponent,
+    ModalLoadingIndicatorComponent,
+    AutoPollingComponent,
   ],
-  providers: [ DecimalPipe ]
+  providers: [
+    DecimalPipe,
+    AutoPollingService,
+  ]
 })
 export class AlertsListModule {}
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.component.html b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.component.html
new file mode 100755
index 0000000..a4d925d
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.component.html
@@ -0,0 +1,17 @@
+<!--
+	Licensed to the Apache Software
+	Foundation (ASF) under one or more contributor license agreements. See the
+	NOTICE file distributed with this work for additional information regarding
+	copyright ownership. The ASF licenses this file to You under the Apache License,
+	Version 2.0 (the "License"); you may not use this file except in compliance
+	with the License. You may obtain a copy of the License at
+  	http://www.apache.org/licenses/LICENSE-2.0
+  	Unless required by applicable law or agreed to in writing, software distributed
+	under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+	OR CONDITIONS OF ANY KIND, either express or implied. See the License for
+  the specific language governing permissions and limitations under the License.
+-->
+<div class="btn pause-play" (click)="onToggle()">
+	<i *ngIf="autoPollingSvc.getIsPollingActive()" class="fa fa-pause" aria-hidden="true"></i>
+	<i *ngIf="!autoPollingSvc.getIsPollingActive()" class="fa fa-play" aria-hidden="true"></i>
+</div>
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.component.scss b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.component.scss
new file mode 100644
index 0000000..ebaeb99
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.component.scss
@@ -0,0 +1,73 @@
+/**
+ * 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 "../../../../vendor.scss";
+@import "../../../../variables.scss";
+
+.pause-play {
+  height: 38px;
+  padding: 0px;
+  border: 1px solid #0F6F9E;
+  width: 38px;
+  line-height: 35px;
+  border-radius: 12px;
+  margin-left: 15px;
+  background: $mine-shaft-2;
+  cursor: pointer;
+
+  i {
+    font-size: 17px;
+    color: $piction-blue;
+  }
+
+  .fa-play {
+    padding-left: 3px;
+  }
+}
+
+.auto-polling {
+  font-size: 0.9rem;
+
+  button.btn-sm {
+    font-size: 0.75rem;
+  }
+
+  button.btn-light {
+    font-size: 0.75rem;
+    background-color: #e1e1e1;
+    border-color: #d2d2d2;
+  }
+}
+
+.card {
+  width: 270px;
+  position: absolute;
+  left: -34px;
+  z-index: 1;
+  top: 50px;
+  border-radius: 3;
+  background: $mine-shaft-2;
+}
+
+.fa-sort-asc {
+  position: absolute;
+  bottom: -40px;
+  left: 96px;
+  font-size: 42px;
+  color: #333333;
+  z-index: 2;
+}
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.component.spec.ts
new file mode 100644
index 0000000..21d966f
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.component.spec.ts
@@ -0,0 +1,80 @@
+/**
+ * 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AutoPollingComponent } from './auto-polling.component';
+import { AutoPollingService } from './auto-polling.service';
+
+describe('AutoPollingComponent', () => {
+  let component: AutoPollingComponent;
+  let fixture: ComponentFixture<AutoPollingComponent>;
+  let autoPollingSvc: AutoPollingService;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ AutoPollingComponent ],
+      providers: [
+        { provide: AutoPollingService, useClass: () => { return {
+          getIsPollingActive: () => {},
+          start: () => {},
+          stop: () => {},
+        } } },
+      ],
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(AutoPollingComponent);
+    component = fixture.componentInstance;
+
+    autoPollingSvc = TestBed.get(AutoPollingService);
+
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should have auto polling service injected', () => {
+    expect(component.autoPollingSvc).toBeTruthy();
+  });
+
+  it('toggle should call stop on svc when polling is active', () => {
+    spyOn(autoPollingSvc, 'getIsPollingActive').and.returnValue(true);
+    spyOn(autoPollingSvc, 'stop');
+    spyOn(autoPollingSvc, 'start');
+
+    component.onToggle();
+
+    expect(autoPollingSvc.start).not.toHaveBeenCalled();
+    expect(autoPollingSvc.stop).toHaveBeenCalled();
+  });
+
+  it('toggle should call start on svc when polling is inactive', () => {
+    spyOn(autoPollingSvc, 'getIsPollingActive').and.returnValue(false);
+    spyOn(autoPollingSvc, 'stop');
+    spyOn(autoPollingSvc, 'start');
+
+    component.onToggle();
+
+    expect(autoPollingSvc.start).toHaveBeenCalled();
+    expect(autoPollingSvc.stop).not.toHaveBeenCalled();
+  });
+});
diff --git a/metron-interface/metron-alerts/src/environments/environment.js b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.component.ts
old mode 100644
new mode 100755
similarity index 60%
rename from metron-interface/metron-alerts/src/environments/environment.js
rename to metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.component.ts
index 29adadc..2248095
--- a/metron-interface/metron-alerts/src/environments/environment.js
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.component.ts
@@ -1,4 +1,3 @@
-"use strict";
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -16,12 +15,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// The file contents for the current environment will overwrite these during build.
-// The build system defaults to the dev environment which uses `environment.ts`, but if you do
-// `ng build --env=prod` then `environment.prod.ts` will be used instead.
-// The list of which env maps to which file can be found in `angular-cli.json`.
-Object.defineProperty(exports, "__esModule", { value: true });
-exports.environment = {
-    production: false,
-    indices: null
-};
+import { Component } from '@angular/core';
+import { AutoPollingService } from './auto-polling.service';
+
+@Component({
+  selector: 'app-auto-polling',
+  templateUrl: './auto-polling.component.html',
+  styleUrls: ['./auto-polling.component.scss']
+})
+export class AutoPollingComponent {
+  constructor(public autoPollingSvc: AutoPollingService) {}
+
+  onToggle() {
+    if (!this.autoPollingSvc.getIsPollingActive()) {
+      this.autoPollingSvc.start();
+    } else {
+      this.autoPollingSvc.stop();
+    }
+  }
+}
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.spec.ts
new file mode 100644
index 0000000..366e023
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.spec.ts
@@ -0,0 +1,519 @@
+/**
+ * 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 { AutoPollingService } from './auto-polling.service';
+import { TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { SearchService } from 'app/service/search.service';
+import { Subject, of, throwError } from 'rxjs';
+import { QueryBuilder } from '../query-builder';
+import { SearchResponse } from 'app/model/search-response';
+import { SearchRequest } from 'app/model/search-request';
+import { Spy } from 'jasmine-core';
+import { DialogService } from 'app/service/dialog.service';
+import { RestError } from 'app/model/rest-error';
+import { DialogType } from 'app/model/dialog-type';
+
+class QueryBuilderFake {
+  private _filter = '';
+  query: '*'
+
+  addOrUpdateFilter() {};
+
+  setFilter(filter: string): void {
+    this._filter = filter;
+  };
+
+  get searchRequest(): SearchRequest {
+    return {
+      query: this._filter,
+      fields: [],
+      size: 2,
+      indices: [],
+      from: 0,
+      sort: [],
+      facetFields: [],
+    };
+  };
+}
+
+describe('AutoPollingService', () => {
+
+  let autoPollingService: AutoPollingService;
+  let searchServiceFake: SearchService;
+
+  function getIntervalInMS(): number {
+    return autoPollingService.getInterval() * 1000;
+  }
+
+  beforeEach(() => {
+    localStorage.getItem = () => null;
+    localStorage.setItem = () => {};
+
+    TestBed.configureTestingModule({
+      providers: [
+        AutoPollingService,
+        { provide: DialogService, useClass: () => {} },
+        { provide: SearchService, useClass: () => { return {
+          search: () => of(new SearchResponse()),
+        } } },
+        { provide: QueryBuilder, useClass: QueryBuilderFake },
+      ]
+    });
+
+    autoPollingService = TestBed.get(AutoPollingService);
+    searchServiceFake = TestBed.get(SearchService);
+  });
+
+  afterEach(() => {
+    autoPollingService.onDestroy();
+  });
+
+
+  describe('polling basics', () => {
+    it('should mark polling as active after start', () => {
+      autoPollingService.start();
+      expect(autoPollingService.getIsPollingActive()).toBe(true);
+    });
+
+    it('should mark polling as inactive after stop', () => {
+      autoPollingService.start();
+      expect(autoPollingService.getIsPollingActive()).toBe(true);
+
+      autoPollingService.stop();
+      expect(autoPollingService.getIsPollingActive()).toBe(false);
+    });
+
+    it('should send an initial request on start', () => {
+      spyOn(searchServiceFake, 'search').and.callThrough();
+
+      autoPollingService.start();
+
+      expect(searchServiceFake.search).toHaveBeenCalled();
+    });
+
+    it('should broadcast response to initial request via data subject', () => {
+      autoPollingService.data.subscribe((result) => {
+        expect(result).toEqual(new SearchResponse());
+      });
+
+      autoPollingService.start();
+    });
+
+    it('should start polling when start called', fakeAsync(() => {
+      spyOn(searchServiceFake, 'search').and.callThrough();
+
+      autoPollingService.start();
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+
+      autoPollingService.stop();
+    }));
+
+    it('should broadcast polling response via data subject', fakeAsync(() => {
+      const searchObservableFake = new Subject<SearchResponse>();
+      const pollResponseFake = new SearchResponse();
+
+      autoPollingService.start();
+      // The reason am mocking the searchService.search here is to not interfere
+      // with the initial request triggered right after the start
+      searchServiceFake.search = () => searchObservableFake;
+
+      autoPollingService.data.subscribe((result) => {
+        expect(result).toBe(pollResponseFake);
+        autoPollingService.stop();
+      });
+
+      tick(autoPollingService.getInterval() * 1000);
+
+      searchObservableFake.next(pollResponseFake);
+    }));
+
+    it('should polling and broadcasting based on the interval', fakeAsync(() => {
+      const searchObservableFake = new Subject<SearchResponse>();
+      const broadcastObserverSpy = jasmine.createSpy('broadcastObserverSpy');
+      const testInterval = 2;
+
+      autoPollingService.setInterval(testInterval);
+      autoPollingService.start();
+
+      // The reason am mocking the searchService.search here is to not interfere
+      // with the initial request triggered right after the start
+      searchServiceFake.search = () => searchObservableFake;
+      spyOn(searchServiceFake, 'search').and.callThrough();
+
+      autoPollingService.data.subscribe(broadcastObserverSpy);
+
+      tick(testInterval * 1000);
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(1);
+
+      searchObservableFake.next({ total: 2 } as SearchResponse);
+      expect(broadcastObserverSpy).toHaveBeenCalledTimes(1);
+      expect(broadcastObserverSpy.calls.argsFor(0)[0]).toEqual({ total: 2 });
+
+      tick(testInterval * 1000);
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+
+      searchObservableFake.next({ total: 3 } as SearchResponse);
+      expect(broadcastObserverSpy).toHaveBeenCalledTimes(2);
+      expect(broadcastObserverSpy.calls.argsFor(1)[0]).toEqual({ total: 3 });
+
+      autoPollingService.stop();
+    }));
+
+    it('interval change should impact the polling even when it is active', fakeAsync(() => {
+      autoPollingService.start();
+
+      // The reason am mocking the searchService.search here is to not interfere
+      // with the initial request triggered right after the start
+      spyOn(searchServiceFake, 'search').and.callThrough();
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(1);
+
+      autoPollingService.setInterval(9);
+
+      tick(4000);
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(1);
+
+      tick(5000);
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+
+      autoPollingService.setInterval(2);
+
+      tick(1000);
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+
+      tick(1000);
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(3);
+
+      autoPollingService.stop();
+    }));
+
+    it('should stop polling when stop triggered', fakeAsync(() => {
+      const searchObservableFake = new Subject<SearchResponse>();
+      const broadcastObserverSpy = jasmine.createSpy('broadcastObserverSpy');
+
+      autoPollingService.start();
+
+      // The reason am mocking the searchService.search here is to not interfere
+      // with the initial request triggered right after the start
+      searchServiceFake.search = () => searchObservableFake;
+      spyOn(searchServiceFake, 'search').and.callThrough();
+
+      autoPollingService.data.subscribe(broadcastObserverSpy);
+
+      tick(getIntervalInMS());
+      searchObservableFake.next({ total: 3 } as SearchResponse);
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(1);
+
+      autoPollingService.stop();
+
+      tick(getIntervalInMS() * 4);
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(1);
+    }));
+
+    it('should use the latest query from query builder', fakeAsync(() => {
+      const queryBuilderFake = TestBed.get(QueryBuilder);
+      spyOn(searchServiceFake, 'search').and.callThrough();
+
+      queryBuilderFake.setFilter('testFieldAA:testValueAA');
+      autoPollingService.start();
+      expect((searchServiceFake.search as Spy).calls.argsFor(0)[0].query).toBe('testFieldAA:testValueAA');
+
+      queryBuilderFake.setFilter('testFieldBB:testValueBB');
+      tick(getIntervalInMS());
+      expect((searchServiceFake.search as Spy).calls.argsFor(1)[0].query).toBe('testFieldBB:testValueBB');
+
+      queryBuilderFake.setFilter('*');
+      tick(getIntervalInMS());
+      expect((searchServiceFake.search as Spy).calls.argsFor(2)[0].query).toBe('*');
+
+      autoPollingService.stop();
+    }));
+
+    it('should show notification on http error', fakeAsync(() => {
+      const fakeDialogService = TestBed.get(DialogService);
+      fakeDialogService.launchDialog = () => {};
+      spyOn(fakeDialogService, 'launchDialog');
+
+      autoPollingService.start();
+
+      spyOn(searchServiceFake, 'search').and.returnValue(throwError(new RestError()));
+
+      tick(getIntervalInMS());
+
+      expect(fakeDialogService.launchDialog).toHaveBeenCalledWith(
+        'Server were unable to apply query string. Evaluate query string and restart polling.',
+        DialogType.Error
+      );
+
+      autoPollingService.stop();
+    }));
+  });
+
+  describe('polling suppression - to prevent collision with other features', () => {
+    it('should suspend polling even if it is started', fakeAsync(() => {
+      spyOn(searchServiceFake, 'search').and.callThrough();
+
+      autoPollingService.start();
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(1);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(3);
+
+      autoPollingService.setSuppression(true);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(3);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(3);
+
+      autoPollingService.stop();
+    }));
+
+    it('should continue polling when freed from suppression if it is started ', fakeAsync(() => {
+      spyOn(searchServiceFake, 'search').and.callThrough();
+
+      autoPollingService.start();
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(1);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(3);
+
+      autoPollingService.setSuppression(true);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(3);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(3);
+
+      autoPollingService.setSuppression(false);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(4);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(5);
+
+      autoPollingService.stop();
+    }));
+
+    it('should have no impact when polling stopped', fakeAsync(() => {
+      spyOn(searchServiceFake, 'search').and.callThrough();
+
+      autoPollingService.start();
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(1);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+
+      autoPollingService.stop();
+      autoPollingService.setSuppression(true);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+
+      autoPollingService.setSuppression(false);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+    }));
+  });
+
+  describe('request congestion handling - when refresh interval faster than response time', () => {
+    it('should skip new poll request when there is congestion', fakeAsync(() => {
+      const searchObservableFake = new Subject<SearchResponse>();
+
+      searchServiceFake.search = () => searchObservableFake;
+      spyOn(searchServiceFake, 'search').and.callThrough();
+
+      autoPollingService.start();
+
+      searchObservableFake.next({ total: 2 } as SearchResponse);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+      expect(autoPollingService.getIsCongestion()).toBe(false);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+      expect(autoPollingService.getIsCongestion()).toBe(true);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+      expect(autoPollingService.getIsCongestion()).toBe(true);
+
+      autoPollingService.stop();
+    }));
+
+    it('should continue polling when congestion resolves', fakeAsync(() => {
+      const searchObservableFake = new Subject<SearchResponse>();
+
+      searchServiceFake.search = () => searchObservableFake;
+      spyOn(searchServiceFake, 'search').and.callThrough();
+
+      autoPollingService.start();
+
+      searchObservableFake.next({ total: 2 } as SearchResponse);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+      expect(autoPollingService.getIsCongestion()).toBe(false);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+      expect(autoPollingService.getIsCongestion()).toBe(true);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+      expect(autoPollingService.getIsCongestion()).toBe(true);
+
+      searchObservableFake.next({ total: 2 } as SearchResponse);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(3);
+      expect(autoPollingService.getIsCongestion()).toBe(false);
+
+      autoPollingService.stop();
+    }));
+  });
+
+  describe('cancellation by manual request', () => {
+
+    it('should be able to drop current response and continue polling', fakeAsync(() => {
+      const broadcastObserverSpy = jasmine.createSpy('broadcastObserverSpy');
+      const searchObservableFake = new Subject<SearchResponse>();
+
+      autoPollingService.start();
+
+      searchServiceFake.search = () => searchObservableFake;
+      spyOn(searchServiceFake, 'search').and.callThrough();
+
+      autoPollingService.data.subscribe(broadcastObserverSpy);
+
+      tick(getIntervalInMS());
+
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(1);
+      searchObservableFake.next({ total: 2 } as SearchResponse);
+      expect(broadcastObserverSpy).toHaveBeenCalledTimes(1);
+
+      tick(getIntervalInMS() / 2);
+      autoPollingService.dropNextAndContinue();
+      tick(getIntervalInMS() / 2);
+
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(1);
+      searchObservableFake.next({ total: 3 } as SearchResponse);
+      expect(broadcastObserverSpy).toHaveBeenCalledTimes(1);
+
+      tick(getIntervalInMS());
+
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+      searchObservableFake.next({ total: 4 } as SearchResponse);
+      expect(broadcastObserverSpy).toHaveBeenCalledTimes(2);
+
+      autoPollingService.stop();
+    }));
+
+  });
+
+  describe('polling state persisting and restoring', () => {
+
+    it('should persist polling state on start', () => {
+      spyOn(localStorage, 'setItem');
+      autoPollingService.start();
+      expect(localStorage.setItem).toHaveBeenCalledWith('autoPolling', '{"isActive":true,"refreshInterval":10}');
+    });
+
+    it('should persist polling state on stop', () => {
+      spyOn(localStorage, 'setItem');
+      autoPollingService.stop();
+      expect(localStorage.setItem).toHaveBeenCalledWith('autoPolling', '{"isActive":false,"refreshInterval":10}');
+    });
+
+    it('should persist polling state on interval change', () => {
+      spyOn(localStorage, 'setItem');
+      autoPollingService.setInterval(4);
+      expect(localStorage.setItem).toHaveBeenCalledWith('autoPolling', '{"isActive":false,"refreshInterval":4}');
+    });
+
+    it('should restore polling state on construction', () => {
+      const queryBuilderFake = TestBed.get(QueryBuilder);
+      const dialogServiceFake = TestBed.get(QueryBuilder);
+
+      spyOn(localStorage, 'getItem').and.returnValue('{"isActive":true,"refreshInterval":443}');
+
+      const localAutoPollingSvc = new AutoPollingService(searchServiceFake, queryBuilderFake, dialogServiceFake);
+
+      expect(localStorage.getItem).toHaveBeenCalledWith('autoPolling');
+      expect(localAutoPollingSvc.getIsPollingActive()).toBe(true);
+      expect(localAutoPollingSvc.getInterval()).toBe(443);
+    });
+
+    it('should start polling on construction when persisted isActive==true', fakeAsync(() => {
+      const queryBuilderFake = TestBed.get(QueryBuilder);
+      const dialogServiceFake = TestBed.get(QueryBuilder);
+
+      spyOn(searchServiceFake, 'search').and.callThrough();
+      spyOn(localStorage, 'getItem').and.returnValue('{"isActive":true,"refreshInterval":10}');
+
+      const localAutoPollingSvc = new AutoPollingService(searchServiceFake, queryBuilderFake, dialogServiceFake);
+
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(1);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+
+      tick(getIntervalInMS());
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(3);
+
+      localAutoPollingSvc.stop();
+    }));
+
+    it('should start polling on construction with the persisted interval', fakeAsync(() => {
+      const queryBuilderFake = TestBed.get(QueryBuilder);
+      const dialogServiceFake = TestBed.get(QueryBuilder);
+
+      spyOn(searchServiceFake, 'search').and.callThrough();
+      spyOn(localStorage, 'getItem').and.returnValue('{"isActive":true,"refreshInterval":4}');
+
+      const localAutoPollingSvc = new AutoPollingService(searchServiceFake, queryBuilderFake, dialogServiceFake);
+
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(1);
+
+      tick(4000);
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(2);
+
+      tick(4000);
+      expect(searchServiceFake.search).toHaveBeenCalledTimes(3);
+
+      localAutoPollingSvc.stop();
+    }));
+  });
+
+});
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.ts
new file mode 100755
index 0000000..1530109
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/auto-polling/auto-polling.service.ts
@@ -0,0 +1,184 @@
+/**
+ * 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 { Subscription, Subject, Observable, interval, onErrorResumeNext } from 'rxjs';
+import { SearchService } from 'app/service/search.service';
+import { QueryBuilder } from '../query-builder';
+import { SearchResponse } from 'app/model/search-response';
+import { switchMap, filter, takeWhile, tap } from 'rxjs/operators';
+import { POLLING_DEFAULT_STATE } from 'app/utils/constants';
+import { RestError } from 'app/model/rest-error';
+import { DialogType } from 'app/shared/metron-dialog/metron-dialog.component';
+import { DialogService } from 'app/service/dialog.service';
+
+interface AutoPollingStateModel {
+  isActive: boolean,
+  refreshInterval: number,
+}
+
+@Injectable()
+export class AutoPollingService {
+  data = new Subject<SearchResponse>();
+
+  private isCongestion = false;
+  private refreshInterval = 10;
+  private isPollingActive = POLLING_DEFAULT_STATE;
+  private isPending = false;
+  private isPollingSuppressed = false;
+  private pollingIntervalSubs: Subscription;
+
+  public readonly AUTO_POLLING_STORAGE_KEY = 'autoPolling';
+
+  constructor(private searchService: SearchService,
+              private queryBuilder: QueryBuilder,
+              private dialogService: DialogService,
+              ) {
+                this.restoreState();
+              }
+
+  start() {
+    if (!this.isPollingActive) {
+      this.sendInitial();
+      this.activate();
+    }
+    this.isPollingActive = true;
+    this.persistState();
+  }
+
+  stop(persist = true) {
+    this.isPollingActive = false;
+    if (this.pollingIntervalSubs) {
+      this.pollingIntervalSubs.unsubscribe();
+      this.pollingIntervalSubs = null;
+    }
+
+    if (persist) {
+      this.persistState();
+    }
+  }
+
+  setSuppression(value: boolean) {
+    this.isPollingSuppressed = value;
+  }
+
+  dropNextAndContinue() {
+    this.reset();
+  }
+
+  setInterval(seconds: number) {
+    this.refreshInterval = seconds;
+    if (this.isPollingActive) {
+      this.reset();
+    }
+    this.persistState();
+  }
+
+  getInterval(): number {
+    return this.refreshInterval;
+  }
+
+  getIsPollingActive() {
+    return this.isPollingActive;
+  }
+
+  getIsCongestion() {
+    return this.isCongestion
+  }
+
+  private sendInitial() {
+    this.isPending = true;
+    this.searchService.search(this.queryBuilder.searchRequest).subscribe(this.onResult.bind(this));
+  }
+
+  private persistState(key = this.AUTO_POLLING_STORAGE_KEY): void {
+    localStorage.setItem(key, JSON.stringify(this.getStateModel()));
+  }
+
+  private restoreState(key = this.AUTO_POLLING_STORAGE_KEY): void {
+    const persistedState = JSON.parse(localStorage.getItem(key)) as AutoPollingStateModel;
+
+    if (persistedState) {
+      this.refreshInterval = persistedState.refreshInterval;
+
+      if (persistedState.isActive) {
+        this.start();
+      }
+    }
+  }
+
+  private getStateModel(): AutoPollingStateModel {
+    return {
+      isActive: this.isPollingActive,
+      refreshInterval: this.refreshInterval,
+    }
+  }
+
+  private reset() {
+    if (this.pollingIntervalSubs) {
+      this.pollingIntervalSubs.unsubscribe();
+      this.isPending = false;
+    }
+    this.activate();
+  }
+
+  private activate() {
+    this.pollingIntervalSubs = this.startPolling()
+      .subscribe(
+        this.onResult.bind(this),
+        this.onError.bind(this),
+      );
+  }
+
+  private onError(error: RestError) {
+    this.stop();
+    this.dialogService.launchDialog(
+      'Server were unable to apply query string. ' +
+      'Evaluate query string and restart polling.'
+      , DialogType.Error);
+  }
+
+  private onResult(result: SearchResponse) {
+    this.data.next(result);
+    this.isPending = false;
+  }
+
+  private startPolling(): Observable<SearchResponse> {
+    return interval(this.refreshInterval * 1000).pipe(
+      tap(() => this.checkCongestionOnTick()),
+      filter(() => !this.isPollingSuppressed && !this.isCongestion),
+      takeWhile(() => this.isPollingActive),
+      switchMap(() => {
+        this.isPending = true;
+        return this.searchService.search(this.queryBuilder.searchRequest);
+      }));
+  }
+
+  private checkCongestionOnTick() {
+    if (this.isPending) {
+      this.isCongestion = true;
+    } else {
+      this.isCongestion = false;
+    }
+  }
+
+  onDestroy() {
+    if (this.getIsPollingActive()) {
+      this.stop(false);
+    }
+  }
+}
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.spec.ts
index 20f0ac4..6c12ed7 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.spec.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.spec.ts
@@ -15,17 +15,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { QueryBuilder } from './query-builder';
+import { QueryBuilder, FilteringMode } from './query-builder';
 import { Filter } from 'app/model/filter';
 import { TIMESTAMP_FIELD_NAME } from '../../utils/constants';
 import { Utils } from 'app/utils/utils';
 
 
-describe('query-builder', () => {
+describe('QueryBuilder', () => {
+  let queryBuilder: QueryBuilder;
+
+  beforeEach(() => {
+    queryBuilder = new QueryBuilder();
+  });
 
   it('should be able to handle multiple filters', () => {
-    const queryBuilder = new QueryBuilder();
-
     queryBuilder.setSearch('alert_status:RESOLVE AND ip_src_addr:0.0.0.0');
 
     expect(queryBuilder.searchRequest.query).toBe(
@@ -34,8 +37,6 @@
   });
 
   it('should be able to handle multiple EXCLUDING filters for the same field', () => {
-    const queryBuilder = new QueryBuilder();
-
     queryBuilder.setSearch('-alert_status:RESOLVE AND -alert_status:DISMISS');
 
     expect(queryBuilder.searchRequest.query).toBe(
@@ -44,8 +45,6 @@
   });
 
   it('should be able to handle group multiple clauses to a single field, aka. field grouping', () => {
-    const queryBuilder = new QueryBuilder();
-
     queryBuilder.setSearch('alert_status:(RESOLVE OR DISMISS)');
 
     expect(queryBuilder.searchRequest.query).toBe(
@@ -54,8 +53,6 @@
   });
 
   it('should trim whitespace', () => {
-    const queryBuilder = new QueryBuilder();
-
     queryBuilder.setSearch(' alert_status:(RESOLVE OR DISMISS) ');
 
     expect(queryBuilder.searchRequest.query).toBe(
@@ -64,8 +61,6 @@
   });
 
   it('should remove wildcard', () => {
-    const queryBuilder = new QueryBuilder();
-
     queryBuilder.setSearch('* alert_status:(RESOLVE OR DISMISS)');
 
     expect(queryBuilder.searchRequest.query).toBe(
@@ -74,8 +69,6 @@
   });
 
   it('should properly parse excluding filters event with wildcard and whitespaces', () => {
-    const queryBuilder = new QueryBuilder();
-
     queryBuilder.setSearch('* -alert_status:(RESOLVE OR DISMISS)');
 
     expect(queryBuilder.searchRequest.query).toBe(
@@ -84,8 +77,6 @@
   });
 
   it('should remove wildcard from an excluding filter', () => {
-    const queryBuilder = new QueryBuilder();
-
     queryBuilder.setSearch('* -alert_status:(RESOLVE OR DISMISS)');
 
     expect(queryBuilder.searchRequest.query).toBe(
@@ -94,26 +85,20 @@
   });
 
   it('should allow only one timerange filter', () => {
-    const queryBuilder = new QueryBuilder();
-
     queryBuilder.addOrUpdateFilter(new Filter(TIMESTAMP_FIELD_NAME, '[1552863600000 TO 1552950000000]'));
     queryBuilder.addOrUpdateFilter(new Filter(TIMESTAMP_FIELD_NAME, '[1552863700000 TO 1552960000000]'));
 
-    expect(queryBuilder.generateSelect()).toBe('(timestamp:[1552863700000 TO 1552960000000] OR ' +
+    expect(queryBuilder.query).toBe('(timestamp:[1552863700000 TO 1552960000000] OR ' +
       'metron_alert.timestamp:[1552863700000 TO 1552960000000])');
   });
 
   it('should escape : chars in ElasticSearch field names', () => {
-    const queryBuilder = new QueryBuilder();
-
     queryBuilder.setSearch('source:type:bro');
 
     expect(queryBuilder.searchRequest.query).toBe('(source\\:type:bro OR metron_alert.source\\:type:bro)');
   });
 
   it('should escape ALL : chars in ElasticSearch field names', () => {
-    const queryBuilder = new QueryBuilder();
-
     queryBuilder.setSearch('enrichments:geo:ip_dst_addr:country:US');
 
     expect(queryBuilder.searchRequest.query).toBe('(enrichments\\:geo\\:ip_dst_addr\\:country:US ' +
@@ -121,8 +106,6 @@
   });
 
   it('should not multiply escaping in field name', () => {
-    const queryBuilder = new QueryBuilder();
-
     queryBuilder.setSearch('source:type:bro');
     queryBuilder.setSearch('source:type:bro');
     queryBuilder.setSearch('source:type:bro');
@@ -131,8 +114,6 @@
   });
 
   it('removeFilter should remove filter by reference', () => {
-    const queryBuilder = new QueryBuilder();
-
     const filter1 = new Filter(TIMESTAMP_FIELD_NAME, '[1552863600000 TO 1552950000000]');
     const filter2 = new Filter('fieldName', 'value');
 
@@ -146,8 +127,6 @@
   });
 
   it('removeFilterByField should remove filter having the passed field name', () => {
-    const queryBuilder = new QueryBuilder();
-
     const filter1 = new Filter('fruit', 'banana');
     const filter2 = new Filter('fruit', 'orange');
     const filter3 = new Filter('animal', 'horse');
@@ -162,4 +141,91 @@
     expect(queryBuilder.filters[0]).toBe(filter3);
   });
 
+  describe('filter query builder modes', () => {
+    it('should have a getter for filtering mode', () => {
+      expect(typeof queryBuilder.getFilteringMode).toBe('function');
+    });
+
+    it('should have a setter for filtering mode', () => {
+      expect(typeof queryBuilder.setFilteringMode).toBe('function');
+
+      expect(queryBuilder.getFilteringMode()).toBe(FilteringMode.BUILDER);
+
+      queryBuilder.setFilteringMode(FilteringMode.MANUAL);
+      expect(queryBuilder.getFilteringMode()).toBe(FilteringMode.MANUAL);
+
+      queryBuilder.setFilteringMode(FilteringMode.BUILDER);
+      expect(queryBuilder.getFilteringMode()).toBe(FilteringMode.BUILDER);
+    });
+
+    it('filtering mode should be builder by default', () => {
+      expect(queryBuilder.getFilteringMode()).toBe(FilteringMode.BUILDER);
+    });
+
+    it('should have a getter for manual query', () => {
+      expect(typeof queryBuilder.getManualQuery).toBe('function');
+    });
+
+    it('should have a setter for manual query string', () => {
+      expect(typeof queryBuilder.setManualQuery).toBe('function');
+
+      queryBuilder.setManualQuery('test manual query');
+      expect(queryBuilder.getManualQuery()).toBe('test manual query');
+
+      queryBuilder.setManualQuery('another test manual query');
+      expect(queryBuilder.getManualQuery()).toBe('another test manual query');
+    });
+
+    it('getManualQuery should return the built query string first', () => {
+      const expected = '(timestamp:[1552863600000 TO 1552950000000] OR metron_alert.timestamp:[1552863600000 ' +
+        'TO 1552950000000]) AND (animal:horse OR metron_alert.animal:horse)';
+
+      queryBuilder.clearSearch();
+
+      queryBuilder.addOrUpdateFilter(new Filter(TIMESTAMP_FIELD_NAME, '[1552863600000 TO 1552950000000]'));
+      queryBuilder.addOrUpdateFilter(new Filter('animal', 'horse'));
+
+      expect(queryBuilder.getManualQuery()).toBe(expected);
+
+      queryBuilder.setManualQuery('test:query');
+
+      expect(queryBuilder.getManualQuery()).toBe('test:query');
+    });
+
+    it('should use manual query string value in manual mode', () => {
+      queryBuilder.addOrUpdateFilter(new Filter(TIMESTAMP_FIELD_NAME, '[1552863600000 TO 1552950000000]'));
+      queryBuilder.addOrUpdateFilter(new Filter('animal', 'horse'));
+
+      queryBuilder.setFilteringMode(FilteringMode.MANUAL);
+      queryBuilder.setManualQuery('test:query');
+
+      expect(queryBuilder.searchRequest.query).toBe('test:query');
+    });
+
+    it('should use built query string value in builder mode', () => {
+      queryBuilder.addOrUpdateFilter(new Filter(TIMESTAMP_FIELD_NAME, '[1552863600000 TO 1552950000000]'));
+      queryBuilder.addOrUpdateFilter(new Filter('animal', 'horse'));
+
+      queryBuilder.setFilteringMode(FilteringMode.BUILDER);
+      queryBuilder.setManualQuery('test:query');
+
+      expect(queryBuilder.searchRequest.query).toBe(
+        '(timestamp:[1552863600000 TO 1552950000000] OR metron_alert.timestamp:[1552863600000 ' +
+        'TO 1552950000000]) AND (animal:horse OR metron_alert.animal:horse)'
+        );
+    });
+
+    it('clearSearch should clear manual query value', () => {
+      queryBuilder.setFilteringMode(FilteringMode.MANUAL);
+      queryBuilder.setManualQuery('manual:test:query');
+
+      expect(queryBuilder.getManualQuery()).toBe('manual:test:query');
+
+      queryBuilder.clearSearch();
+
+      expect(queryBuilder.getManualQuery()).toBe('*');
+    });
+
+  });
+
 });
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts
index a55a609..500cbb5 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts
@@ -24,20 +24,27 @@
 import {Group} from '../../model/group';
 import { Injectable } from '@angular/core';
 
+export enum FilteringMode {
+  MANUAL = 'FilteringModeIsManual',
+  BUILDER = 'FilteringModeIsBuilder',
+}
+
 @Injectable()
 export class QueryBuilder {
   private _searchRequest = new SearchRequest();
   private _groupRequest = new GroupRequest();
-  private _query = '*';
-  private _displayQuery = this._query;
+
+  private _manualQuery;
   private _filters: Filter[] = [];
 
+  private filteringMode: FilteringMode = FilteringMode.BUILDER;
+
   get query(): string {
-    return this._query;
+    return this.searchRequest.query;
   }
 
   get displayQuery(): string {
-    return this._displayQuery;
+    return this.generateSelectForDisplay();
   }
 
   set filters(filters: Filter[]) {
@@ -51,7 +58,7 @@
   }
 
   get searchRequest(): SearchRequest {
-    this._searchRequest.query = this.generateSelect();
+    this._searchRequest.query = this.getQueryString() || '*';
     return this._searchRequest;
   }
 
@@ -61,19 +68,18 @@
   }
 
   groupRequest(scoreField): GroupRequest {
-    this._groupRequest.query = this.generateSelect();
+    this._groupRequest.query = this.getQueryString() || '*';
     this._groupRequest.scoreField = scoreField;
     return this._groupRequest;
   }
 
   setSearch(query: string) {
     this.updateFilters(query, true);
-    this.onSearchChange();
   }
 
   clearSearch() {
     this._filters = [];
-    this.onSearchChange();
+    this._manualQuery = null;
   }
 
   addOrUpdateFilter(filter: Filter) {
@@ -85,7 +91,6 @@
         this.removeFilter(existingTimeRangeFilter);
       }
       this._filters.push(filter);
-      this.onSearchChange();
       return;
     }
 
@@ -102,13 +107,18 @@
     } else {
       this._filters.push(filter);
     }
-
-    this.onSearchChange();
   }
 
-  generateSelect() {
-    let select = this._filters.map(filter => filter.getQueryString()).join(' AND ');
-    return (select.length === 0) ? '*' : select;
+  private getQueryString() {
+    if (this.filteringMode === FilteringMode.MANUAL) {
+      return this.getManualQuery();
+    } else {
+      return this.getBuilderQueryString();
+    }
+  }
+
+  private getBuilderQueryString() {
+    return this._filters.map(filter => filter.getQueryString()).join(' AND ');
   }
 
   generateNameForSearchRequest() {
@@ -117,41 +127,26 @@
   }
 
   generateSelectForDisplay() {
-    let appliedFilters = [];
-    this._filters.reduce((appliedFilters, filter) => {
+    return this._filters.reduce((appliedFilters, filter) => {
       if (filter.display) {
         appliedFilters.push(ColumnNamesService.getColumnDisplayValue(filter.field) + ':' + filter.value);
       }
-
       return appliedFilters;
-    }, appliedFilters);
-
-    let select = appliedFilters.join(' AND ');
-    return (select.length === 0) ? '*' : select;
+    }, []).join(' AND ') || '*';
   }
 
   isTimeStampFieldPresent(): boolean {
     return this._filters.some(filter => (filter.field === TIMESTAMP_FIELD_NAME &&  !isNaN(Number(filter.value))));
   }
 
-  onSearchChange() {
-    this._query = this.generateSelect();
-    this._displayQuery = this.generateSelectForDisplay();
-  }
-
   removeFilter(filter: Filter) {
     this._filters = this._filters.filter(fItem => fItem !== filter );
-    this.onSearchChange();
   }
 
   removeFilterByField(field: string): void {
     this._filters = this._filters.filter(fItem => fItem.field !== field );
   }
 
-  setFields(fieldNames: string[]) {
-      // this.searchRequest._source = fieldNames;
-  }
-
   setFromAndSize(from: number, size: number) {
     this.searchRequest.from = from;
     this.searchRequest.size = size;
@@ -166,6 +161,25 @@
     this.searchRequest.sort = [sortField];
   }
 
+  setFilteringMode(mode: FilteringMode) {
+    this.filteringMode = mode;
+  }
+
+  getFilteringMode() {
+    return this.filteringMode;
+  }
+
+  setManualQuery(query: string) {
+    this._manualQuery = query;
+  }
+
+  getManualQuery(): string {
+    if (!this._manualQuery) {
+      this._manualQuery = this.getBuilderQueryString() || '*';
+    }
+    return this._manualQuery;
+  }
+
   private updateFilters(query: string, updateNameTransform = false) {
     this.removeDisplayedFilters();
 
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts
index ef3cc35..61d3a49 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts
@@ -39,7 +39,6 @@
 import { DialogType } from 'app/model/dialog-type';
 import { ConfirmationType } from 'app/model/confirmation-type';
 import { AlertSource } from '../../../model/alert-source';
-import { QueryBuilder } from '../query-builder';
 import { GroupRequest } from 'app/model/group-request';
 import { Group } from 'app/model/group';
 import { TimezoneConfigService } from 'app/alerts/configure-rows/timezone-config/timezone-config.service';
diff --git a/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.html b/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.html
index 98c62fd..ed3e988 100644
--- a/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.html
+++ b/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.html
@@ -17,28 +17,28 @@
     <h6 class="card-title">Settings</h6>
     <form>
       <label> REFRESH RATE </label>
-      <div #refreshInterval class="preset-row refresh-interval" (click)="onRefreshIntervalChange($event, refreshInterval)">
-          <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.refreshInterval===5}" [attr.value]="5">  5s   </div>
-          <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.refreshInterval===10}" [attr.value]="10">  10s  </div>
-          <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.refreshInterval===15}" [attr.value]="15">  15s  </div>
-          <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.refreshInterval===30}" [attr.value]="30">  30s  </div>
-          <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.refreshInterval===60}" [attr.value]="60">  1m   </div>
-          <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.refreshInterval===600}" [attr.value]="600">   10m  </div>
-          <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.refreshInterval===3600}" [attr.value]="3600">  1h   </div>
+      <div #refreshIntervalEl class="preset-row refresh-interval" (click)="onRefreshIntervalChange($event, refreshIntervalEl)">
+          <div class="preset-cell" [class.is-active]="refreshInterval===5" [attr.value]="5">  5s   </div>
+          <div class="preset-cell" [class.is-active]="refreshInterval===10" [attr.value]="10">  10s  </div>
+          <div class="preset-cell" [class.is-active]="refreshInterval===15" [attr.value]="15">  15s  </div>
+          <div class="preset-cell" [class.is-active]="refreshInterval===30" [attr.value]="30">  30s  </div>
+          <div class="preset-cell" [class.is-active]="refreshInterval===60" [attr.value]="60">  1m   </div>
+          <div class="preset-cell" [class.is-active]="refreshInterval===600" [attr.value]="600">   10m  </div>
+          <div class="preset-cell" [class.is-active]="refreshInterval===3600" [attr.value]="3600">  1h   </div>
       </div>
       <label> ROWS PER PAGE </label>
-      <div #pageSize class="preset-row page-size" (click)="onPageSizeChange($event, pageSize)">
-        <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.size===10}">  10   </div>
-        <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.size===25}">  25   </div>
-        <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.size===50}">  50   </div>
-        <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.size===100}">  100  </div>
-        <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.size===250}">  250  </div>
-        <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.size===500}">  500  </div>
-        <div class="preset-cell" [ngClass]="{'is-active': tableMetadata.size===1000}">  1000  </div>
+      <div #pageSizeEl class="preset-row page-size" (click)="onPageSizeChange($event, pageSizeEl)">
+        <div class="preset-cell" [class.is-active]="pageSize===10">  10   </div>
+        <div class="preset-cell" [class.is-active]="pageSize===25">  25   </div>
+        <div class="preset-cell" [class.is-active]="pageSize===50">  50   </div>
+        <div class="preset-cell" [class.is-active]="pageSize===100">  100  </div>
+        <div class="preset-cell" [class.is-active]="pageSize===250">  250  </div>
+        <div class="preset-cell" [class.is-active]="pageSize===500">  500  </div>
+        <div class="preset-cell" [class.is-active]="pageSize===1000">  1000  </div>
       </div>
 
       <label> HIDE ALERT ENTRIES </label>
-      <app-show-hide-alert-entries (changed)="configRowsChange.emit($event)" ></app-show-hide-alert-entries>
+      <app-show-hide-alert-entries (changed)="onShowHideChange($event)" ></app-show-hide-alert-entries>
 
       <label class="pt-2"> TIMEZONE CONFIGURATION </label>
       <app-timezone-config></app-timezone-config>
diff --git a/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.ts b/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.ts
index f643e51..61c1693 100644
--- a/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.ts
@@ -16,53 +16,29 @@
  * limitations under the License.
  */
 import { Component, Input, HostListener, ElementRef, Output, EventEmitter } from '@angular/core';
-import { TableMetadata } from '../../model/table-metadata';
-import { ConfigureTableService } from '../../service/configure-table.service';
 
+export interface ConfigureRowsModel {
+  values: {
+    pageSize: number;
+    refreshInterval: number;
+  },
+  triggerQuery: boolean
+}
 @Component({
   selector: 'app-configure-rows',
   templateUrl: './configure-rows.component.html',
   styleUrls: ['./configure-rows.component.scss']
 })
 export class ConfigureRowsComponent  {
-
   showView = false;
-  tableMetadata = new TableMetadata();
 
   @Input() srcElement: HTMLElement;
-  @Output() sizeChange = new EventEmitter();
-  @Output() intervalChange = new EventEmitter();
-  @Output() configRowsChange = new EventEmitter();
+  @Input() pageSize: number;
+  @Input() refreshInterval: number;
 
-  constructor(private elementRef: ElementRef,
-              private configureTableService: ConfigureTableService) {}
+  @Output() configRowsChange = new EventEmitter<ConfigureRowsModel>();
 
-  @Input()
-  get size() {
-    return this.tableMetadata.size;
-  }
-
-  set size(val) {
-    this.tableMetadata.size = val;
-  }
-
-  @Input()
-  get interval() {
-    return this.tableMetadata.refreshInterval;
-  }
-
-  set interval(val) {
-    this.tableMetadata.refreshInterval = val;
-  }
-
-  @Input()
-  get tableMetaData() {
-    return this.tableMetadata;
-  }
-
-  set tableMetaData(val) {
-    this.tableMetadata = val;
-  }
+  constructor(private elementRef: ElementRef) {}
 
   @HostListener('document:click', ['$event', '$event.target'])
   public onClick(event: MouseEvent, targetElement: HTMLElement): void {
@@ -85,29 +61,29 @@
     parentElement.querySelector('.is-active').classList.remove('is-active');
     $event.target.classList.add('is-active');
 
-    this.size = parseInt($event.target.textContent.trim(), 10);
-    this.sizeChange.emit(this.tableMetadata.size);
-    this.configRowsChange.emit();
-    this.saveSettings();
+    this.pageSize = parseInt($event.target.textContent.trim(), 10);
+    this.propagateChanges(true);
   }
 
   onRefreshIntervalChange($event, parentElement) {
     parentElement.querySelector('.is-active').classList.remove('is-active');
     $event.target.classList.add('is-active');
 
-
-    this.interval = parseInt($event.target.getAttribute('value').trim(), 10);
-    this.intervalChange.emit(this.tableMetadata.refreshInterval);
-    this.configRowsChange.emit();
-    this.saveSettings();
+    this.refreshInterval = parseInt($event.target.getAttribute('value').trim(), 10);
+    this.propagateChanges();
   }
 
-  saveSettings() {
-    if ( this.showView ) {
-      this.configureTableService.saveTableMetaData(this.tableMetadata).subscribe(() => {
-      }, error => {
-        console.log('Unable to save settings ....');
-      });
-    }
+  onShowHideChange() {
+    this.propagateChanges(true);
+  }
+
+  private propagateChanges(triggerQuery = false): void {
+    this.configRowsChange.emit({
+      values: {
+        pageSize: this.pageSize,
+        refreshInterval: this.refreshInterval,
+      },
+      triggerQuery,
+    });
   }
 }
diff --git a/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide-alert-entries.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide-alert-entries.component.spec.ts
index 3539d07..fdb559c 100644
--- a/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide-alert-entries.component.spec.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide-alert-entries.component.spec.ts
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { ShowHideAlertEntriesComponent, ShowHideChanged } from './show-hide-alert-entries.component';
+import { ShowHideAlertEntriesComponent, ShowHideStateModel } from './show-hide-alert-entries.component';
 import { ComponentFixture, async, TestBed } from '@angular/core/testing';
 import { SwitchComponent } from 'app/shared/switch/switch.component';
 import { By } from '@angular/platform-browser';
@@ -104,29 +104,26 @@
     expect(component.onVisibilityChanged).toHaveBeenCalledWith('DISMISS', false);
   });
 
-  it('should trigger changed event on any toggle changes', () => {
+  it('should trigger changed event on any toggle changes and propagate state', () => {
+    const serviceSpy = TestBed.get(ShowHideService);
     spyOn(component.changed, 'emit');
     fixture.detectChanges();
 
-    fixture.debugElement.query(By.css('[data-qe-id="hideDismissedAlertsToggle"] input')).nativeElement.click();
-    fixture.detectChanges();
-
-    expect((component.changed.emit as Spy).calls.argsFor(0)[0]).toEqual(new ShowHideChanged('DISMISS', true));
-
-    fixture.debugElement.query(By.css('[data-qe-id="hideResolvedAlertsToggle"] input')).nativeElement.click();
-    fixture.detectChanges();
-
-    expect((component.changed.emit as Spy).calls.argsFor(1)[0]).toEqual(new ShowHideChanged('RESOLVE', true));
+    component.showHideService.hideResolved = false;
+    component.showHideService.hideDismissed = true;
 
     fixture.debugElement.query(By.css('[data-qe-id="hideDismissedAlertsToggle"] input')).nativeElement.click();
     fixture.detectChanges();
 
-    expect((component.changed.emit as Spy).calls.argsFor(2)[0]).toEqual(new ShowHideChanged('DISMISS', false));
+    expect((component.changed.emit as Spy).calls.argsFor(0)[0]).toEqual({ hideResolved: false, hideDismissed: true });
+
+    component.showHideService.hideResolved = true;
+    component.showHideService.hideDismissed = true;
 
     fixture.debugElement.query(By.css('[data-qe-id="hideResolvedAlertsToggle"] input')).nativeElement.click();
     fixture.detectChanges();
 
-    expect((component.changed.emit as Spy).calls.argsFor(3)[0]).toEqual(new ShowHideChanged('RESOLVE', false));
+    expect((component.changed.emit as Spy).calls.argsFor(1)[0]).toEqual({ hideResolved: true, hideDismissed: true });
   })
 
 });
diff --git a/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide-alert-entries.component.ts b/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide-alert-entries.component.ts
index 9076282..b8be2de 100644
--- a/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide-alert-entries.component.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/configure-rows/show-hide/show-hide-alert-entries.component.ts
@@ -18,14 +18,9 @@
 import { Component, Output, EventEmitter } from '@angular/core';
 import { ShowHideService } from './show-hide.service';
 
-export class ShowHideChanged {
-  value: string;
-  isHide: boolean;
-
-  constructor(value: string, isHide: boolean) {
-    this.value = value;
-    this.isHide = isHide;
-  }
+export interface ShowHideStateModel {
+  hideResolved: boolean,
+  hideDismissed: boolean,
 }
 
 @Component({
@@ -39,13 +34,16 @@
 })
 export class ShowHideAlertEntriesComponent {
 
-  @Output() changed = new EventEmitter<ShowHideChanged>();
+  @Output() changed = new EventEmitter<ShowHideStateModel>();
 
   constructor(public showHideService: ShowHideService) {}
 
-  onVisibilityChanged(alertStatus, isHide) {
+  onVisibilityChanged(alertStatus: string, isHide: boolean): void {
     this.showHideService.setFilterFor(alertStatus, isHide);
-    this.changed.emit(new ShowHideChanged(alertStatus, isHide));
+    this.changed.emit({
+      hideResolved: this.showHideService.hideResolved,
+      hideDismissed: this.showHideService.hideDismissed,
+    });
   }
 
 }
diff --git a/metron-interface/metron-alerts/src/app/model/search-response.ts b/metron-interface/metron-alerts/src/app/model/search-response.ts
index c71f9be..b3fc933 100644
--- a/metron-interface/metron-alerts/src/app/model/search-response.ts
+++ b/metron-interface/metron-alerts/src/app/model/search-response.ts
@@ -23,7 +23,7 @@
   total = 0;
   groupedBy: string;
   results: Alert[] = [];
-  facetCounts: Facets;
+  facetCounts: Facets = {};
   groups: SearchResultGroup[];
 }
 
diff --git a/metron-interface/metron-alerts/src/app/model/table-metadata.ts b/metron-interface/metron-alerts/src/app/model/table-metadata.ts
index 0417041..791400e 100644
--- a/metron-interface/metron-alerts/src/app/model/table-metadata.ts
+++ b/metron-interface/metron-alerts/src/app/model/table-metadata.ts
@@ -15,23 +15,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {PageSize, RefreshInterval} from '../alerts/configure-rows/configure-rows-enums';
-import {ColumnMetadata} from './column-metadata';
+import { PageSize } from '../alerts/configure-rows/configure-rows-enums';
+import { ColumnMetadata } from './column-metadata';
 
 export class TableMetadata {
   size = PageSize.TWENTY_FIVE;
-  refreshInterval = RefreshInterval.TEN_MIN;
-  hideResolvedAlerts = true;
-  hideDismissedAlerts = true;
   tableColumns: ColumnMetadata[];
 
   static fromJSON(obj: any): TableMetadata {
     let tableMetadata = new TableMetadata();
     if (obj) {
       tableMetadata.size = obj.size;
-      tableMetadata.refreshInterval = obj.refreshInterval;
-      tableMetadata.hideResolvedAlerts = obj.hideResolvedAlerts;
-      tableMetadata.hideDismissedAlerts = obj.hideDismissedAlerts;
       tableMetadata.tableColumns = (typeof (obj.tableColumns) === 'string') ? JSON.parse(obj.tableColumns) : obj.tableColumns;
     }
 
diff --git a/metron-interface/metron-alerts/src/app/service/elasticsearch-localstorage-impl.ts b/metron-interface/metron-alerts/src/app/service/elasticsearch-localstorage-impl.ts
index 88036a4..5544e75 100644
--- a/metron-interface/metron-alerts/src/app/service/elasticsearch-localstorage-impl.ts
+++ b/metron-interface/metron-alerts/src/app/service/elasticsearch-localstorage-impl.ts
@@ -1,7 +1,3 @@
-
-import {throwError as observableThrowError} from 'rxjs';
-
-import {catchError, map, onErrorResumeNext} from 'rxjs/operators';
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -20,6 +16,8 @@
  * limitations under the License.
  */
 import {Observable} from 'rxjs';
+import {throwError as observableThrowError} from 'rxjs';
+import {catchError, map, onErrorResumeNext} from 'rxjs/operators';
 import { Injectable } from '@angular/core';
 import {HttpUtil} from '../utils/httpUtil';
 import {DataSource} from './data-source';
diff --git a/metron-interface/metron-alerts/src/app/service/search.service.spec.ts b/metron-interface/metron-alerts/src/app/service/search.service.spec.ts
new file mode 100644
index 0000000..3518070
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/service/search.service.spec.ts
@@ -0,0 +1,78 @@
+/**
+ * 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, fakeAsync } from '@angular/core/testing';
+import { SearchService } from './search.service';
+import { HttpTestingController, HttpClientTestingModule, TestRequest } from '@angular/common/http/testing';
+import { AppConfigService } from './app-config.service';
+import { SearchRequest } from 'app/model/search-request';
+import { noop } from 'rxjs';
+import { HttpUtil } from 'app/utils/httpUtil';
+
+describe('SearchService', () => {
+
+  let searchService: SearchService;
+  let mockBackend: HttpTestingController;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      imports: [ HttpClientTestingModule ],
+      providers: [
+        SearchService,
+        { provide: AppConfigService, useClass: () => {
+          return {
+            getApiRoot: () => '/api/v1',
+          }
+        } },
+      ]
+    });
+
+    searchService = TestBed.get(SearchService);
+    mockBackend = TestBed.get(HttpTestingController);
+  });
+
+  it('should not swallow errors', fakeAsync(() => {
+    searchService.search(new SearchRequest())
+      .subscribe(
+        noop,
+        (error) => {
+          expect(error.status).toBe(500);
+        },
+      );
+
+    const expectedReq: TestRequest = mockBackend.expectOne('/api/v1/search/search');
+    expect(expectedReq.request.method).toEqual('POST');
+
+    expectedReq.error(new ErrorEvent('internal server error'), { status: 500 });
+  }));
+
+  it('should redirect to login on session expiration or unauthorized access', () => {
+    spyOn(HttpUtil, 'navigateToLogin');
+
+    searchService.search(new SearchRequest()).subscribe(
+      noop,
+      (error) => {
+        expect(HttpUtil.navigateToLogin).toHaveBeenCalled();
+      },
+    );
+
+    const expectedReq: TestRequest = mockBackend.expectOne('/api/v1/search/search');
+    expect(expectedReq.request.method).toEqual('POST');
+
+    expectedReq.error(new ErrorEvent('internal server error'), { status: 401 });
+  });
+});
diff --git a/metron-interface/metron-alerts/src/app/service/search.service.ts b/metron-interface/metron-alerts/src/app/service/search.service.ts
index 47f211b..35a5a9c 100644
--- a/metron-interface/metron-alerts/src/app/service/search.service.ts
+++ b/metron-interface/metron-alerts/src/app/service/search.service.ts
@@ -16,10 +16,9 @@
  * limitations under the License.
  */
 import { HttpClient } from '@angular/common/http';
-import {Injectable, NgZone} from '@angular/core';
+import {Injectable} from '@angular/core';
 import {Observable} from 'rxjs';
-import { map, onErrorResumeNext, catchError, switchMap } from 'rxjs/operators';
-import { interval as observableInterval } from 'rxjs';
+import { map, onErrorResumeNext, catchError } from 'rxjs/operators';
 import {HttpUtil} from '../utils/httpUtil';
 import {SearchResponse} from '../model/search-response';
 import {SearchRequest} from '../model/search-request';
@@ -34,8 +33,6 @@
 @Injectable()
 export class SearchService {
 
-  interval = 80000;
-
   private static extractColumnNameDataFromRestApi(res): ColumnMetadata[] {
     let response: any = res || {};
     let processedKeys: string[] = [];
@@ -52,7 +49,7 @@
   }
 
   constructor(private http: HttpClient,
-              private ngZone: NgZone, private appConfigService: AppConfigService) { }
+              private appConfigService: AppConfigService) { }
 
   groups(groupRequest: GroupRequest): Observable<GroupResult> {
     let url = this.appConfigService.getApiRoot() + '/search/group';
@@ -79,21 +76,12 @@
     catchError(HttpUtil.handleError));
   }
 
-  public pollSearch(searchRequest: SearchRequest): Observable<SearchResponse> {
-    return this.ngZone.runOutsideAngular(() => {
-      return this.ngZone.run(() => {
-        return observableInterval(this.interval * 1000).pipe(switchMap(() => {
-          return this.search(searchRequest);
-        }));
-      });
-    });
-  }
-
   public search(searchRequest: SearchRequest): Observable<SearchResponse> {
     let url = this.appConfigService.getApiRoot() + '/search/search';
+
     return this.http.post(url, searchRequest).pipe(
-    map(HttpUtil.extractData),
-    catchError(HttpUtil.handleError),
-    onErrorResumeNext());
+      map(HttpUtil.extractData),
+      catchError(HttpUtil.sessionExpiration),
+    );
   }
 }
diff --git a/metron-interface/metron-alerts/src/app/shared/modal-loading-indicator/modal-loading-indicator.component.html b/metron-interface/metron-alerts/src/app/shared/modal-loading-indicator/modal-loading-indicator.component.html
new file mode 100644
index 0000000..29030f6
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/shared/modal-loading-indicator/modal-loading-indicator.component.html
@@ -0,0 +1,25 @@
+<!--
+  Licensed to the Apache Software
+	Foundation (ASF) under one or more contributor license agreements. See the
+	NOTICE file distributed with this work for additional information regarding
+	copyright ownership. The ASF licenses this file to You under the Apache License,
+	Version 2.0 (the "License"); you may not use this file except in compliance
+	with the License. You may obtain a copy of the License at
+  http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software distributed
+	under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+	OR CONDITIONS OF ANY KIND, either express or implied. See the License for
+  the specific language governing permissions and limitations under the License.
+  -->
+<div *ngIf="show" class="modal-backdrop show"></div>
+<div *ngIf="show" class="modal modal-loader">
+    <div class="modal-dialog modal-dialog-centered" role="document">
+        <div class="modal-content">
+            <div class="modal-body">
+                <img src="/assets/images/logo.png">
+                <div class="spinner-border text-info" role="status"></div>
+                <div class="pt-2">Fetching alerts...</div>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/metron-interface/metron-alerts/src/environments/environment.prod.js b/metron-interface/metron-alerts/src/app/shared/modal-loading-indicator/modal-loading-indicator.component.scss
similarity index 73%
rename from metron-interface/metron-alerts/src/environments/environment.prod.js
rename to metron-interface/metron-alerts/src/app/shared/modal-loading-indicator/modal-loading-indicator.component.scss
index abb15bb..871f0a0 100644
--- a/metron-interface/metron-alerts/src/environments/environment.prod.js
+++ b/metron-interface/metron-alerts/src/app/shared/modal-loading-indicator/modal-loading-indicator.component.scss
@@ -15,7 +15,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-"use strict";
-exports.environment = {
-    production: true
-};
+.modal-loader {
+  display: block;
+
+  img {
+    width: 140px;
+  }
+
+  .spinner-border {
+    margin: 0.8rem 0 0.4rem 0;
+  }
+
+  .modal-dialog {
+    width: 200px;
+
+    .modal-content {
+      background-color: #232323;
+    }
+
+    .modal-body {
+      text-align: center;
+    }
+  }
+}
\ No newline at end of file
diff --git a/metron-interface/metron-alerts/src/app/shared/modal-loading-indicator/modal-loading-indicator.component.spec.ts b/metron-interface/metron-alerts/src/app/shared/modal-loading-indicator/modal-loading-indicator.component.spec.ts
new file mode 100644
index 0000000..ec031af
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/shared/modal-loading-indicator/modal-loading-indicator.component.spec.ts
@@ -0,0 +1,42 @@
+/**
+ * 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ModalLoadingIndicatorComponent } from './modal-loading-indicator.component';
+
+describe('ModalLoadingIndicatorComponent', () => {
+  let component: ModalLoadingIndicatorComponent;
+  let fixture: ComponentFixture<ModalLoadingIndicatorComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ ModalLoadingIndicatorComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ModalLoadingIndicatorComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/metron-interface/metron-alerts/src/app/shared/modal-loading-indicator/modal-loading-indicator.component.ts b/metron-interface/metron-alerts/src/app/shared/modal-loading-indicator/modal-loading-indicator.component.ts
new file mode 100644
index 0000000..ea15813
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/shared/modal-loading-indicator/modal-loading-indicator.component.ts
@@ -0,0 +1,29 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+import { Component, OnInit, Input } from '@angular/core';
+
+@Component({
+  selector: 'app-modal-loading-indicator',
+  templateUrl: './modal-loading-indicator.component.html',
+  styleUrls: ['./modal-loading-indicator.component.scss']
+})
+export class ModalLoadingIndicatorComponent {
+
+  @Input() show = false;
+
+}
diff --git a/metron-interface/metron-alerts/src/app/utils/constants.ts b/metron-interface/metron-alerts/src/app/utils/constants.ts
index 929d140..6e05de0 100644
--- a/metron-interface/metron-alerts/src/app/utils/constants.ts
+++ b/metron-interface/metron-alerts/src/app/utils/constants.ts
@@ -36,7 +36,7 @@
 
 export const TREE_SUB_GROUP_SIZE = 5;
 export const INDEXES =  environment.indices ? environment.indices.split(',') : [];
-export const POLLING_DEFAULT_STATE = !environment.defaultPollingState;
+export const POLLING_DEFAULT_STATE = environment.defaultPollingState;
 
 export const MAX_ALERTS_IN_META_ALERTS = 350;
 
diff --git a/metron-interface/metron-alerts/src/app/utils/httpUtil.ts b/metron-interface/metron-alerts/src/app/utils/httpUtil.ts
index e1a5f8e..718b71c 100644
--- a/metron-interface/metron-alerts/src/app/utils/httpUtil.ts
+++ b/metron-interface/metron-alerts/src/app/utils/httpUtil.ts
@@ -18,8 +18,8 @@
  */
 import {HttpErrorResponse, HttpResponse} from '@angular/common/http';
 import {RestError} from '../model/rest-error';
-import {throwError as observableThrowError, Observable} from 'rxjs';
-import {AppConfigService} from "../service/app-config.service";
+import {throwError, Observable} from 'rxjs';
+import {AppConfigService} from '../service/app-config.service';
 
 export class HttpUtil {
 
@@ -33,9 +33,12 @@
     return body || {};
   }
 
+  /**
+   * @deprecated Turning all errors to 404 and hiding actual errors from the consumers
+   *             could limit how we can recover or react to errors.
+   *             Use sessionExpiration instead and/or introduce new composable error handlers.
+   */
   public static handleError(res: HttpErrorResponse): Observable<RestError> {
-    // In a real world app, we might use a remote logging infrastructure
-    // We'd also dig deeper into the error to get a better message
     let restError: RestError;
     if (res.status === 401) {
       HttpUtil.navigateToLogin();
@@ -45,7 +48,14 @@
       restError = new RestError();
       restError.status = 404;
     }
-    return observableThrowError(restError);
+    return throwError(restError);
+  }
+
+  public static sessionExpiration(res: HttpErrorResponse): Observable<RestError> {
+    if (res.status === 401) {
+      HttpUtil.navigateToLogin();
+    }
+    return throwError(res);
   }
 
   public static navigateToLogin() {