metron-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jsir...@apache.org
Subject [2/2] metron git commit: METRON-1123 Add group by option using faceted search capabilities of metron-rest-api (iraghumitra via james-sirota) closes apache/metron#768
Date Sat, 14 Oct 2017 14:46:00 GMT
METRON-1123 Add group by option using faceted search capabilities of metron-rest-api (iraghumitra via james-sirota) closes apache/metron#768


Project: http://git-wip-us.apache.org/repos/asf/metron/repo
Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/942aaaf2
Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/942aaaf2
Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/942aaaf2

Branch: refs/heads/master
Commit: 942aaaf2219d58af4d08744b49a90e7cf34b751c
Parents: 39bb856
Author: iraghumitra <raghumitra.ksv@gmail.com>
Authored: Sat Oct 14 07:44:27 2017 -0700
Committer: jsirota <jsirota@apache.org>
Committed: Sat Oct 14 07:44:27 2017 -0700

----------------------------------------------------------------------
 metron-interface/metron-alerts/angular-cli.json |   1 +
 .../e2e/alert-details/alert-details.po.ts       |   4 +
 .../alert-details-status.e2e-spec.ts            |  47 ++-
 .../alert-status/alerts-list-status.e2e-spec.ts |  42 +++
 .../e2e/alerts-list/alerts-list.e2e-spec.ts     |   2 +-
 .../e2e/alerts-list/alerts-list.po.ts           |  11 +-
 .../alerts-list/tree-view/tree-view.e2e-spec.ts | 236 +++++++++++++
 .../e2e/alerts-list/tree-view/tree-view.po.ts   | 162 +++++++++
 .../metron-alerts/e2e/login/login.po.ts         |   1 +
 .../metron-alerts/e2e/utils/e2e_util.ts         |   7 +-
 metron-interface/metron-alerts/package.json     |   1 +
 .../metron-alerts/protractor.conf.js            |   1 +
 .../metron-alerts/src/_hexagon.scss             |  91 +++++
 .../metron-alerts/src/_variables.scss           |  16 +
 .../alerts-list/alerts-list.component.html      |  32 +-
 .../alerts-list/alerts-list.component.scss      |   2 +-
 .../alerts/alerts-list/alerts-list.component.ts |  44 ++-
 .../alerts/alerts-list/alerts-list.module.ts    |   9 +-
 .../src/app/alerts/alerts-list/query-builder.ts |  12 +
 .../table-view/table-view.component.html        |   2 -
 .../table-view/table-view.component.scss        |   2 +-
 .../alerts-list/tree-view/tree-group-data.ts    |  65 ++++
 .../tree-view/tree-view.component.html          | 114 ++++++
 .../tree-view/tree-view.component.scss          | 153 ++++++++
 .../tree-view/tree-view.component.spec.ts       |  25 ++
 .../tree-view/tree-view.component.ts            | 352 +++++++++++++++++++
 .../configure-rows.component.scss               |   4 +-
 .../metron-alerts/src/app/app.component.ts      |  21 +-
 .../metron-alerts/src/app/model/group-order.ts  |  22 ++
 .../src/app/model/group-request.ts              |  26 ++
 .../src/app/model/group-response.ts             |  23 ++
 .../metron-alerts/src/app/model/group-result.ts |  25 ++
 .../metron-alerts/src/app/model/group.ts        |  29 ++
 .../src/app/model/search-request.ts             |   8 +-
 .../src/app/model/search-response.ts            |   4 +
 .../src/app/model/search-result-group.ts        |  26 ++
 .../src/app/service/search.service.ts           |  13 +-
 .../alert-severity-hexagon.directive.spec.ts    |   8 +
 .../alert-severity-hexagon.directive.ts         |  45 +++
 .../directives/alert-severity.directive.ts      |  12 +-
 .../shared/group-by/group-by-component-data.ts  |  28 ++
 .../app/shared/group-by/group-by.component.html |  28 ++
 .../app/shared/group-by/group-by.component.scss | 132 +++++++
 .../app/shared/group-by/group-by.component.ts   |  92 +++++
 .../src/app/shared/group-by/group-by.module.ts  |  35 ++
 .../metron-sorter/metron-sorter.component.ts    |  16 +-
 .../metron-table/metron-table.directive.ts      |   4 +-
 .../src/app/shared/shared.module.ts             |   7 +-
 .../metron-alerts/src/app/utils/constants.ts    |   8 +-
 .../src/app/utils/elasticsearch-utils.ts        |   8 +-
 metron-interface/metron-alerts/src/styles.scss  |  12 +-
 51 files changed, 2002 insertions(+), 68 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/angular-cli.json
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/angular-cli.json b/metron-interface/metron-alerts/angular-cli.json
index 141f29b..833a778 100644
--- a/metron-interface/metron-alerts/angular-cli.json
+++ b/metron-interface/metron-alerts/angular-cli.json
@@ -20,6 +20,7 @@
       "prefix": "app",
       "styles": [
         "../node_modules/font-awesome/css/font-awesome.css",
+        "../node_modules/dragula/dist/dragula.css",
         "vendor.scss",
         "styles.scss"
       ],

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/e2e/alert-details/alert-details.po.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alert-details/alert-details.po.ts b/metron-interface/metron-alerts/e2e/alert-details/alert-details.po.ts
index 39aea0b..ed71627 100644
--- a/metron-interface/metron-alerts/e2e/alert-details/alert-details.po.ts
+++ b/metron-interface/metron-alerts/e2e/alert-details/alert-details.po.ts
@@ -109,6 +109,10 @@ export class MetronAlertDetailsPage {
     return element.all(by.css('app-table-view .fa.fa-comments-o')).count();
   }
 
+  getCommentIconCountInTreeView() {
+    return element.all(by.css('app-tree-view .fa.fa-comments-o')).count();
+  }
+
   waitForTextChange(element, previousText) {
     let EC = protractor.ExpectedConditions;
     return browser.wait(EC.not(EC.textToBePresentInElement(element, previousText)));

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts
index 58d4892..4e25f82 100644
--- a/metron-interface/metron-alerts/e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts
+++ b/metron-interface/metron-alerts/e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts
@@ -21,16 +21,19 @@ import {customMatchers} from '../../matchers/custom-matchers';
 import {LoginPage} from '../../login/login.po';
 import {loadTestData, deleteTestData} from '../../utils/e2e_util';
 import { MetronAlertsPage } from '../../alerts-list/alerts-list.po';
+import {TreeViewPage} from '../../alerts-list/tree-view/tree-view.po';
 
 describe('metron-alerts alert status', function() {
   let page: MetronAlertDetailsPage;
   let listPage: MetronAlertsPage;
+  let treePage: TreeViewPage;
   let loginPage: LoginPage;
 
   beforeAll(() => {
     loadTestData();
     loginPage = new LoginPage();
     listPage = new MetronAlertsPage();
+    treePage = new TreeViewPage();
     loginPage.login();
   });
 
@@ -65,7 +68,7 @@ describe('metron-alerts alert status', function() {
     page.clickNew();
   });
 
-  it('should add comments', () => {
+  it('should add comments for table view', () => {
     let comment1 = 'This is a sample comment';
     let comment2 = 'This is a sample comment again';
     let userNameAndTimestamp = '- admin - a few seconds ago';
@@ -97,4 +100,46 @@ describe('metron-alerts alert status', function() {
     page.closeDetailPane();
   });
 
+  it('should add comments for tree view', () => {
+    let comment1 = 'This is a sample comment';
+    let comment2 = 'This is a sample comment again';
+    let userNameAndTimestamp = '- admin - a few seconds ago';
+
+    treePage.selectGroup('source:type');
+    treePage.expandDashGroup('alerts_ui_e2e');
+
+    treePage.clickOnRow('acf5a641-9cdb-d7ec-c309-6ea316e14fbe');
+    page.clickCommentsInSideNav();
+    page.addCommentAndSave(comment1, 0);
+
+    expect(page.getCommentsText()).toEqual([comment1]);
+    expect(page.getCommentsUserNameAndTimeStamp()).toEqual([userNameAndTimestamp]);
+    expect(page.getCommentIconCountInTreeView()).toEqual(1);
+
+    page.deleteComment();
+    page.clickYesForConfirmation();
+    expect(page.getCommentsText()).toEqual([]);
+    page.closeDetailPane();
+
+    treePage.unGroup();
+
+    treePage.selectGroup('source:type');
+    treePage.selectGroup('enrichments:geo:ip_dst_addr:country');
+    treePage.expandDashGroup('alerts_ui_e2e');
+    treePage.expandSubGroup('alerts_ui_e2e', 'FR');
+
+    treePage.clickOnRow('7cd91565-132f-3340-db76-3ade5be54a6e');
+    page.clickCommentsInSideNav();
+    page.addCommentAndSave(comment2, 0);
+
+    expect(page.getCommentsText()).toEqual([comment2]);
+    expect(page.getCommentsUserNameAndTimeStamp()).toEqual([userNameAndTimestamp]);
+    expect(page.getCommentIconCountInTreeView()).toEqual(1);
+
+    page.deleteComment();
+    page.clickYesForConfirmation();
+    expect(page.getCommentsText()).toEqual([]);
+    page.closeDetailPane();
+  });
+
 });

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/e2e/alerts-list/alert-status/alerts-list-status.e2e-spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/alert-status/alerts-list-status.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/alert-status/alerts-list-status.e2e-spec.ts
index e793d6f..828f290 100644
--- a/metron-interface/metron-alerts/e2e/alerts-list/alert-status/alerts-list-status.e2e-spec.ts
+++ b/metron-interface/metron-alerts/e2e/alerts-list/alert-status/alerts-list-status.e2e-spec.ts
@@ -20,9 +20,11 @@ import { MetronAlertsPage } from '../alerts-list.po';
 import {customMatchers} from '../../matchers/custom-matchers';
 import {LoginPage} from '../../login/login.po';
 import {loadTestData, deleteTestData} from '../../utils/e2e_util';
+import {TreeViewPage} from '../tree-view/tree-view.po';
 
 describe('metron-alerts alert status', function() {
   let page: MetronAlertsPage;
+  let treePage: TreeViewPage;
   let loginPage: LoginPage;
 
   beforeAll(() => {
@@ -38,6 +40,7 @@ describe('metron-alerts alert status', function() {
 
   beforeEach(() => {
     page = new MetronAlertsPage();
+    treePage = new TreeViewPage();
     jasmine.addMatchers(customMatchers);
   });
 
@@ -82,4 +85,43 @@ describe('metron-alerts alert status', function() {
     expect(page.getAlertStatus(11, 'NEW')).toEqual('RESOLVE');
   });
 
+
+  it('should change alert status for multiple alerts to OPEN in tree view', () => {
+    treePage.selectGroup('source:type');
+    treePage.selectGroup('enrichments:geo:ip_dst_addr:country');
+    
+    treePage.expandDashGroup('alerts_ui_e2e');
+    treePage.expandSubGroup('alerts_ui_e2e', 'US');
+    treePage.expandSubGroup('alerts_ui_e2e', 'RU');
+    treePage.expandSubGroup('alerts_ui_e2e', 'FR');
+
+    treePage.toggleAlertInTree(1);
+    treePage.toggleAlertInTree(2);
+    treePage.toggleAlertInTree(3);
+    page.clickActionDropdownOption('Open');
+    expect(treePage.getAlertStatusForTreeView(1, 'NEW')).toEqual('OPEN');
+    expect(treePage.getAlertStatusForTreeView(2, 'NEW')).toEqual('OPEN');
+    expect(treePage.getAlertStatusForTreeView(3, 'NEW')).toEqual('OPEN');
+
+    treePage.toggleAlertInTree(4);
+    treePage.toggleAlertInTree(5);
+    page.clickActionDropdownOption('Dismiss');
+    expect(treePage.getAlertStatusForTreeView(4, 'NEW')).toEqual('DISMISS');
+    expect(treePage.getAlertStatusForTreeView(5, 'NEW')).toEqual('DISMISS');
+
+    treePage.toggleAlertInTree(8);
+    treePage.toggleAlertInTree(9);
+    page.clickActionDropdownOption('Escalate');
+    expect(treePage.getAlertStatusForTreeView(8, 'NEW')).toEqual('ESCALATE');
+    expect(treePage.getAlertStatusForTreeView(9, 'NEW')).toEqual('ESCALATE');
+
+    treePage.toggleAlertInTree(10);
+    treePage.toggleAlertInTree(11);
+    treePage.toggleAlertInTree(12);
+    page.clickActionDropdownOption('Resolve');
+    expect(treePage.getAlertStatusForTreeView(10, 'NEW')).toEqual('RESOLVE');
+    expect(treePage.getAlertStatusForTreeView(11, 'NEW')).toEqual('RESOLVE');
+    expect(treePage.getAlertStatusForTreeView(12, 'NEW')).toEqual('RESOLVE');
+  });
+
 });

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts
index 43290fe..6b2ffd0 100644
--- a/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts
+++ b/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts
@@ -25,7 +25,7 @@ describe('metron-alerts App', function() {
   let page: MetronAlertsPage;
   let loginPage: LoginPage;
   let columnNames = [ 'Score', 'id', 'timestamp', 'source:type', 'ip_src_addr', 'enrichm...:country',
-                      'ip_dst_addr', 'host', 'alert_status', '', '', '' ];
+                      'ip_dst_addr', 'host', 'alert_status', '', ''];
   let colNamesColumnConfig = [ 'score', 'id', 'timestamp', 'source:type', 'ip_src_addr', 'enrichments:geo:ip_dst_addr:country',
                                 'ip_dst_addr', 'host', 'alert_status' ];
 

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts b/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts
index 39aefa7..7fee303 100644
--- a/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts
+++ b/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts
@@ -17,6 +17,7 @@
  */
 
 import {browser, element, by, protractor} from 'protractor';
+import {waitForElementVisibility, waitForElementPresence} from '../utils/e2e_util';
 
 export class MetronAlertsPage {
   navigateTo() {
@@ -69,12 +70,15 @@ export class MetronAlertsPage {
   }
 
   clickActionDropdown() {
-    return element(by.buttonText('ACTIONS')).click();
+    let actionsDropDown = element(by.buttonText('ACTIONS'));
+    browser.actions().mouseMove(actionsDropDown).perform();
+    return actionsDropDown.click();
   }
 
   clickActionDropdownOption(option: string) {
     this.clickActionDropdown().then(() => {
       element(by.cssContainingText('.dropdown-menu span', option)).click();
+      browser.sleep(2000);
     });
   }
 
@@ -115,7 +119,8 @@ export class MetronAlertsPage {
   }
 
   clickSettings() {
-    return element(by.css('.btn.settings')).click();
+    let settingsIcon = element(by.css('.btn.settings'));
+    return waitForElementVisibility(settingsIcon).then(() => settingsIcon.click());
   }
 
   getSettingsLabels() {
@@ -165,7 +170,7 @@ export class MetronAlertsPage {
   }
 
   clickTableText(name: string) {
-    element.all(by.linkText(name)).get(0).click();
+    waitForElementPresence(element.all(by.css('app-table-view tbody tr'))).then(() => element.all(by.linkText(name)).get(0).click());
   }
 
   clickClearSearch() {

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.e2e-spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.e2e-spec.ts
new file mode 100644
index 0000000..caa3754
--- /dev/null
+++ b/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.e2e-spec.ts
@@ -0,0 +1,236 @@
+/// <reference path="../../matchers/custom-matchers.d.ts"/>
+/**
+ * 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 {customMatchers} from  '../../matchers/custom-matchers';
+import {LoginPage} from '../../login/login.po';
+import {TreeViewPage} from './tree-view.po';
+import {loadTestData, deleteTestData} from '../../utils/e2e_util';
+import {MetronAlertsPage} from '../alerts-list.po';
+
+describe('metron-alerts tree view', function () {
+  let page: TreeViewPage;
+  let listPage: MetronAlertsPage;
+  let loginPage: LoginPage;
+
+  beforeAll(() => {
+    loadTestData();
+    loginPage = new LoginPage();
+    page = new TreeViewPage();
+    listPage = new MetronAlertsPage();
+    loginPage.login();
+    page.navigateToAlertsList();
+  });
+
+  afterAll(() => {
+    loginPage.logout();
+    deleteTestData();
+  });
+
+  beforeEach(() => {
+    jasmine.addMatchers(customMatchers);
+  });
+
+  it('should have all group by elements', () => {
+    let groupByItems = {
+      'source:type': '1',
+      'ip_dst_addr': '8',
+      'host': '9',
+      'enrichm...:country': '3',
+      'ip_src_addr': '2'
+    };
+    expect(page.getGroupByCount()).toEqualBcoz(Object.keys(groupByItems).length, '5 Group By Elements should be present');
+    expect(page.getGroupByItemNames()).toEqualBcoz(Object.keys(groupByItems), 'Group By Elements names should be present');
+    expect(page.getGroupByItemCounts()).toEqualBcoz(Object.keys(groupByItems).map(key => groupByItems[key]),
+                                                    '5 Group By Elements values should be present');
+  });
+
+  it('drag and drop should change group order', () => {
+    let before = {
+      'firstDashRow': ['0', 'alerts_ui_e2e', 'ALERTS', '169'],
+      'firstSubGroup': '0 US (22)',
+      'secondSubGroup': '0 RU (44)',
+      'thirdSubGroup': '0 FR (25)'
+    };
+
+    let after = {
+      'firstDashRow': ['0', 'US', 'ALERTS', '22'],
+      'secondDashRow': ['0', 'RU', 'ALERTS', '44'],
+      'thirdDashRow': ['0', 'FR', 'ALERTS', '25'],
+      'firstDashSubGroup': '0 alerts_ui_e2e (22)',
+      'secondDashSubGroup': '0 alerts_ui_e2e (44)',
+      'thirdDashSubGroup': '0 alerts_ui_e2e (25)'
+    };
+
+    page.selectGroup('source:type');
+    page.selectGroup('enrichments:geo:ip_dst_addr:country');
+    page.expandDashGroup('alerts_ui_e2e');
+    expect(page.getDashGroupValues('alerts_ui_e2e')).toEqualBcoz(before.firstDashRow, 'First Dash Row should be correct');
+    expect(page.getSubGroupValues('alerts_ui_e2e', 'US')).toEqualBcoz(before.firstSubGroup,
+        'Dash Group Values should be correct for US');
+    expect(page.getSubGroupValues('alerts_ui_e2e', 'RU')).toEqualBcoz(before.secondSubGroup,
+        'Dash Group Values should be present for RU');
+    expect(page.getSubGroupValues('alerts_ui_e2e', 'FR')).toEqualBcoz(before.thirdSubGroup,
+        'Dash Group Values should be present for FR');
+
+    page.dragGroup('source:type', 'ip_src_addr');
+    //page.selectGroup('source:type');
+    expect(page.getDashGroupValues('US')).toEqualBcoz(after.firstDashRow, 'First Dash Row after ' +
+        'reorder should be correct');
+    expect(page.getDashGroupValues('RU')).toEqualBcoz(after.secondDashRow, 'Second Dash Row after ' +
+        'reorder should be correct');
+    expect(page.getDashGroupValues('FR')).toEqualBcoz(after.thirdDashRow, 'Third Dash Row after ' +
+        'reorder should be correct');
+
+    page.expandDashGroup('US');
+    expect(page.getSubGroupValues('US', 'alerts_ui_e2e')).toEqualBcoz(after.firstDashSubGroup,
+        'First Dash Group Values should be present for alerts_ui_e2e');
+
+    page.expandDashGroup('RU');
+    expect(page.getSubGroupValues('RU', 'alerts_ui_e2e')).toEqualBcoz(after.secondDashSubGroup,
+        'Second Dash Group Values should be present for alerts_ui_e2e');
+
+    page.expandDashGroup('FR');
+    expect(page.getSubGroupValues('FR', 'alerts_ui_e2e')).toEqualBcoz(after.thirdDashSubGroup,
+        'Third Dash Group Values should be present for alerts_ui_e2e');
+
+    page.dragGroup('source:type', 'ip_dst_addr');
+    page.unGroup();
+  });
+
+  it('should have group details for single group by', () => {
+    let dashRowValues = ['0', 'alerts_ui_e2e', 'ALERTS', '169'];
+    let row1_page1 = ['-', 'dcda4423-7...0962fafc47', '2017-09-13 17:59:32', 'alerts_ui_e2e',
+      '192.168.138.158', 'US', '72.34.49.86', 'comarksecurity.com', 'NEW', '', ''];
+    let row1_page2 = ['-', '07b29c29-9...ff19eaa888', '2017-09-13 17:59:37', 'alerts_ui_e2e',
+      '192.168.138.158', 'FR', '62.75.195.236', '62.75.195.236', 'NEW', '', ''];
+
+    page.selectGroup('source:type');
+    expect(page.getActiveGroups()).toEqualBcoz(['source:type'], 'only source type group should be selected');
+    expect(page.getDashGroupValues('alerts_ui_e2e')).toEqualBcoz(dashRowValues, 'Dash Group Values should be present');
+
+    page.expandDashGroup('alerts_ui_e2e');
+    expect(page.getDashGroupTableValuesForRow('alerts_ui_e2e', 0)).toEqualBcoz(row1_page1, 'Dash Group Values should be present');
+
+    page.clickOnNextPage('alerts_ui_e2e');
+    expect(page.getTableValuesByRowId('alerts_ui_e2e', 0, 'FR')).toEqualBcoz(row1_page2, 'Dash Group Values should be present');
+
+    page.unGroup();
+    expect(page.getActiveGroups()).toEqualBcoz([], 'no groups should be selected');
+  });
+
+  it('should have group details for multiple group by', () => {
+
+    let dashRow_runLoveUs = {
+      'dashRow': ['0', 'runlove.us', 'ALERTS', '13'],
+      'firstSubGroup': '0 US (13)',
+      'firstSubGroupIdCol': ['9a969c64-b...001cb011a3', 'a651f7c3-1...a97d4966c9', 'afc36901-3...d931231ab2',
+        'd860ac35-1...f9e282d571', '04a5c3d0-9...af17c06fbc']
+    };
+
+    let dashRow_62_75_195_236 = {
+      'dashRow': ['0', '62.75.195.236', 'ALERTS', '18'],
+      'firstSubGroup': '0 FR (18)',
+      'firstSubGroupIdCol': ['07b29c29-9...ff19eaa888', '7cd91565-1...de5be54a6e', 'ca5bde58-a...f3a88d2df4',
+        '5d6faf83-8...b88a407647', 'e2883424-f...79bb8b0606']
+    };
+
+    page.selectGroup('host');
+    page.selectGroup('enrichments:geo:ip_dst_addr:country');
+    expect(page.getActiveGroups()).toEqualBcoz(['host', 'enrichments:geo:ip_dst_addr:country'], 'two groups should be selected');
+
+    expect(page.getDashGroupValues('runlove.us')).toEqualBcoz(dashRow_runLoveUs.dashRow,
+                                                              'Dash Group Values should be present for runlove.us');
+    page.expandDashGroup('runlove.us');
+    expect(page.getSubGroupValues('runlove.us', 'US')).toEqualBcoz(dashRow_runLoveUs.firstSubGroup,
+                                                                    'Dash Group Values should be present for runlove.us');
+    page.expandSubGroup('runlove.us', 'US');
+    expect(page.getCellValuesFromTable('runlove.us', 'id', '04a5c3d0-9...af17c06fbc')).toEqual(dashRow_runLoveUs.firstSubGroupIdCol,
+                                                                                                'id should not be sorted');
+
+    expect(page.getDashGroupValues('62.75.195.236')).toEqualBcoz(dashRow_62_75_195_236.dashRow, 'Dash Group Values should be present');
+    page.expandDashGroup('62.75.195.236');
+    expect(page.getSubGroupValues('62.75.195.236', 'FR')).toEqualBcoz(dashRow_62_75_195_236.firstSubGroup,
+                                                                      'Dash Group Values should be present for 62.75.195.236');
+    page.expandSubGroup('62.75.195.236', 'FR');
+    expect(page.getCellValuesFromTable('62.75.195.236', 'id', 'e2883424-f...79bb8b0606')).toEqual(dashRow_62_75_195_236.firstSubGroupIdCol,
+                                                                                                  'id should not be sorted');
+
+    page.unGroup();
+    expect(page.getActiveGroups()).toEqualBcoz([], 'no groups should be selected');
+  });
+
+
+  it('should have sort working for group details for multiple sub groups', () => {
+
+    let usIDCol = ['dcda4423-7...0962fafc47', '9a969c64-b...001cb011a3', 'a651f7c3-1...a97d4966c9',
+                    'afc36901-3...d931231ab2', 'd860ac35-1...f9e282d571'];
+    let ruIDCol = ['350c0e9f-a...3cbe5b29d2', '9b47e24a-e...2ca6627943', '4cac5e2c-3...3deb1ebcc6',
+                    'eb54c3fa-c...e02719c3b0', 'cace11d0-c...b1bd7b9499'];
+    let frIDCol = ['07b29c29-9...ff19eaa888', '7cd91565-1...de5be54a6e', 'ca5bde58-a...f3a88d2df4',
+                    '5d6faf83-8...b88a407647', 'e2883424-f...79bb8b0606'];
+
+    let usSortedIDCol = ['04a5c3d0-9...af17c06fbc', '06e70f55-4...f486927126', '105529cb-2...61b58237cc',
+                          '4c732cb0-0...6a93129aba', '500eb5e2-6...37b0f98772'];
+    let ruSortedIDCol = ['001b5451-6...38ec4221ee', '00814048-d...c9e6f27800', '0454b31e-e...0a711a36e7',
+                          '09552ace-9...e146579030', '0e99ba49-4...456c107bc9'];
+    let frSortedIDCol = ['07b29c29-9...ff19eaa888', '2681ed49-b...c33a80d429', '29ffaeb4-e...36822e5f81',
+                          '2cc174d7-c...8073777309', '436b9ecf-b...5f1ece4c4d'];
+
+    page.selectGroup('source:type');
+    page.selectGroup('enrichments:geo:ip_dst_addr:country');
+
+    page.expandDashGroup('alerts_ui_e2e');
+    page.expandSubGroup('alerts_ui_e2e', 'US');
+    page.expandSubGroup('alerts_ui_e2e', 'RU');
+    page.expandSubGroup('alerts_ui_e2e', 'FR');
+
+    let unsortedIds = [...usIDCol, ...ruIDCol, ...frIDCol];
+    let sortedIds = [...usSortedIDCol, ...ruSortedIDCol, ...frSortedIDCol];
+
+    expect(page.getCellValuesFromTable('alerts_ui_e2e', 'id', 'e2883424-f...79bb8b0606')).toEqual(unsortedIds, 'id should not be sorted');
+
+    page.sortSubGroup('alerts_ui_e2e', 'id');
+
+    expect(page.getCellValuesFromTable('alerts_ui_e2e', 'id', '436b9ecf-b...5f1ece4c4d')).toEqual(sortedIds, 'id should be sorted');
+
+    page.unGroup();
+    expect(page.getActiveGroups()).toEqualBcoz([], 'no groups should be selected');
+  });
+
+  it('should have search working for group details for multiple sub groups', () => {
+
+    page.selectGroup('source:type');
+    page.selectGroup('enrichments:geo:ip_dst_addr:country');
+
+    page.expandDashGroup('alerts_ui_e2e');
+    expect(page.getNumOfSubGroups('alerts_ui_e2e')).toEqual(3, 'three sub groups should be present');
+
+    listPage.setSearchText('enrichments:geo:ip_dst_addr:country:FR');
+
+    expect(page.getNumOfSubGroups('alerts_ui_e2e')).toEqual(1, 'one sub groups should be present');
+    page.expandSubGroup('alerts_ui_e2e', 'FR');
+
+    let expected = ['FR', 'FR', 'FR', 'FR', 'FR'];
+    expect(page.getCellValuesFromTable('alerts_ui_e2e', 'enrichments:geo:ip_dst_addr:country', 'FR')).toEqual(expected,
+                                                                                                              'id should be sorted');
+
+    page.unGroup();
+    expect(page.getActiveGroups()).toEqualBcoz([], 'no groups should be selected');
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.po.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.po.ts b/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.po.ts
new file mode 100644
index 0000000..b8472df
--- /dev/null
+++ b/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.po.ts
@@ -0,0 +1,162 @@
+/**
+ * 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 {browser, element, by, protractor} from 'protractor';
+import {waitForElementPresence, waitForTextChange} from '../../utils/e2e_util';
+
+export class TreeViewPage {
+  navigateToAlertsList() {
+    browser.waitForAngularEnabled(false);
+    return browser.get('/alerts-list');
+  }
+
+  clickOnRow(id: string) {
+    let idElement = element(by.css('a[title="' + id +'"]'));
+    waitForElementPresence(idElement)
+    .then(() => browser.actions().mouseMove(idElement).perform())
+    .then(() => idElement.element(by.xpath('../..')).all(by.css('td')).get(9).click());
+    browser.sleep(2000);
+  }
+
+  getActiveGroups() {
+    return element.all(by.css('app-group-by .group-by-items.active')).getAttribute('data-name');
+  }
+
+  getGroupByCount() {
+    return waitForElementPresence(element.all(by.css('app-group-by .group-by-items'))).then(() => {
+      return element.all(by.css('app-group-by .group-by-items')).count();
+    });
+  }
+
+  getGroupByItemNames() {
+    return element.all(by.css('app-group-by .group-by-items .name')).getText();
+  }
+
+  getGroupByItemCounts() {
+    return element.all(by.css('app-group-by .group-by-items .count')).getText();
+  }
+
+  getSubGroupValues(name: string, rowName: string) {
+    return element(by.css('[data-name="' + name + '"] table tbody tr[data-name="' + rowName + '"]')).getText();
+  }
+
+  selectGroup(name: string) {
+    return element(by.css('app-group-by div[data-name="' + name + '"]')).click();
+  }
+
+  dragGroup(from: string, to: string) {
+    browser.actions().dragAndDrop(
+        element(by.css('app-group-by div[data-name="' + from + '"]')),
+        element(by.css('app-group-by div[data-name="' + to + '"]'))
+    ).perform();
+  }
+
+  getDashGroupValues(name: string) {
+    return waitForElementPresence(element(by.css('[data-name="' + name + '"] .card-header span'))).then(() => {
+      return element.all(by.css('[data-name="' + name + '"] .card-header span')).getText();
+    });
+  }
+
+  expandDashGroup(name: string) {
+    waitForElementPresence( element(by.css('[data-name="' + name + '"] .card-header'))).then(() => {
+      this.scrollToDashRow(name);
+      element(by.css('[data-name="' + name + '"] .card-header i')).click();
+      browser.sleep(2000);
+    });
+  }
+
+  expandSubGroup(groupName: string, rowName: string) {
+    browser.actions().mouseMove(element(by.css('[data-name="' + groupName + '"] tr[data-name="' + rowName + '"]'))).perform();
+    return element(by.css('[data-name="' + groupName + '"] tr[data-name="' + rowName + '"]')).click();
+  }
+
+  getDashGroupTableValuesForRow(name: string, rowId: number) {
+    this.scrollToDashRow(name);
+    return waitForElementPresence(element(by.css('[data-name="' + name + '"] table tbody tr'))).then(() => {
+      return element.all(by.css('[data-name="' + name + '"] table tbody tr')).get(rowId).all(by.css('td')).getText();
+    });
+  }
+
+  getTableValuesByRowId(name: string, rowId: number, waitForAnchor: string) {
+    return waitForElementPresence(element(by. cssContainingText('[data-name="' + name + '"] a', waitForAnchor))).then(() => {
+      return element.all(by.css('[data-name="' + name + '"] table tbody tr')).get(rowId).all(by.css('td')).getText();
+    });
+  }
+
+  getTableValuesForRow(name: string, rowName: string, waitForAnchor: string) {
+    return waitForElementPresence(element(by. cssContainingText('[data-name="' + name + '"] a', waitForAnchor))).then(() => {
+      return element.all(by.css('[data-name="' + name + '"] tr[data-name="' + rowName + '"]')).all(by.css('td')).getText();
+    });
+  }
+
+  scrollToDashRow(name: string) {
+    let scrollToEle = element(by.css('[data-name="' + name + '"] .card-header'));
+    waitForElementPresence(scrollToEle).then(() => {
+      return browser.actions().mouseMove(scrollToEle).perform();
+    });
+  }
+
+  clickOnNextPage(name: string) {
+    return element(by.css('[data-name="' + name + '"] i.fa-chevron-right')).click();
+  }
+
+  unGroup() {
+    return element(by.css('app-group-by .ungroup-button')).click();
+  }
+
+  getIdOfAllExpandedRows() {
+    return element.all(by.css('[data-name="' + name + '"] table tbody tr')).then(row => {
+      browser.pause();
+    });
+  }
+
+  getNumOfSubGroups(groupName: string) {
+    return element.all(by.css('[data-name="' + groupName + '"] table tbody tr')).count();
+  }
+
+  getCellValuesFromTable(groupName: string, cellName: string, waitForAnchor: string) {
+    return waitForElementPresence(element(by. cssContainingText('[data-name="' + cellName + '"] a', waitForAnchor))).then(() => {
+      return element.all(by.css('[data-name="' + groupName + '"] table tbody [data-name="' + cellName + '"]')).map(element => {
+        browser.actions().mouseMove(element).perform();
+        return (element.getText());
+      });
+    });
+  }
+
+  sortSubGroup(groupName: string, colName: string) {
+    return element.all(by.css('[data-name="' + groupName + '"] metron-config-sorter[title="' + colName + '"]')).click();
+  }
+
+  toggleAlertInTree(index: number) {
+    let selector = by.css('app-tree-view tbody tr');
+    let checkbox = element.all(selector).get(index).element(by.css('label'));
+    waitForElementPresence(checkbox).then(() => {
+      browser.actions().mouseMove(checkbox).perform().then(() => {
+        checkbox.click();
+      });
+    });
+  }
+
+  getAlertStatusForTreeView(rowIndex: number, previousText) {
+    let row = element.all(by.css('app-tree-view tbody tr')).get(rowIndex);
+    let column = row.all(by.css('td a')).get(8);
+    return waitForTextChange(column, previousText).then(() => {
+      return column.getText();
+    });
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/e2e/login/login.po.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/login/login.po.ts b/metron-interface/metron-alerts/e2e/login/login.po.ts
index 2f0f81d..8d37800 100644
--- a/metron-interface/metron-alerts/e2e/login/login.po.ts
+++ b/metron-interface/metron-alerts/e2e/login/login.po.ts
@@ -51,6 +51,7 @@ export class LoginPage {
         browser.waitForAngularEnabled(false);
         let errElement = element(by.css('.login-failed-msg'));
         return waitForElementVisibility(errElement).then(() => {
+            browser.sleep(1000);
             return errElement.getText().then((message) => {
                 return message.replace(/\n/, '').replace(/LOG\ IN$/, '');
             });

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/e2e/utils/e2e_util.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/utils/e2e_util.ts b/metron-interface/metron-alerts/e2e/utils/e2e_util.ts
index 341e668..47f01e2 100644
--- a/metron-interface/metron-alerts/e2e/utils/e2e_util.ts
+++ b/metron-interface/metron-alerts/e2e/utils/e2e_util.ts
@@ -17,7 +17,12 @@ export function waitForURL(url: string) {
 
 export function waitForText(element, text) {
   let EC = protractor.ExpectedConditions;
-  return browser.wait(EC.textToBePresentInElement(element, text));
+  return browser.wait(EC.textToBePresentInElementValue(element, text));
+}
+
+export function waitForTextChange(element, previousText) {
+  let EC = protractor.ExpectedConditions;
+  return browser.wait(EC.not(EC.textToBePresentInElement(element, previousText)));
 }
 
 export function waitForElementInVisibility (_element ) {

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/package.json
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/package.json b/metron-interface/metron-alerts/package.json
index 6ff3c3c..1322a65 100644
--- a/metron-interface/metron-alerts/package.json
+++ b/metron-interface/metron-alerts/package.json
@@ -25,6 +25,7 @@
     "bootstrap": "4.0.0-alpha.6",
     "core-js": "^2.4.1",
     "font-awesome": "^4.7.0",
+    "ng2-dragula": "^1.5.0",
     "moment": "^2.18.1",
     "rxjs": "^5.1.0",
     "web-animations-js": "^2.2.2",

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/protractor.conf.js
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/protractor.conf.js b/metron-interface/metron-alerts/protractor.conf.js
index fe0fec0..4fc25be 100644
--- a/metron-interface/metron-alerts/protractor.conf.js
+++ b/metron-interface/metron-alerts/protractor.conf.js
@@ -29,6 +29,7 @@ exports.config = {
     './e2e/alerts-list/alerts-list.e2e-spec.ts',
     './e2e/alerts-list/configure-table/configure-table.e2e-spec.ts',
     './e2e/alerts-list/save-search/save-search.e2e-spec.ts',
+    './e2e/alerts-list/tree-view/tree-view.e2e-spec.ts',
     './e2e/alerts-list/alert-filters/alert-filters.e2e-spec.ts',
     './e2e/alerts-list/alert-status/alerts-list-status.e2e-spec.ts',
     './e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts'

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/_hexagon.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/_hexagon.scss b/metron-interface/metron-alerts/src/_hexagon.scss
new file mode 100644
index 0000000..667008a
--- /dev/null
+++ b/metron-interface/metron-alerts/src/_hexagon.scss
@@ -0,0 +1,91 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@import "variables";
+
+.hexagon {
+  position: absolute;
+  left: -15px;
+  top: 17px;
+  width: 30px;
+  height: 17.321px;
+  background-color: #64C7CC;
+  margin: 8.660px 0;
+
+  &:before,
+  &:after {
+    content: "";
+    position: absolute;
+    width: 0;
+    border-left: 15px solid transparent;
+    border-right: 15px solid transparent;
+  }
+
+  &:before {
+    bottom: 100%;
+    border-bottom: 8.660px solid #64C7CC;
+  }
+
+  &:after {
+    top: 100%;
+    width: 0;
+    border-top: 8.660px solid #64C7CC;
+  }
+
+  &.error {
+    background-color: $errors-red;
+    &:before  {
+      border-bottom: 8.660px solid $errors-red;
+    }
+
+    &:after  {
+      border-top: 8.660px solid $errors-red;
+    }
+  }
+
+  &.warning {
+    background-color: $warning-yellow;
+    &:before  {
+      border-bottom: 8.660px solid $warning-yellow;
+    }
+
+    &:after  {
+      border-top: 8.660px solid $warning-yellow;
+    }
+  }
+
+  &.info {
+    background-color: $info-yellow;
+    &:before  {
+      border-bottom: 8.660px solid $info-yellow;
+    }
+
+    &:after  {
+      border-top: 8.660px solid $info-yellow;
+    }
+  }
+
+  span {
+    line-height: 1.7;
+    position: absolute;
+    font-size: 12px;
+    text-align: center;
+    width: 100%;
+    color: $white;
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/_variables.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/_variables.scss b/metron-interface/metron-alerts/src/_variables.scss
index e4055ce..44ed9f6 100644
--- a/metron-interface/metron-alerts/src/_variables.scss
+++ b/metron-interface/metron-alerts/src/_variables.scss
@@ -32,6 +32,10 @@ $list-group-border-color: #404040;
 $list-group-active-bg: #28A9E2;
 $list-group-item-padding-y: 4px;
 $list-group-item-padding-x: 10px;
+$tooltip-bg: #494411;
+$tooltip-padding-y: 2px;
+$tooltip-padding-x: 5px;
+$tooltip-arrow-width: 0px;
 
 //Metron variables
 $mine-shaft: #2E2E2E;
@@ -40,26 +44,36 @@ $mine-shaft-2: #333333;
 $mine-shaft-3: #262626;
 $mine-shaft-4: #383838;
 $mine-shaft-5: #3D3D3D;
+$mine-shaft-6: #252525;
+$mine-shaft-7: #2C2C2C;
+$mine-shaft-8: #353535;
+$mine-shaft-9: #2B2B2B;
+$mine-shaft-10: #303030;
 $dove-grey: #737373;
 $tundora: #4D4D4D;
 $tundora-1: #404040;
+$tundora-2: #4C4C4C;
 $curious-blue: #27AAE1;
 $blue-chill: #0F6F9E;
 $piction-blue: #32ABDF;
+$picton-blue-1: #53C3EA;
 $dove-grey: #6B6B6B;
 $dove-grey-1: #676767;
+$dove-grey-2: #6D6D6D;
 $checkbox-checked-color: #32abe2;
 $edit-panel-background: #083b44;
 $errors-red: #D60A15;
 $warning-yellow: #D6711D;
 $info-yellow: #AC9B5A;
 $eden: #0C3B43;
+$eden-1: #0F4450;
 $all-ports: #006EA0;
 $blue-mine: #1B596C;
 $gothic: #689AA9;
 $tiber: #0B363E;
 $silver: #BDBDBD;
 $silver-1: #B8B8B8;
+$silver-2: #C8C8C8;
 $breaker-bay: #669AAA;
 $gray: #909090;
 $silver-chalice: #B2B2B2;
@@ -72,6 +86,8 @@ $eastern-blue: #1F91BE;
 $mantis: #80BF4D;
 $sky-blue: #75D2ED;
 $outer-space: #2E3A3F;
+$white: #FFFFFF;
+$iron: #D1D3D4;
 $rolling-stone: #808285;
 $nile-blue: #18404E;
 $apple-blossom: #A94442;

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html
----------------------------------------------------------------------
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
index 1183223..bcecef3 100644
--- 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
@@ -40,6 +40,9 @@
         </div>
         <div class="col-md-3 px-0">
             <div class="pull-right" style="position: relative; display: block;">
+                <div class="btn cog">
+                    <i class="fa fa-cog configure-table-icon" (click)="showConfigureTable()"></i>
+                </div>
                 <div class="btn settings">
                     <i #settingsIcon class="fa fa-sliders" aria-hidden="true"></i>
                 </div>
@@ -64,25 +67,36 @@
 
 <div class="container-fluid no-gutters">
     <div class="row">
-        <div class="px-0" style="width: 200px;max-width: 200px;">
-            <app-alert-filters [facets]="searchResponse.facetCounts" (facetFilterChange)="onAddFacetFilter($event)"> </app-alert-filters>
+      <div class="px-0" style="width: 200px;max-width: 200px;">
+        <app-alert-filters [facets]="searchResponse.facetCounts" (facetFilterChange)="onAddFacetFilter($event)"> </app-alert-filters>
+      </div>
+      <div class="col px-0 ml-4">
+        <div class="col-sm-12 pl-0 pb-3">
+          <app-group-by [facets]="searchResponse.facetCounts" (groupsChange)="onGroupsChange($event)"> </app-group-by>
         </div>
-        <div class="col">
+        <div class="col-sm-12 px-0">
             <app-table-view #dataViewComponent
-                            [alerts]="alerts"
+                            [alerts]="alerts" *ngIf="queryBuilder.groupRequest.groups.length === 0"
                             [queryBuilder]="queryBuilder"
                             [pagination]="pagination"
                             [alertsColumnsToDisplay]="alertsColumnsToDisplay"
-                            [(selectedAlerts)]="selectedAlerts"
+                            [selectedAlerts]="selectedAlerts"
                             (onResize)="onResize()"
                             (onAddFilter)="onAddFilter($event)"
                             (onRefreshData)="onRefreshData($event)"
                             (onShowDetails)="showDetails($event)"
-                            (onShowConfigureTable)="showConfigureTable()"
-                            (onSelectedAlertsChange)="onSelectedAlertsChange($event)">
-
-            </app-table-view>
+                            (onSelectedAlertsChange)="onSelectedAlertsChange($event)"></app-table-view>
+            <app-tree-view #dataViewComponent *ngIf="queryBuilder.groupRequest.groups.length !== 0"
+                           [alerts]="alerts"
+                           [queryBuilder]="queryBuilder"
+                           [alertsColumnsToDisplay]="alertsColumnsToDisplay"
+                           [selectedAlerts]="selectedAlerts"
+                           (onResize)="onResize()"
+                           (onAddFilter)="onAddFilter($event)"
+                           (onShowDetails)="showDetails($event)"
+                           (onSelectedAlertsChange)="onSelectedAlertsChange($event)"></app-tree-view>
         </div>
     </div>
+  </div>
 </div>
 

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss
----------------------------------------------------------------------
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 6a26d3c..a803df0 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
@@ -205,7 +205,7 @@ $searchbox-height: 42px;
   }
 }
 
-.settings {
+.settings, .cog {
   height: 38px;
   padding: 0px;
   line-height: 40px;

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts
----------------------------------------------------------------------
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
index 46b7796..06d3fb2 100644
--- 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
@@ -38,7 +38,6 @@ import {ElasticsearchUtils} from '../../utils/elasticsearch-utils';
 import {TableViewComponent} from './table-view/table-view.component';
 import {Filter} from '../../model/filter';
 import {Pagination} from '../../model/pagination';
-import {environment} from '../../../environments/environment';
 import {PatchRequest} from '../../model/patch-request';
 
 @Component({
@@ -60,7 +59,6 @@ export class AlertsListComponent implements OnInit, OnDestroy {
   pauseRefresh = false;
   lastPauseRefreshValue = false;
   threatScoreFieldName = 'threat:triage:score';
-  indices: string[];
 
   @ViewChild('table') table: ElementRef;
   @ViewChild('dataViewComponent') dataViewComponent: TableViewComponent;
@@ -85,9 +83,6 @@ export class AlertsListComponent implements OnInit, OnDestroy {
         this.restoreRefreshState();
       }
     });
-    if (environment.indices) {
-      this.indices = environment.indices.split(',');
-    }
   }
 
   addAlertChangedListner() {
@@ -107,6 +102,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
   addLoadSavedSearchListner() {
     this.saveSearchService.loadSavedSearch$.subscribe((savedSearch: SaveSearch) => {
       let queryBuilder = new QueryBuilder();
+      queryBuilder.setGroupby(this.queryBuilder.groupRequest.groups.map(group => group.field));
       queryBuilder.searchRequest = savedSearch.searchRequest;
       this.queryBuilder = queryBuilder;
       this.prepareColumnData(savedSearch.tableColumns, []);
@@ -115,7 +111,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
   }
 
   calcColumnsToDisplay() {
-    let availableWidth = document.documentElement.clientWidth - (200 + (15 * 3)); /* screenwidth - (navPaneWidth + (paddings))*/
+    let availableWidth = document.documentElement.clientWidth - (200 + (15 * 4)); /* screenwidth - (navPaneWidth + (paddings))*/
     availableWidth = availableWidth - (55 + 25 + 25); /* availableWidth - (score + colunSelectIcon +selectCheckbox )*/
     let tWidth = 0;
     this.alertsColumnsToDisplay =  this.alertsColumns.filter(colMetaData => {
@@ -181,6 +177,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
   }
 
   onSelectedAlertsChange(selectedAlerts) {
+    this.selectedAlerts = selectedAlerts;
     if (selectedAlerts.length > 0) {
       this.pause();
     } else {
@@ -198,6 +195,11 @@ export class AlertsListComponent implements OnInit, OnDestroy {
     this.search();
   }
 
+  onGroupsChange(groups) {
+    this.queryBuilder.setGroupby(groups);
+    this.search();
+  }
+
   onPausePlay() {
     this.pauseRefresh = !this.pauseRefresh;
     if (this.pauseRefresh) {
@@ -232,8 +234,6 @@ export class AlertsListComponent implements OnInit, OnDestroy {
     this.updateService.updateAlertState(this.selectedAlerts, 'ESCALATE').subscribe(results => {
       this.updateSelectedAlertStatus('ESCALATE');
     });
-    this.alertsService.escalate(this.selectedAlerts).subscribe();
-   
   }
 
   processDismiss() {
@@ -269,14 +269,9 @@ export class AlertsListComponent implements OnInit, OnDestroy {
     if (resetPaginationParams) {
       this.pagination.from = 0;
     }
-    this.queryBuilder.searchRequest.from = this.pagination.from;
-    if (this.tableMetaData.size) {
-      this.pagination.size = this.tableMetaData.size;
-    }
-    this.queryBuilder.searchRequest.size = this.pagination.size;
-    if (this.indices) {
-      this.queryBuilder.searchRequest.indices = this.indices;
-    }
+
+    this.setSearchRequestSize();
+
     this.searchService.search(this.queryBuilder.searchRequest).subscribe(results => {
       this.setData(results);
     }, error => {
@@ -287,6 +282,19 @@ export class AlertsListComponent implements OnInit, OnDestroy {
     this.tryStartPolling();
   }
 
+  setSearchRequestSize() {
+    if (this.queryBuilder.groupRequest.groups.length == 0) {
+      this.queryBuilder.searchRequest.from = this.pagination.from;
+      if (this.tableMetaData.size) {
+        this.pagination.size = this.tableMetaData.size;
+      }
+      this.queryBuilder.searchRequest.size = this.pagination.size;
+    } else {
+      this.queryBuilder.searchRequest.from = 0;
+      this.queryBuilder.searchRequest.size = 0;
+    }
+  }
+
   saveCurrentSearch(savedSearch: SaveSearch) {
     if (this.queryBuilder.query !== '*') {
       if (!savedSearch) {
@@ -375,9 +383,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
   
   updateSelectedAlertStatus(status: string) {
     for (let selectedAlert of this.selectedAlerts) {
-      selectedAlert.status = status;
-      this.alerts.filter(alert => alert.source.guid === selectedAlert.source.guid)
-      .map(alert => alert.source['alert_status'] = status);
+      selectedAlert.source['alert_status'] = status;
     }
     this.selectedAlerts = [];
     this.resume();

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts
----------------------------------------------------------------------
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
index 8ee194e..27b7e2e 100644
--- 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
@@ -16,6 +16,7 @@
  * limitations under the License.
  */
 import {NgModule} from '@angular/core';
+import {DecimalPipe} from '@angular/common';
 
 import {AlertsListComponent}   from './alerts-list.component';
 import {routing} from './alerts-list.routing';
@@ -26,15 +27,17 @@ import {ListGroupModule} from '../../shared/list-group/list-grup.module';
 import {CollapseModule} from '../../shared/collapse/collapse.module';
 import {MetronTablePaginationModule} from '../../shared/metron-table/metron-table-pagination/metron-table-pagination.module';
 import {ConfigureRowsModule} from '../configure-rows/configure-rows.module';
+import {GroupByModule} from '../../shared/group-by/group-by.module';
 import {AlertFiltersComponent} from './alert-filters/alert-filters.component';
 import {TableViewComponent} from './table-view/table-view.component';
+import {TreeViewComponent} from './tree-view/tree-view.component';
 
 @NgModule({
     imports: [routing, SharedModule, ConfigureRowsModule, MetronSorterModule, MetronTablePaginationModule,
-                ListGroupModule, CollapseModule],
+                ListGroupModule, CollapseModule, GroupByModule],
     exports: [AlertsListComponent],
-    declarations: [AlertsListComponent, TableViewComponent, AlertFiltersComponent],
-    providers: [SearchService]
+    declarations: [AlertsListComponent, TableViewComponent, TreeViewComponent, AlertFiltersComponent],
+    providers: [DecimalPipe, SearchService]
 })
 export class AlertsListModule {
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts
----------------------------------------------------------------------
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 0b76ee1..863e127 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
@@ -19,9 +19,12 @@ import {Filter} from '../../model/filter';
 import {ColumnNamesService} from '../../service/column-names.service';
 import {SearchRequest} from '../../model/search-request';
 import {SortField} from '../../model/sort-field';
+import {GroupRequest} from '../../model/group-request';
+import {Group} from '../../model/group';
 
 export class QueryBuilder {
   private _searchRequest = new SearchRequest();
+  private _groupRequest = new GroupRequest();
   private _query = '*';
   private _displayQuery = this._query;
   private _filters: Filter[] = [];
@@ -62,6 +65,11 @@ export class QueryBuilder {
     this.query = this._searchRequest.query;
   }
 
+  get groupRequest(): GroupRequest {
+    this._groupRequest.query = this.generateSelect();
+    return this._groupRequest;
+  }
+
   addOrUpdateFilter(filter: Filter) {
     let existingFilter = this._filters.find(tFilter => tFilter.field === filter.field);
     if (existingFilter) {
@@ -111,6 +119,10 @@ export class QueryBuilder {
     this.searchRequest.size = size;
   }
 
+  setGroupby(groups: string[]) {
+    this.groupRequest.groups = groups.map(groupName => new Group(groupName));
+  }
+
   setSort(sortBy: string, order: string) {
     let sortField = new SortField();
     sortField.field = sortBy;

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.html b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.html
index 561b299..b8fd14f 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.html
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.html
@@ -18,7 +18,6 @@
       <th style="width:55px"> <metron-config-sorter [type]="'number'" [sortBy]="threatScoreFieldName"> Score </metron-config-sorter> </th>
       <th *ngFor="let column of alertsColumnsToDisplay" [id]="column.name"> <metron-config-sorter [type]="column.type" [sortBy]="column.name" title="{{column.name}}"> {{ column.name | columnNameTranslate | centerEllipses:15 }}</metron-config-sorter> </th>
       <th></th>
-      <th style="width:25px"><i class="fa fa-cog configure-table-icon" (click)="showConfigureTable()"></i></th>
       <th style="width:25px"><input id="select-deselect-all" class="fontawesome-checkbox" type="checkbox" (click)="selectAllRows($event)"><label for="select-deselect-all"></label></th>
     </tr>
     </thead>
@@ -31,7 +30,6 @@
         <a (click)="addFilter(column.name, getValue(alert, column, false))" title="{{getValue(alert, column, true)}}" style="color:#689AA9">{{ getValue(alert, column, true) | centerEllipses:20:cell }}</a>
       </td>
       <td> <i class="fa fa-comments-o" aria-hidden="true" *ngIf="alert.source.comments && alert.source.comments.length > 0"></i> </td>
-      <td> </td>
       <td><input id="{{ alert.id }}" class="fontawesome-checkbox" type="checkbox" name="{{alert.id}}" (click)="selectRow($event, alert)" [checked]="selectedAlerts.indexOf(alert) != -1"><label attr.for="{{ alert.id }}"></label></td>
     </tr>
     </tbody>

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss
index fa2417e..eec7f92 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss
@@ -18,7 +18,7 @@
 @import "../../../../variables.scss";
 
 .table-wrapper {
-  min-height: calc(100vh - 250px);
+  min-height: calc(100vh - 320px);
 }
 
 .configure-table-icon {

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-group-data.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-group-data.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-group-data.ts
new file mode 100644
index 0000000..ae65a67
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-group-data.ts
@@ -0,0 +1,65 @@
+/**
+ * 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 {Subscription} from 'rxjs/Rx';
+import {SearchResponse} from '../../../model/search-response';
+import {Pagination} from '../../../model/pagination';
+import {TREE_SUB_GROUP_SIZE} from '../../../utils/constants';
+import {SortField} from '../../../model/sort-field';
+import {SortEvent} from '../../../shared/metron-table/metron-table.directive';
+import {Sort} from '../../../utils/enums';
+
+export class TreeGroupData {
+  key: string;
+  total: number;
+  level: number;
+  show: boolean;
+  expand = false;
+  score: number;
+
+  // Used by only Dashrow
+  sortField: SortField;
+  sortEvent: SortEvent = { sortBy: '', type: '', sortOrder: Sort.ASC};
+  treeSubGroups: TreeGroupData[] = [];
+
+  // Used by only Leafnodes
+  groupQueryMap = null;
+  response: SearchResponse = new SearchResponse();
+  pagingData: Pagination = new Pagination();
+
+
+  constructor(key: string, total: number, score: number, level: number, expand: boolean) {
+    this.key = key;
+    this.total = total;
+    this.score = score;
+    this.level = level;
+    this.show = expand;
+
+    this.pagingData.size = TREE_SUB_GROUP_SIZE;
+  }
+}
+
+
+export class TreeAlertsSubscription {
+  refreshTimer: Subscription;
+  group: TreeGroupData;
+
+  constructor(refreshTimer: Subscription, group: TreeGroupData) {
+    this.refreshTimer = refreshTimer;
+    this.group = group;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.html b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.html
new file mode 100644
index 0000000..e89cdc9
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.html
@@ -0,0 +1,114 @@
+<!--
+  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 role="tablist"  class="tree-wrapper" aria-multiselectable="true">
+  <div class="card" *ngFor="let group of topGroups; let i = index;" [attr.data-name]="group.key">
+    <div class="card-header" role="tab" [attr.id]="'title-' + i">
+      <div class="hexagon" appAlertSeverityHexagon [severity]="group.score">
+        <span *ngIf="group.score < 1000" class="dash-score">{{ group.score }}</span>
+        <span *ngIf="group.score >= 1000" class="dash-score">999<sup>+</sup></span>
+      </div>
+      <div class="mrow top-group" (click)="toggleTopLevelGroup(group)">
+        <div class="col-5 text-light severity-padding"> <span class="title"> {{ group.key | centerEllipses:45 }} </span> </div>
+        <div class="col-6 text-light two-line"> <span class="text-dark"> ALERTS </span> <br> <span class="title"> {{ group.total | number }} </span> </div>
+        <div class="col-1 text-right pr-4">
+            <i class="down-arrow" data-animation="false" data-toggle="tooltip" data-placement="left" title="Open Group"
+                   aria-expanded="false" [attr.href]="'#body-' + i" [attr.aria-controls]="'body-' + i"> </i>
+        </div>
+      </div>
+    </div>
+    <div class="collapse" role="tabpanel"  [ngClass]="{'show': group.expand}">
+      <div class="card-block">
+        <div class="table-wrapper">
+          <table class="table table-sm" metron-config-table [data]="alerts" [cellSelectable]="true"  (onSort)="sortTreeSubGroup($event, group)" style="white-space: nowrap;" (window:resize)="resize()" #table>
+            <thead>
+            <tr>
+              <th> </th>
+              <th class="table-score-col">
+                <metron-config-sorter [type]="'number'" [sortBy]="threatScoreFieldName" [sortOnCol]="group.sortEvent.sortBy"  [sortOrder]="group.sortEvent.sortOrder"> Score </metron-config-sorter>
+              </th>
+              <th *ngFor="let column of alertsColumnsToDisplay" [id]="column.name">
+                <metron-config-sorter [type]="column.type" [sortBy]="column.name" title="{{column.name}}" [sortOnCol]="group.sortEvent.sortBy"  [sortOrder]="group.sortEvent.sortOrder"> {{ column.name | columnNameTranslate | centerEllipses:15 }}</metron-config-sorter>
+              </th>
+              <th style="width: 15px"></th>
+              <th style="width:25px"><input id="select-deselect-all-{{ group.key }}" class="fontawesome-checkbox" type="checkbox" (click)="selectAllGroupRows($event, group)">
+                <label for="select-deselect-all-{{ group.key }}"></label>
+              </th>
+            </tr>
+            </thead>
+            <tbody>
+              <ng-container>
+                <tr  *ngFor="let alert of group.response.results"  [ngClass]="{'selected' : selectedAlerts.indexOf(alert) != -1, 'd-none': !group.expand || !group.show}" (click)="showDetails($event, alert)">
+                  <td [attr.colspan]="2" (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])">
+                    <div appAlertSeverity [severity]="alert.source[threatScoreFieldName]">
+                      <a> {{ alert.source[threatScoreFieldName] ? alert.source[threatScoreFieldName] : '-' }}</a>
+                    </div>
+                  </td>
+                  <td #cell *ngFor="let column of alertsColumnsToDisplay"  [attr.data-name]="column.name">
+                    <a title="{{getValue(alert, column, true)}}" style="color:#689AA9" (click)="addFilter(column.name, getValue(alert, column, false))">{{ getValue(alert, column, true) | centerEllipses:20:cell }}</a>
+                  </td>
+                  <td> <i class="fa fa-comments-o" aria-hidden="true" *ngIf="alert.source.comments && alert.source.comments.length > 0"></i> </td>
+                  <td>
+                    <input id="{{ alert.id }}" class="fontawesome-checkbox" type="checkbox" name="{{alert.id}}" (click)="selectRow($event, alert)" [checked]="selectedAlerts.indexOf(alert) != -1">
+                    <label attr.for="{{ alert.id }}"></label>
+                  </td>
+                </tr>
+
+                <tr *ngIf="group.response.results.length > 0" class="no-hover" [ngClass]="{'d-none': !group.expand || !group.show}">
+                  <td [attr.colspan]="alertsColumnsToDisplay.length + 3" class="text-right">
+                    <metron-table-pagination [(pagination)]="group.pagingData" (pageChange)="groupPageChange(group)"> </metron-table-pagination>
+                  </td>
+                </tr>
+              </ng-container>
+              <ng-container *ngFor="let subGroup of group.treeSubGroups;let i = index">
+
+                <tr class="table-group-row" [ngClass]="{'d-none': !subGroup.show}" (click)="toggleSubGroups(group, subGroup, i)" [attr.data-name]="subGroup.key">
+                  <td [attr.colspan]="alertsColumnsToDisplay.length + 4" [ngStyle]="{'padding-left.px': (16 * (subGroup.level -1))}">
+                    <span class="table-group-icon-col" data-animation="false" data-toggle="tooltip" data-placement="bottom" title="Open Group">
+                      <i class="fa" aria-hidden="true" [ngClass]="{'fa-caret-down': subGroup.expand, 'fa-caret-right': !subGroup.expand}"></i>
+                    </span>
+                    <span class="score" appAlertSeverity [severity]="subGroup.score"> {{ subGroup.score }} </span>
+                    <span class="group-value"> <span class="text-light"> {{ subGroup.key }} </span> ({{ subGroup.total}})</span>
+                  </td>
+                </tr>
+
+                <tr *ngFor="let alert of subGroup.response.results" [ngClass]="{'selected' : selectedAlerts.indexOf(alert) != -1, 'd-none': !subGroup.expand || !subGroup.show}" (click)="showDetails($event, alert)">
+                  <td [attr.colspan]="2" [ngStyle]="{'padding-left.px': (16 * (subGroup.level -1)) + 23}" (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])">
+                    <div appAlertSeverity [severity]="alert.source[threatScoreFieldName]">
+                      <a> {{ alert.source[threatScoreFieldName] ? alert.source[threatScoreFieldName] : '-' }}</a>
+                    </div>
+                  </td>
+                  <td #cell *ngFor="let column of alertsColumnsToDisplay" [attr.data-name]="column.name">
+                    <a (click)="addFilter(column.name, getValue(alert, column, false))" title="{{getValue(alert, column, true)}}" style="color:#689AA9" (click)="addFilter(column.name, getValue(alert, column, false))">{{ getValue(alert, column, true) | centerEllipses:20:cell }}</a>
+                  </td>
+                  <td> <i class="fa fa-comments-o" aria-hidden="true" *ngIf="alert.source.comments && alert.source.comments.length > 0"></i> </td>
+                  <td>
+                    <input id="{{ alert.id }}" class="fontawesome-checkbox" type="checkbox" name="{{alert.id}}" (click)="selectRow($event, alert)" [checked]="selectedAlerts.indexOf(alert) != -1">
+                    <label attr.for="{{ alert.id }}"></label>
+                  </td>
+                </tr>
+
+                <tr *ngIf="subGroup.response.results.length > 0" class="no-hover" [ngClass]="{'d-none': !subGroup.expand || !subGroup.show}">
+                  <td [attr.colspan]="alertsColumnsToDisplay.length + 3" class="text-right">
+                    <metron-table-pagination [(pagination)]="subGroup.pagingData" (pageChange)="groupPageChange(subGroup)"> </metron-table-pagination>
+                  </td>
+                </tr>
+
+              </ng-container>
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.scss b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.scss
new file mode 100644
index 0000000..8668b49
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.scss
@@ -0,0 +1,153 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@import "../../../../variables.scss";
+
+$group-height: 70px;
+
+.card {
+  background: none;
+  border-radius: 0px;
+  margin-bottom: 12px;
+
+  .title {
+    font-size: 20px;
+  }
+
+  .group-value {
+    padding-left: 12px;
+  }
+
+  .severity-padding {
+    padding-left: 30px;
+  }
+
+  .text-light {
+    font-family: Roboto;
+    color: $iron;
+  }
+
+  .text-dark {
+    font-size: 12px;
+    font-family: Roboto;
+    color: $dusty-grey;
+  }
+
+  .card-header {
+    padding: 0px;
+    border-radius: 0px;
+    height: $group-height;
+    line-height: $group-height;
+    background: $mine-shaft-8;
+  }
+
+  .down-arrow {
+    padding: 10px 20px;
+
+    &:hover {
+      background: $eden-1;
+      border: 2px solid $blue-mine;
+    }
+
+    &:after {
+      top: 2px;
+      right: 37px;
+      color: $gothic;
+      font-size: 25px;
+      content: '\f107';
+      font-style: normal;
+      position: absolute;
+      font-family: "FontAwesome";
+    }
+  }
+
+  .two-line {
+    padding-top: 16px;
+    line-height: 1;
+  }
+
+  .collapse {
+    background: $mine-shaft-9;
+  }
+  .card-block {
+    padding-bottom: 0px;
+    background: $mine-shaft-9;
+  }
+
+  .fa-caret-right, .fa-caret-down {
+    padding-right: 4px;
+    font-size: 15px;
+  }
+
+  .table-group-icon-col {
+    width:5px;
+    box-sizing: border-box;
+    padding: 6px 0px 7px 5px;
+    border: 1px solid transparent;
+
+    &:hover {
+      background: $eden-1;
+      border: 1px solid $blue-mine;
+    }
+  }
+
+  .table-score-col {
+    width:55px;
+  }
+
+  .table-group-row {
+    background: $mine-shaft-10;
+  }
+
+  .no-hover {
+    height: $group-height;
+
+    td {
+      text-align:center;
+      vertical-align:middle;
+      border-bottom: none;
+
+      &:hover {
+        background: none;
+      }
+    }
+  }
+}
+
+.table {
+  tr {
+    line-height: 25px;
+    height: 35px;
+  }
+}
+
+sup {
+  font-size: 100%;
+}
+
+.configure-table-icon {
+  font-size: 16px;
+  cursor: pointer;
+}
+
+.tree-wrapper {
+  min-height: calc(100vh - 280px);
+}
+
+.top-group {
+  cursor: pointer;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts
new file mode 100644
index 0000000..8a50404
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TreeViewComponent } from './tree-view.component';
+
+describe('TreeViewComponent', () => {
+  let component: TreeViewComponent;
+  let fixture: ComponentFixture<TreeViewComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ TreeViewComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(TreeViewComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should be created', () => {
+    expect(component).toBeTruthy();
+  });
+});

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..75a7e1c
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts
@@ -0,0 +1,352 @@
+/**
+ * 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, Input, OnChanges, SimpleChanges } from '@angular/core';
+import {Router} from '@angular/router';
+import {Subscription} from 'rxjs/Rx';
+
+import {TableViewComponent} from '../table-view/table-view.component';
+import {SearchResponse} from '../../../model/search-response';
+import {SearchService} from '../../../service/search.service';
+import {TreeGroupData, TreeAlertsSubscription} from './tree-group-data';
+import {GroupResponse} from '../../../model/group-response';
+import {GroupResult} from '../../../model/group-result';
+import {Group} from '../../../model/group';
+import {SortField} from '../../../model/sort-field';
+import {Sort} from '../../../utils/enums';
+import {MetronDialogBox, DialogType} from '../../../shared/metron-dialog-box';
+import {ElasticsearchUtils} from '../../../utils/elasticsearch-utils';
+import {SearchRequest} from '../../../model/search-request';
+import {UpdateService} from '../../../service/update.service';
+import {PatchRequest} from '../../../model/patch-request';
+
+@Component({
+  selector: 'app-tree-view',
+  templateUrl: './tree-view.component.html',
+  styleUrls: ['./tree-view.component.scss']
+})
+
+export class TreeViewComponent extends TableViewComponent implements OnChanges {
+
+  groupByFields: string[] = [];
+  topGroups: TreeGroupData[] = [];
+  groupResponse: GroupResponse = new GroupResponse();
+  treeGroupSubscriptionMap: {[key: string]: TreeAlertsSubscription } = {};
+
+  constructor(router: Router,
+              searchService: SearchService,
+              metronDialogBox: MetronDialogBox,
+              private updateService: UpdateService) {
+    super(router, searchService, metronDialogBox);
+  }
+
+  addAlertChangedListner() {
+    this.updateService.alertChanged$.subscribe(patchRequest => {
+      this.updateAlert(patchRequest);
+    });
+  }
+
+  collapseGroup(groupArray: TreeGroupData[], level: number, index: number) {
+    for (let i = index + 1; i < groupArray.length; i++) {
+      if (groupArray[i].level > (level)) {
+        groupArray[i].show = false;
+        groupArray[i].expand = false;
+      } else {
+        break;
+      }
+    }
+  }
+
+  createQuery(selectedGroup: TreeGroupData) {
+    let searchQuery = this.queryBuilder.generateSelect();
+    let groupQery = Object.keys(selectedGroup.groupQueryMap).map(key => {
+      return key.replace(/:/g, '\\:') +
+          ':' +
+          String(selectedGroup.groupQueryMap[key])
+          .replace(/[\*\+\-=~><\"\?^\${}\(\)\:\!\/[\]\\\s]/g, '\\$&') // replace single  special characters
+          .replace(/\|\|/g, '\\||') // replace ||
+          .replace(/\&\&/g, '\\&&'); // replace &&
+    }).join(' AND ');
+
+    groupQery += searchQuery === '*' ? '' : (' AND ' + searchQuery);
+    return groupQery;
+  }
+
+  expandGroup(groupArray: TreeGroupData[], level: number, index: number) {
+    for (let i = index + 1; i < groupArray.length; i++) {
+      if (groupArray[i].level === (level + 1)) {
+        groupArray[i].show = true;
+      } else {
+        break;
+      }
+    }
+  }
+
+  getAlerts(selectedGroup: TreeGroupData): Subscription {
+    let searchRequest = new SearchRequest();
+    searchRequest.query = this.createQuery(selectedGroup);
+    searchRequest.from = selectedGroup.pagingData.from;
+    searchRequest.size = selectedGroup.pagingData.size;
+    searchRequest.sort = selectedGroup.sortField ? [selectedGroup.sortField] : [];
+
+    return this.searchGroup(selectedGroup, searchRequest);
+  }
+
+  getGroups() {
+    let groupRequest = this.queryBuilder.groupRequest;
+    groupRequest.query = this.queryBuilder.generateSelect();
+
+    this.searchService.groups(groupRequest).subscribe(groupResponse => {
+      this.updateGroupData(groupResponse);
+    });
+  }
+
+  updateGroupData(groupResponse) {
+    this.selectedAlerts = [];
+    this.groupResponse = groupResponse;
+    this.parseTopLevelGroup();
+  }
+
+  groupPageChange(group: TreeGroupData) {
+    this.getAlerts(group);
+  }
+
+  createTopGroups(groupByFields: string[]) {
+    this.topGroups = [];
+    this.treeGroupSubscriptionMap = {};
+
+    this.groupResponse.groupResults.forEach((groupResult: GroupResult) => {
+      let treeGroupData = new TreeGroupData(groupResult.key, groupResult.total, groupResult.score, 0, false);
+      if (groupByFields.length === 1) {
+        treeGroupData.groupQueryMap  = this.createTopGroupQueryMap(groupByFields[0], groupResult);
+      }
+
+      this.topGroups.push(treeGroupData);
+    });
+  }
+
+  createTopGroupQueryMap(groupByFields: string, groupResult: GroupResult) {
+    let groupQueryMap = {};
+    groupQueryMap[groupByFields] = groupResult.key;
+    return groupQueryMap;
+  }
+
+  initTopGroups() {
+    let groupByFields =  this.queryBuilder.groupRequest.groups.map(group => group.field);
+    let currentTopGroupKeys = this.groupResponse.groupResults.map(groupResult => groupResult.key);
+    let previousTopGroupKeys = this.topGroups.map(group => group.key);
+
+    if (this.topGroups.length === 0 || JSON.stringify(this.groupByFields) !== JSON.stringify(groupByFields) ||
+        JSON.stringify(currentTopGroupKeys) !== JSON.stringify(previousTopGroupKeys)) {
+      this.createTopGroups(groupByFields);
+    }
+
+    this.groupByFields = groupByFields;
+  }
+
+  search(resetPaginationParams = true, pageSize: number = null) {
+    this.getGroups();
+  }
+
+  ngOnChanges(changes: SimpleChanges) {
+    if ((changes['alerts'] && changes['alerts'].currentValue)) {
+      this.search();
+    }
+  }
+
+  ngOnInit() {
+    this.addAlertChangedListner();
+  }
+
+  searchGroup(selectedGroup: TreeGroupData, searchRequest: SearchRequest): Subscription {
+    return this.searchService.search(searchRequest).subscribe(results => {
+      this.setData(selectedGroup, results);
+    }, error => {
+      this.metronDialogBox.showConfirmationMessage(ElasticsearchUtils.extractESErrorMessage(error), DialogType.Error);
+    });
+  }
+
+  setData(selectedGroup: TreeGroupData, results: SearchResponse) {
+    selectedGroup.response.results = results.results;
+    selectedGroup.pagingData.total = results.total;
+    selectedGroup.total = results.total;
+
+    this.topGroups.map(topGroup => {
+      if (topGroup.treeSubGroups.length > 0) {
+        topGroup.total = topGroup.treeSubGroups.reduce((total, subGroup) => { return total + subGroup.total }, 0);
+      }
+    });
+  }
+
+  checkAndToSubscription(group: TreeGroupData) {
+    if (group.groupQueryMap) {
+      let key = JSON.stringify(group.groupQueryMap);
+      if (this.treeGroupSubscriptionMap[key]) {
+        this.removeFromSubscription(group);
+      }
+
+      let subscription = this.getAlerts(group);
+      this.treeGroupSubscriptionMap[key] = new TreeAlertsSubscription(subscription, group);
+    }
+  }
+
+  removeFromSubscription(group: TreeGroupData) {
+    if (group.groupQueryMap) {
+      let key = JSON.stringify(group.groupQueryMap);
+      let subscription = this.treeGroupSubscriptionMap[key].refreshTimer;
+      if (subscription && !subscription.closed) {
+        subscription.unsubscribe();
+      }
+      delete this.treeGroupSubscriptionMap[key];
+    }
+  }
+
+  toggleSubGroups(topLevelGroup: TreeGroupData, selectedGroup: TreeGroupData, index: number) {
+    selectedGroup.expand = !selectedGroup.expand;
+
+    if (selectedGroup.expand) {
+      this.expandGroup(topLevelGroup.treeSubGroups, selectedGroup.level, index);
+      this.checkAndToSubscription(selectedGroup);
+    } else {
+      this.collapseGroup(topLevelGroup.treeSubGroups, selectedGroup.level, index);
+      this.removeFromSubscription(selectedGroup);
+    }
+  }
+
+  toggleTopLevelGroup(group: TreeGroupData) {
+    group.expand = !group.expand;
+    group.show = !group.show;
+
+    if (group.expand) {
+      this.checkAndToSubscription(group);
+    } else {
+      this.removeFromSubscription(group);
+    }
+  }
+
+  parseSubGroups(group: GroupResult, groupAsArray: TreeGroupData[],
+                 groupQueryMap: {[key: string]: string}, groupedBy: string, level: number, index: number): number {
+    index++;
+    groupQueryMap[groupedBy] = group.key;
+
+    let currentTreeNodeData = (groupAsArray.length > 0) ? groupAsArray[index] : null;
+
+    if (currentTreeNodeData && (currentTreeNodeData.key === group.key) && (currentTreeNodeData.level === level)) {
+      currentTreeNodeData.total = group.total;
+    } else {
+      let newTreeNodeData = new TreeGroupData(group.key, group.total, group.score, level, level === 1);
+      if (!currentTreeNodeData) {
+        groupAsArray.push(newTreeNodeData);
+      } else {
+        groupAsArray.splice(index, 1, newTreeNodeData);
+      }
+    }
+
+    if (!group.groupResults) {
+      groupAsArray[index].groupQueryMap = JSON.parse(JSON.stringify(groupQueryMap));
+      if (groupAsArray[index].expand && groupAsArray[index].show && groupAsArray[index].groupQueryMap) {
+        this.checkAndToSubscription(groupAsArray[index]);
+      }
+      return index;
+    }
+
+    group.groupResults.forEach(subGroup => {
+      index = this.parseSubGroups(subGroup, groupAsArray, groupQueryMap, group.groupedBy, level + 1, index);
+    });
+
+    return index;
+  }
+
+  parseTopLevelGroup() {
+    let groupedBy = this.groupResponse.groupedBy;
+
+    this.initTopGroups();
+
+    for (let i = 0; i < this.groupResponse.groupResults.length; i++) {
+      let index = -1;
+      let topGroup = this.topGroups[i];
+      let resultGroup = this.groupResponse.groupResults[i];
+      let groupQueryMap = this.createTopGroupQueryMap(groupedBy, resultGroup);
+
+      topGroup.total = resultGroup.total;
+
+      if (resultGroup.groupResults) {
+        resultGroup.groupResults.forEach(subGroup => {
+          index = this.parseSubGroups(subGroup, topGroup.treeSubGroups, groupQueryMap, resultGroup.groupedBy, 1, index);
+        });
+
+        topGroup.treeSubGroups.splice(index + 1);
+      }
+    }
+
+    if (this.groupByFields.length === 1) {
+      this.refreshAllExpandedGroups();
+    }
+  }
+
+  sortTreeSubGroup($event, treeGroup: TreeGroupData) {
+    let sortBy = $event.sortBy === 'id' ? '_uid' : $event.sortBy;
+
+    let sortField = new SortField();
+    sortField.field = sortBy;
+    sortField.sortOrder = $event.sortOrder === Sort.ASC ? 'asc' : 'desc';
+
+    treeGroup.sortEvent = $event;
+    treeGroup.sortField = sortField;
+    treeGroup.treeSubGroups.forEach(treeSubGroup => treeSubGroup.sortField = sortField);
+
+    this.refreshAllExpandedGroups();
+  }
+
+  selectAllGroupRows($event, group: TreeGroupData) {
+    this.selectedAlerts = [];
+
+    if ($event.target.checked) {
+      if (group.expand && group.show && group.response) {
+        this.selectedAlerts = group.response.results;
+      }
+
+      group.treeSubGroups.forEach(subGroup => {
+        if (subGroup.expand && subGroup.show && subGroup.response) {
+          this.selectedAlerts = this.selectedAlerts.concat(subGroup.response.results);
+        }
+      });
+    }
+    
+    this.onSelectedAlertsChange.emit(this.selectedAlerts);
+  }
+
+  refreshAllExpandedGroups() {
+    Object.keys(this.treeGroupSubscriptionMap).forEach(key => {
+      this.getAlerts(this.treeGroupSubscriptionMap[key].group);
+    });
+  }
+
+  updateAlert(patchRequest: PatchRequest) {
+    this.searchService.getAlert(patchRequest.sensorType, patchRequest.guid).subscribe(alertSource => {
+
+      Object.keys(this.treeGroupSubscriptionMap).forEach(key => {
+        let group = this.treeGroupSubscriptionMap[key].group;
+        if(group.response && group.response.results && group.response.results.length > 0) {
+          group.response.results.filter(alert => alert.source.guid === patchRequest.guid)
+          .map(alert => alert.source = alertSource);
+        }
+      });
+    });
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.scss b/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.scss
index 4c29e28..7d16a4f 100644
--- a/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.scss
+++ b/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.scss
@@ -25,7 +25,7 @@ label {
 .card {
   width: 349px;
   position: absolute;
-  left: -140px;
+  left: -115px;
   z-index: 1;
   top: 50px;
   border-radius: 0;
@@ -78,7 +78,7 @@ label {
 .fa-sort-asc {
   position: absolute;
   bottom: -50px;
-  left: 10px;
+  left: 44px;
   font-size: 62px;
   color: #333333;
   z-index: 2;


Mime
View raw message