Repository: metron
Updated Branches:
  refs/heads/master a1408d787 -> d07833a25


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.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.ts
 
b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.ts
index fa2f97e..91489b8 100644
--- 
a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.ts
+++ 
b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.ts
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } 
from '@angular/core';
 import {Router} from '@angular/router';
 
 import {Pagination} from '../../../model/pagination';
@@ -28,6 +28,18 @@ import {MetronDialogBox} from 
'../../../shared/metron-dialog-box';
 import {QueryBuilder} from '../query-builder';
 import {Sort} from '../../../utils/enums';
 import {Filter} from '../../../model/filter';
+import {AlertSource} from '../../../model/alert-source';
+import {PatchRequest} from '../../../model/patch-request';
+import {Patch} from '../../../model/patch';
+import {UpdateService} from '../../../service/update.service';
+import {META_ALERTS_INDEX} from '../../../utils/constants';
+import {MetaAlertService} from '../../../service/meta-alert.service';
+import {MetaAlertAddRemoveRequest} from 
'../../../model/meta-alert-add-remove-request';
+import {GetRequest} from '../../../model/get-request';
+
+export enum MetronAlertDisplayState {
+  COLLAPSE, EXPAND
+}
 
 @Component({
   selector: 'app-table-view',
@@ -35,21 +47,25 @@ import {Filter} from '../../../model/filter';
   styleUrls: ['./table-view.component.scss']
 })
 
-export class TableViewComponent {
+export class TableViewComponent implements OnChanges {
 
-  
   threatScoreFieldName = 'threat:triage:score';
 
   router: Router;
   searchService: SearchService;
+  updateService: UpdateService;
+  isStatusFieldPresent = false;
   metronDialogBox: MetronDialogBox;
+  metaAlertService: MetaAlertService;
+  metaAlertsDisplayState: {[key: string]: MetronAlertDisplayState} = {};
+  metronAlertDisplayState = MetronAlertDisplayState;
 
   @Input() alerts: Alert[] = [];
   @Input() queryBuilder: QueryBuilder;
   @Input() pagination: Pagination;
   @Input() alertsColumnsToDisplay: ColumnMetadata[] = [];
   @Input() selectedAlerts: Alert[] = [];
-  
+
   @Output() onResize = new EventEmitter<void>();
   @Output() onAddFilter = new EventEmitter<Filter>();
   @Output() onRefreshData = new EventEmitter<boolean>();
@@ -59,43 +75,89 @@ export class TableViewComponent {
 
   constructor(router: Router,
               searchService: SearchService,
-              metronDialogBox: MetronDialogBox) {
+              metronDialogBox: MetronDialogBox,
+              updateService: UpdateService,
+              metaAlertService: MetaAlertService) {
     this.router = router;
     this.searchService = searchService;
     this.metronDialogBox = metronDialogBox;
+    this.updateService = updateService;
+    this.metaAlertService = metaAlertService;
+  }
+
+  ngOnChanges(changes: SimpleChanges) {
+    if (changes && changes['alerts'] && changes['alerts'].currentValue) {
+      let expandedMetaAlerts = this.getGUIDOfAllExpandedMetaAlerts();
+      this.updateExpandedStateForChangedData(expandedMetaAlerts);
+    }
+
+    if (changes && changes['alertsColumnsToDisplay'] && 
changes['alertsColumnsToDisplay'].currentValue) {
+      this.isStatusFieldPresent = this.alertsColumnsToDisplay.some(col => 
col.name === 'alert_status');
+    }
+  }
+
+  updateExpandedStateForChangedData(expandedMetaAlerts: string[]) {
+    this.alerts.forEach(alert => {
+      if (alert.source.alert && alert.source.alert.length > 0) {
+        this.metaAlertsDisplayState[alert.id] = 
expandedMetaAlerts.indexOf(alert.id) === -1 ?
+                                                  
MetronAlertDisplayState.COLLAPSE : MetronAlertDisplayState.EXPAND;
+      }
+    });
+  }
+
+  getGUIDOfAllExpandedMetaAlerts(): string[] {
+    let expandedMetaAlerts = [];
+    Object.keys(this.metaAlertsDisplayState).forEach(id => {
+      if (this.metaAlertsDisplayState[id] === MetronAlertDisplayState.EXPAND) {
+        expandedMetaAlerts.push(id);
+      }
+    });
+
+    return expandedMetaAlerts;
   }
 
   onSort(sortEvent: SortEvent) {
     let sortOrder = (sortEvent.sortOrder === Sort.ASC ? 'asc' : 'desc');
-    let sortBy = sortEvent.sortBy === 'id' ? '_uid' : sortEvent.sortBy;
+    let sortBy = sortEvent.sortBy === 'id' ? 'guid' : sortEvent.sortBy;
     this.queryBuilder.setSort(sortBy, sortOrder);
     this.onRefreshData.emit(true);
   }
 
   getValue(alert: Alert, column: ColumnMetadata, formatData: boolean) {
+    if (column.name === 'id') {
+      return this.formatValue(column, alert[column.name]);
+    }
+
+    return this.getValueFromSource(alert.source, column, formatData);
+  }
+
+  getValueFromSource(alertSource: AlertSource, column: ColumnMetadata, 
formatData: boolean) {
     let returnValue = '';
     try {
       switch (column.name) {
         case 'id':
-          returnValue = alert[column.name];
+          returnValue = alertSource['guid'];
           break;
         case 'alert_status':
-          let alertStatus = alert.source['alert_status'];
-          returnValue = alertStatus ? alertStatus : 'NEW';
+          returnValue = alertSource['alert_status'] ? 
alertSource['alert_status'] : 'NEW';
           break;
         default:
-          returnValue = alert.source[column.name];
+          returnValue = alertSource[column.name];
           break;
       }
-    } catch (e) {}
+    } catch (e) {
+    }
 
     if (formatData) {
       returnValue = this.formatValue(column, returnValue);
     }
-
     return returnValue;
   }
 
+  fireSelectedAlertsChanged() {
+    this.onSelectedAlertsChange.emit(this.selectedAlerts);
+  }
+
   formatValue(column: ColumnMetadata, returnValue: string) {
     try {
       if (column.name.endsWith(':ts') || column.name.endsWith('timestamp')) {
@@ -117,8 +179,7 @@ export class TableViewComponent {
     } else {
       this.selectedAlerts.splice(this.selectedAlerts.indexOf(alert), 1);
     }
-
-    this.onSelectedAlertsChange.emit(this.selectedAlerts);
+    this.fireSelectedAlertsChanged();
   }
 
   selectAllRows($event) {
@@ -126,8 +187,7 @@ export class TableViewComponent {
     if ($event.target.checked) {
       this.selectedAlerts = this.alerts;
     }
-
-    this.onSelectedAlertsChange.emit(this.selectedAlerts);
+    this.fireSelectedAlertsChanged();
   }
 
   resize() {
@@ -135,10 +195,17 @@ export class TableViewComponent {
   }
 
   addFilter(field: string, value: string) {
-    field = (field === 'id') ? '_uid' : field;
+    field = (field === 'id') ? 'guid' : field;
     this.onAddFilter.emit(new Filter(field, value));
   }
 
+  showMetaAlertDetails($event, alertSource: AlertSource) {
+    let alert = new Alert();
+    alert.source = alertSource;
+    alert.index = META_ALERTS_INDEX;
+    this.showDetails($event, alert);
+  }
+
   showDetails($event, alert: Alert) {
     if ($event.target.parentElement.firstElementChild.type !== 'checkbox' && 
$event.target.nodeName !== 'A') {
       this.onShowDetails.emit(alert);
@@ -148,4 +215,48 @@ export class TableViewComponent {
   showConfigureTable() {
     this.onShowConfigureTable.emit();
   }
+
+  toggleExpandCollapse($event, alert: Alert) {
+    if (this.metaAlertsDisplayState[alert.id] === 
MetronAlertDisplayState.COLLAPSE) {
+      this.metaAlertsDisplayState[alert.id] = MetronAlertDisplayState.EXPAND;
+    } else {
+      this.metaAlertsDisplayState[alert.id] = MetronAlertDisplayState.COLLAPSE;
+    }
+
+    $event.stopPropagation();
+    return false;
+  }
+
+  deleteOneAlertFromMetaAlert($event, alert: Alert, metaAlertIndex: number) {
+    this.metronDialogBox.showConfirmationMessage('Do you wish to remove the 
alert from the meta alert?').subscribe(response => {
+      if (response) {
+        this.doDeleteOneAlertFromMetaAlert(alert, metaAlertIndex);
+      }
+    });
+    $event.stopPropagation();
+  }
+
+  deleteMetaAlert($event, alert: Alert, index: number) {
+    this.metronDialogBox.showConfirmationMessage('Do you wish to remove all 
the alerts from meta alert?').subscribe(response => {
+      if (response) {
+        this.doDeleteMetaAlert(alert, index);
+      }
+    });
+    $event.stopPropagation();
+  }
+
+  doDeleteOneAlertFromMetaAlert(alert, metaAlertIndex) {
+    let alertToRemove = alert.source.alert[metaAlertIndex];
+    let metaAlertAddRemoveRequest = new MetaAlertAddRemoveRequest();
+    metaAlertAddRemoveRequest.metaAlertGuid = alert.source.guid;
+    metaAlertAddRemoveRequest.alerts = [new GetRequest(alertToRemove.guid, 
alertToRemove['source:type'], '')];
+
+    
this.metaAlertService.removeAlertsFromMetaAlert(metaAlertAddRemoveRequest).subscribe(()
 => {
+    });
+  }
+
+  doDeleteMetaAlert(alert: Alert, index: number) {
+    this.metaAlertService.updateMetaAlertStatus(alert.source.guid, 
'inactive').subscribe(() => {
+    });
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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
index ae65a67..ae5cdd3 100644
--- 
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
@@ -30,6 +30,7 @@ export class TreeGroupData {
   show: boolean;
   expand = false;
   score: number;
+  isLeafNode = false;
 
   // Used by only Dashrow
   sortField: SortField;

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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
index e89cdc9..582117e 100644
--- 
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
@@ -20,7 +20,10 @@
       </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-5 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="fa fa-link top-group-link" aria-hidden="true" 
(click)="createMetaAlert($event, group, i)" data-animation="false" 
data-toggle="tooltip" data-placement="left"  title="Merge Alerts"></i>
+        </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>
@@ -73,13 +76,17 @@
               <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))}">
+                  <td [attr.colspan]="alertsColumnsToDisplay.length + 2" 
[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>
+                  <td>
+                    <i style="color: #32ABDF;" class="fa fa-link" 
aria-hidden="true" (click)="createMetaAlert($event, subGroup, i)" 
data-animation="false" data-toggle="tooltip" data-placement="left"  
title="Merge Alerts"></i>
+                  </td>
+                  <td> </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)">

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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
index 8668b49..084ba5d 100644
--- 
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
@@ -55,6 +55,17 @@ $group-height: 70px;
     background: $mine-shaft-8;
   }
 
+  .fa-link, .fa-chain-broken {
+
+    color: $piction-blue;
+    border: 2px solid transparent;
+
+    &:hover {
+      background: $eden-1;
+      border: 2px solid $blue-mine;
+    }
+  }
+
   .down-arrow {
     padding: 10px 20px;
 
@@ -150,4 +161,10 @@ sup {
 
 .top-group {
   cursor: pointer;
+}
+
+.top-group-link {
+  padding: 10px;
+  font-size: 22px;
+  margin-top: 12px;
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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
index 75a7e1c..bb2b174 100644
--- 
a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts
+++ 
b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts
@@ -16,9 +16,9 @@
  * limitations under the License.
  */
 
-import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { Component, OnInit, OnChanges, SimpleChanges, OnDestroy } from 
'@angular/core';
 import {Router} from '@angular/router';
-import {Subscription} from 'rxjs/Rx';
+import {Subscription, Observable} from 'rxjs/Rx';
 
 import {TableViewComponent} from '../table-view/table-view.component';
 import {SearchResponse} from '../../../model/search-response';
@@ -26,14 +26,17 @@ 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 {MetaAlertCreateRequest} from 
'../../../model/meta-alert-create-request';
+import {MetaAlertService} from '../../../service/meta-alert.service';
+import {INDEXES, MAX_ALERTS_IN_META_ALERTS} from '../../../utils/constants';
 import {UpdateService} from '../../../service/update.service';
 import {PatchRequest} from '../../../model/patch-request';
+import {GetRequest} from '../../../model/get-request';
 
 @Component({
   selector: 'app-tree-view',
@@ -41,26 +44,32 @@ import {PatchRequest} from '../../../model/patch-request';
   styleUrls: ['./tree-view.component.scss']
 })
 
-export class TreeViewComponent extends TableViewComponent implements OnChanges 
{
+export class TreeViewComponent extends TableViewComponent implements OnInit, 
OnChanges, OnDestroy {
 
   groupByFields: string[] = [];
   topGroups: TreeGroupData[] = [];
   groupResponse: GroupResponse = new GroupResponse();
   treeGroupSubscriptionMap: {[key: string]: TreeAlertsSubscription } = {};
+  alertsChangedSubscription: Subscription;
 
   constructor(router: Router,
               searchService: SearchService,
               metronDialogBox: MetronDialogBox,
-              private updateService: UpdateService) {
-    super(router, searchService, metronDialogBox);
+              updateService: UpdateService,
+              metaAlertService: MetaAlertService) {
+    super(router, searchService, metronDialogBox, updateService, 
metaAlertService);
   }
 
   addAlertChangedListner() {
-    this.updateService.alertChanged$.subscribe(patchRequest => {
+    this.alertsChangedSubscription = 
this.updateService.alertChanged$.subscribe(patchRequest => {
       this.updateAlert(patchRequest);
     });
   }
 
+  removeAlertChangedLister() {
+    this.alertsChangedSubscription.unsubscribe();
+  }
+
   collapseGroup(groupArray: TreeGroupData[], level: number, index: number) {
     for (let i = index + 1; i < groupArray.length; i++) {
       if (groupArray[i].level > (level)) {
@@ -132,6 +141,8 @@ export class TreeViewComponent extends TableViewComponent 
implements OnChanges {
 
     this.groupResponse.groupResults.forEach((groupResult: GroupResult) => {
       let treeGroupData = new TreeGroupData(groupResult.key, 
groupResult.total, groupResult.score, 0, false);
+      treeGroupData.isLeafNode = (groupByFields.length === 1);
+
       if (groupByFields.length === 1) {
         treeGroupData.groupQueryMap  = 
this.createTopGroupQueryMap(groupByFields[0], groupResult);
       }
@@ -173,6 +184,10 @@ export class TreeViewComponent extends TableViewComponent 
implements OnChanges {
     this.addAlertChangedListner();
   }
 
+  ngOnDestroy(): void {
+    this.removeAlertChangedLister();
+  }
+
   searchGroup(selectedGroup: TreeGroupData, searchRequest: SearchRequest): 
Subscription {
     return this.searchService.search(searchRequest).subscribe(results => {
       this.setData(selectedGroup, results);
@@ -188,13 +203,13 @@ export class TreeViewComponent extends TableViewComponent 
implements OnChanges {
 
     this.topGroups.map(topGroup => {
       if (topGroup.treeSubGroups.length > 0) {
-        topGroup.total = topGroup.treeSubGroups.reduce((total, subGroup) => { 
return total + subGroup.total }, 0);
+        topGroup.total = topGroup.treeSubGroups.reduce((total, subGroup) => { 
return total + subGroup.total; }, 0);
       }
     });
   }
 
   checkAndToSubscription(group: TreeGroupData) {
-    if (group.groupQueryMap) {
+    if (group.isLeafNode) {
       let key = JSON.stringify(group.groupQueryMap);
       if (this.treeGroupSubscriptionMap[key]) {
         this.removeFromSubscription(group);
@@ -206,7 +221,7 @@ export class TreeViewComponent extends TableViewComponent 
implements OnChanges {
   }
 
   removeFromSubscription(group: TreeGroupData) {
-    if (group.groupQueryMap) {
+    if (group.isLeafNode) {
       let key = JSON.stringify(group.groupQueryMap);
       let subscription = this.treeGroupSubscriptionMap[key].refreshTimer;
       if (subscription && !subscription.closed) {
@@ -240,9 +255,8 @@ export class TreeViewComponent extends TableViewComponent 
implements OnChanges {
   }
 
   parseSubGroups(group: GroupResult, groupAsArray: TreeGroupData[],
-                 groupQueryMap: {[key: string]: string}, groupedBy: string, 
level: number, index: number): number {
+                 parentQueryMap: {[key: string]: string}, currentGroupKey: 
string, level: number, index: number): number {
     index++;
-    groupQueryMap[groupedBy] = group.key;
 
     let currentTreeNodeData = (groupAsArray.length > 0) ? groupAsArray[index] 
: null;
 
@@ -257,8 +271,12 @@ export class TreeViewComponent extends TableViewComponent 
implements OnChanges {
       }
     }
 
+    groupAsArray[index].isLeafNode = false;
+    groupAsArray[index].groupQueryMap = 
JSON.parse(JSON.stringify(parentQueryMap));
+    groupAsArray[index].groupQueryMap[currentGroupKey] = group.key;
+
     if (!group.groupResults) {
-      groupAsArray[index].groupQueryMap = 
JSON.parse(JSON.stringify(groupQueryMap));
+      groupAsArray[index].isLeafNode = true;
       if (groupAsArray[index].expand && groupAsArray[index].show && 
groupAsArray[index].groupQueryMap) {
         this.checkAndToSubscription(groupAsArray[index]);
       }
@@ -266,7 +284,7 @@ export class TreeViewComponent extends TableViewComponent 
implements OnChanges {
     }
 
     group.groupResults.forEach(subGroup => {
-      index = this.parseSubGroups(subGroup, groupAsArray, groupQueryMap, 
group.groupedBy, level + 1, index);
+      index = this.parseSubGroups(subGroup, groupAsArray, 
groupAsArray[index].groupQueryMap, group.groupedBy, level + 1, index);
     });
 
     return index;
@@ -281,13 +299,13 @@ export class TreeViewComponent extends TableViewComponent 
implements OnChanges {
       let index = -1;
       let topGroup = this.topGroups[i];
       let resultGroup = this.groupResponse.groupResults[i];
-      let groupQueryMap = this.createTopGroupQueryMap(groupedBy, resultGroup);
 
       topGroup.total = resultGroup.total;
+      topGroup.groupQueryMap = this.createTopGroupQueryMap(groupedBy, 
resultGroup);
 
       if (resultGroup.groupResults) {
         resultGroup.groupResults.forEach(subGroup => {
-          index = this.parseSubGroups(subGroup, topGroup.treeSubGroups, 
groupQueryMap, resultGroup.groupedBy, 1, index);
+          index = this.parseSubGroups(subGroup, topGroup.treeSubGroups, 
topGroup.groupQueryMap, resultGroup.groupedBy, 1, index);
         });
 
         topGroup.treeSubGroups.splice(index + 1);
@@ -300,11 +318,9 @@ export class TreeViewComponent extends TableViewComponent 
implements OnChanges {
   }
 
   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';
+    let sortBy = $event.sortBy === 'id' ? 'guid' : $event.sortBy;
+    let sortOrder = $event.sortOrder === Sort.ASC ? 'asc' : 'desc';
+    let sortField = new SortField(sortBy, sortOrder);
 
     treeGroup.sortEvent = $event;
     treeGroup.sortField = sortField;
@@ -327,7 +343,7 @@ export class TreeViewComponent extends TableViewComponent 
implements OnChanges {
         }
       });
     }
-    
+
     this.onSelectedAlertsChange.emit(this.selectedAlerts);
   }
 
@@ -337,12 +353,67 @@ export class TreeViewComponent extends TableViewComponent 
implements OnChanges {
     });
   }
 
+  canCreateMetaAlert(count: number) {
+    if (count > MAX_ALERTS_IN_META_ALERTS) {
+      let errorMessage = 'Meta Alert cannot have more than ' + 
MAX_ALERTS_IN_META_ALERTS +' alerts within it';
+      this.metronDialogBox.showConfirmationMessage(errorMessage, 
DialogType.Error).subscribe((response) => {});
+      return false;
+    }
+    return true;
+  }
+
+  createGetRequestArray(searchResponse: SearchResponse): any {
+    return searchResponse.results.map(alert => new 
GetRequest(alert.source.guid, alert.source['source:type'], alert.index));
+  }
+
+  getAllAlertsForSlectedGroup(group: TreeGroupData): 
Observable<SearchResponse> {
+    let dashRowKey = Object.keys(group.groupQueryMap);
+    let searchRequest = new SearchRequest();
+    searchRequest.fields = ['guid', 'source:type'];
+    searchRequest.from = 0;
+    searchRequest.indices = INDEXES;
+    searchRequest.query = this.createQuery(group);
+    searchRequest.size =  MAX_ALERTS_IN_META_ALERTS;
+    searchRequest.facetFields =  [];
+    return this.searchService.search(searchRequest);
+  }
+
+  doCreateMetaAlert(group: TreeGroupData, index: number) {
+    this.getAllAlertsForSlectedGroup(group).subscribe((searchResponse: 
SearchResponse) => {
+      if (this.canCreateMetaAlert(searchResponse.total)) {
+        let metaAlert = new MetaAlertCreateRequest();
+        metaAlert.alerts = this.createGetRequestArray(searchResponse);
+        metaAlert.groups = this.queryBuilder.groupRequest.groups.map(grp => 
grp.field);
+
+        this.metaAlertService.create(metaAlert).subscribe(() => {
+          setTimeout(() => this.onRefreshData.emit(true), 1000);
+          console.log('Meta alert created successfully');
+        });
+      }
+    });
+  }
+
+  createMetaAlert($event, group: TreeGroupData, index: number) {
+    if (this.canCreateMetaAlert(group.total)) {
+      let confirmationMsg = 'Do you wish to create a meta alert with ' +
+                            (group.total === 1 ? ' alert' : group.total + ' 
selected alerts') + '?';
+      
this.metronDialogBox.showConfirmationMessage(confirmationMsg).subscribe((response)
 => {
+        if (response) {
+          this.doCreateMetaAlert(group, index);
+        }
+      });
+    }
+
+    $event.stopPropagation();
+    return false;
+  }
+
   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) {
+        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/d07833a2/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.html
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.html
 
b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.html
new file mode 100644
index 0000000..46b1d7d
--- /dev/null
+++ 
b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.html
@@ -0,0 +1,52 @@
+<!--
+  Licensed to the Apache Software
+       Foundation (ASF) under one or more contributor license agreements. See 
the
+       NOTICE file distributed with this work for additional information 
regarding
+       copyright ownership. The ASF licenses this file to You under the Apache 
License,
+       Version 2.0 (the "License"); you may not use this file except in 
compliance
+       with the License. You may obtain a copy of the License at
+  http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software 
distributed
+       under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+       OR CONDITIONS OF ANY KIND, either express or implied. See the License 
for
+  the specific language governing permissions and limitations under the 
License.
+  -->
+<div class="metron-slider-pane-details load-right-to-left dialog1x">
+  <div class="container-fluid my-3">
+
+    <div class="row mb-3">
+      <div class="col-md-10">
+        <div class="form-title">Add to Alert</div>
+      </div>
+      <div class="col-md-2">
+        <i class="fa fa-times pull-right close-button" aria-hidden="true" 
(click)="goBack()"></i>
+      </div>
+    </div>
+
+    <div>
+      <div class="title"> SELECT OPEN ALERT</div>
+      <div class="container-fluid">
+        <div class="row meta-alert-row" *ngFor="let alert of 
searchResponse.results">
+          <div class="col-1 px-0">
+            <label class="radio-container">
+              <input type="radio" name="radio"><span class="checkmark" 
(click)="selectedMetaAlert=alert.source.guid"></span>
+            </label>
+          </div>
+          <div class="col-11 px-0">
+            <span class="severity" appAlertSeverity 
[severity]="alert.source['threat:triage:score']"></span><sup> {{ 
alert.source['threat:triage:score'] }} </sup>
+            <div class="px-0 guid-name-container">
+              <div [ngClass]="{'selected': 
selectedMetaAlert===alert.source.guid}"> {{(alert.source.name && 
alert.source.name.length > 0) ? alert.source.name : alert.source.guid | 
centerEllipses:20 }} ({{ alert.source.alert.length }})</div>
+              <span class="pull-left sub-text"> {{ (alert.source.alert_status 
&& alert.source.alert_status.length > 0) ? alert.source.alert_status : 'NEW' }} 
</span>
+              <span class="pull-right sub-text"> {{ alert.source.timestamp | 
timeLapse }} </span>
+            </div>
+          </div>
+        </div>
+
+        <div class="my-4">
+          <button type="submit" class="btn btn-all_ports" 
(click)="addAlertToMetaAlert()" [disabled]="selectedMetaAlert.length===0" 
title="Please select a meta alert">ADD</button>
+          <button class="btn btn-mine_shaft_2" 
(click)="goBack()">CANCEL</button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.scss
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.scss
 
b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.scss
new file mode 100644
index 0000000..6a0f3a9
--- /dev/null
+++ 
b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.scss
@@ -0,0 +1,58 @@
+/**
+ * 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";
+
+input {
+  height: 13px;
+}
+
+.radio-container {
+  margin-top: 12px;
+}
+
+.title {
+  font-size: 12px;
+  padding-bottom: 2px;
+  border-bottom: 1px solid $tundora-1;
+}
+
+.meta-alert-row {
+  padding: 4px 0px;
+  border-bottom: 1px solid $tundora-1;
+
+  .severity {
+    height: 20px;
+  }
+
+  .guid-name-container {
+    width: 84%;
+    font-size: 15px;
+    line-height: 1.4;
+    margin-left: 5px;
+    display: inline-block;
+  }
+
+  .sub-text {
+    font-size: 10px;
+  }
+
+  sup, .selected {
+    color: $silver;
+    font-weight: bold;
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.ts
 
b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.ts
new file mode 100644
index 0000000..18a20da
--- /dev/null
+++ 
b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.ts
@@ -0,0 +1,79 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * 'License'); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Component, OnInit } from '@angular/core';
+import {Router} from '@angular/router';
+
+import {MetaAlertService} from '../../service/meta-alert.service';
+import {UpdateService} from '../../service/update.service';
+import {SearchRequest} from '../../model/search-request';
+import {SearchService} from '../../service/search.service';
+import {SearchResponse} from '../../model/search-response';
+import {SortField} from '../../model/sort-field';
+import { META_ALERTS_SENSOR_TYPE } from '../../utils/constants';
+import {MetronDialogBox} from '../../shared/metron-dialog-box';
+import {MetaAlertAddRemoveRequest} from 
'../../model/meta-alert-add-remove-request';
+import {GetRequest} from '../../model/get-request';
+
+@Component({
+  selector: 'app-meta-alerts',
+  templateUrl: './meta-alerts.component.html',
+  styleUrls: ['./meta-alerts.component.scss']
+})
+export class MetaAlertsComponent implements OnInit {
+
+  selectedMetaAlert = '';
+  searchResponse: SearchResponse = new SearchResponse();
+
+  constructor(private router: Router,
+              private metaAlertService: MetaAlertService,
+              private updateService: UpdateService,
+              private searchService: SearchService,
+              private metronDialogBox: MetronDialogBox) {
+  }
+
+  goBack() {
+    this.router.navigateByUrl('/alerts-list');
+    return false;
+  }
+
+  ngOnInit() {
+    let searchRequest = new SearchRequest();
+    searchRequest.query = '*';
+    searchRequest.from = 0;
+    searchRequest.size = 999;
+    searchRequest.facetFields = [];
+    searchRequest.indices =  [META_ALERTS_SENSOR_TYPE];
+    searchRequest.sort = [new SortField('threat:triage:score', 'desc')];
+
+    this.searchService.search(searchRequest).subscribe(resp => 
this.searchResponse = resp);
+  }
+
+  addAlertToMetaAlert() {
+    let getRequest = this.metaAlertService.selectedAlerts.map(alert =>
+          new GetRequest(alert.source.guid, alert.source['source:type'], 
alert.index));
+    let metaAlertAddRemoveRequest = new MetaAlertAddRemoveRequest();
+    metaAlertAddRemoveRequest.metaAlertGuid = this.selectedMetaAlert;
+    metaAlertAddRemoveRequest.alerts = getRequest;
+
+    
this.metaAlertService.addAlertsToMetaAlert(metaAlertAddRemoveRequest).subscribe(()
 => {
+      console.log('Meta alert saved');
+      this.goBack();
+    });
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.module.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.module.ts
 
b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.module.ts
new file mode 100644
index 0000000..578063d
--- /dev/null
+++ 
b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.module.ts
@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+
+import {routing} from './meta-alerts.routing';
+import { MetaAlertsComponent } from './meta-alerts.component';
+import {MetaAlertService} from '../../service/meta-alert.service';
+import {SharedModule} from '../../shared/shared.module';
+
+@NgModule({
+  imports: [ routing,  SharedModule ],
+  declarations: [ MetaAlertsComponent ],
+  providers: [ MetaAlertService ],
+})
+export class MetaAlertsModule { }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.routing.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.routing.ts
 
b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.routing.ts
new file mode 100644
index 0000000..f4b2413
--- /dev/null
+++ 
b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.routing.ts
@@ -0,0 +1,24 @@
+/**
+ * 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 { ModuleWithProviders }  from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { MetaAlertsComponent } from './meta-alerts.component';
+
+export const routing: ModuleWithProviders = RouterModule.forChild([
+    { path: 'add-to-meta-alert', component: MetaAlertsComponent, outlet: 
'dialog'}
+]);

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/app.module.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/app.module.ts 
b/metron-interface/metron-alerts/src/app/app.module.ts
index c817831..bd34e82 100644
--- a/metron-interface/metron-alerts/src/app/app.module.ts
+++ b/metron-interface/metron-alerts/src/app/app.module.ts
@@ -42,6 +42,9 @@ import {AuthGuard} from './shared/auth-guard';
 import {AuthenticationService} from './service/authentication.service';
 import {LoginGuard} from './shared/login-guard';
 import {UpdateService} from './service/update.service';
+import {MetaAlertService} from './service/meta-alert.service';
+import {MetaAlertsModule} from './alerts/meta-alerts/meta-alerts.module';
+import {SearchService} from './service/search.service';
 
 
 
@@ -61,6 +64,7 @@ export function initConfig(config: ColumnNamesService) {
     LoginModule,
     AlertsListModule,
     AlertDetailsModule,
+    MetaAlertsModule,
     ConfigureTableModule,
     ConfigureRowsModule,
     SaveSearchModule,
@@ -73,10 +77,12 @@ export function initConfig(config: ColumnNamesService) {
               AuthGuard,
               LoginGuard,
               ConfigureTableService,
+              SearchService,
               SaveSearchService,
               MetronDialogBox,
               ColumnNamesService,
-              UpdateService],
+              UpdateService,
+              MetaAlertService],
   bootstrap: [AppComponent]
 })
 

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/alert-source.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/model/alert-source.ts 
b/metron-interface/metron-alerts/src/app/model/alert-source.ts
index 4e3a655..d5477dc 100644
--- a/metron-interface/metron-alerts/src/app/model/alert-source.ts
+++ b/metron-interface/metron-alerts/src/app/model/alert-source.ts
@@ -44,6 +44,7 @@ export class AlertSource {
   guid: string;
   sig_id: number;
   sig_generator: number;
+  alert: AlertSource[] = [];
   comments: AlertComment[] = [];
   'threat:triage:score': number;
   'threatinteljoinbolt:joiner:ts': number;

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/filter.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/model/filter.ts 
b/metron-interface/metron-alerts/src/app/model/filter.ts
index 441add4..200e33c 100644
--- a/metron-interface/metron-alerts/src/app/model/filter.ts
+++ b/metron-interface/metron-alerts/src/app/model/filter.ts
@@ -15,9 +15,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {ElasticsearchUtils} from '../utils/elasticsearch-utils';
-import {TIMESTAMP_FIELD_NAME} from '../utils/constants';
 import {Utils} from '../utils/utils';
+import {TIMESTAMP_FIELD_NAME} from '../utils/constants';
 import {DateFilterValue} from './date-filter-value';
 
 export class Filter {
@@ -43,16 +42,33 @@ export class Filter {
   }
 
   getQueryString(): string {
+    if (this.field === 'guid') {
+      let valueWithQuote = '\"' + this.value + '\"';
+      return this.createNestedQueryWithoutValueEscaping(this.field, 
valueWithQuote);
+    }
+
     if (this.field === TIMESTAMP_FIELD_NAME && !this.display) {
       this.dateFilterValue = Utils.timeRangeToDateObj(this.value);
       if (this.dateFilterValue !== null && this.dateFilterValue.toDate !== 
null) {
-        return ElasticsearchUtils.escapeESField(this.field) + ':' +
-            '(>=' + this.dateFilterValue.fromDate + ' AND ' + ' <=' + 
this.dateFilterValue.toDate + ')';
+        return this.createNestedQueryWithoutValueEscaping(this.field,
+            '(>=' + this.dateFilterValue.fromDate + ' AND ' + ' <=' + 
this.dateFilterValue.toDate + ')');
       } else {
-        return ElasticsearchUtils.escapeESField(this.field) + ':' + this.value;
+        return this.createNestedQueryWithoutValueEscaping(this.field,  
this.value);
       }
     }
 
-    return ElasticsearchUtils.escapeESField(this.field) + ':' +  
ElasticsearchUtils.escapeESValue(this.value);
+    return this.createNestedQuery(this.field, this.value);
+  }
+
+  private createNestedQuery(field: string, value: string): string {
+
+    return '(' + Utils.escapeESField(field) + ':' +  
Utils.escapeESValue(value)  + ' OR ' +
+                Utils.escapeESField('alert.' + field) + ':' +  
Utils.escapeESValue(value) + ')';
+  }
+
+  private createNestedQueryWithoutValueEscaping(field: string, value: string): 
string {
+
+    return '(' + Utils.escapeESField(field) + ':' +  value  + ' OR ' +
+        Utils.escapeESField('alert.' + field) + ':' +  value + ')';
   }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/get-request.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/model/get-request.ts 
b/metron-interface/metron-alerts/src/app/model/get-request.ts
new file mode 100644
index 0000000..dcd3f12
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/model/get-request.ts
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export class GetRequest {
+  guid: string;
+  sensorType: string;
+  index: string;
+
+  constructor(guid: string, sensorType: string, index: string) {
+    this.guid = guid;
+    this.sensorType = sensorType;
+    this.index = index;
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/meta-alert-add-remove-request.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-alerts/src/app/model/meta-alert-add-remove-request.ts 
b/metron-interface/metron-alerts/src/app/model/meta-alert-add-remove-request.ts
new file mode 100644
index 0000000..49cdbf8
--- /dev/null
+++ 
b/metron-interface/metron-alerts/src/app/model/meta-alert-add-remove-request.ts
@@ -0,0 +1,23 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {GetRequest} from './get-request';
+
+export class MetaAlertAddRemoveRequest {
+  metaAlertGuid: string;
+  alerts: GetRequest[];
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/meta-alert-create-request.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-alerts/src/app/model/meta-alert-create-request.ts 
b/metron-interface/metron-alerts/src/app/model/meta-alert-create-request.ts
new file mode 100644
index 0000000..0d7c63e
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/model/meta-alert-create-request.ts
@@ -0,0 +1,24 @@
+/**
+ * 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 {GetRequest} from './get-request';
+
+export class MetaAlertCreateRequest {
+  alerts: GetRequest[];
+  groups: string[];
+}
+

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/replace-request.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/model/replace-request.ts 
b/metron-interface/metron-alerts/src/app/model/replace-request.ts
new file mode 100644
index 0000000..d7c18b2
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/model/replace-request.ts
@@ -0,0 +1,24 @@
+/**
+ * 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.
+ */
+
+export class ReplaceRequest {
+  replacement: {[key: string]: any};
+  guid: string;
+  sensorType: string;
+  index: string;
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/search-request.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/model/search-request.ts 
b/metron-interface/metron-alerts/src/app/model/search-request.ts
index d8dbcda..44e23db 100644
--- a/metron-interface/metron-alerts/src/app/model/search-request.ts
+++ b/metron-interface/metron-alerts/src/app/model/search-request.ts
@@ -19,7 +19,7 @@ import {SortField} from './sort-field';
 import {DEFAULT_FACETS, DEFAULT_GROUPS, INDEXES} from '../utils/constants';
 
 export class SearchRequest {
-  // _source: string[]; //TODO: This needs to be removed
+  fields: string[];
   from: number;
   indices: string[] = INDEXES;
   query: string;

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/sort-field.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/model/sort-field.ts 
b/metron-interface/metron-alerts/src/app/model/sort-field.ts
index 601b370..8fe7ce2 100644
--- a/metron-interface/metron-alerts/src/app/model/sort-field.ts
+++ b/metron-interface/metron-alerts/src/app/model/sort-field.ts
@@ -18,4 +18,9 @@
 export class SortField {
   field: string;
   sortOrder: string;
+
+  constructor(field: string, sortOrder: string) {
+    this.field = field;
+    this.sortOrder = sortOrder;
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/service/meta-alert.service.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-alerts/src/app/service/meta-alert.service.ts 
b/metron-interface/metron-alerts/src/app/service/meta-alert.service.ts
new file mode 100644
index 0000000..c9bb26d
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/service/meta-alert.service.ts
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Injectable} from '@angular/core';
+import {Headers, RequestOptions} from '@angular/http';
+import {Subject}    from 'rxjs/Subject';
+import {Observable} from 'rxjs/Rx';
+import 'rxjs/add/observable/interval';
+import 'rxjs/add/operator/switchMap';
+import 'rxjs/add/operator/onErrorResumeNext';
+
+import {HttpUtil} from '../utils/httpUtil';
+import {Alert} from '../model/alert';
+import {Http} from '@angular/http';
+import {MetaAlertCreateRequest} from '../model/meta-alert-create-request';
+import {MetaAlertAddRemoveRequest} from 
'../model/meta-alert-add-remove-request';
+
+@Injectable()
+export class MetaAlertService {
+  private _selectedAlerts: Alert[];
+  alertChangedSource = new Subject<MetaAlertAddRemoveRequest>();
+  alertChanged$ = this.alertChangedSource.asObservable();
+  defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 
'XMLHttpRequest'};
+
+  constructor(private http: Http) {
+  }
+
+  get selectedAlerts(): Alert[] {
+    return this._selectedAlerts;
+  }
+
+  set selectedAlerts(value: Alert[]) {
+    this._selectedAlerts = value;
+  }
+
+  public create(metaAlertCreateRequest: MetaAlertCreateRequest): 
Observable<{}> {
+    let url = '/api/v1/metaalert/create';
+    return this.http.post(url, metaAlertCreateRequest, new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+    .catch(HttpUtil.handleError);
+  }
+
+  public addAlertsToMetaAlert(metaAlertAddRemoveRequest: 
MetaAlertAddRemoveRequest) {
+    let url = '/api/v1/metaalert/add/alert';
+    return this.http.post(url, metaAlertAddRemoveRequest, new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+    .catch(HttpUtil.handleError)
+    .map(result => {
+        this.alertChangedSource.next(metaAlertAddRemoveRequest);
+      return result;
+    });
+  }
+
+  public  removeAlertsFromMetaAlert(metaAlertAddRemoveRequest: 
MetaAlertAddRemoveRequest) {
+    let url = '/api/v1/metaalert/remove/alert';
+    return this.http.post(url, metaAlertAddRemoveRequest, new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+    .catch(HttpUtil.handleError)
+    .map(result => {
+      this.alertChangedSource.next(metaAlertAddRemoveRequest);
+      return result;
+    });
+  }
+
+  public updateMetaAlertStatus(guid: string, status: string) {
+    let url = `/api/v1/metaalert/update/status/${guid}/${status}`;
+    return this.http.post(url, {}, new RequestOptions({headers: new 
Headers(this.defaultHeaders)}))
+    .catch(HttpUtil.handleError)
+    .map(result => {
+      let metaAlertAddRemoveRequest = new MetaAlertAddRemoveRequest();
+      metaAlertAddRemoveRequest.metaAlertGuid = guid;
+      metaAlertAddRemoveRequest.alerts = null;
+      this.alertChangedSource.next(metaAlertAddRemoveRequest);
+      return result;
+    });
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/service/update.service.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/service/update.service.ts 
b/metron-interface/metron-alerts/src/app/service/update.service.ts
index a1bf4a1..68d9fc0 100644
--- a/metron-interface/metron-alerts/src/app/service/update.service.ts
+++ b/metron-interface/metron-alerts/src/app/service/update.service.ts
@@ -27,6 +27,9 @@ import {HttpUtil} from '../utils/httpUtil';
 import {Alert} from '../model/alert';
 import {Http} from '@angular/http';
 import {PatchRequest} from '../model/patch-request';
+import {Utils} from '../utils/utils';
+import {Patch} from '../model/patch';
+import {META_ALERTS_INDEX, META_ALERTS_SENSOR_TYPE} from '../utils/constants';
 
 @Injectable()
 export class UpdateService {
@@ -38,27 +41,32 @@ export class UpdateService {
 
   constructor(private http: Http) { }
 
-  public patch(patchRequest: PatchRequest): Observable<{}> {
+  public patch(patchRequest: PatchRequest, fireChangeListener = true): 
Observable<{}> {
     let url = '/api/v1/update/patch';
     return this.http.patch(url, patchRequest, new RequestOptions({headers: new 
Headers(this.defaultHeaders)}))
     .catch(HttpUtil.handleError)
     .map(result => {
-      this.alertChangedSource.next(patchRequest);
+      if (fireChangeListener) {
+        this.alertChangedSource.next(patchRequest);
+      }
       return result;
     });
   }
 
-  public updateAlertState(alerts: Alert[], state: string): Observable<{}> {
+  public updateAlertState(alerts: Alert[], state: string, fireChangeListener = 
true): Observable<{}> {
     let patchRequests: PatchRequest[] = alerts.map(alert => {
       let patchRequest = new PatchRequest();
       patchRequest.guid = alert.source.guid;
-      patchRequest.sensorType = alert.source['source:type'];
-      patchRequest.patch = [{'op': 'add', 'path': '/alert_status', 'value': 
state}];
+      patchRequest.sensorType = Utils.getAlertSensorType(alert);
+      patchRequest.patch = [new Patch('add', '/alert_status', state)];
+      if (patchRequest.sensorType === META_ALERTS_SENSOR_TYPE) {
+        patchRequest.index = META_ALERTS_INDEX;
+      }
       return patchRequest;
     });
     let patchObservables = [];
     for (let patchRequest of patchRequests) {
-      patchObservables.push(this.patch(patchRequest));
+      patchObservables.push(this.patch(patchRequest, fireChangeListener));
     }
     return Observable.forkJoin(patchObservables);
   }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts 
b/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts
index 1b8e2bf..aaa2791 100644
--- a/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts
+++ b/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts
@@ -36,14 +36,14 @@ export class MetronDialogBox {
 
   private createDialogBox(message: string, type: DialogType) {
     let cancelButtonHTML = this.getCancelButton(type);
-    let html = `<div class="metron-dialog modal fade"  data-backdrop="static" >
+    let html = `<div class="metron-dialog modal"  data-backdrop="static" >
                   <div class="modal-dialog modal-sm" role="document">
                     <div class="modal-content">
                       <div class="modal-header">
+                        <span class="modal-title"><b>` + 
MetronDialogBox.dialogType[type] + `</b></span>
                         <button type="button" class="close" 
data-dismiss="modal" aria-label="Close"> 
                             <span aria-hidden="true">&times;</span> 
                         </button>
-                        <span class="modal-title"><b>` + 
MetronDialogBox.dialogType[type] + `</b></span>
                       </div>
                       <div class="modal-body">
                         <p>` +  message + `</p>

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/shared/pipes/time-lapse.pipe.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-alerts/src/app/shared/pipes/time-lapse.pipe.ts 
b/metron-interface/metron-alerts/src/app/shared/pipes/time-lapse.pipe.ts
new file mode 100644
index 0000000..5515ef5
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/shared/pipes/time-lapse.pipe.ts
@@ -0,0 +1,17 @@
+import { Pipe, PipeTransform } from '@angular/core';
+import * as moment from 'moment/moment';
+
+@Pipe({
+  name: 'timeLapse'
+})
+export class TimeLapsePipe implements PipeTransform {
+
+  transform(value: any): any {
+    if (isNaN(value)) {
+      return '';
+    }
+
+    return moment(new Date(value)).fromNow();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/shared/shared.module.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/shared/shared.module.ts 
b/metron-interface/metron-alerts/src/app/shared/shared.module.ts
index 41290a4..d5e4531 100644
--- a/metron-interface/metron-alerts/src/app/shared/shared.module.ts
+++ b/metron-interface/metron-alerts/src/app/shared/shared.module.ts
@@ -26,6 +26,7 @@ import { AlertSearchDirective } from 
'./directives/alert-search.directive';
 import { ColumnNameTranslatePipe } from './pipes/column-name-translate.pipe';
 import { MapKeysPipe } from './pipes/map-keys.pipe';
 import { AlertSeverityHexagonDirective } from 
'./directives/alert-severity-hexagon.directive';
+import { TimeLapsePipe } from './pipes/time-lapse.pipe';
 
 @NgModule({
   imports:  [
@@ -38,8 +39,9 @@ import { AlertSeverityHexagonDirective } from 
'./directives/alert-severity-hexag
     CenterEllipsesPipe,
     AlertSearchDirective,
     ColumnNameTranslatePipe,
+    AlertSeverityHexagonDirective,
+    TimeLapsePipe,
     MapKeysPipe,
-    AlertSeverityHexagonDirective
   ],
   exports:  [
     CommonModule,
@@ -50,8 +52,9 @@ import { AlertSeverityHexagonDirective } from 
'./directives/alert-severity-hexag
     CenterEllipsesPipe,
     AlertSearchDirective,
     ColumnNameTranslatePipe,
+    AlertSeverityHexagonDirective,
+    TimeLapsePipe,
     MapKeysPipe,
-    AlertSeverityHexagonDirective
   ]
 })
 export class SharedModule { }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/shared/time-range/time-range.component.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-alerts/src/app/shared/time-range/time-range.component.ts
 
b/metron-interface/metron-alerts/src/app/shared/time-range/time-range.component.ts
index 89f57a1..2600189 100644
--- 
a/metron-interface/metron-alerts/src/app/shared/time-range/time-range.component.ts
+++ 
b/metron-interface/metron-alerts/src/app/shared/time-range/time-range.component.ts
@@ -30,7 +30,7 @@ import {DateFilterValue} from '../../model/date-filter-value';
   templateUrl: './time-range.component.html',
   styleUrls: ['./time-range.component.scss']
 })
-export class TimeRangeComponent implements OnInit, OnChanges {
+export class TimeRangeComponent implements OnChanges {
   toDateStr = '';
   fromDateStr = '';
   datePickerFromDate = '';
@@ -88,9 +88,6 @@ export class TimeRangeComponent implements OnInit, OnChanges {
     }
   }
 
-  ngOnInit() {
-  }
-
   onSelectedTimeRangeChange() {
     let foundQuickRange = false;
     let merged = Object.assign({}, this.timeRangeMappingCol1, 
this.timeRangeMappingCol2, this.timeRangeMappingCol3, 
this.timeRangeMappingCol4);
@@ -149,7 +146,7 @@ export class TimeRangeComponent implements OnInit, 
OnChanges {
   applyCustomDate() {
     this.hideDatePicker();
     this.selectedTimeRangeValue = CUSTOMM_DATE_RANGE_LABEL;
-    this.toDateStr = this.datePickerToDate.length > 0  ? 
moment(this.datePickerToDate).format(DEFAULT_TIMESTAMP_FORMAT) : 'NOW';
+    this.toDateStr = this.datePickerToDate.length > 0  ? 
moment(this.datePickerToDate).format(DEFAULT_TIMESTAMP_FORMAT) : 'now';
     this.fromDateStr = 
moment(this.datePickerFromDate).format(DEFAULT_TIMESTAMP_FORMAT);
 
     let toDate = this.datePickerToDate.length > 0 ? new 
Date(this.toDateStr).getTime() : null;

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/utils/constants.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/utils/constants.ts 
b/metron-interface/metron-alerts/src/app/utils/constants.ts
index b0a5a17..6fcea2b 100644
--- a/metron-interface/metron-alerts/src/app/utils/constants.ts
+++ b/metron-interface/metron-alerts/src/app/utils/constants.ts
@@ -18,6 +18,9 @@
 
 import {environment} from '../../environments/environment';
 
+export const META_ALERTS_SENSOR_TYPE = 'metaalert';
+export const META_ALERTS_INDEX = 'metaalert_index';
+
 export const NUM_SAVED_SEARCH = 10;
 export const ALERTS_RECENT_SEARCH = 'metron-alerts-recent-saved-search';
 export const ALERTS_SAVED_SEARCH = 'metron-alerts-saved-search';
@@ -35,3 +38,5 @@ export let TREE_SUB_GROUP_SIZE = 5;
 export let DEFAULT_FACETS = ['source:type', 'ip_src_addr', 'ip_dst_addr', 
'host', 'enrichments:geo:ip_dst_addr:country'];
 export let DEFAULT_GROUPS = ['source:type', 'ip_src_addr', 'ip_dst_addr', 
'host', 'enrichments:geo:ip_dst_addr:country'];
 export let INDEXES =  environment.indices ? environment.indices.split(',') : 
[];
+
+export let MAX_ALERTS_IN_META_ALERTS = 350;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/utils/utils.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/utils/utils.ts 
b/metron-interface/metron-alerts/src/app/utils/utils.ts
index 57a6355..34d2241 100644
--- a/metron-interface/metron-alerts/src/app/utils/utils.ts
+++ b/metron-interface/metron-alerts/src/app/utils/utils.ts
@@ -17,10 +17,29 @@
  */
 import * as moment from 'moment/moment';
 
-import {DEFAULT_TIMESTAMP_FORMAT, TIMESTAMP_FIELD_NAME} from './constants';
+import {DEFAULT_TIMESTAMP_FORMAT, META_ALERTS_SENSOR_TYPE} from './constants';
+import {Alert} from '../model/alert';
 import {DateFilterValue} from '../model/date-filter-value';
 
 export class Utils {
+  public static escapeESField(field: string): string {
+    return field.replace(/:/g, '\\:');
+  }
+
+  public static escapeESValue(value: string): string {
+    return String(value)
+    .replace(/[\*\+\-=~><\"\?^\${}\(\)\:\!\/[\]\\\s]/g, '\\$&') // replace 
single  special characters
+    .replace(/\|\|/g, '\\||') // replace ||
+    .replace(/\&\&/g, '\\&&'); // replace &&
+  }
+
+  public static getAlertSensorType(alert: Alert): string {
+    if (alert.source['source:type'] && alert.source['source:type'].length > 0) 
{
+      return alert.source['source:type'];
+    } else {
+      return META_ALERTS_SENSOR_TYPE;
+    }
+  }
 
   public static timeRangeToDateObj(range:string) {
     let timeRangeToDisplayStr = Utils.timeRangeToDisplayStr(range);
@@ -181,4 +200,5 @@ export class Utils {
 
     return {toDate: toDate, fromDate: fromDate};
   }
+
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/environments/environment.e2e.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/environments/environment.e2e.ts 
b/metron-interface/metron-alerts/src/environments/environment.e2e.ts
index 4a11048..df414b1 100644
--- a/metron-interface/metron-alerts/src/environments/environment.e2e.ts
+++ b/metron-interface/metron-alerts/src/environments/environment.e2e.ts
@@ -17,5 +17,5 @@
  */
 export const environment = {
   production: false,
-  indices: 'alerts_ui_e2e'
-};
\ No newline at end of file
+  indices: 'alerts_ui_e2e,metaalert'
+};

http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/styles.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/styles.scss 
b/metron-interface/metron-alerts/src/styles.scss
index 0958685..e9e1d51 100644
--- a/metron-interface/metron-alerts/src/styles.scss
+++ b/metron-interface/metron-alerts/src/styles.scss
@@ -267,3 +267,62 @@ hr {
   margin: 0.3rem 0;
   padding: 0;
 }
+
+/** Custom Radio box **/
+$background_color_1: #eee;
+$background_color_2: #ccc;
+$background_color_3: #2196F3;
+.radio-container {
+  display: block;
+  position: relative;
+  padding-left: 35px;
+  margin-bottom: 12px;
+  cursor: pointer;
+  font-size: 22px;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  input {
+    position: absolute;
+    opacity: 0;
+    &:checked {
+      &~.checkmark {
+        background-color: $eastern-blue-2;
+        &:after {
+          display: block;
+        }
+      }
+    }
+  }
+  &:hover {
+    input {
+      &~.checkmark {
+        background-color: $eastern-blue-2;
+      }
+    }
+  }
+  .checkmark {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 12px;
+    width: 12px;
+    background-color: $mine-shaft-2;
+    border: 1px solid $tundora;
+    border-radius: 50%;
+
+    &:after {
+      top: 2px;
+      left: 2px;
+      width: 6px;
+      height: 6px;
+      border-radius: 50%;
+      background: $white;
+      content: "";
+      position: absolute;
+      display: none;
+    }
+  }
+}
+

Reply via email to