You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by ni...@apache.org on 2017/11/27 22:44:45 UTC

[2/2] metron git commit: METRON-1252 Build UI for grouping alerts into meta-alerts (iraghumitra via nickwallen) closes apache/metron#803

METRON-1252 Build UI for grouping alerts into meta-alerts (iraghumitra via nickwallen) closes apache/metron#803


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

Branch: refs/heads/master
Commit: d07833a25470bafafdeee5e9e4054cb4a013bedb
Parents: a1408d7
Author: iraghumitra <ra...@gmail.com>
Authored: Mon Nov 27 17:14:13 2017 -0500
Committer: nickallen <ni...@apache.org>
Committed: Mon Nov 27 17:14:13 2017 -0500

----------------------------------------------------------------------
 .../e2e/alert-details/alert-details.po.ts       |  31 ++-
 .../alert-details-status.e2e-spec.ts            |   3 +-
 .../e2e/alerts-list/alerts-list.e2e-spec.ts     |  18 +-
 .../e2e/alerts-list/alerts-list.po.ts           |  63 ++++-
 .../configure-table/configure-table.e2e-spec.ts |   3 +-
 .../meta-alerts/meta-alert.e2e-spec.ts          | 248 +++++++++++++++++++
 .../alerts-list/meta-alerts/meta-alert.po.ts    |  43 ++++
 .../alerts-list/tree-view/tree-view.e2e-spec.ts |  95 ++++---
 .../e2e/alerts-list/tree-view/tree-view.po.ts   |  74 +++++-
 .../metron-alerts/e2e/utils/e2e_util.ts         |  12 +
 .../metron-alerts/protractor.conf.js            |   9 +-
 .../metron-alerts/src/_variables.scss           |   2 +
 .../alert-details/alert-details-keys.pipe.ts    |  31 +++
 .../alert-details/alert-details.component.html  |  34 ++-
 .../alert-details/alert-details.component.scss  |  33 +++
 .../alert-details/alert-details.component.ts    |  44 +++-
 .../alert-details/alerts-details.module.ts      |   6 +-
 .../alerts-list/alerts-list.component.html      |  10 +-
 .../alerts/alerts-list/alerts-list.component.ts |  77 ++++--
 .../alerts/alerts-list/alerts-list.module.ts    |   3 +-
 .../src/app/alerts/alerts-list/query-builder.ts |   5 +-
 .../table-view/table-view.component.html        | 101 +++++++-
 .../table-view/table-view.component.scss        |  15 +-
 .../table-view/table-view.component.ts          | 145 +++++++++--
 .../alerts-list/tree-view/tree-group-data.ts    |   1 +
 .../tree-view/tree-view.component.html          |  11 +-
 .../tree-view/tree-view.component.scss          |  17 ++
 .../tree-view/tree-view.component.ts            | 117 +++++++--
 .../meta-alerts/meta-alerts.component.html      |  52 ++++
 .../meta-alerts/meta-alerts.component.scss      |  58 +++++
 .../alerts/meta-alerts/meta-alerts.component.ts |  79 ++++++
 .../alerts/meta-alerts/meta-alerts.module.ts    |  13 +
 .../alerts/meta-alerts/meta-alerts.routing.ts   |  24 ++
 .../metron-alerts/src/app/app.module.ts         |   8 +-
 .../metron-alerts/src/app/model/alert-source.ts |   1 +
 .../metron-alerts/src/app/model/filter.ts       |  28 ++-
 .../metron-alerts/src/app/model/get-request.ts  |  29 +++
 .../app/model/meta-alert-add-remove-request.ts  |  23 ++
 .../src/app/model/meta-alert-create-request.ts  |  24 ++
 .../src/app/model/replace-request.ts            |  24 ++
 .../src/app/model/search-request.ts             |   2 +-
 .../metron-alerts/src/app/model/sort-field.ts   |   5 +
 .../src/app/service/meta-alert.service.ts       |  88 +++++++
 .../src/app/service/update.service.ts           |  20 +-
 .../src/app/shared/metron-dialog-box.ts         |   4 +-
 .../src/app/shared/pipes/time-lapse.pipe.ts     |  17 ++
 .../src/app/shared/shared.module.ts             |   7 +-
 .../shared/time-range/time-range.component.ts   |   7 +-
 .../metron-alerts/src/app/utils/constants.ts    |   5 +
 .../metron-alerts/src/app/utils/utils.ts        |  22 +-
 .../src/environments/environment.e2e.ts         |   4 +-
 metron-interface/metron-alerts/src/styles.scss  |  59 +++++
 52 files changed, 1645 insertions(+), 209 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 ed71627..18cb273 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
@@ -106,11 +106,13 @@ export class MetronAlertDetailsPage {
   }
 
   getCommentIconCountInListView() {
-    return element.all(by.css('app-table-view .fa.fa-comments-o')).count();
+    let commentsElement = element.all(by.css('app-table-view .fa.fa-comments-o'));
+    return waitForElementPresence(commentsElement).then(() => commentsElement.count());
   }
 
   getCommentIconCountInTreeView() {
-    return element.all(by.css('app-tree-view .fa.fa-comments-o')).count();
+    let commentsElement = element.all(by.css('app-tree-view .fa.fa-comments-o'));
+    return waitForElementPresence(commentsElement).then(() => commentsElement.count());
   }
 
   waitForTextChange(element, previousText) {
@@ -118,4 +120,29 @@ export class MetronAlertDetailsPage {
     return browser.wait(EC.not(EC.textToBePresentInElement(element, previousText)));
   }
 
+  getAlertNameOrId() {
+    let nameSelector = element(by.css('app-alert-details .editable-text'));
+    return waitForElementVisibility(nameSelector).then(() => nameSelector.getText());
+  }
+
+  clickRenameMetaAlert() {
+    element(by.css('app-alert-details .editable-text')).click();
+  }
+
+  renameMetaAlert(name: string) {
+    element(by.css('app-alert-details input.form-control')).sendKeys(name);
+  }
+
+  cancelRename() {
+    element(by.css('app-alert-details .input-group .fa.fa-times')).click();
+  }
+
+  saveRename() {
+    element(by.css('app-alert-details .fa.fa-check')).click();
+  }
+
+  getAlertDetailsCount() {
+    let titleElement = element.all(by.css('app-alert-details .alert-details-title')).get(0);
+    return waitForElementVisibility(titleElement).then(() => element.all(by.css('app-alert-details .alert-details-title')).count());
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 4e25f82..d051a78 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
@@ -50,9 +50,10 @@ describe('metron-alerts alert status', function() {
 
   it('should change alert statuses', () => {
     let alertId = 'c4c5e418-3938-099e-bb0d-37028a98dca8';
-    
+
     page.navigateTo(alertId);
     page.clickNew();
+    expect(page.getAlertStatus('ANY')).toEqual('NEW');
     page.clickOpen();
     expect(page.getAlertStatus('NEW')).toEqual('OPEN');
     expect(listPage.getAlertStatusById(alertId)).toEqual('OPEN');

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 b0574ee..358878d 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
@@ -24,8 +24,8 @@ import { loadTestData, deleteTestData } from '../utils/e2e_util';
 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', '', ''];
+  let columnNames = [ '', 'Score', 'id', 'timestamp', 'source:type', 'ip_src_addr', 'enrichm...:country',
+                      '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' ];
 
@@ -58,8 +58,9 @@ describe('metron-alerts App', function() {
     expect(page.isPausePlayRefreshButtonPresent()).toEqualBcoz(true, 'for pause/play button');
     expect(page.isConfigureTableColumnsPresent()).toEqualBcoz(true, 'for alerts table column configure button');
 
-    expect(page.getAlertTableTitle()).toEqualBcoz('Alerts (169)', 'for alerts title');
-    expect(page.getActionDropdownItems()).toEqualBcoz([ 'Open', 'Dismiss', 'Escalate', 'Resolve' ], 'for default dropdown actions');
+    expect(page.getChangesAlertTableTitle('Alerts (0)')).toEqualBcoz('Alerts (169)', 'for alerts title');
+    expect(page.getActionDropdownItems()).toEqualBcoz([ 'Open', 'Dismiss', 'Escalate', 'Resolve', 'Add to Alert' ],
+                                                        'for default dropdown actions');
     expect(page.getTableColumnNames()).toEqualBcoz(columnNames, 'for default column names for alert list table');
   });
 
@@ -300,6 +301,9 @@ describe('metron-alerts App', function() {
   });
   
   it('should have all time-range included while searching', () => {
+    let startDate = new Date(1505325575000);
+    let endDate = new Date(1505325580000);
+
     page.clearLocalStorage();
     page.clickDateSettings();
 
@@ -309,8 +313,10 @@ describe('metron-alerts App', function() {
 
     /* Select custom date for time range */
     page.clickDateSettings();
-    page.setDate(0, '2017', 'September', '13', '23', '29', '35');
-    page.setDate(1, '2017', 'September', '13', '23', '29', '40');
+    page.setDate(0, String(startDate.getFullYear()), startDate.toLocaleString('en-us', { month: "long" }), String(startDate.getDate()),
+                String(startDate.getHours()), String(startDate.getMinutes()), String(startDate.getSeconds()));
+    page.setDate(1, String(endDate.getFullYear()), endDate.toLocaleString('en-us', { month: "long" }), String(endDate.getDate()),
+                String(endDate.getHours()), String(endDate.getMinutes()), String(endDate.getSeconds()));
     page.selectTimeRangeApplyButton();
     expect(page.getChangesAlertTableTitle('Alerts (169)')).toEqual('Alerts (5)');
 

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 4a97917..45f4fee 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 * as moment from 'moment/moment';
 import {waitForElementVisibility, waitForElementPresence, waitForElementInVisibility} from '../utils/e2e_util';
 
 export class MetronAlertsPage {
@@ -172,7 +173,7 @@ export class MetronAlertsPage {
   }
 
   clickTableText(name: string) {
-    waitForElementPresence(element.all(by.css('app-table-view tbody tr a'))).then(() => element.all(by.linkText(name)).get(0).click());
+    waitForElementPresence(element.all(by.linkText(name))).then(() => element.all(by.linkText(name)).get(0).click());
   }
 
   clickClearSearch() {
@@ -278,14 +279,20 @@ export class MetronAlertsPage {
     });
   }
 
-  getAlertStatus(rowIndex: number, previousText) {
+  getAlertStatus(rowIndex: number, previousText: string, colIndex = 8) {
     let row = element.all(by.css('app-alerts-list tbody tr')).get(rowIndex);
-    let column = row.all(by.css('td a')).get(8);
+    let column = row.all(by.css('td a')).get(colIndex);
     return this.waitForTextChange(column, previousText).then(() => {
       return column.getText();
     });
   }
 
+  waitForMetaAlert() {
+    browser.sleep(2000);
+    return element(by.css('button[data-name="search"]')).click()
+    .then(() => waitForElementPresence(element(by.css('.icon-cell.dropdown-cell'))));
+  }
+
   isDateSeettingDisabled() {
     return element.all(by.css('app-time-range button.btn.btn-search[disabled=""]')).count().then((count) => { return (count === 1); });
   }
@@ -298,7 +305,7 @@ export class MetronAlertsPage {
   getTimeRangeTitles() {
     return element.all(by.css('app-time-range .title')).getText();
   }
-  
+
   getQuickTimeRanges() {
     return element.all(by.css('app-time-range .quick-ranges span')).getText();
   }
@@ -315,7 +322,7 @@ export class MetronAlertsPage {
     element.all(by.cssContainingText('.quick-ranges span', quickRange)).get(0).click();
     browser.sleep(2000);
   }
-  
+
   getTimeRangeButtonText() {
     return element.all(by.css('app-time-range button.btn-search span')).get(0).getText();
   }
@@ -351,10 +358,44 @@ export class MetronAlertsPage {
   }
 
   getAlertStatusById(id: string) {
-    return element(by.css('a[title="' + id +'"]'))
+    return element(by.css('a[title="' + id + '"]'))
           .element(by.xpath('../..')).all(by.css('td a')).get(8).getText();
   }
 
+  sortTable(colName: string) {
+    element.all(by.css('table thead th')).all(by.linkText(colName)).get(0).click();
+  }
+
+  getCellValue(rowIndex: number, colIndex: number, previousText: string) {
+    let cellElement = element.all(by.css('table tbody tr')).get(rowIndex).all(by.css('td')).get(colIndex);
+    return this.waitForTextChange(cellElement, previousText).then(() => cellElement.getText());
+  }
+
+  expandMetaAlert(rowIndex: number) {
+    element.all(by.css('table tbody tr')).get(rowIndex).element(by.css('.icon-cell.dropdown-cell')).click();
+  }
+
+  getHiddenRowCount() {
+    return element.all(by.css('table tbody tr.d-none')).count();
+  }
+
+  getNonHiddenRowCount() {
+    return element.all(by.css('table tbody tr:not(.d-none)')).count();
+  }
+
+  getAllRowsCount() {
+    return element.all(by.css('table tbody tr')).count();
+  }
+
+  clickOnMetaAlertRow(rowIndex: number) {
+    element.all(by.css('table tbody tr')).get(rowIndex).all(by.css('td')).get(5).click();
+    browser.sleep(2000);
+  }
+
+  removeAlert(rowIndex: number) {
+    return element.all(by.css('app-table-view .fa-chain-broken')).get(rowIndex).click();
+  }
+
   loadSavedSearch(name: string) {
     element.all(by.css('app-saved-searches metron-collapse')).get(1).element(by.css('li[title="'+ name +'"]')).click();
     browser.sleep(1000);
@@ -376,18 +417,22 @@ export class MetronAlertsPage {
         let retArr = [arr[0]];
         for (let i=1; i < arr.length; i++) {
           let dateStr = arr[i].split(' to ');
-          let fromTime = new Date(dateStr[0]).getTime();
-          let toTime = new Date(dateStr[1]).getTime();
+          let fromTime = moment.utc(dateStr[0], 'YYYY-MM-DD HH:mm:ss Z').unix() * 1000;
+          let toTime = moment.utc(dateStr[1], 'YYYY-MM-DD HH:mm:ss Z').unix() * 1000;
           retArr.push((toTime - fromTime) + '');
         }
         return retArr;
     });
   }
-  
+
   renameColumn(name: string, value: string) {
     element(by.cssContainingText('app-configure-table span', name))
     .element(by.xpath('../..'))
     .element(by.css('.input')).sendKeys(value);
   }
 
+  getTableCellValues(cellIndex: number, startRowIndex: number, endRowIndex: number): any {
+    return element.all(by.css('table tbody tr td:nth-child(' + cellIndex + ')')).getText()
+    .then(val => val.slice(startRowIndex, endRowIndex));
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts
index ddad558..6741e2f 100644
--- a/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts
+++ b/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts
@@ -76,8 +76,7 @@ describe('metron-alerts configure table', function() {
     expect(page.getChangesAlertTableTitle('Alerts (169)')).toEqual('Alerts (25)');
     page.clickClearSearch();
 
-    let columnNames = ['Score','id', 'timestamp','source:type','ip_src_addr','Country','ip_dst_addr','host','alert_status','',''];
-    expect(page.getTableColumnNames()).toEqualBcoz(columnNames, 'for renamed column names for alert list table');
+    expect(page.getTableColumnNames()).toContain('Country', 'for renamed column names for alert list table');
 
   });
 

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.e2e-spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.e2e-spec.ts
new file mode 100644
index 0000000..38bacee
--- /dev/null
+++ b/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.e2e-spec.ts
@@ -0,0 +1,248 @@
+/// <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 { 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';
+import {MetronAlertDetailsPage} from '../../alert-details/alert-details.po';
+import {MetaAlertPage} from './meta-alert.po';
+import {AlertFacetsPage} from '../alert-filters/alert-filters.po';
+
+describe('meta-alerts workflow', function() {
+  let detailsPage: MetronAlertDetailsPage;
+  let tablePage: MetronAlertsPage;
+  let metaAlertPage: MetaAlertPage;
+  let treePage: TreeViewPage;
+  let loginPage: LoginPage;
+  let alertFacetsPage: AlertFacetsPage;
+
+  beforeAll(() => {
+    loadTestData();
+
+    loginPage = new LoginPage();
+    loginPage.login();
+    tablePage = new MetronAlertsPage();
+    treePage = new TreeViewPage();
+    tablePage = new MetronAlertsPage();
+    metaAlertPage = new MetaAlertPage();
+    detailsPage = new MetronAlertDetailsPage();
+    alertFacetsPage = new AlertFacetsPage();
+  });
+
+  afterAll(() => {
+    loginPage.logout();
+    deleteTestData();
+  });
+
+  beforeEach(() => {
+    jasmine.addMatchers(customMatchers);
+  });
+
+  it('should have all the steps for meta alerts workflow', () => {
+    let comment1 = 'This is a sample comment';
+    let userNameAndTimestamp = '- admin - a few seconds ago';
+    let confirmText = 'Do you wish to create a meta alert with 113 selected alerts?';
+    let dashRowValues = {
+      'firstDashRow': ['0', '192.168.138.158', 'ALERTS', '113'],
+      'secondDashRow': ['0', '192.168.66.1', 'ALERTS', '56']
+    };
+
+    tablePage.navigateTo();
+
+    /* Create Meta Alert */
+    treePage.selectGroup('ip_src_addr');
+    expect(treePage.getDashGroupValues('192.168.138.158')).toEqualBcoz(dashRowValues.firstDashRow, 'First Dashrow to be present');
+    expect(treePage.getDashGroupValues('192.168.66.1')).toEqualBcoz(dashRowValues.secondDashRow, 'Second Dashrow to be present');
+
+    treePage.clickOnMergeAlerts('192.168.138.158');
+    expect(treePage.getConfirmationText()).toEqualBcoz(confirmText, 'confirmation text to be present');
+    treePage.clickNoForConfirmation();
+
+    treePage.clickOnMergeAlerts('192.168.138.158');
+    treePage.clickYesForConfirmation();
+
+    treePage.waitForElementToDisappear('192.168.138.158');
+
+    treePage.unGroup();
+
+    /* Table should have all alerts */
+    tablePage.waitForMetaAlert();
+    expect(tablePage.getPaginationText()).toEqualBcoz('1 - 25 of 57', 'pagination text to be present');
+    expect(tablePage.getCellValue(0, 2, '(114)')).toContain('(113)', 'number of alerts in a meta alert should be correct');
+    expect(tablePage.getNonHiddenRowCount()).toEqualBcoz(25, '25 rows to be visible');
+    expect(tablePage.getAllRowsCount()).toEqualBcoz(138, '138 rows to be available');
+    expect(tablePage.getHiddenRowCount()).toEqualBcoz(113, '113 rows to be hidden');
+    tablePage.expandMetaAlert(0);
+    expect(tablePage.getNonHiddenRowCount()).toEqualBcoz(138, '138 rows to be visible after group expand');
+    expect(tablePage.getAllRowsCount()).toEqualBcoz(138, '138 rows to be available after group expand');
+    expect(tablePage.getHiddenRowCount()).toEqualBcoz(0, '0 rows to be hidden after group expand');
+
+    /* Meta Alert Status Change */
+    tablePage.toggleAlertInList(0);
+    tablePage.clickActionDropdownOption('Open');
+    expect(tablePage.getAlertStatus(0, 'NEW', 2)).toEqual('OPEN');
+    expect(tablePage.getAlertStatus(1, 'NEW')).toEqual('OPEN');
+    expect(tablePage.getAlertStatus(2, 'NEW')).toEqual('OPEN');
+
+    /* Details Edit Should work - add comments - remove comments - multiple alerts */
+    tablePage.clickOnMetaAlertRow(0);
+    expect(detailsPage.getAlertDetailsCount()).toEqualBcoz(113, '113 alert details should be present');
+    detailsPage.clickRenameMetaAlert();
+    detailsPage.renameMetaAlert('e2e-meta-alert');
+    detailsPage.cancelRename();
+    expect(detailsPage.getAlertNameOrId()).not.toEqual('e2e-meta-alert');
+    detailsPage.clickRenameMetaAlert();
+    detailsPage.renameMetaAlert('e2e-meta-alert');
+    detailsPage.saveRename();
+    expect(detailsPage.getAlertNameOrId()).toEqual('e2e-meta-alert');
+
+    detailsPage.clickCommentsInSideNav();
+    detailsPage.addCommentAndSave(comment1, 0);
+    expect(detailsPage.getCommentsText()).toEqual([comment1]);
+    expect(detailsPage.getCommentsUserNameAndTimeStamp()).toEqual([userNameAndTimestamp]);
+    expect(detailsPage.getCommentIconCountInListView()).toEqual(1);
+
+    detailsPage.deleteComment();
+    detailsPage.clickYesForConfirmation();
+
+    detailsPage.closeDetailPane();
+
+    /* Add to alert */
+    tablePage.toggleAlertInList(3);
+    tablePage.clickActionDropdownOption('Add to Alert');
+    expect(metaAlertPage.getPageTitle()).toEqualBcoz('Add to Alert', 'Add Alert Title should be present');
+    expect(metaAlertPage.getMetaAlertsTitle()).toEqualBcoz('SELECT OPEN ALERT', 'select open alert title should be present');
+    expect(metaAlertPage.getAvailableMetaAlerts()).toEqualBcoz('e2e-meta-alert (113)', 'Meta alert should be present');
+    metaAlertPage.selectRadio();
+    metaAlertPage.addToMetaAlert();
+    expect(tablePage.getCellValue(0, 2, '(113')).toContain('(114)', 'alert count should be incremented');
+
+    /* Remove from alert */
+    let removAlertConfirmText = 'Do you wish to remove the alert from the meta alert?';
+    tablePage.removeAlert(2);
+    expect(treePage.getConfirmationText()).toEqualBcoz(removAlertConfirmText, 'confirmation text to remove alert from meta alert');
+    treePage.clickYesForConfirmation();
+    expect(tablePage.getCellValue(0, 2, '(114')).toContain('(113)', 'alert count should be decremented');
+
+    /* Delete Meta Alert */
+    let removeMetaAlertConfirmText = 'Do you wish to remove all the alerts from meta alert?';
+    tablePage.removeAlert(0);
+    expect(treePage.getConfirmationText()).toEqualBcoz(removeMetaAlertConfirmText, 'confirmation text to remove meta alert');
+    treePage.clickYesForConfirmation();
+  });
+
+  it('should create a meta alert from nesting of more than one level', () => {
+    let groupByItems = {
+      'source:type': '1',
+      'ip_dst_addr': '7',
+      'host': '9',
+      'enrichm...:country': '3',
+      'ip_src_addr': '2'
+    };
+    let alertsInMetaAlerts = [
+      '82f8046d-d...03b17480dd',
+      '5c1825f6-7...da3abe3aec',
+      '9041285e-9...a04a885b53',
+      'ed906df7-2...91cc54c2f3',
+      'c894bbcf-3...74cf0cc1fe',
+      'e63ff7ae-d...cddbe0c0b3',
+      '3c346bf9-b...cb04b43210',
+      'dcc483af-c...7bb802b652',
+      'b71f085d-6...a4904d8fcf',
+      '754b4f63-3...b39678207f',
+      'd9430af3-e...9a18600ab2',
+      '9a943c94-c...3b9046b782',
+      'f39dc401-3...1f9cf02cd9',
+      'd887fe69-c...2fdba06dbc',
+      'e38be207-b...60a43e3378',
+      'eba8eccb-b...0005325a90',
+      'adca96e3-1...979bf0b5f1',
+      '42f4ce28-8...b3d575b507',
+      'aed3d10f-b...8b8a139f25',
+      'a5e95569-a...0e2613b29a'
+    ];
+
+    let alertsAfterDeletedInMetaAlerts = [
+      '82f8046d-d...03b17480dd',
+      '5c1825f6-7...da3abe3aec',
+      '9041285e-9...a04a885b53',
+      'ed906df7-2...91cc54c2f3',
+      'e63ff7ae-d...cddbe0c0b3',
+      '3c346bf9-b...cb04b43210',
+      'dcc483af-c...7bb802b652',
+      'b71f085d-6...a4904d8fcf',
+      '754b4f63-3...b39678207f',
+      'd9430af3-e...9a18600ab2',
+      '9a943c94-c...3b9046b782',
+      'f39dc401-3...1f9cf02cd9',
+      'd887fe69-c...2fdba06dbc',
+      'e38be207-b...60a43e3378',
+      'eba8eccb-b...0005325a90',
+      'adca96e3-1...979bf0b5f1',
+      '42f4ce28-8...b3d575b507',
+      'aed3d10f-b...8b8a139f25',
+      'a5e95569-a...0e2613b29a'
+    ];
+
+    // Create a meta alert from a group that is nested by more than 1 level
+    treePage.selectGroup('source:type');
+    treePage.selectGroup('ip_dst_addr');
+    treePage.expandDashGroup('alerts_ui_e2e');
+
+    treePage.clickOnMergeAlertsInTable('alerts_ui_e2e', '224.0.0.251', 0);
+    treePage.clickYesForConfirmation();
+
+    treePage.unGroup();
+    tablePage.waitForMetaAlert();
+
+    expect(tablePage.getPaginationText()).toEqualBcoz('1 - 25 of 150', 'pagination text to be present');
+
+    // Meta Alert should appear in Filters
+    alertFacetsPage.toggleFacetState(4);
+    expect(alertFacetsPage.getFacetValues(4)).toEqual({'metaalert': '1' }, 'for source:type facet');
+
+    // Meta Alert should not appear in Groups
+    expect(treePage.getGroupByItemNames()).toEqualBcoz(Object.keys(groupByItems), 'Group By Elements names should be present');
+    expect(treePage.getGroupByItemCounts()).toEqualBcoz(Object.keys(groupByItems).map(key => groupByItems[key]),
+        '5 Group By Elements values should be present');
+
+
+    tablePage.setSearchText('guid:c894bbcf-3195-0708-aebe-0574cf0cc1fe');
+    expect(tablePage.getChangesAlertTableTitle('Alerts (150)')).toEqual('Alerts (1)');
+    tablePage.expandMetaAlert(0);
+    expect(tablePage.getAllRowsCount()).toEqual(21);
+    tablePage.expandMetaAlert(0);
+    tablePage.clickClearSearch();
+    expect(tablePage.getChangesAlertTableTitle('Alerts (1)')).toEqual('Alerts (150)');
+
+    // Delete a meta alert from the middle and check the data
+    tablePage.expandMetaAlert(0);
+    expect(tablePage.getTableCellValues(3, 1, 21)).toEqual(alertsInMetaAlerts);
+    tablePage.removeAlert(5);
+    treePage.clickYesForConfirmation();
+    expect(tablePage.getCellValue(0, 2, '(20')).toContain('(19)', 'alert count should be decremented');
+    expect(tablePage.getTableCellValues(3, 1, 20)).toEqual(alertsAfterDeletedInMetaAlerts);
+
+    //Remove the meta alert
+    tablePage.removeAlert(0);
+    treePage.clickYesForConfirmation();
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.po.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.po.ts b/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.po.ts
new file mode 100644
index 0000000..762deb8
--- /dev/null
+++ b/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.po.ts
@@ -0,0 +1,43 @@
+/**
+ * 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} from 'protractor';
+
+export class MetaAlertPage {
+
+  getPageTitle() {
+    return element(by.css('app-meta-alerts .form-title')).getText();
+  }
+
+  getMetaAlertsTitle() {
+    return element(by.css('app-meta-alerts .title')).getText();
+  }
+
+  getAvailableMetaAlerts() {
+    return element(by.css('app-meta-alerts .guid-name-container div')).getText();
+  }
+
+  selectRadio() {
+    return element.all(by.css('app-meta-alerts .checkmark')).click();
+  }
+
+  addToMetaAlert() {
+    element.all(by.css('app-meta-alerts')).get(0).element(by.buttonText('ADD')).click();
+    browser.sleep(2000);
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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
index caa3754..87636cd 100644
--- 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
@@ -135,41 +135,40 @@ describe('metron-alerts tree view', function () {
 
   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']
-    };
+    let usGroupIds = ['9a969c64-b...001cb011a3','a651f7c3-1...a97d4966c9','afc36901-3...d931231ab2','d860ac35-1...f9e282d571','04a5c3d0-9...af17c06fbc'];
+    let frGroupIds = ['07b29c29-9...ff19eaa888','7cd91565-1...de5be54a6e','ca5bde58-a...f3a88d2df4','5d6faf83-8...b88a407647','e2883424-f...79bb8b0606'];
 
-    page.selectGroup('host');
+    page.selectGroup('source:type');
+    page.selectGroup('ip_dst_addr');
     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');
+    expect(page.getActiveGroups()).toEqualBcoz(['source:type', 'ip_dst_addr', 'enrichments:geo:ip_dst_addr:country'], '3 groups should be selected');
+
+    expect(page.getDashGroupValues('alerts_ui_e2e')).toEqualBcoz(['0', 'alerts_ui_e2e', 'ALERTS', '169'],
+                                                              'Top Level Group Values should be present for alerts_ui_e2e');
+
+    page.expandDashGroup('alerts_ui_e2e');
+    expect(page.getSubGroupValuesByPosition('alerts_ui_e2e', '204.152.254.221', 0)).toEqualBcoz('0 204.152.254.221 (13)',
+                                                                    'Second Level Group Values should be present for 204.152.254.221');
 
+    page.expandSubGroupByPosition('alerts_ui_e2e', '204.152.254.221', 0);
+    expect(page.getSubGroupValuesByPosition('alerts_ui_e2e', 'US', 0)).toEqualBcoz('0 US (13)',
+        'Third Level Group Values should be present for US');
+
+    page.expandSubGroup('alerts_ui_e2e', 'US');
+    expect(page.getSubGroupValuesByPosition('alerts_ui_e2e', 'US', 0)).toEqualBcoz('0 US (13)',
+        'Third Level Group Values should not change when expanded for US');
+    expect(page.getCellValuesFromTable('alerts_ui_e2e', 'id', '04a5c3d0-9...af17c06fbc')).toEqual(usGroupIds, 'rows should be present for US');
+
+
+    page.expandSubGroup('alerts_ui_e2e', '62.75.195.236');
+    expect(page.getSubGroupValuesByPosition('alerts_ui_e2e', 'FR', 1)).toEqualBcoz('0 FR (23)',
+        'Third Level Group Values should be present for FR');
+
+    page.expandSubGroupByPosition('alerts_ui_e2e', 'FR', 1);
+    expect(page.getSubGroupValuesByPosition('alerts_ui_e2e', 'FR', 1)).toEqualBcoz('0 FR (23)',
+        'Third Level Group Values should not change when expanded for FR');
+    expect(page.getCellValuesFromTable('alerts_ui_e2e', 'id', 'e2883424-f...79bb8b0606')).toEqual(usGroupIds.concat(frGroupIds), 'rows should be present for FR');
+    
     page.unGroup();
     expect(page.getActiveGroups()).toEqualBcoz([], 'no groups should be selected');
   });
@@ -177,19 +176,13 @@ describe('metron-alerts tree view', function () {
 
   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 usTSCol = ['2017-09-13 17:59:32', '2017-09-13 17:59:42', '2017-09-13 17:59:53', '2017-09-13 18:00:02', '2017-09-13 18:00:14'];
+    let ruTSCol = ['2017-09-13 17:59:33', '2017-09-13 17:59:48', '2017-09-13 17:59:51', '2017-09-13 17:59:54', '2017-09-13 17:59:57'];
+    let frTSCol = ['2017-09-13 17:59:37', '2017-09-13 17:59:46', '2017-09-13 18:00:31', '2017-09-13 18:00:33', '2017-09-13 18:00:37'];
 
-    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'];
+    let usSortedTSCol = ['2017-09-13 18:02:19', '2017-09-13 18:02:16', '2017-09-13 18:02:09', '2017-09-13 18:01:58', '2017-09-13 18:01:52'];
+    let ruSortedTSCol = ['2017-09-14 06:29:40', '2017-09-14 06:29:40', '2017-09-14 06:29:40', '2017-09-14 06:29:40', '2017-09-13 18:02:13'];
+    let frSortedTSCol = ['2017-09-14 06:29:40', '2017-09-14 04:29:40', '2017-09-13 18:02:20', '2017-09-13 18:02:05', '2017-09-13 18:02:04'];
 
     page.selectGroup('source:type');
     page.selectGroup('enrichments:geo:ip_dst_addr:country');
@@ -199,14 +192,18 @@ describe('metron-alerts tree view', function () {
     page.expandSubGroup('alerts_ui_e2e', 'RU');
     page.expandSubGroup('alerts_ui_e2e', 'FR');
 
-    let unsortedIds = [...usIDCol, ...ruIDCol, ...frIDCol];
-    let sortedIds = [...usSortedIDCol, ...ruSortedIDCol, ...frSortedIDCol];
+    let unsortedTS = [...usTSCol, ...ruTSCol, ...frTSCol];
+    let sortedTS = [...usSortedTSCol, ...ruSortedTSCol, ...frSortedTSCol];
+
+    page.sortSubGroup('alerts_ui_e2e', 'timestamp');
 
-    expect(page.getCellValuesFromTable('alerts_ui_e2e', 'id', 'e2883424-f...79bb8b0606')).toEqual(unsortedIds, 'id should not be sorted');
+    expect(page.getCellValuesFromTable('alerts_ui_e2e', 'timestamp', '2017-09-13 18:00:37')).toEqual(unsortedTS,
+                                                                                                      'timestamp should be sorted asc');
 
-    page.sortSubGroup('alerts_ui_e2e', 'id');
+    page.sortSubGroup('alerts_ui_e2e', 'timestamp');
 
-    expect(page.getCellValuesFromTable('alerts_ui_e2e', 'id', '436b9ecf-b...5f1ece4c4d')).toEqual(sortedIds, 'id should be sorted');
+    expect(page.getCellValuesFromTable('alerts_ui_e2e', 'timestamp', '2017-09-13 18:02:04')).toEqual(sortedTS,
+                                                                                                      'timestamp should be sorted dsc');
 
     page.unGroup();
     expect(page.getActiveGroups()).toEqualBcoz([], 'no groups should be selected');

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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
index b8472df..368f709 100644
--- 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
@@ -16,8 +16,11 @@
  * limitations under the License.
  */
 
-import {browser, element, by, protractor} from 'protractor';
-import {waitForElementPresence, waitForTextChange} from '../../utils/e2e_util';
+import {browser, element, by} from 'protractor';
+import {
+  waitForElementPresence, waitForTextChange, waitForElementVisibility,
+  waitForElementInVisibility
+} from '../../utils/e2e_util';
 
 export class TreeViewPage {
   navigateToAlertsList() {
@@ -52,7 +55,11 @@ export class TreeViewPage {
   }
 
   getSubGroupValues(name: string, rowName: string) {
-    return element(by.css('[data-name="' + name + '"] table tbody tr[data-name="' + rowName + '"]')).getText();
+    return this.getSubGroupValuesByPosition(name, rowName, 0);
+  }
+
+  getSubGroupValuesByPosition(name: string, rowName: string, position: number) {
+    return element.all(by.css('[data-name="' + name + '"] table tbody tr[data-name="' + rowName + '"]')).get(position).getText();
   }
 
   selectGroup(name: string) {
@@ -73,16 +80,25 @@ export class TreeViewPage {
   }
 
   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);
-    });
+    let cardElement = element(by.css('.card[data-name="' + name +'"]'));
+    let downArrowElement = element(by.css('.card[data-name="' + name + '"] .mrow.top-group'));
+
+    return waitForElementVisibility(cardElement)
+    .then(() => browser.actions().mouseMove(cardElement).perform())
+    .then(() => waitForElementVisibility(downArrowElement))
+    .then(() => downArrowElement.click())
+    .then(() => 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();
+    return this.expandSubGroupByPosition(groupName, rowName, 0);
+  }
+
+  expandSubGroupByPosition(groupName: string, rowName: string, position: number) {
+    let subGroupElement = element.all(by.css('[data-name="' + groupName + '"] tr[data-name="' + rowName + '"]')).get(position);
+    return waitForElementVisibility(subGroupElement)
+    .then(() => browser.actions().mouseMove(subGroupElement).perform())
+    .then(() => subGroupElement.click());
   }
 
   getDashGroupTableValuesForRow(name: string, rowId: number) {
@@ -130,7 +146,7 @@ export class TreeViewPage {
   }
 
   getCellValuesFromTable(groupName: string, cellName: string, waitForAnchor: string) {
-    return waitForElementPresence(element(by. cssContainingText('[data-name="' + cellName + '"] a', waitForAnchor))).then(() => {
+    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());
@@ -159,4 +175,40 @@ export class TreeViewPage {
       return column.getText();
     });
   }
+
+  clickOnMergeAlerts(groupName: string) {
+    return element(by.css('[data-name="' + groupName + '"] .fa-link')).click();
+  }
+
+  clickOnMergeAlertsInTable(groupName: string, waitForAnchor: string, rowIndex: number) {
+    let elementFinder = element.all(by.css('[data-name="' + groupName + '"] table tbody tr')).get(rowIndex).element(by.css('.fa-link'));
+    return waitForElementVisibility(elementFinder)
+    .then(() => elementFinder.click());
+  }
+
+  getConfirmationText() {
+    let maskElement = element(by.className('modal-backdrop'));
+    return waitForElementVisibility(maskElement)
+    .then(() =>  element(by.css('.metron-dialog .modal-body')).getText());
+  }
+
+  clickNoForConfirmation() {
+    let maskElement = element(by.className('modal-backdrop'));
+    let closeButton = element(by.css('.metron-dialog')).element(by.buttonText('Cancel'));
+    waitForElementVisibility(maskElement)
+    .then(() => closeButton.click())
+    .then(() => waitForElementInVisibility(maskElement));
+  }
+
+  clickYesForConfirmation() {
+    let okButton = element(by.css('.metron-dialog')).element(by.buttonText('OK'));
+    let maskElement = element(by.className('modal-backdrop'));
+    waitForElementVisibility(maskElement)
+    .then(() => okButton.click())
+    .then(() => waitForElementInVisibility(maskElement));
+  }
+
+  waitForElementToDisappear(groupName: string) {
+    return waitForElementInVisibility(element.all(by.css('[data-name="' + groupName + '"]')));
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 47f01e2..92476a4 100644
--- a/metron-interface/metron-alerts/e2e/utils/e2e_util.ts
+++ b/metron-interface/metron-alerts/e2e/utils/e2e_util.ts
@@ -47,6 +47,7 @@ export function waitForStalenessOf (_element ) {
 
 export function loadTestData() {
   deleteTestData();
+
   fs.createReadStream('e2e/mock-data/alerts_ui_e2e_index.template')
     .pipe(request.post('http://node1:9200/_template/alerts_ui_e2e_index'));
   fs.createReadStream('e2e/mock-data/alerts_ui_e2e_index.data')
@@ -56,3 +57,14 @@ export function loadTestData() {
 export function deleteTestData() {
   request.delete('http://node1:9200/alerts_ui_e2e_index*');
 }
+
+export function createMetaAlertsIndex() {
+  deleteMetaAlertsIndex();
+  fs.createReadStream('./../../metron-deployment/packaging/ambari/metron-mpack/src/main/resources/common-services/METRON/CURRENT/package/files/metaalert_index.template')
+  .pipe(request.post('http://node1:9200/metaalert_index'));
+}
+
+export function deleteMetaAlertsIndex() {
+  request.delete('http://node1:9200/metaalert_index*');
+}
+

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 4fc25be..82f5c09 100644
--- a/metron-interface/metron-alerts/protractor.conf.js
+++ b/metron-interface/metron-alerts/protractor.conf.js
@@ -32,7 +32,8 @@ exports.config = {
     './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'
+    './e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts',
+    './e2e/alerts-list/meta-alerts/meta-alert.e2e-spec.ts'
   ],
   capabilities: {
     'browserName': 'chrome',
@@ -59,6 +60,8 @@ exports.config = {
     });
   },
   onPrepare: function() {
+    var createMetaAlertsIndex =  require('./e2e/utils/e2e_util').createMetaAlertsIndex;
+    createMetaAlertsIndex();
     jasmine.getEnv().addReporter(new SpecReporter());
     setTimeout(function() {
       browser.driver.executeScript(function() {
@@ -70,5 +73,9 @@ exports.config = {
         browser.driver.manage().window().setSize(result.width, result.height);
       });
     });
+  },
+  onComplete: function() {
+    var createMetaAlertsIndex =  require('./e2e/utils/e2e_util').createMetaAlertsIndex;
+    createMetaAlertsIndex();
   }
 };

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 21cdfdf..d7e9359 100644
--- a/metron-interface/metron-alerts/src/_variables.scss
+++ b/metron-interface/metron-alerts/src/_variables.scss
@@ -90,11 +90,13 @@ $outer-space: #2E3A3F;
 $abbey: #58595B;
 $white: #FFFFFF;
 $iron: #D1D3D4;
+
 $rolling-stone: #808285;
 $nile-blue: #18404E;
 $apple-blossom: #A94442;
 
 $eastern-blue-1: #1190C0;
+$eastern-blue-2: #2192BF;
 $matisse: #1E7490;
 $downy: #77BBD0;
 $trout: #515760;

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details-keys.pipe.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details-keys.pipe.ts b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details-keys.pipe.ts
new file mode 100644
index 0000000..d5756da
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details-keys.pipe.ts
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+  name: 'alertDetailsKeys'
+})
+export class AlertDetailsKeysPipe implements PipeTransform {
+
+  transform(value: any): any {
+    let keys = value ? Object.keys(value) : [];
+    return keys.filter(field => !field.includes(':ts') && field !== 'original_string' && field !== 'comments').sort();
+  }
+}
+
+

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html
index 1b5330e..8b0efae 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html
+++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html
@@ -11,10 +11,10 @@
 	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="metron-slider-pane-details load-right-to-left dialog1x">
-    <div class="container-fluid pl-0 h-100">
+<div class="metron-slider-pane-details load-right-to-left dialog1x" [ngClass]="{'is-meta-alert': isMetaAlert}">
+    <div class="container-fluid pl-0 h-100" [ngClass]="{'pr-0': isMetaAlert}">
         <div class="h-100 d-flex">
-            <div class="nav-container">
+            <div class="nav-container" *ngIf="!isMetaAlert">
                 <ul class="nav flex-column">
                     <li class="nav-item">
                         <a class="nav-link" [ngClass]="{'active': activeTab === tabs.DETAILS}" (click)="activeTab=tabs.DETAILS">
@@ -32,7 +32,20 @@
                 <div class="container-fluid h-100">
                     <div class="row title-container">
                         <div class="col-md-10 px-0">
-                            <div class="form-title" appAlertSeverity [severity]="alertSource['threat:triage:score']"> {{ alertSource['threat:triage:score'] }} {{ alertSource.guid }}</div>
+                            <div class="form-title row ml-2">
+                                <div class="col px-0">
+                                    <span appAlertSeverity [severity]="alertSource['threat:triage:score']"> </span>
+                                    <span> {{ alertSource['threat:triage:score'] }} </span>
+                                </div>
+                                <div class="px-0" style="width: 205px">
+                                    <span [ngClass]="{'editable-text': alertSources.length > 1}" *ngIf="!showEditor" (click)="toggleNameEditor()"> {{ (alertSource.name && alertSource.name.length > 0)? alertSource.name : alertId | centerEllipses:20 }} </span>
+                                    <div class="input-group" *ngIf="showEditor">
+                                        <input type="text" class="form-control"  [(ngModel)]="alertName">
+                                        <span class="input-group-addon" [ngClass]="{'disabled': alertName.length === 0}" (click)="saveName()" ><i class="fa fa-check" aria-hidden="true"></i></span>
+                                        <span class="input-group-addon" (click)="toggleNameEditor()"><i class="fa fa-times" aria-hidden="true"></i></span>
+                                    </div>
+                                </div>
+                            </div>
                         </div>
                         <div class="col-md-2 px-0">
                             <i class="fa fa-times pull-right close-button" aria-hidden="true" (click)="goBack()"></i>
@@ -57,11 +70,16 @@
                             </tr>
                         </table>
                     </div>
-                    <div class="ml-1 my-4 form" *ngIf="activeTab === tabs.DETAILS">
-                        <div *ngFor="let field of alertFields" class="row">
-                            <div class="col-6 mb-1 key">{{ field }}</div>   <div class="col-6 value"> {{ alertSource[field] }} </div>
-                        </div>
+
+                    <div class="ml-1 my-3 form" *ngIf="activeTab === tabs.DETAILS">
+                        <ng-container *ngFor="let alert of alertSources; let i = index;" >
+                            <div class="pb-2 alert-details-title"> Alert {{ i + 1 }} of {{ alertSources.length }}</div>
+                            <div *ngFor="let field of alert | alertDetailsKeys" class="row ml-1">
+                                <div class="col-6 mb-1 key">{{ field }}</div>   <div class="col-6"> {{ alert[field] }} </div>
+                            </div>
+                        </ng-container>
                     </div>
+
                     <div *ngIf="activeTab === tabs.COMMENTS" class="my-4">
                         <div> Comments <span *ngIf="alertCommentsWrapper.length > 0"> ({{alertCommentsWrapper.length}}) </span></div>
                         <textarea class="form-control" [(ngModel)]="alertCommentStr"> </textarea>

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss
index 5899140..3b10c8f 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss
+++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss
@@ -27,6 +27,8 @@
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
+  height: 35px;
+  line-height: 2.1  ;
 }
 
 .actions {
@@ -71,8 +73,30 @@
   }
 }
 
+.alert-details-title {
+  font-size: 15px;
+}
+
+.editable-text {
+  cursor: pointer;
+  border-bottom: 1px dashed $piction-blue;
+}
+
+.input-group-addon {
+  height: 35px;
+  background: #333333;
+  border: 1px solid #4D4D4D;
+  i {
+    font-size: 15px;
+    color: #999999;
+  }
+}
 .dialog1x {
   width: 400px;
+
+  &.is-meta-alert {
+    width: 346px;
+  }
 }
 
 .nav-container {
@@ -143,3 +167,12 @@ textarea {
 .comment-container:hover i {
   display: block;
 }
+
+.input-group-addon {
+  cursor: pointer;
+}
+
+.disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.ts b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.ts
index 3d5b70e..8335ad7 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.ts
@@ -29,6 +29,7 @@ import {Patch} from '../../model/patch';
 import {AlertComment} from './alert-comment';
 import {AuthenticationService} from '../../service/authentication.service';
 import {MetronDialogBox} from '../../shared/metron-dialog-box';
+import {META_ALERTS_INDEX, META_ALERTS_SENSOR_TYPE} from '../../utils/constants';
 
 export enum AlertState {
   NEW, OPEN, ESCALATE, DISMISS, RESOLVE
@@ -56,13 +57,17 @@ class AlertCommentWrapper {
 export class AlertDetailsComponent implements OnInit {
 
   alertId = '';
+  alertName = '';
   alertSourceType = '';
+  showEditor = false;
+  isMetaAlert = false;
   alertIndex = '';
   alertState = AlertState;
   tabs = Tabs;
   activeTab = Tabs.DETAILS;
   selectedAlertState: AlertState = AlertState.NEW;
   alertSource: AlertSource = new AlertSource();
+  alertSources = [];
   alertFields: string[] = [];
   alertCommentStr = '';
   alertCommentsWrapper: AlertCommentWrapper[] = [];
@@ -82,14 +87,17 @@ export class AlertDetailsComponent implements OnInit {
     return false;
   }
 
-  getData() {
+  getData(fireToggleEditor = false) {
     this.alertCommentStr = '';
-    this.searchService.getAlert(this.alertSourceType, this.alertId).subscribe(alert => {
-      this.alertSource = alert;
-      this.alertFields = Object.keys(alert).filter(field => !field.includes(':ts') && field !== 'original_string' && field !== 'comments')
-                          .sort();
-      this.selectedAlertState = this.getAlertState(alert['alert_status']);
-      this.setComments(alert);
+    this.searchService.getAlert(this.alertSourceType, this.alertId).subscribe(alertSource => {
+      this.alertSource = alertSource;
+      this.selectedAlertState = this.getAlertState(alertSource['alert_status']);
+      this.alertSources = (alertSource.alert && alertSource.alert.length > 0) ? alertSource.alert : [alertSource];
+      this.setComments(alertSource);
+
+      if (fireToggleEditor) {
+        this.toggleNameEditor();
+      }
     });
   }
 
@@ -118,6 +126,7 @@ export class AlertDetailsComponent implements OnInit {
       this.alertId = params['guid'];
       this.alertSourceType = params['sourceType'];
       this.alertIndex = params['index'];
+      this.isMetaAlert = (this.alertIndex === META_ALERTS_INDEX && this.alertSourceType !== META_ALERTS_SENSOR_TYPE) ? true : false;
       this.getData();
     });
   };
@@ -173,6 +182,27 @@ export class AlertDetailsComponent implements OnInit {
     });
   }
 
+  toggleNameEditor() {
+    if (this.alertSources.length > 1) {
+      this.alertName = '';
+      this.showEditor = !this.showEditor;
+    }
+  }
+
+  saveName() {
+    if (this.alertName.length > 0) {
+      let patchRequest = new PatchRequest();
+      patchRequest.guid = this.alertId;
+      patchRequest.sensorType = 'metaalert';
+      patchRequest.index = META_ALERTS_INDEX;
+      patchRequest.patch = [new Patch('add', '/name', this.alertName)];
+
+      this.updateService.patch(patchRequest).subscribe(rep => {
+        this.getData(true);
+      });
+    }
+  }
+
   onAddComment() {
     let alertComment = new AlertComment(this.alertCommentStr, this.authenticationService.getCurrentUserName(), new Date().getTime());
     let tAlertComments = this.alertCommentsWrapper.map(alertsWrapper => alertsWrapper.alertComment);

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.module.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.module.ts b/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.module.ts
index d1e36a6..9722cd3 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.module.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.module.ts
@@ -20,11 +20,13 @@ import {routing} from './alerts-details.routing';
 import {SharedModule} from '../../shared/shared.module';
 import {AlertDetailsComponent} from './alert-details.component';
 import {AlertsService} from '../../service/alerts.service';
+import {UpdateService} from '../../service/update.service';
+import { AlertDetailsKeysPipe } from './alert-details-keys.pipe';
 import {AuthenticationService} from '../../service/authentication.service';
 
 @NgModule ({
     imports: [ routing,  SharedModule],
-    declarations: [ AlertDetailsComponent ],
-    providers: [ AuthenticationService, AlertsService ]
+    declarations: [ AlertDetailsComponent, AlertDetailsKeysPipe ],
+    providers: [ AuthenticationService, AlertsService, UpdateService ],
 })
 export class AlertDetailsModule { }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 63b4e41..611cdaf 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
@@ -27,7 +27,7 @@
                         <app-time-range (timeRangeChange)="onTimeRangeChange($event)" [disabled]="timeStampfilterPresent" [selectedTimeRange]="selectedTimeRange"> </app-time-range>
                     </span>
                     <span class="input-group-btn">
-                        <button class="btn btn-secondary btn-search" type="button" (click)="onSearch(alertSearchDirective.getSeacrhText())"></button>
+                        <button class="btn btn-secondary btn-search" type="button" data-name="search" (click)="onSearch(alertSearchDirective.getSeacrhText())"></button>
                     </span>
                 </div>
             </div>
@@ -61,6 +61,7 @@
                         <span class="dropdown-item" [class.disabled]="selectedAlerts.length == 0" (click)="processDismiss()">Dismiss</span>
                         <span class="dropdown-item" [class.disabled]="selectedAlerts.length == 0" (click)="processEscalate()">Escalate</span>
                         <span class="dropdown-item" [class.disabled]="selectedAlerts.length == 0" (click)="processResolve()">Resolve</span>
+                        <span class="dropdown-item" [class.disabled]="selectedAlerts.length == 0 || isMetaAlertPresentInSelectedAlerts" (click)="processAddToAlert()">Add to Alert</span>
                     </div>
                 </div>
             </div>
@@ -74,10 +75,10 @@
         <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 class="col-xs-12 pl-0 pb-3">
+          <app-group-by [facets]="groupFacets" (groupsChange)="onGroupsChange($event)"> </app-group-by>
         </div>
-        <div class="col-sm-12 px-0">
+        <div class="col-xs-12 px-0">
             <app-table-view #dataViewComponent
                             [alerts]="alerts" *ngIf="queryBuilder.groupRequest.groups.length === 0"
                             [queryBuilder]="queryBuilder"
@@ -96,6 +97,7 @@
                            [selectedAlerts]="selectedAlerts"
                            (onResize)="onResize()"
                            (onAddFilter)="onAddFilter($event)"
+                           (onRefreshData)="onRefreshData($event)"
                            (onShowDetails)="showDetails($event)"
                            (onSelectedAlertsChange)="onSelectedAlertsChange($event)"></app-tree-view>
         </div>

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 228c4f7..138606e 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
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, OnInit, ViewChild, ElementRef, OnDestroy, ChangeDetectorRef} from '@angular/core';
+import {Component, OnInit, ViewChild, ElementRef, OnDestroy} from '@angular/core';
 import {Router, NavigationStart} from '@angular/router';
 import {Observable, Subscription} from 'rxjs/Rx';
 
@@ -39,7 +39,9 @@ import {Filter} from '../../model/filter';
 import {THREAT_SCORE_FIELD_NAME, TIMESTAMP_FIELD_NAME, ALL_TIME} from '../../utils/constants';
 import {TableViewComponent} from './table-view/table-view.component';
 import {Pagination} from '../../model/pagination';
-import {PatchRequest} from '../../model/patch-request';
+import {META_ALERTS_SENSOR_TYPE, META_ALERTS_INDEX} from '../../utils/constants';
+import {MetaAlertService} from '../../service/meta-alert.service';
+import {Facets} from '../../model/facets';
 
 @Component({
   selector: 'app-alerts-list',
@@ -59,6 +61,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
   refreshTimer: Subscription;
   pauseRefresh = false;
   lastPauseRefreshValue = false;
+  isMetaAlertPresentInSelectedAlerts = false;
   timeStampfilterPresent = false;
   selectedTimeRange = new Filter(TIMESTAMP_FIELD_NAME, ALL_TIME, false);
   threatScoreFieldName = THREAT_SCORE_FIELD_NAME;
@@ -70,6 +73,8 @@ export class AlertsListComponent implements OnInit, OnDestroy {
   tableMetaData = new TableMetadata();
   queryBuilder: QueryBuilder = new QueryBuilder();
   pagination: Pagination = new Pagination();
+  alertChangedSubscription: Subscription;
+  groupFacets: Facets;
 
   constructor(private router: Router,
               private searchService: SearchService,
@@ -79,7 +84,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
               private clusterMetaDataService: ClusterMetaDataService,
               private saveSearchService: SaveSearchService,
               private metronDialogBox: MetronDialogBox,
-              private changeDetector: ChangeDetectorRef) {
+              private metaAlertsService: MetaAlertService) {
     router.events.subscribe(event => {
       if (event instanceof NavigationStart && event.url === '/alerts-list') {
         this.selectedAlerts = [];
@@ -89,8 +94,12 @@ export class AlertsListComponent implements OnInit, OnDestroy {
   }
 
   addAlertChangedListner() {
-    this.updateService.alertChanged$.subscribe(patchRequest => {
-      this.updateAlert(patchRequest);
+    this.metaAlertsService.alertChanged$.subscribe(metaAlertAddRemoveRequest => {
+      this.updateAlert(META_ALERTS_SENSOR_TYPE, metaAlertAddRemoveRequest.metaAlertGuid, (metaAlertAddRemoveRequest.alerts === null));
+    });
+
+    this.alertChangedSubscription = this.updateService.alertChanged$.subscribe(patchRequest => {
+      this.updateAlert(patchRequest.sensorType, patchRequest.guid, false);
     });
   }
 
@@ -125,8 +134,8 @@ export class AlertsListComponent implements OnInit, OnDestroy {
   }
 
   calcColumnsToDisplay() {
-    let availableWidth = document.documentElement.clientWidth - (200 + (15 * 4)); /* screenwidth - (navPaneWidth + (paddings))*/
-    availableWidth = availableWidth - (55 + 25 + 25); /* availableWidth - (score + colunSelectIcon +selectCheckbox )*/
+    let availableWidth = document.documentElement.clientWidth - (200 + (15 + 15 + 25)); /* screenwidth - (navPaneWidth + (paddings))*/
+    availableWidth = availableWidth - ((20 * 3) + 55 + 25); /* availableWidth - (score + colunSelectIcon +selectCheckbox )*/
     let tWidth = 0;
     this.alertsColumnsToDisplay =  this.alertsColumns.filter(colMetaData => {
       if (colMetaData.type.toUpperCase() === 'DATE') {
@@ -145,8 +154,8 @@ export class AlertsListComponent implements OnInit, OnDestroy {
 
   getAlertColumnNames(resetPaginationForSearch: boolean) {
     Observable.forkJoin(
-      this.configureTableService.getTableMetadata(),
-      this.clusterMetaDataService.getDefaultColumns()
+        this.configureTableService.getTableMetadata(),
+        this.clusterMetaDataService.getDefaultColumns()
     ).subscribe((response: any) => {
       this.prepareData(response[0], response[1], resetPaginationForSearch);
     });
@@ -161,6 +170,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
 
   ngOnDestroy() {
     this.tryStopPolling();
+    this.removeAlertChangedListner();
   }
 
   ngOnInit() {
@@ -194,6 +204,8 @@ export class AlertsListComponent implements OnInit, OnDestroy {
 
   onSelectedAlertsChange(selectedAlerts) {
     this.selectedAlerts = selectedAlerts;
+    this.isMetaAlertPresentInSelectedAlerts = this.selectedAlerts.some(alert => (alert.source.alert && alert.source.alert.length > 0));
+
     if (selectedAlerts.length > 0) {
       this.pause();
     } else {
@@ -258,29 +270,34 @@ export class AlertsListComponent implements OnInit, OnDestroy {
   }
 
   processEscalate() {
-    this.updateService.updateAlertState(this.selectedAlerts, 'ESCALATE').subscribe(results => {
+    this.updateService.updateAlertState(this.selectedAlerts, 'ESCALATE', false).subscribe(results => {
       this.updateSelectedAlertStatus('ESCALATE');
     });
   }
 
   processDismiss() {
-    this.updateService.updateAlertState(this.selectedAlerts, 'DISMISS').subscribe(results => {
+    this.updateService.updateAlertState(this.selectedAlerts, 'DISMISS', false).subscribe(results => {
       this.updateSelectedAlertStatus('DISMISS');
     });
   }
 
   processOpen() {
-    this.updateService.updateAlertState(this.selectedAlerts, 'OPEN').subscribe(results => {
+    this.updateService.updateAlertState(this.selectedAlerts, 'OPEN', false).subscribe(results => {
       this.updateSelectedAlertStatus('OPEN');
     });
   }
 
   processResolve() {
-    this.updateService.updateAlertState(this.selectedAlerts, 'RESOLVE').subscribe(results => {
+    this.updateService.updateAlertState(this.selectedAlerts, 'RESOLVE', false).subscribe(results => {
       this.updateSelectedAlertStatus('RESOLVE');
     });
   }
 
+  processAddToAlert() {
+    this.metaAlertsService.selectedAlerts = this.selectedAlerts;
+    this.router.navigateByUrl('/alerts-list(dialog:add-to-meta-alert)');
+  }
+
   removeFilter(field: string) {
     this.timeStampfilterPresent = (field === TIMESTAMP_FIELD_NAME) ? false : this.timeStampfilterPresent;
     this.queryBuilder.removeFilter(field);
@@ -311,7 +328,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
   }
 
   setSearchRequestSize() {
-    if (this.queryBuilder.groupRequest.groups.length == 0) {
+    if (this.queryBuilder.groupRequest.groups.length === 0) {
       this.queryBuilder.searchRequest.from = this.pagination.from;
       if (this.tableMetaData.size) {
         this.pagination.size = this.tableMetaData.size;
@@ -345,6 +362,14 @@ export class AlertsListComponent implements OnInit, OnDestroy {
     this.pagination.total = results.total;
     this.alerts = results.results ? results.results : [];
     this.setSelectedTimeRange(this.queryBuilder.filters);
+    this.createGroupFacets(results);
+  }
+
+  private createGroupFacets(results: SearchResponse) {
+    this.groupFacets = JSON.parse(JSON.stringify(results.facetCounts));
+    if (this.groupFacets['source:type']) {
+      delete this.groupFacets['source:type']['metaalert'];
+    }
   }
 
   showConfigureTable() {
@@ -353,10 +378,12 @@ export class AlertsListComponent implements OnInit, OnDestroy {
   }
 
   showDetails(alert: Alert) {
-    let url = '/alerts-list(dialog:details/' + alert.source['source:type'] + '/' + alert.source.guid + '/' + alert.index + ')';
     this.selectedAlerts = [];
     this.selectedAlerts = [alert];
     this.saveRefreshState();
+    let sourceType = (alert.index === META_ALERTS_INDEX && !alert.source['source:type'])
+        ? META_ALERTS_SENSOR_TYPE : alert.source['source:type'];
+    let url = '/alerts-list(dialog:details/' + sourceType + '/' + alert.source.guid + '/' + alert.index + ')';
     this.router.navigateByUrl(url);
   }
 
@@ -405,13 +432,22 @@ export class AlertsListComponent implements OnInit, OnDestroy {
     this.searchService.interval = this.refreshInterval;
   }
 
-  updateAlert(patchRequest: PatchRequest) {
-    this.searchService.getAlert(patchRequest.sensorType, patchRequest.guid).subscribe(alertSource => {
-      this.alerts.filter(alert => alert.source.guid === patchRequest.guid)
+  updateAlert(sensorType: string, guid: string, isDelete: boolean) {
+    if (isDelete) {
+      let alertIndex = -1;
+      this.alerts.forEach((alert, index) => {
+        alertIndex = (alert.source.guid === guid) ? index : alertIndex;
+      });
+      this.alerts.splice(alertIndex, 1);
+      return;
+    }
+
+    this.searchService.getAlert(sensorType, guid).subscribe(alertSource => {
+      this.alerts.filter(alert => alert.source.guid === guid)
       .map(alert => alert.source = alertSource);
     });
   }
-  
+
   updateSelectedAlertStatus(status: string) {
     for (let selectedAlert of this.selectedAlerts) {
       selectedAlert.source['alert_status'] = status;
@@ -420,4 +456,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
     this.resume();
   }
 
+  removeAlertChangedListner() {
+    this.alertChangedSubscription.unsubscribe();
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 6e0dd2a..d1c3cc0 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
@@ -21,7 +21,6 @@ import {DecimalPipe} from '@angular/common';
 import {AlertsListComponent}   from './alerts-list.component';
 import {routing} from './alerts-list.routing';
 import {SharedModule} from '../../shared/shared.module';
-import {SearchService} from '../../service/search.service';
 import {MetronSorterModule} from '../../shared/metron-table/metron-sorter/metron-sorter.module';
 import {ListGroupModule} from '../../shared/list-group/list-grup.module';
 import {CollapseModule} from '../../shared/collapse/collapse.module';
@@ -38,7 +37,7 @@ import {TreeViewComponent} from './tree-view/tree-view.component';
                 ListGroupModule, CollapseModule, GroupByModule, TimeRangeModule],
     exports: [AlertsListComponent],
     declarations: [AlertsListComponent, TableViewComponent, TreeViewComponent, AlertFiltersComponent],
-    providers: [DecimalPipe, SearchService]
+    providers: [DecimalPipe]
 })
 export class AlertsListModule {
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 e9f96eb..9ac5f6e 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
@@ -147,10 +147,7 @@ export class QueryBuilder {
   }
 
   setSort(sortBy: string, order: string) {
-    let sortField = new SortField();
-    sortField.field = sortBy;
-    sortField.sortOrder = order;
-
+    let sortField = new SortField(sortBy, order);
     this.searchRequest.sort = [sortField];
   }
 

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 b8fd14f..d2b1108 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
@@ -15,23 +15,98 @@
   <table class="table table-sm" metron-config-table [data]="alerts" [cellSelectable]="true" (onSort)="onSort($event)" style="white-space: nowrap;" (window:resize)="resize()" #table>
     <thead>
     <tr>
-      <th style="width:55px"> <metron-config-sorter [type]="'number'" [sortBy]="threatScoreFieldName"> Score </metron-config-sorter> </th>
+      <th width="15" class="dropdown-cell"> </th>
+      <th width="55"> <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"><input id="select-deselect-all" class="fontawesome-checkbox" type="checkbox" (click)="selectAllRows($event)"><label for="select-deselect-all"></label></th>
+      <th width="20" class="icon-cell"></th>
+      <th width="20" class="icon-cell"></th>
+      <th width="25"><input id="select-deselect-all" class="fontawesome-checkbox" type="checkbox" (click)="selectAllRows($event)"><label for="select-deselect-all"></label></th>
     </tr>
     </thead>
     <tbody>
-    <tr *ngFor="let alert of alerts" (click)="showDetails($event, alert)" [ngClass]="{'selected' : selectedAlerts.indexOf(alert) != -1}">
-      <td (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])">
-        <div appAlertSeverity [severity]="alert.source[threatScoreFieldName]"> <a> {{ alert.source[threatScoreFieldName] ? alert.source[threatScoreFieldName] : '-' }} </a> </div>
-      </td>
-      <td *ngFor="let column of alertsColumnsToDisplay" #cell>
-        <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><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>
+    <ng-container *ngFor="let alert of alerts; let alertIndex = index;">
+
+      <ng-container *ngIf="!alert.source.alert || alert.source.alert.length === 0">
+        <tr (click)="showDetails($event, alert)" [ngClass]="{'selected' : selectedAlerts.indexOf(alert) != -1}">
+          <td width="15" class="icon-cell"></td>
+          <td (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])">
+            <div appAlertSeverity [severity]="alert.source[threatScoreFieldName]">
+              <a> {{ alert.source[threatScoreFieldName] ? alert.source[threatScoreFieldName] : '-' }} </a>
+            </div>
+          </td>
+          <td *ngFor="let column of alertsColumnsToDisplay" #cell>
+            <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 width="20" class="icon-cell"></td>
+          <td width="20" class="icon-cell">
+            <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>
+      </ng-container>
+
+      <ng-container *ngIf="alert.source.alert && alert.source.alert.length > 0">
+        <tr (click)="showDetails($event, alert)" [ngClass]="{'selected' : selectedAlerts.indexOf(alert) != -1}">
+          <td width="15" class="icon-cell dropdown-cell" (click)="toggleExpandCollapse($event, alert)">
+            <i class="fa" aria-hidden="true"
+               [ngClass]="{'fa-caret-right': metaAlertsDisplayState[alert.id] === metronAlertDisplayState.COLLAPSE, 'fa-caret-down': metaAlertsDisplayState[alert.id] === metronAlertDisplayState.EXPAND}">
+            </i>
+          </td>
+          <td (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])">
+            <span appAlertSeverity [severity]="alert.source[threatScoreFieldName]"> <a> {{ alert.source[threatScoreFieldName] ? alert.source[threatScoreFieldName] : '-' }} </a> </span>
+          </td>
+          <td [attr.colspan]="alertsColumnsToDisplay.length - 1">
+            <a (click)="addFilter('guid', alert.id)" [attr.title]="alert.id" style="color:#689AA9"> {{ alert.source['name'] ? alert.source['name'] : alert.id | centerEllipses:20:cell }}</a>
+              <span> ({{ alert.source.alert.length }})</span>
+          </td>
+          <td>
+            <a *ngIf="isStatusFieldPresent" (click)="addFilter('alert_status', alert.source['alert_status'])" style="color:#689AA9">
+              {{ alert.source['alert_status'] ?alert.source['alert_status'] : 'New' | centerEllipses:20:cell }}
+            </a>
+          </td>
+          <td width="20" class="icon-cell" (click)="deleteMetaAlert($event, alert, alertIndex)">
+            <i class="fa fa-chain-broken" aria-hidden="true"></i>
+          </td>
+          <td width="20" class="icon-cell">
+            <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 *ngFor="let metaAlerts of alert.source.alert; let metaAlertIndex = index;" (click)="showMetaAlertDetails($event, metaAlerts)"
+            [ngClass]="{'selected' : selectedAlerts.indexOf(metaAlerts) != -1 , 'd-none': metaAlertsDisplayState[alert.id] === metronAlertDisplayState.COLLAPSE}">
+          <td width="15" class="icon-cell" class="dropdown-cell"></td>
+          <td (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])" style="padding-left: 15px">
+            <div appAlertSeverity [severity]="metaAlerts[threatScoreFieldName]">
+              <a> {{ metaAlerts[threatScoreFieldName] ? metaAlerts[threatScoreFieldName] : '-' }} </a>
+            </div>
+          </td>
+          <td *ngFor="let column of alertsColumnsToDisplay">
+            <a *ngIf="column.name !== 'alert_status'" (click)="addFilter(column.name, getValueFromSource(metaAlerts, column, false))" title="{{ getValueFromSource(metaAlerts, column, true) }}" style="color:#689AA9">
+              {{ getValueFromSource(metaAlerts, column, true) | centerEllipses:20:cell }}
+            </a>
+            <a *ngIf="column.name === 'alert_status'" (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 width="20" class="icon-cell" (click)="deleteOneAlertFromMetaAlert($event, alert, metaAlertIndex)">
+            <i class="fa fa-chain-broken" aria-hidden="true"></i>
+          </td>
+          <td width="20" class="icon-cell">
+            <i class="fa fa-comments-o" aria-hidden="true" *ngIf="metaAlerts.comments && metaAlerts.comments.length > 0"></i>
+          </td>
+          <td></td>
+        </tr>
+      </ng-container>
+
+    </ng-container>
     </tbody>
   </table>
 </div>

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 eec7f92..f648ab2 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
@@ -24,4 +24,17 @@
 .configure-table-icon {
   font-size: 16px;
   cursor: pointer;
-}
\ No newline at end of file
+}
+
+.fa-chain-broken {
+  color: $piction-blue;
+}
+
+.dropdown-cell {
+  padding-left: 0.6rem;
+}
+
+.icon-cell {
+  padding: 0.3rem 3px;
+}
+