You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by im...@apache.org on 2021/05/27 22:10:20 UTC

[asterixdb] branch master updated: [ASTERIXDB-2859][DASHBOARD] New Asterix Query Console

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

imaxon pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 24b3e50  [ASTERIXDB-2859][DASHBOARD] New Asterix Query Console
24b3e50 is described below

commit 24b3e503e235bc20bf2a99a0f8f1f06c22cc1bd9
Author: mileshong1 <mi...@uci.edu>
AuthorDate: Sat May 22 20:28:34 2021 -0700

    [ASTERIXDB-2859][DASHBOARD] New Asterix Query Console
    
    -user model changes: no
    -storage format changes: no
    -interface changes: yes
    
    Full Documentation at: /asterix-doc/src/site/markdown/dashboard.md
    
    Details:
    -Metadata inspector
        -Inspector that supports nested schema and anonymous types
    -Interactive Plan Viewer
    -Revamped query navigation (history)
    -Output for JSON / CSV
    -Export for JSON / JSONL / CSV
    -Autodetect dataverse
    
    Change-Id: I6d8ee6b1bfb1a6e5dc7072566d76841f9123b093
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/9152
    Integration-Tests: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Tested-by: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Contrib: Ian Maxon <im...@uci.edu>
    Reviewed-by: Ian Maxon <im...@uci.edu>
---
 asterixdb/asterix-dashboard/pom.xml                |    4 +-
 asterixdb/asterix-dashboard/src/node/angular.json  |   10 +-
 asterixdb/asterix-dashboard/src/node/package.json  |   87 +-
 .../asterix-dashboard/src/node/proxy.config.js     |    7 +-
 .../src/node/src/app/app-config.service.ts         |   52 +-
 .../src/node/src/app/app.component.ts              |    2 +-
 .../src/node/src/app/app.module.ts                 |   36 +-
 .../node/src/app/dashboard/appbar.component.html   |   15 +-
 .../node/src/app/dashboard/appbar.component.scss   |    4 +-
 .../src/node/src/app/dashboard/appbar.component.ts |    9 +-
 .../node/src/app/dashboard/apptab.component.scss   |    4 +-
 .../app/dashboard/query/dialog-export-picker.html  |   33 +
 .../app/dashboard/query/dialog-export-picker.scss  |   53 +
 .../src/app/dashboard/query/input.component.html   |   60 +-
 .../src/app/dashboard/query/input.component.scss   |   75 +-
 .../src/app/dashboard/query/input.component.ts     |  803 +++++++-----
 .../query/metadata-inspector.component.html        |  109 +-
 .../query/metadata-inspector.component.scss        |   78 +-
 .../app/dashboard/query/metadata.component.html    |   95 +-
 .../app/dashboard/query/metadata.component.scss    |   14 +-
 .../src/app/dashboard/query/metadata.component.ts  |  311 ++++-
 .../src/app/dashboard/query/output.component.html  |   13 +-
 .../src/app/dashboard/query/output.component.scss  |   11 +-
 .../src/app/dashboard/query/output.component.ts    |   17 +-
 .../dashboard/query/plan-node-svg.component.html   |   79 --
 .../dashboard/query/plan-node-svg.component.scss   |  230 ----
 .../app/dashboard/query/plan-node-svg.component.ts |  196 ---
 .../app/dashboard/query/plan-view.component.html   |   37 -
 .../app/dashboard/query/plan-view.component.scss   |   98 --
 .../src/app/dashboard/query/plan-view.component.ts |  115 --
 .../app/dashboard/query/plan-viewer.component.html |  184 +++
 .../app/dashboard/query/plan-viewer.component.scss |  231 ++++
 .../app/dashboard/query/plan-viewer.component.ts   |  681 +++++++++++
 .../dashboard/query/query-container.component.html |   18 +-
 .../dashboard/query/query-container.component.scss |    5 +-
 .../dashboard/query/query-container.component.ts   |   25 +-
 .../app/dashboard/query/tree-view.component.html   |   28 +-
 .../app/dashboard/query/tree-view.component.scss   |   20 +-
 .../src/app/dashboard/query/tree-view.component.ts |  430 +++++--
 .../src/node/src/app/material.module.ts            |   77 +-
 .../node/src/app/shared/actions/cancel.actions.ts  |   43 +
 .../node/src/app/shared/actions/dataset.actions.ts |   27 +-
 .../src/app/shared/actions/function.actions.ts     |   46 +
 .../node/src/app/shared/actions/query.actions.ts   |    2 +-
 .../src/node/src/app/shared/effects/app.effects.ts |    4 +-
 .../node/src/app/shared/effects/cancel.effects.ts  |   43 +
 .../node/src/app/shared/effects/dataset.effects.ts |   69 +-
 .../src/app/shared/effects/datatype.effects.ts     |   54 +-
 .../src/app/shared/effects/dataverse.effects.ts    |   63 +-
 .../src/app/shared/effects/function.effects.ts     |   40 +
 .../node/src/app/shared/effects/index.effects.ts   |   51 +-
 .../node/src/app/shared/effects/query.effects.ts   |   37 +-
 .../node/src/app/shared/reducers/cancel.reducer.ts |   56 +
 .../src/app/shared/reducers/dataset.reducer.ts     |   30 +-
 .../src/app/shared/reducers/function.reducer.ts    |   68 ++
 .../src/node/src/app/shared/reducers/index.ts      |    7 +-
 .../node/src/app/shared/reducers/query.reducer.ts  |   18 +-
 .../src/app/shared/services/async-query.service.ts |   98 +-
 .../asterix-dashboard/src/node/src/index.html      |    2 +-
 asterixdb/asterix-dashboard/src/node/src/main.scss |    2 +
 asterixdb/asterix-dashboard/src/node/src/main.ts   |    2 +-
 .../asterix-dashboard/src/node/src/polyfills.ts    |    4 +-
 .../src/node/src/styles/_general.scss              |    8 +-
 asterixdb/asterix-dashboard/src/node/src/test.ts   |   26 +-
 .../src/node/src/tests/appbar.component.spec.ts    |   68 ++
 .../src/node/src/tests/input.component.spec.ts     |  213 ++++
 .../asterix-doc/src/site/markdown/dashboard.md     |  236 ++++
 .../main/licenses/templates/3rdpartylicenses.txt   | 1292 +++++++++++++++++++-
 68 files changed, 5349 insertions(+), 1616 deletions(-)

diff --git a/asterixdb/asterix-dashboard/pom.xml b/asterixdb/asterix-dashboard/pom.xml
index b8ac630..20e507d 100644
--- a/asterixdb/asterix-dashboard/pom.xml
+++ b/asterixdb/asterix-dashboard/pom.xml
@@ -68,8 +68,8 @@
             <artifactId>frontend-maven-plugin</artifactId>
             <version>1.6</version>
             <configuration>
-              <nodeVersion>v10.3.0</nodeVersion>
-              <npmVersion>6.1.0</npmVersion>
+              <nodeVersion>v14.15.4</nodeVersion>
+              <npmVersion>6.14.11</npmVersion>
               <workingDirectory>target/dashboard</workingDirectory>
               <installDirectory>target/dashboard</installDirectory>
             </configuration>
diff --git a/asterixdb/asterix-dashboard/src/node/angular.json b/asterixdb/asterix-dashboard/src/node/angular.json
index 768339e..839ff38 100644
--- a/asterixdb/asterix-dashboard/src/node/angular.json
+++ b/asterixdb/asterix-dashboard/src/node/angular.json
@@ -28,6 +28,11 @@
             ],
             "scripts": [
               "node_modules/codemirror/lib/codemirror.js"
+            ],
+            "allowedCommonJsDependencies": [
+              "hammerjs",
+              "lodash",
+              "file-saver",
             ]
           },
           "configurations": {
@@ -57,7 +62,8 @@
           },
           "configurations": {
             "production": {
-              "browserTarget": "asterixdb-web-console:build:production"
+              "browserTarget": "asterixdb-web-console:build:production",
+              "sourceMap": true
             }
           }
         },
@@ -140,4 +146,4 @@
       "prefix": "app"
     }
   }
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/package.json b/asterixdb/asterix-dashboard/src/node/package.json
index c716289..da627b9 100755
--- a/asterixdb/asterix-dashboard/src/node/package.json
+++ b/asterixdb/asterix-dashboard/src/node/package.json
@@ -13,52 +13,53 @@
   },
   "private": true,
   "dependencies": {
-    "@angular-devkit/core": "0.6.8",
-    "@angular/animations": "6.0.7",
-    "@angular/cdk": "6.3.3",
-    "@angular/common": "6.0.7",
-    "@angular/compiler": "6.0.7",
-    "@angular/core": "6.0.7",
-    "@angular/forms": "6.0.7",
-    "@angular/http": "6.0.7",
-    "@angular/material": "6.3.3",
-    "@angular/platform-browser": "6.0.7",
-    "@angular/platform-browser-dynamic": "6.0.7",
-    "@angular/router": "6.0.7",
-    "@ngrx/effects": "6.0.0",
-    "@ngrx/entity": "6.0.0",
-    "@ngrx/store": "6.0.0",
-    "@ngrx/store-devtools": "6.0.0",
-    "codemirror": "5.31.0",
-    "core-js": "2.4.1",
-    "file-saver": "1.3.3",
+    "@angular-devkit/core": "10.2.1",
+    "@angular/animations": "10.2.4",
+    "@angular/cdk": "10.2.4",
+    "@angular/common": "10.2.4",
+    "@angular/compiler": "10.2.4",
+    "@angular/core": "10.2.4",
+    "@angular/forms": "10.2.4",
+    "@angular/material": "10.2.4",
+    "@angular/platform-browser": "10.2.4",
+    "@angular/platform-browser-dynamic": "10.2.4",
+    "@angular/router": "10.2.4",
+    "@ngrx/effects": "10.1.2",
+    "@ngrx/entity": "10.1.2",
+    "@ngrx/store": "10.1.2",
+    "@ngrx/store-devtools": "10.1.2",
+    "@swimlane/ngx-charts": "^16.0.0",
+    "@swimlane/ngx-graph": "^7.2.0",
+    "codemirror": "5.59.2",
+    "core-js": "3.8.3",
+    "file-saver": "2.0.5",
     "hammerjs": "2.0.8",
-    "lodash": "^4.17.10",
+    "lodash": "^4.17.20",
     "roboto-fontface": "^0.10.0",
-    "rxjs": "6.2.1",
-    "rxjs-compat": "6.2.1",
-    "zone.js": "0.8.26"
+    "rxjs": "6.6.3",
+    "rxjs-compat": "6.6.3",
+    "zone.js": "0.10.3"
   },
   "devDependencies": {
-    "@angular/cli": "6.0.8",
-    "@angular/compiler-cli": "6.0.7",
-    "@angular/language-service": "6.0.7",
-    "@types/file-saver": "1.3.0",
-    "@types/jasmine": "2.5.53",
-    "@types/jasminewd2": "2.0.2",
-    "@types/node": "6.0.60",
-    "jasmine-core": "2.6.2",
-    "jasmine-spec-reporter": "4.1.0",
-    "karma": "1.7.0",
-    "karma-chrome-launcher": "2.1.1",
-    "karma-cli": "1.0.1",
-    "karma-coverage-istanbul-reporter": "1.2.1",
-    "karma-jasmine": "1.1.0",
-    "karma-jasmine-html-reporter": "0.2.2",
-    "protractor": "5.1.2",
-    "ts-node": "3.2.2",
-    "tslint": "5.7.0",
-    "typescript": "2.7.2",
-    "@angular-devkit/build-angular": "0.6.8"
+    "@angular-devkit/build-angular": "0.1002.1",
+    "@angular/cli": "10.2.1",
+    "@angular/compiler-cli": "10.2.4",
+    "@angular/language-service": "10.2.4",
+    "@types/file-saver": "2.0.1",
+    "@types/jasmine": "3.6.3",
+    "@types/jasminewd2": "2.0.8",
+    "@types/node": "14.14.22",
+    "eslint": "^7.1.0",
+    "jasmine-core": "3.6.0",
+    "jasmine-spec-reporter": "5.0.2",
+    "karma": "5.0.9",
+    "karma-chrome-launcher": "3.1.0",
+    "karma-cli": "2.0.0",
+    "karma-coverage-istanbul-reporter": "3.0.2",
+    "karma-jasmine": "3.2.0",
+    "karma-jasmine-html-reporter": "1.5.4",
+    "protractor": "7.0.0",
+    "ts-node": "8.10.1",
+    "typescript": "4.0.5"
   }
 }
diff --git a/asterixdb/asterix-dashboard/src/node/proxy.config.js b/asterixdb/asterix-dashboard/src/node/proxy.config.js
index 58752c3..22fcc71 100755
--- a/asterixdb/asterix-dashboard/src/node/proxy.config.js
+++ b/asterixdb/asterix-dashboard/src/node/proxy.config.js
@@ -17,7 +17,12 @@ const PROXY_CONFIG = {
         "secure": false,
         logLevel: "debug",
         pathRewrite: function (path) { return path.replace('/query-service', '/query/service')}
+    },
+    "/admin/requests/running": {
+      "target": "http://localhost:19002",
+      "secure": false,
+      logLevel: "debug"
     }
 }
 
-module.exports = PROXY_CONFIG;
\ No newline at end of file
+module.exports = PROXY_CONFIG;
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/app-config.service.ts b/asterixdb/asterix-dashboard/src/node/src/app/app-config.service.ts
index 5227227..0c86099 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/app-config.service.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/app-config.service.ts
@@ -1,5 +1,5 @@
 
-import {throwError as observableThrowError,  Observable } from 'rxjs';
+import {throwError as observableThrowError } from 'rxjs';
 /*
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -14,40 +14,54 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 import { Injectable } from '@angular/core';
-import { Http, Headers, RequestOptions } from '@angular/http';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
 import 'rxjs/add/operator/map';
 import 'rxjs/add/operator/catch';
 
+interface ENV_DATA {
+  env: string
+}
+
 @Injectable()
 export class ConfigService {
 
     private config: Object
     private env: Object
 
-    constructor(private http: Http) {}
+    constructor(private http: HttpClient) {}
+
     /**
      * Loads the environment config file first. Reads the environment variable from the file
      * and based on that loads the appropriate configuration file - development or production
      */
     load() {
         return new Promise((resolve, reject) => {
-            let headers = new Headers({ 'Accept': 'application/json', 'Content-Type': 'application/json', 'DataType': 'application/json' });
-            let options = new RequestOptions({ headers: headers });
-            this.http.get('/config/env.json')
-            .map(res => res.json())
-            .subscribe((env_data) => {
-                this.env = env_data;
+            let headers = new HttpHeaders({ 'Accept': 'application/json', 'Content-Type': 'application/json', 'DataType': 'application/json' });
+            const HttpOptions = {
+              headers: new HttpHeaders({
+                'Accept': 'application/json',
+                'Content-Type': 'application/json',
+                'DataType': 'application/json',
+              })
+            }
+
+            this.http.get('/config/env.json', HttpOptions)
+              .subscribe({
+                next(env_data: ENV_DATA) {
+                  this.env = env_data;
 
-                this.http.get('/config/' + env_data.env + '.json')
-                    .map(res => res.json())
+                  this.http.get('/config/' + env_data.env + '.json')
                     .catch((error: any) => {
-                        return observableThrowError(error.json().error || 'Server error');
-                  })
-                  .subscribe((data) => {
-                      this.config = data;
-                      resolve(true);
-                  });
-            });
+                      return observableThrowError(error.json().error || 'Server error');
+                    })
+                    .subscribe({
+                      next(data) {
+                        this.config = data;
+                        resolve(true);
+                      }
+                    })
+                }
+              });
         });
     }
 
@@ -68,4 +82,4 @@ export class ConfigService {
     get(key: any) {
         return this.config[key];
     }
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/app.component.ts b/asterixdb/asterix-dashboard/src/node/src/app/app.component.ts
index 6cd84a9..bad2140 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/app.component.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/app.component.ts
@@ -27,4 +27,4 @@ export class AppComponent {
     title = 'Asterix DB Web Console';
 
     constructor() {}
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/app.module.ts b/asterixdb/asterix-dashboard/src/node/src/app/app.module.ts
index e49d8e2..94d9a05 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/app.module.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/app.module.ts
@@ -11,7 +11,7 @@ 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 { NgModule } from '@angular/core';
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
 import { AppComponent } from './app.component';
 import { AppEffects } from './shared/effects/app.effects';
 import { BrowserModule } from '@angular/platform-browser';
@@ -22,6 +22,7 @@ import { DataverseEffects } from './shared/effects/dataverse.effects';
 import { DatasetEffects } from './shared/effects/dataset.effects';
 import { DatatypeEffects } from './shared/effects/datatype.effects';
 import { IndexEffects } from './shared/effects/index.effects';
+import { FunctionEffects } from "./shared/effects/function.effects";
 import { SQLQueryEffects } from './shared/effects/query.effects';
 import { AppBarComponent }  from './dashboard/appbar.component';
 import { DialogMetadataInspector, MetadataComponent }  from './dashboard/query/metadata.component';
@@ -35,10 +36,12 @@ import { FormsModule } from '@angular/forms';
 import { MaterialModule } from './material.module';
 import { StoreModule,  } from '@ngrx/store';
 import { StoreDevtoolsModule } from '@ngrx/store-devtools';
-import { PlanViewComponent } from './dashboard/query/plan-view.component';
-import { PlanNodeSVGComponent } from './dashboard/query/plan-node-svg.component';
+import { NgxGraphModule } from "@swimlane/ngx-graph";
+import { NgxChartsModule } from "@swimlane/ngx-charts";
+import { PlanViewerComponent } from "./dashboard/query/plan-viewer.component";
 import { TreeNodeComponent } from './dashboard/query/tree-node.component';
-import { TreeViewComponent } from './dashboard/query/tree-view.component';
+import { DialogExportPicker, TreeViewComponent } from './dashboard/query/tree-view.component';
+import {SQLCancelEffects} from "./shared/effects/cancel.effects";
 
 @NgModule({
     declarations: [
@@ -50,27 +53,38 @@ import { TreeViewComponent } from './dashboard/query/tree-view.component';
         QueryContainerComponent,
         AppTabComponent,
         DialogMetadataInspector,
-        PlanNodeSVGComponent,
-        PlanViewComponent,
+        PlanViewerComponent,
         TreeNodeComponent,
+        DialogExportPicker,
         TreeViewComponent,
     ],
     imports: [
         FormsModule,
         BrowserModule,
         BrowserAnimationsModule,
-        EffectsModule.forRoot([AppEffects, DataverseEffects, DatasetEffects, DatatypeEffects, IndexEffects, SQLQueryEffects]),
+        EffectsModule.forRoot([AppEffects, DataverseEffects, DatasetEffects, DatatypeEffects, IndexEffects, FunctionEffects, SQLQueryEffects, SQLCancelEffects]),
         HttpClientModule,
         MaterialModule,
-        StoreModule.forRoot(reducers),
+        StoreModule.forRoot(reducers, {
+          runtimeChecks: {
+            strictStateImmutability: false,
+            strictActionImmutability: false,
+            strictStateSerializability: false,
+            strictActionSerializability: false,
+          },
+        }),
         StoreDevtoolsModule.instrument({
             maxAge: 10
-        })
+        }),
+        NgxGraphModule,
+        NgxChartsModule
     ],
+    schemas: [CUSTOM_ELEMENTS_SCHEMA],
     entryComponents: [
-        DialogMetadataInspector
+        DialogMetadataInspector,
+        DialogExportPicker,
     ],
     providers: [SQLService],
     bootstrap: [AppComponent]
 })
-export class AppModule {}
\ No newline at end of file
+export class AppModule {}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/appbar.component.html b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/appbar.component.html
index 45e3008..3c3d1f3 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/appbar.component.html
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/appbar.component.html
@@ -18,24 +18,21 @@ limitations under the License.
     <div class="flex-spacer"></div>
     <nav class="awc-navbar-header">
         <div class="menu">
-            <a mat-button class="menu awc-button awc-navbar-hide-small" href="https://asterixDB.apache.org" aria-label="WEBSITE">
+            <a mat-button class="menu awc-button awc-navbar-hide-small" href="https://asterixdb.apache.org/" target="_blank" aria-label="WEBSITE">
                 WEBSITE
             </a>
-            <a mat-button class="menu awc-button awc-navbar-hide-small" href="https://issues.apache.org/jira/browse/ASTERIXDB" aria-label="FILE ISSUES">
+            <a mat-button class="menu awc-button awc-navbar-hide-small" href="https://issues.apache.org/jira/browse/ASTERIXDB" target="_blank" aria-label="FILE ISSUES">
                 FILE ISSUES
             </a>
-            <a mat-button class="menu awc-button awc-navbar-hide-small" href="https://ci.apache.org/projects/asterixdb/index.html" aria-label="DOCUMENTATION">
+            <a mat-button class="menu awc-button awc-navbar-hide-small" href="https://ci.apache.org/projects/asterixdb/index.html" target="_blank" aria-label="DOCUMENTATION">
                 DOCUMENTATION
             </a>
-            <a mat-button class="menu awc-button docs-navbar-hide-small" href="https://asterixdb.apache.org/community.html" aria-label="CONTACT">
+            <a mat-button class="menu awc-button docs-navbar-hide-small" href="https://asterixdb.apache.org/community.html" target="_blank" aria-label="CONTACT">
                 CONTACT
             </a>
-            <a mat-button class="menu awc-button docs-navbar-hide-small" href="https://github.com/apache/asterixdb/" aria-label="GITHUB">
+            <a mat-button class="menu awc-button docs-navbar-hide-small" href="https://github.com/apache/asterixdb/" target="_blank" aria-label="GITHUB">
                 GITHUB
             </a>
-            <a mat-button class="menu awc-button docs-navbar-hide-small" aria-label="METADATA" (click)='showMetadata()'>
-                METADATA
-            </a>
         </div>
     </nav>
-</header>
\ No newline at end of file
+</header>
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/appbar.component.scss b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/appbar.component.scss
index 288bdb9..c16a172 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/appbar.component.scss
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/appbar.component.scss
@@ -61,7 +61,7 @@ limitations under the License.
 }
 
 .menu {
-    /deep/ .mat-tab-label {
+    >>> .mat-tab-label {
         font-size: 0.80rem !important;
         font-weight: 500 !important;
     }
@@ -71,4 +71,4 @@ limitations under the License.
     margin: 0;
     margin-right: 15px;
     margin-left: 5px;
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/appbar.component.ts b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/appbar.component.ts
index c399883..7538bf2 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/appbar.component.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/appbar.component.ts
@@ -23,12 +23,5 @@ import * as appActions from '../shared/actions/app.actions'
 })
 
 export class AppBarComponent {
-    sideMenuVisible = false;
-
     constructor(private store: Store <any> ) {}
-
-    showMetadata() {
-        this.sideMenuVisible = !this.sideMenuVisible;
-        this.store.dispatch(new appActions.setSideMenuVisible(this.sideMenuVisible));
-    }
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/apptab.component.scss b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/apptab.component.scss
index c14f327..1745fd6 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/apptab.component.scss
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/apptab.component.scss
@@ -29,10 +29,10 @@ limitations under the License.
 }
 
 .menu {
-    /deep/ .mat-tab-label {
+    >>> .mat-tab-label {
         font-size: 0.80rem !important;
         font-weight: 500 !important;
         color: white;
     }
     background-color: blue;
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/dialog-export-picker.html b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/dialog-export-picker.html
new file mode 100644
index 0000000..d12b18b
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/dialog-export-picker.html
@@ -0,0 +1,33 @@
+<!--/*
+Licensed 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="export-header">
+  <pre class="export-title">Choose Export Format:</pre>
+</div>
+<div class="button-row" *ngIf="data.exportFormat == 'json' || data.exportFormat == 'jsonl'">
+  <button mat-button class="pick-btn" (click)="data.exportFormat = 'json'">JSON</button>
+  <button mat-button class="pick-btn" (click)="data.exportFormat = 'jsonl'">JSONL</button>
+</div>
+<div class="filename-input">
+  <mat-form-field>
+    <mat-label>Filename:</mat-label>
+    <input matInput [(ngModel)]="data.fileName" value="asterix-query-results">
+  </mat-form-field>
+  <pre *ngIf="data.exportFormat == 'json'">.json</pre>
+  <pre *ngIf="data.exportFormat == 'jsonl'">.jsonl</pre>
+  <pre *ngIf="data.exportFormat == 'csv'">.csv</pre>
+</div>
+<div class="cancel-row">
+  <button mat-button class="cancel-btn btn" [mat-dialog-close]="['cancel', data.fileName]">CANCEL</button>
+  <button mat-button class="export-btn btn" [mat-dialog-close]="[data.exportFormat, data.fileName]">EXPORT</button>
+</div>
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/dialog-export-picker.scss b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/dialog-export-picker.scss
new file mode 100644
index 0000000..456b260
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/dialog-export-picker.scss
@@ -0,0 +1,53 @@
+/*
+Licensed 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-header {
+  border-bottom: 1px dashed gray;
+  text-align: center;
+}
+
+.export-title {
+  color: blue;
+}
+
+.button-row {
+  padding-top: 20px;
+  display: flex;
+  flex-flow: row;
+  justify-content: space-between;
+  border-bottom: 1px dashed gray;
+  padding-bottom: 20px;
+}
+
+.filename-input {
+  padding-top: 20px;
+  display: flex;
+  flex-flow: row;
+  border-bottom: 1px dashed gray;
+  padding-bottom: 20px;
+}
+
+.cancel-row {
+  display: flex;
+  flex-flow: row;
+}
+
+.cancel-btn {
+  margin-left: auto;
+}
+
+.pick-btn {
+  border: 2px solid blue;
+  border-radius: 5px;
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.html b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.html
index d410ff1..549615f 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.html
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.html
@@ -14,7 +14,7 @@ limitations under the License.
 <mat-expansion-panel class="card" hideToggle [expanded]="true">
     <mat-expansion-panel-header class="header" >
         <mat-panel-title>
-            <mat-panel-title>SQL++ INPUT ({{currentQuery+1}}/{{preparedQueryCount}})
+            <mat-panel-title>QUERY INPUT ({{currentQuery+1}}/{{preparedQueryCount}})
             </mat-panel-title>
             <mat-panel-description></mat-panel-description>
             <mat-spinner *ngIf="querySpinnerVisible" [color]="blue" [diameter]="15" class="spinner"></mat-spinner>
@@ -25,8 +25,7 @@ limitations under the License.
             <div class='dataverses'>
                 <div class='d1'>
                     <mat-form-field>
-                        <mat-select placeholder="USE DATAVERSE" [(ngModel)]="selected" (selectionChange)="dataverseSelected()">
-                        <mat-option value='None'>None</mat-option>
+                        <mat-select placeholder="Default" [(ngModel)]="selected" (selectionChange)="dataverseSelected()">
                         <mat-option *ngFor="let dataverse of dataverses" [value]="dataverse.DataverseName">
                             {{dataverse.DataverseName}}
                         </mat-option>
@@ -34,20 +33,35 @@ limitations under the License.
                     </mat-form-field>
                 </div>
                 <div class='d1'>
-                    <mat-form-field>
-                        <mat-select placeholder="PLAN FORMAT" [(ngModel)]="formatOptions">
+                    <mat-form-field class="plan-output-format">
+                        <mat-select id="plan-format" placeholder="PLAN FORMAT" [(ngModel)]="formatOptions">
                             <mat-option value="JSON">JSON</mat-option>
                             <mat-option value="STRING">STRING</mat-option>
                         </mat-select>
                     </mat-form-field>
                 </div>
+                <div class="d1">
+                  <mat-form-field class="plan-output-format">
+                    <mat-select class="mat-select output-format" placeholder="OUTPUT FORMAT" [(ngModel)]="outputOptions">
+                      <mat-option value="JSON">JSON</mat-option>
+                      <mat-option id="csv-option" value="CSV">CSV (no header)</mat-option>
+                      <mat-option value="CSV_header">CSV (header)</mat-option>
+                    </mat-select>
+                  </mat-form-field>
+                </div>
                 <div class='d1'>
                     <mat-form-field class='sql-history'>
-                        <mat-select placeholder="SQL++ HISTORY" [(ngModel)]="historyStringSelected" (selectionChange)="historySelected()">
-                        <mat-option *ngFor="let query of history" [value]="query">{{query}}</mat-option>
+                        <mat-select placeholder="QUERY HISTORY" [(ngModel)]="historyIdxSelected" (ngModelChange)="historySelected($event)">
+                        <mat-option *ngFor="let query of history" [value]="query.index">
+                          {{query.query}}
+                        </mat-option>
                         </mat-select>
                     </mat-form-field>
                 </div>
+                <div class="space">
+                  <button mat-icon-button class='input-button next-prev-btn' (click)="onClickNext()" [disabled]="checkNext()" matTooltip="Next Query Input"><mat-icon>keyboard_arrow_right</mat-icon></button>
+                  <button mat-icon-button class='input-button next-prev-btn' (click)="onClickPrevious()" [disabled]="checkPrevious()" matTooltip="Previous Query Input"><mat-icon>keyboard_arrow_left</mat-icon></button>
+                </div>
             </div>
         </div>
         <div class="codemirror-container">
@@ -55,15 +69,33 @@ limitations under the License.
         </div>
     </mat-panel-description>
     <div class="message">
-        <span *ngIf="querySuccess" class="metrics">{{metricsString}}</span>
+        <span *ngIf="querySuccess==true && queryWarningsShow==false" class="metrics">{{metricsString}}</span>
         <span *ngIf="queryError" class="queryErrorMessage">{{queryErrorMessageString}}</span>
+        <span *ngIf="querySuccess==true && queryWarningsShow==true" class="queryWarningMessage" matTooltip="{{queryWarningsMessages}}">{{metricsString}}</span>
+        <mat-divider></mat-divider>
+        <div class="obj-returned-div">
+          <span *ngIf="querySuccess" class="obj-returned-cnt">Objects Returned: {{objectsReturned}}</span>
+        </div>
+        <mat-divider *ngIf="querySuccess"></mat-divider>
+        <mat-expansion-panel [disabled]="queryWarningsCount == 0" class="tree-node mat-elevation-z0">
+          <mat-expansion-panel-header>
+            <div>
+              <b>WARNINGS({{queryWarningsCount}})</b>
+            </div>
+          </mat-expansion-panel-header>
+          <mat-panel-description class="content">
+            <div class="warning-msgs">
+              <pre *ngFor="let warning of queryWarningsMessages">{{warning}}</pre>
+            </div>
+          </mat-panel-description>
+        </mat-expansion-panel>
     </div>
     <mat-action-row>
+      <button mat-button class='input-button clear' (click)="onClickClear()" matTooltip="Clear Query Input">CLEAR</button>
         <div class="space"></div>
-        <button mat-button class='input-button' (click)="onClickNew()" matTooltip="New Query Input">NEW</button>
-        <button mat-button class='input-button' (click)="onClickClear()" matTooltip="Clear Query Input">CLEAR</button>
-        <button mat-button class='input-button run' (click)="onClickRun()" matTooltip="Execute Query Input">RUN</button>
-        <button mat-button class='input-button' (click)="onClickPrevious()" [disabled]="checkPrevious()" matTooltip="Previous Query Input">PREVIOUS</button>
-        <button mat-button class='input-button' (click)="onClickNext()" [disabled]="checkNext()" matTooltip="Next Query Input">NEXT</button>
+        <button mat-button class="input-button explain" (click)="onClickExplain()" matTooltip="Explain Query">EXPLAIN</button>
+        <!--<button mat-button class='input-button run' (click)="onClickRun()" matTooltip="Execute Query Input">EXECUTE</button>-->
+      <button mat-icon-button class='input-button stop' (click)="onClickStop()" [disabled]="!querySpinnerVisible" matTooltip="Cancel Query"><mat-icon>stop</mat-icon></button>
+      <button mat-icon-button class='input-button run' (click)="onClickRun()" matTooltip="Execute Query"><mat-icon>play_arrow</mat-icon></button>
     </mat-action-row>
-</mat-expansion-panel>
\ No newline at end of file
+</mat-expansion-panel>
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.scss b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.scss
index aa66fb9..94311f8 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.scss
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.scss
@@ -58,16 +58,30 @@ $query-spacing-unit: 5px;
     border: 1px dashed gainsboro;
 }
 
+#clear-history-btn {
+    color: black;
+}
+
 .input-button {
     font-size: 12px !important;
 }
 
+.next-prev-btn {
+  color: black;
+  float: right;
+}
+
+.next-prev-btn[disabled] {
+  color: rgba(0, 0, 0, 0.26);
+}
+
+
 .message {
     margin-top: 30px;
 }
 
 .metrics {
-    color: blue;
+    color: green;
     font-size: 14px !important;
     font-size: 1.0rem;
     font-weight: 500;
@@ -89,8 +103,39 @@ $query-spacing-unit: 5px;
     padding: 0;
 }
 
+.queryWarningMessage {
+  color: #ffae42;
+  font-size: 14px !important;
+  word-break: break-all;
+  font-size: 1.0rem;
+  font-weight: 500;
+  margin: 0;
+  padding: 0;
+}
+
+.obj-returned-cnt {
+  font-size: 14px !important;
+  word-break: break-all;
+  font-size: 1.0rem;
+  font-weight: 500;
+  margin: 0;
+}
+
+.obj-returned-div {
+  padding-top: 10px;
+  padding-bottom: 10px;
+}
+
 .run {
-    color: blue;
+    color: #00ab66;
+}
+
+.explain {
+  color: blue;
+}
+
+.stop {
+  color: #cf142b;
 }
 
 .dataverses {
@@ -103,6 +148,10 @@ $query-spacing-unit: 5px;
     font-weight: 500 !important;
 }
 
+.mat-action-row {
+    padding-right: 24px;
+}
+
 .d1 {
     margin-right: 15px;
 }
@@ -124,4 +173,24 @@ $query-spacing-unit: 5px;
 
 .sql-history {
     width: 500px !important;
-}
\ No newline at end of file
+}
+
+.plan-output-format {
+  width: 90px !important;
+}
+
+.content {
+    border-style: dotted;
+    border-width: 1px;
+    border-color: black;
+}
+
+.warning-msgs {
+    overflow: scroll;
+    white-space: initial;
+}
+
+.error-line {
+  background-color: red;
+  color: red;
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.ts b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.ts
index 7702db7..582b2f2 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.ts
@@ -1,35 +1,43 @@
-/*
-Licensed 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, ViewChild } from '@angular/core';
-import { Observable } from 'rxjs';
-import { Store } from '@ngrx/store';
-import * as sqlQueryActions from '../../shared/actions/query.actions';
-import * as appActions from '../../shared/actions/app.actions'
-import * as dataverseActions from '../../shared/actions/dataverse.actions'
-import * as CodeMirror from 'codemirror';
-/*
- * Query component
- * has editor (codemirror)
- */
-@Component({
+  /*
+  Licensed 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, EventEmitter, Output, ViewChild} from '@angular/core';
+  import { Observable } from 'rxjs';
+  import { Store } from '@ngrx/store';
+  import * as sqlQueryActions from '../../shared/actions/query.actions';
+  import * as sqlCancelActions from '../../shared/actions/cancel.actions';
+  import * as appActions from '../../shared/actions/app.actions'
+  import * as dataverseActions from '../../shared/actions/dataverse.actions'
+  import * as CodeMirror from 'codemirror';
+  import 'codemirror/addon/edit/closebrackets';
+  import 'codemirror/mode/sql/sql';
+  import 'codemirror/addon/hint/show-hint';
+  import 'codemirror/addon/hint/sql-hint';
+
+  /*
+   * Query component
+   * has editor (codemirror)
+   */
+  @Component({
     moduleId: module.id,
     selector: 'awc-query',
     templateUrl: 'input.component.html',
     styleUrls: ['input.component.scss']
-})
+  })
+
+  export class InputQueryComponent {
+    @Output() inputToOutputEmitter: EventEmitter<Object> = new EventEmitter<Object>();
+    @Output() isErrorEmitter: EventEmitter<Boolean> = new EventEmitter<Boolean>();
+    @Output() hideOutputEmitter: EventEmitter<Boolean> = new EventEmitter<Boolean>();
 
-export class InputQueryComponent {
     currentQuery = 0;
     queryString: string = "";
     metricsString: {};
@@ -42,6 +50,12 @@ export class InputQueryComponent {
     queryMetrics: {};
     querySuccess$: Observable <any> ;
     querySuccess: Boolean = false;
+    querySuccessWarnings: Boolean = false;
+    queryWarnings: any;
+    queryWarnings$: Observable<any> ;
+    queryWarningsMessages: string[] = [];
+    queryWarningsCount = 0;
+    queryWarningsShow: Boolean = false;
     queryError$: Observable <any> ;
     queryError: Boolean = false;
     queryErrorMessage$: Observable <any> ;
@@ -50,6 +64,8 @@ export class InputQueryComponent {
     queryPrepared: {};
     queryPlanFormats$: Observable <any> ;
     queryPlanFormats: {};
+    cancelQuery$: Observable<any>;
+    isCanceled: boolean = false;
     preparedQueryCount: number;
     previousDisabled = true;
     nextDisabled = true;
@@ -58,7 +74,7 @@ export class InputQueryComponent {
     dataverses$: Observable<any>;
     dataverses: any;
     defaultDataverse = 'Default';
-    selected = 'None';
+    selected = 'Default';
     history = [];
     currentHistory = 0;
     viewCurrentHistory = 0; // for the view
@@ -67,283 +83,404 @@ export class InputQueryComponent {
     none = 'None';
     planFormat = 'JSON';
     historyStringSelected = '';
+    historyIdxSelected = 0;
     formatOptions = 'JSON';
+    outputOptions = 'JSON';
+    explainMode = false;
+    inputToOutput = {};
+    objectsReturned = 0;
     /* Codemirror configuration */
-    codemirrorConfig = {
-    mode: "asterix",
-    lineWrapping: true,
-    showCursorWhenSelecting: true,
-    autofocus: true,
-    lineNumbers: true,
-    };
+    //sql++ keywords
+    sqlppKeywords = "alter and as asc between by count create delete desc distinct drop from group having in insert into " +
+      "is join like not on or order select set union update values where limit use let dataverse dataset exists with index type" +
+      "inner outer offset value type if exists declare function";
+
+    //sql++ builtin types
+    sqlppTypes = "boolean tinyint smallint integer int bigint string float double binary point line rectangle circle polygon" +
+      "uuid object array multiset date time datetime duration year_month_duration day_time_duration interval"
 
     constructor(private store: Store < any > ) {
-        this.currentQuery = 0;
-        this.querySuccess$ = this.store.select(s => s.sqlQuery.successHash);
-        this.querySuccess$.subscribe((data: any) => {
-            this.querySuccesResults = data;
-            this.querySpinnerVisible = false;
-            if (data != undefined && data[this.currentQuery] === true) {
-                this.querySuccess = true;
-            } else {
-                this.querySuccess = false;
-            }
-        })
-
-        /* Watching for SQL Input Errors in current Query */
-        this.queryError$ = this.store.select(s => s.sqlQuery.errorHash);
-        this.queryError$.subscribe((data: any) => {
-            this.querySpinnerVisible = false;
-            if (data != undefined && data[this.currentQuery] === true) {
-                this.queryError = true;
-                this.showErrors();
-            } else {
-                this.queryError = false;
-            }
-        })
-
-        /* Watching for Queries that are in prepared state,
-        * those are SQL strings that still has not been executed
-        */
-        this.queryPrepared$ = this.store.select(s => s.sqlQuery.sqlQueryPrepared);
-        this.queryPrepared$.subscribe((data: any) => {
-            if (data) {
-                this.queryPrepared = data
-                this.preparedQueryCount = Object.keys(this.queryPrepared).length;
-                if (this.preparedQueryCount == 0) {
-                    // Initialize Query Editor, prepare the default query
-                    this.queryPrepare = {
-                        editorId: String(this.currentQuery),
-                        queryString: this.queryString,
-                        planFormat: this.planFormat
-                    };
-                    this.store.dispatch(new sqlQueryActions.PrepareQuery(this.queryPrepare));
-                } else {
-                    if (this.queryPrepared && this.queryPrepared[this.currentQuery]) {
-                        this.queryString = this.queryPrepared[this.currentQuery];
-                    }
-                }
-            } else {
-                this.queryPrepared = {};
-            }
-        })
-
-        /* Watching for Metrics */
-        this.queryMetrics$ = this.store.select(s => s.sqlQuery.sqlQueryMetrics);
-        this.queryMetrics$.subscribe((data: any) => {
-            if (data != undefined) {
-                this.queryMetrics = Object.assign(data);
-                if (this.queryMetrics && this.queryMetrics[this.currentQuery]) {
-                    this.metricsString = "SUCCESS: ";
-                    this.metricsString += " Execution time: " + this.queryMetrics[this.currentQuery].executionTime;
-                    this.metricsString += " Elapsed time: " + this.queryMetrics[this.currentQuery].elapsedTime;
-                    this.metricsString += " Size: " + (this.queryMetrics[this.currentQuery].resultSize/1024).toFixed(2) + ' Kb';
-                }
-            } else {
-                this.queryMetrics = {};
-            }
-        })
-
-        /* Watching for SQL Input Errors: Error Message stored in Query Cache */
-        this.queryErrorMessage$ = this.store.select(s => s.sqlQuery.sqlQueryErrorHash);
-        this.queryErrorMessage$.subscribe((data: any) => {
-            if (data) {
-                this.queryErrorMessages = data;
-                this.showErrors();
-            } else {
-                this.queryErrorMessages = {};
-            }
-        })
-
-        /* Watching for SQL Input Errors: Error Message stored in Query Cache */
-        this.queryPlanFormats$ = this.store.select(s => s.sqlQuery.sqlQueryPlanFormatHash);
-        this.queryPlanFormats$.subscribe((data: any) => {
-            if (data) {
-                this.queryPlanFormats = data;
-            } else {
-                this.queryPlanFormats = {};
+      this.currentQuery = 0;
+      this.querySuccess$ = this.store.select(s => s.sqlQuery.successHash);
+      this.querySuccess$.subscribe((data: any) => {
+        this.isCanceled = false;
+        this.querySuccesResults = data;
+        this.querySpinnerVisible = false;
+        if (data != undefined && data[this.currentQuery] === true) {
+          this.querySuccess = true;
+        } else {
+          this.querySuccess = false;
+        }
+      })
+
+      /* Watching for SQL Input Errors in current Query */
+      this.queryError$ = this.store.select(s => s.sqlQuery.errorHash);
+      this.queryError$.subscribe((data: any) => {
+        this.querySpinnerVisible = false;
+        if (data != undefined && data[this.currentQuery] === true) {
+          this.queryError = true;
+          this.showErrors();
+        } else {
+          this.queryError = false;
+        }
+      })
+
+      /* Watching for Queries that are in prepared state,
+      * those are SQL strings that still has not been executed
+      */
+      this.queryPrepared$ = this.store.select(s => s.sqlQuery.sqlQueryPrepared);
+      this.queryPrepared$.subscribe((data: any) => {
+        if (data) {
+          this.queryPrepared = data;
+          this.preparedQueryCount = Object.keys(this.queryPrepared).length;
+
+          if (this.queryPrepared && this.queryPrepared[this.currentQuery]) {
+            this.queryString = this.queryPrepared[this.currentQuery];
+          }
+        } else {
+          this.queryPrepared = {};
+        }
+      })
+
+      /* Watching for Metrics */
+      this.queryMetrics$ = this.store.select(s => s.sqlQuery.sqlQueryMetrics);
+      this.queryMetrics$.subscribe((data: any) => {
+        if (data != undefined) {
+          this.queryMetrics = Object.assign(data);
+          if (this.queryMetrics && this.queryMetrics[this.currentQuery]) {
+            this.objectsReturned = this.queryMetrics[this.currentQuery].resultCount;
+            this.isErrorEmitter.emit(false);
+            this.hideOutputEmitter.emit(false);
+            this.metricsString = "SUCCESS: ";
+
+            if ('warningCount' in this.queryMetrics[this.currentQuery]) {
+                this.metricsString += " (WITH " + this.queryMetrics[this.currentQuery].warningCount + " WARNINGS)";
             }
-        })
-
-        this.preparedQueryCount = 0;
-        // Initialize Query Editor, prepare the default query
-        this.queryPrepare = {
-            editorId: String(this.currentQuery),
-            queryString: this.queryString,
-            planFormat: this.formatOptions
-        };
-        this.store.dispatch(new sqlQueryActions.PrepareQuery(this.queryPrepare));
-        // lets inform other views what's the current SQL editor
-        this.store.dispatch(new appActions.setEditorIndex(String(this.currentQuery)));
+
+            this.metricsString += " Execution time: " + this.queryMetrics[this.currentQuery].executionTime;
+            this.metricsString += " Elapsed time: " + this.queryMetrics[this.currentQuery].elapsedTime;
+            this.metricsString += " Size: " + (this.queryMetrics[this.currentQuery].resultSize/1024).toFixed(2) + ' Kb';
+          }
+        } else {
+          this.queryMetrics = {};
+          this.objectsReturned = 0;
+        }
+      })
+
+      /* Watching for SQL Input Errors: Error Message stored in Query Cache */
+      this.queryErrorMessage$ = this.store.select(s => s.sqlQuery.sqlQueryErrorHash);
+      this.queryErrorMessage$.subscribe((data: any) => {
+        if (data) {
+          this.queryErrorMessages = data;
+          this.showErrors();
+        } else {
+          this.queryErrorMessages = {};
+        }
+      })
+
+      /* Watching for SQL Input Warnings in current Query*/
+      this.queryWarnings$ = this.store.select(s => s.sqlQuery.sqlQueryWarnings);
+      this.queryWarnings$.subscribe((data: any) => {
+        if (data) {
+          this.queryWarnings = data;
+          this.showWarnings();
+        } else {
+          this.queryWarnings = {};
+        }
+      })
+
+      /*
+      * Watching for SQL Cancel Query
+       */
+      this.cancelQuery$ = this.store.select(s => s.cancelQuery.success);
+      this.cancelQuery$.subscribe((data: any) => {
+        if (data) {
+          this.querySpinnerVisible = false;
+          this.isCanceled = true;
+        }
+      })
+
+      /* Watching for SQL Input Errors: Error Message stored in Query Cache */
+      this.queryPlanFormats$ = this.store.select(s => s.sqlQuery.sqlQueryPlanFormatHash);
+      this.queryPlanFormats$.subscribe((data: any) => {
+        if (data) {
+          this.queryPlanFormats = data;
+        } else {
+          this.queryPlanFormats = {};
+        }
+      })
+
+      this.preparedQueryCount = 0;
+      // Initialize Query Editor, prepare the default query
+      this.saveQuery(String(this.currentQuery), this.queryString, this.formatOptions, "JSON");
+
+      // lets inform other views what's the current SQL editor
+      this.store.dispatch(new appActions.setEditorIndex(String(this.currentQuery)));
     }
 
     ngOnInit() {
-        this.dataverses$ = this.store.select(s => s.dataverse.dataverses.results);
-        // Watching for Dataverses
-        this.dataverses$ = this.store.select(s => s.dataverse.dataverses.results);
-        this.dataverses$.subscribe((data: any[]) => {
-            this.dataverses = data;
-            this.defaultDataverse = ''
-        });
-        this.store.dispatch(new dataverseActions.SelectDataverses('-'), );
+      this.dataverses$ = this.store.select(s => s.dataverse.dataverses.results);
+      // Watching for Dataverses
+      this.dataverses$ = this.store.select(s => s.dataverse.dataverses.results);
+      this.dataverses$.subscribe((data: any[]) => {
+        this.dataverses = data;
+        this.defaultDataverse = ''
+      });
+      this.store.dispatch(new dataverseActions.SelectDataverses('-'), );
     }
 
     showMetrics() {
-        this.querySuccess = false;
-        if (this.queryMetrics && this.queryMetrics[this.currentQuery] && this.querySuccesResults[this.currentQuery]) {
-            this.metricsString = "SUCCESS: ";
-            this.metricsString += " Execution time: " + this.queryMetrics[this.currentQuery].executionTime;
-            this.metricsString += " Elapsed time: " + this.queryMetrics[this.currentQuery].elapsedTime;
-            this.metricsString += " Size: " + (this.queryMetrics[this.currentQuery].resultSize/1024).toFixed(2) + ' Kb';
-            this.querySuccess = true;
+      this.querySuccess = false;
+      if (this.queryMetrics && this.queryMetrics[this.currentQuery] && this.querySuccesResults[this.currentQuery]) {
+        this.objectsReturned = this.queryMetrics[this.currentQuery].resultCount;
+        this.metricsString = "SUCCESS: ";
+
+        if ('warningCount' in this.queryMetrics[this.currentQuery]) {
+            this.metricsString += " [WITH " + this.queryMetrics[this.currentQuery].warningCount + " WARNING(S)]";
+        }
+
+        this.metricsString += " Execution time: " + this.queryMetrics[this.currentQuery].executionTime;
+        this.metricsString += " Elapsed time: " + this.queryMetrics[this.currentQuery].elapsedTime;
+        this.metricsString += " Size: " + (this.queryMetrics[this.currentQuery].resultSize/1024).toFixed(2) + ' Kb';
+
+        this.querySuccess = true;
+      }
+    }
+
+    showWarnings() {
+      this.queryWarningsShow = false;
+      if (this.queryWarnings && this.queryWarnings[this.currentQuery]) {
+        let warningObject = this.queryWarnings[this.currentQuery];
+
+        this.queryWarningsMessages = [];
+        this.queryWarningsCount = warningObject.length;
+        if (warningObject.length != 0) {
+          for (let warning of warningObject.reverse()) {
+              let warningString = "WARNING: Code: " + JSON.stringify(warning.code, null, 8);
+              warningString += " " + JSON.stringify(warning.msg, null, 8);
+
+              this.queryWarningsMessages.push(warningString);
+          }
+
+          this.queryWarningsShow = true;
         }
+      }
     }
 
     showErrors() {
-        this.queryError = false;
-        if (this.queryErrorMessages && this.queryErrorMessages[this.currentQuery]) {
-            let errorObject = this.queryErrorMessages[this.currentQuery];
-            if (errorObject.length != 0) {
-            this.queryErrorMessageString = "ERROR: Code: " + JSON.stringify(errorObject[0].code, null, 8);
-            this.queryErrorMessageString += " " + JSON.stringify(errorObject[0].msg, null, 8);
-            this.queryError = true;
-            }
+      this.queryError = false;
+      if (this.queryErrorMessages && this.queryErrorMessages[this.currentQuery]) {
+        let errorObject = this.queryErrorMessages[this.currentQuery];
+        if (errorObject.length != 0) {
+          this.queryErrorMessageString = "ERROR: Code: " + JSON.stringify(errorObject[0].code, null, 8);
+          this.queryErrorMessageString += " " + JSON.stringify(errorObject[0].msg, null, 8);
+          this.queryError = true;
+
+          this.isErrorEmitter.emit(true);
         }
+      }
+    }
+
+    getQueryResults(queryString: string, planFormat: string, outputFormat: string) {
+      let QueryOrder = this.currentQuery;
+      this.queryRequest = {
+        requestId: String(QueryOrder),
+        queryString: queryString,
+        planFormat: planFormat,
+        format: outputFormat
+      };
+      this.store.dispatch(new sqlQueryActions.ExecuteQuery(this.queryRequest));
+      this.querySpinnerVisible = true;
     }
 
-    getQueryResults(queryString: string, planFormat: string) {
-        let QueryOrder = this.currentQuery;
-        this.queryRequest = {
-            requestId: String(QueryOrder),
-            queryString: queryString,
-            planFormat: planFormat
-        };
-        this.store.dispatch(new sqlQueryActions.ExecuteQuery(this.queryRequest));
-        this.querySpinnerVisible = true;
+    onClickExplain() {
+      let use_regex = /use .*?;/i
+
+      let explainString = "";
+
+      if (use_regex.test(this.queryString))
+        explainString = this.queryString.replace(use_regex, "$& explain ");
+      else
+        explainString = "explain " + this.queryString;
+
+      this.runQuery(explainString, true);
+
+      this.explainMode = true;
+      this.sendInputToOutput();
     }
 
     onClickRun() {
-        let planFormat = this.formatOptions;
-        this.getQueryResults(this.queryString, planFormat); // .replace(/\n/g, " "));
-        if (this.history.length === 0) {
-            this.history.push('Clear');
-        }
-        this.history.push(this.queryString);
-        this.currentHistory = this.history.length - 1;
-        this.viewCurrentHistory = this.history.length;
+      this.explainMode = false;
+      this.sendInputToOutput();
+
+      this.runQuery(this.queryString, this.explainMode);
     }
 
-    onClickNew() {
-        // Saving first
-        this.queryPrepare = {
-            editorId: String(this.currentQuery),
-            queryString: this.queryString,
-            planFormat: this.formatOptions
-        };
-        this.store.dispatch(new sqlQueryActions.PrepareQuery(this.queryPrepare));
-        // Prepare a new Query String, cleanup screen messages
-        this.currentQuery = Object.keys(this.queryPrepared).length;
-        this.queryString = "";
-        this.editor.getDoc().setValue(this.queryString);
-        this.queryErrorMessageString = "";
-        this.metricsString = "";
-        this.querySuccess = false;
-        this.queryError = false;
-        this.queryPrepare = {
-            editorId: String(this.currentQuery),
-            queryString: "",
-            planFormat: this.formatOptions
-        };
-        this.store.dispatch(new sqlQueryActions.PrepareQuery(this.queryPrepare));
-        // lets inform other views what's the current SQL editor
+    runQuery(stringToRun: string, isExplain: boolean) {
+      this.autodetectDataverse();
+
+      if (this.queryString != this.queryPrepared[Object.keys(this.queryPrepared).length - 1]) {
+        //not the same query as before, currentQuery needs to be incremented and another needs to be dispatched
+        //increment currentQuery
+        if (this.queryPrepared[Object.keys(this.queryPrepared).length - 1] != '')
+          this.currentQuery = Object.keys(this.queryPrepared).length;
+
+        this.saveQuery(String(this.currentQuery), this.queryString, this.formatOptions, this.outputOptions);
+
+        this.history.unshift({query: this.queryString, index: this.currentQuery});
+
+        //this.currentHistory = this.history.length - 1;
+        //this.viewCurrentHistory = this.history.length;
+
+        //display
         let currentQueryIndex = String(this.currentQuery);
         this.store.dispatch(new appActions.setEditorIndex(currentQueryIndex));
-        this.selected = "None";
         this.editor.focus();
+
+      } else {
+        //the same query as before, currentQuery is not incremented
+        //save the current Query
+        this.saveQuery(String(this.currentQuery), this.queryString, this.formatOptions, this.outputOptions);
+      }
+
+      let planFormat = this.formatOptions;
+      let outputFormat = this.outputOptions;
+      this.historyIdxSelected = this.currentQuery;
+
+      this.getQueryResults(stringToRun, planFormat, outputFormat); // .replace(/\n/g, " "));
+    }
+
+    onClickStop() {
+      let cancel_request = {
+        requestId: String(this.currentQuery)
+      }
+
+      this.store.dispatch(new sqlCancelActions.CancelQuery(cancel_request));
+    }
+
+    onClickNew() {
+      // Saving first
+      this.saveQuery(String(this.currentQuery), this.queryString, this.formatOptions, this.outputOptions);
+
+      //let output section know to hide
+      this.hideOutputEmitter.emit(true);
+
+      this.historyIdxSelected = -1;
+
+      // Prepare a new Query String, cleanup screen messages
+      this.currentQuery = Object.keys(this.queryPrepared).length;
+      this.queryString = "";
+      this.editor.getDoc().setValue(this.queryString);
+      this.queryErrorMessageString = "";
+      this.metricsString = "";
+      this.querySuccess = false;
+      this.queryError = false;
+      this.queryWarningsShow = false;
+
+      this.saveQuery(String(this.currentQuery), "", this.formatOptions, this.outputOptions);
+      // lets inform other views what's the current SQL editor
+      let currentQueryIndex = String(this.currentQuery);
+      this.store.dispatch(new appActions.setEditorIndex(currentQueryIndex));
+      this.selected = "Default";
+      this.editor.focus();
     }
 
     onClickClear() {
-        let queryClear = {
-            editorId: String(this.currentQuery),
-            queryString: "",
-            planFormat: "JSON"
-        };
-        this.store.dispatch(new sqlQueryActions.CleanQuery(queryClear));
-        this.queryErrorMessageString = "";
-        this.queryString = "";
-        this.metricsString = "";
-        this.dataverseSelected();
-        this.editor.getDoc().setValue(this.queryString);
-        this.editor.focus();
+      let queryClear = {
+        editorId: String(this.currentQuery),
+        queryString: "",
+        planFormat: "JSON"
+      };
+      this.store.dispatch(new sqlQueryActions.CleanQuery(queryClear));
+      this.queryErrorMessageString = "";
+      this.queryString = "";
+      this.metricsString = "";
+      this.queryWarningsCount = 0;
+      this.queryWarningsMessages = [];
+
+      this.dataverseSelected();
+      this.editor.getDoc().setValue(this.queryString);
+      this.editor.execCommand('goDocEnd')
+      this.editor.focus();
+
+      //hide output on clear
+      this.hideOutputEmitter.emit(true);
     }
 
     onClickPrevious() {
-        if (this.currentQuery > 0) {
-            this.nextSQLEditor(-1);
-        }
+      if (this.currentQuery > 0) {
+        this.nextSQLEditor(this.currentQuery - 1);
+      }
     }
 
     onClickNext() {
-        if (this.currentQuery < this.preparedQueryCount - 1) {
-            this.nextSQLEditor(1);
+      if (this.currentQuery < this.preparedQueryCount - 1) {
+        this.nextSQLEditor(this.currentQuery + 1);
+      }
+      else {
+        if (this.queryString != '') {
+            this.onClickNew();
         }
+      }
     }
 
     checkNext() {
-        if (this.currentQuery == this.preparedQueryCount - 1) {
-          return true;
-        } else {
-          return false;
-        }
+      if (this.currentQuery == this.preparedQueryCount) {
+        return true;
+      } else {
+        return false;
+      }
     }
 
     checkPrevious() {
-        if (this.currentQuery == 0) {
-            return true;
-        } else {
-            return false;
-        }
+      if (this.currentQuery == 0) {
+        return true;
+      } else {
+        return false;
+      }
     }
 
     nextSQLEditor(next) {
-        // Saving First
-        this.queryPrepare = {
-            editorId: String(this.currentQuery),
-            queryString: this.queryString,
-            planFormat: this.formatOptions
-        };
-        this.store.dispatch(new sqlQueryActions.PrepareQuery(this.queryPrepare));
-        this.currentQuery = this.currentQuery + next;
-        this.queryErrorMessageString = "";
-        this.metricsString = "";
-
-        // Retrieve Metrics or Error Message if Query was executed
-        this.showMetrics();
-
-        // Retrieve Metrics or Error Message if Query was executed
-        this.showErrors();
-
-        // Retrieve the prepared SQL string
-        this.queryString = this.queryPrepared[this.currentQuery];
-        this.editor.getDoc().setValue(this.queryString);
+      // Saving First
+      this.saveQuery(String(this.currentQuery), this.queryString, this.formatOptions, this.outputOptions);
 
-        // Retrieve the prepared SQL plan Format
-        this.formatOptions = this.queryPlanFormats[this.currentQuery];
+      //this.currentQuery = this.currentQuery + next;
+      this.currentQuery = next;
+      this.queryErrorMessageString = "";
+      this.metricsString = "";
 
-        // lets inform other views what's the current SQL editor
-        let currentQueryIndex = String(this.currentQuery);
+      // Retrieve Metrics or Error Message if Query was executed
+      this.showMetrics();
 
-        // Inform the app we are now active in next editor
-        this.store.dispatch(new appActions.setEditorIndex(currentQueryIndex));
+      // Retrieve Metrics or Error Message if Query was executed
+      this.showErrors();
+
+      // Retrieve the prepared SQL string
+      this.queryString = this.queryPrepared[this.currentQuery];
+      this.editor.getDoc().setValue(this.queryString);
+
+      // Select the query from the QUERY History
+      this.historyIdxSelected = this.currentQuery;
+
+      this.autodetectDataverse();
+
+      // Retrieve the prepared SQL plan Format
+      this.formatOptions = this.queryPlanFormats[this.currentQuery];
+
+      // lets inform other views what's the current SQL editor
+      let currentQueryIndex = String(this.currentQuery);
+
+      // Inform the app we are now active in next editor
+      this.store.dispatch(new appActions.setEditorIndex(currentQueryIndex));
     }
 
     onClickInputCardCollapse() {
-        this.collapse = !this.collapse;
-        if (this.collapse) {
-            this.input_expanded_icon = 'expand_more';
-        } else {
-            this.input_expanded_icon = 'expand_less';
-        }
+      this.collapse = !this.collapse;
+      if (this.collapse) {
+        this.input_expanded_icon = 'expand_more';
+      } else {
+        this.input_expanded_icon = 'expand_less';
+      }
     }
 
     /**
@@ -351,65 +488,141 @@ export class InputQueryComponent {
      */
     @ViewChild('editor') editor: CodeMirror.Editor;
     ngAfterViewInit() {
-        this.codemirrorInit(this.codemirrorConfig);
+      this.codemirrorInit();
     }
 
     /**
      * Initialize codemirror
      */
-    codemirrorInit(config) {
-        this.editor = CodeMirror.fromTextArea(this.editor.nativeElement, config);
-        this.editor.setSize(null, 'auto');
-        this.editor.getDoc().setValue(this.queryString);
-        this.editor.on('changes', () => {
-            this.queryString = this.editor.getValue();
-        });
+    codemirrorInit() {
+      this.editor = CodeMirror.fromTextArea(this.editor.nativeElement, {
+        mode: {
+          name: "sql",
+          keywords: this.set(this.sqlppKeywords),
+          builtin: this.set(this.sqlppTypes),
+          atoms: this.set("true false null missing"),
+          dateSQL: this.set("date time datetime duration year_month_duration day_time_duration interval"),
+          support: this.set("ODBCdotTable doubleQuote binaryNumber hexNumber commentSlashSlash")
+        },
+        lineWrapping: true,
+        showCursorWhenSelecting: true,
+        autofocus: true,
+        lineNumbers: true,
+        autoCloseBrackets: {
+          pairs: "()[]{}''\"\"``",
+          closeBefore: ")]}'\":;>",
+          triples: "",
+          explode: "[]{}()",
+          override: true
+        },
+        extraKeys: {"Ctrl-Space": "autocomplete"},
+        hint: CodeMirror.hint.sql,
+      });
+      this.editor.setSize(null, 'auto');
+      this.editor.getDoc().setValue(this.queryString);
+      this.editor.on('changes', () => {
+        this.queryString = this.editor.getValue();
+      });
     }
 
     dataverseSelected() {
-        if (this.selected == undefined) {
-            this.queryString = 'None';
-        } else if (this.selected === 'None') {
-            this.queryString = '';
-            this.selected = 'None';
-        } else {
-            this.queryString = 'Use ' + this.selected + '; ';
-        }
+      if (this.selected == undefined) {
+        this.queryString = 'None';
+      } else if (this.selected === 'None' || this.selected === 'Default') {
+        this.queryString = '';
+        this.selected = 'Default';
+      } else {
+        this.queryString = 'USE ' + this.selected + '; \n';
+      }
+      this.editor.getDoc().setValue(this.queryString);
+      this.editor.execCommand('goDocEnd')
+      this.editor.focus();
+    }
+
+    historySelected(idx: number) {
+      if (this.historyStringSelected == undefined) {
+        this.historyStringSelected = '';
+      } else if (this.historyStringSelected === 'Clear') {
+        this.history = [];
+        this.historyStringSelected = '';
+      }
+
+      this.nextSQLEditor(idx);
+    }
+
+    planFormatSelected() {}
+
+    onClickNextHistory() {
+      if (this.currentHistory < this.history.length - 1) {
+        this.currentHistory++;
+        this.viewCurrentHistory++;
+        this.queryString = this.history[this.currentHistory];
         this.editor.getDoc().setValue(this.queryString);
         this.editor.focus();
+      }
     }
 
-    historySelected() {
-        if (this.historyStringSelected == undefined) {
-            this.historyStringSelected = '';
-        } else if (this.historyStringSelected === 'Clear') {
-            this.history = [];
-            this.historyStringSelected = '';
-        }
-        this.queryString = this.historyStringSelected;
+    onClickPrevHistory() {
+      if (this.currentHistory > 0) {
+        this.currentHistory--;
+        this.viewCurrentHistory--;
+        this.queryString = this.history[this.currentHistory];
         this.editor.getDoc().setValue(this.queryString);
         this.editor.focus();
+      }
     }
 
-    planFormatSelected() {}
+    saveQuery(editorId, queryString, planFormat, outputFormat) {
+      this.queryPrepare = {
+        editorId: String(editorId),
+        queryString: queryString,
+        planFormat: planFormat,
+        format: outputFormat
+      };
+      this.store.dispatch(new sqlQueryActions.PrepareQuery(this.queryPrepare));
+    }
 
-    onClickNextHistory() {
-        if (this.currentHistory < this.history.length - 1) {
-          this.currentHistory++;
-          this.viewCurrentHistory++;
-          this.queryString = this.history[this.currentHistory];
-          this.editor.getDoc().setValue(this.queryString);
-          this.editor.focus();
-        }
+    sendInputToOutput() {
+      this.inputToOutput['isExplain'] = this.explainMode;
+      this.inputToOutput['outputFormat'] = this.outputOptions;
+
+      this.inputToOutputEmitter.emit(this.inputToOutput);
     }
 
-    onClickPrevHistory() {
-        if (this.currentHistory > 0) {
-            this.currentHistory--;
-            this.viewCurrentHistory--;
-            this.queryString = this.history[this.currentHistory];
-            this.editor.getDoc().setValue(this.queryString);
-            this.editor.focus();
+    autodetectDataverse() {
+      let dataverseRegex = /use (.*?);/i
+
+      let matches = this.queryString.match(dataverseRegex);
+
+      let detectedDataverse = "";
+
+      if (matches) {
+        if (matches.length == 2) {
+          detectedDataverse = matches[1];
         }
+      }
+
+      if (detectedDataverse != "") {
+        let dataverseNames = this.dataverses.map(function (e) {
+          return e.DataverseName
+        });
+
+        if (dataverseNames.includes(detectedDataverse)) {
+          this.selected = detectedDataverse;
+        } else {
+          this.selected = "Default";
+        }
+      } else {
+        this.selected = "Default";
+      }
+    }
+
+    set(str: string) {
+      let obj = {};
+      let words = str.split(" ");
+      for (let i = 0; i < words.length; i++) {
+        obj[words[i]] = true;
+      }
+      return obj;
     }
-}
\ No newline at end of file
+  }
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata-inspector.component.html b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata-inspector.component.html
index e8869ca..6810a41 100644
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata-inspector.component.html
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata-inspector.component.html
@@ -11,12 +11,109 @@ 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="inspector-dialog">
-    <p mat-dialog-title class="header">METADATA INSPECTOR</p>
-    <mat-dialog-content>
-        <pre class="content">{{data}}</pre>
+<div class="inspector-dialog" cdkDrag cdkDragRootElement=".cdk-overlay-pane">
+    <div class="top-row" cdkDragHandle>
+      <p mat-dialog-title class="header"  *ngIf="data.MetadataType === 'dataset'">DATASET: {{data.DatasetName}}</p>
+      <p mat-dialog-title class="header"  *ngIf="data.MetadataType === 'datatype'">DATATYPE: {{data.DatatypeName}}</p>
+      <p mat-dialog-title class="header"  *ngIf="data.MetadataType === 'index'">INDEX: {{data.IndexName}}</p>
+      <p mat-dialog-title class="header"  *ngIf="data.MetadataType === 'function'">FUNCTION: {{data.Name}}</p>
+      <button mat-icon-button class='input-button' (click)="onClickClose()" matTooltip="Close Inspector Window"><mat-icon>close</mat-icon></button>
+    </div>
+    <mat-dialog-content class="metadata-inspector" *ngIf="showGuts == false">
+        <div *ngIf="data.MetadataType === 'dataset'">
+            <pre class="content"><b>Dataverse: </b>{{data.DataverseName}}</pre>
+            <pre class="content"><b>Dataset: </b>{{data.DatasetName}}</pre>
+            <pre class="content"><b>Datatype Name: </b>{{data.DatatypeName}}</pre>
+            <div *ngIf="data.InternalDetails.PrimaryKey.length > 0">
+              <pre class="list-title content"><b>Primary Keys:</b></pre>
+              <li class="content" *ngFor="let pkey of data.InternalDetails.PrimaryKey">{{pkey}}</li>
+            </div>
+            <pre class="content"><b>Sample:</b></pre>
+            <mat-card class="sample-card">
+              <mat-card-content>
+                <pre class="content" *ngIf="data.sample != undefined">{{data.sample}}</pre>
+                <pre class="content" *ngIf="data.sample === undefined">No Data inputed</pre>
+              </mat-card-content>
+            </mat-card>
+        </div>
+        <div *ngIf="data.MetadataType === 'datatype'">
+            <pre class="content"><b>Dataverse: </b>{{data.DataverseName}}</pre>
+            <pre class="content"><b>Datatype Name: </b>{{data.DatatypeName}}</pre>
+            <pre class="list-title content list-tag" *ngIf="data.Derived.Tag === 'ORDEREDLIST'"><b>[</b></pre>
+            <pre class="list-title content list-tag" *ngIf="data.Derived.Tag === 'UNORDEREDLIST'"><b>{{</b></pre>
+            <pre class="list-title content" *ngIf="data.Derived.Tag === 'RECORD'"><b>Fields:</b></pre>
+            <mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
+              <mat-tree-node *matTreeNodeDef="let node;" matTreeNodeToggle>
+                <mat-expansion-panel hideToggle disabled class="tree-node mat-elevation-z0 right-aligned-header" (click)="$event.stopPropagation();">
+                  <mat-expansion-panel-header [collapsedHeight]="'25px'">
+                    <mat-panel-title class="not-nested-title" *ngIf="node.DatatypeType !== undefined">{{node.DatatypeName}} ({{node.DatatypeType}})</mat-panel-title>
+                    <mat-panel-title class="not-nested-title" *ngIf="node.DatatypeType === undefined">{{node.DatatypeName}}</mat-panel-title>
+
+                    <mat-panel-description class="not-nested-options" *ngIf="node.isNullable == true && node.isMissable == false">Nullable | Required</mat-panel-description>
+                    <mat-panel-description class="not-nested-options" *ngIf="node.isNullable == true && node.isMissable == true">Nullable | Not Required</mat-panel-description>
+                    <mat-panel-description class="not-nested-options" *ngIf="node.isNullable == false && node.isMissable == false">Not Nullable | Required</mat-panel-description>
+                    <mat-panel-description class="not-nested-options" *ngIf="node.isNullable == false && node.isMissable == true">Not Nullable | Not Required</mat-panel-description>
+                  </mat-expansion-panel-header>
+                </mat-expansion-panel>
+              </mat-tree-node>
+              <mat-nested-tree-node *matTreeNodeDef="let node;when: hasChild">
+                <div class="mat-tree-node">
+                  <mat-expansion-panel class="tree-node mat-elevation-z2 right-aligned-header">
+                    <mat-expansion-panel-header [collapsedHeight]="'25px'" [expandedHeight]="'35px'">
+                      <mat-panel-title *ngIf="node.anonymous == false">{{node.DatatypeName}} ({{node.DatatypeType}})</mat-panel-title>
+                      <mat-panel-title *ngIf="node.anonymous == true">{{node.DatatypeName}} (<i>{{node.DatatypeType}}</i>)</mat-panel-title>
+
+                      <mat-panel-description *ngIf="node.isNullable == true && node.isMissable == false">Nullable | Required</mat-panel-description>
+                      <mat-panel-description *ngIf="node.isNullable == true && node.isMissable == true">Nullable | Not Required</mat-panel-description>
+                      <mat-panel-description *ngIf="node.isNullable == false && node.isMissable == false">Not Nullable | Required</mat-panel-description>
+                      <mat-panel-description *ngIf="node.isNullable == false && node.isMissable == true">Not Nullable | Not Required</mat-panel-description>
+                    </mat-expansion-panel-header>
+                    <pre class="list-title content list-tag" *ngIf="node.OrderedList == true"><b>[</b></pre>
+                    <pre class="list-title content list-tag" *ngIf="node.UnorderedList == true"><b>{{</b></pre>
+                    <div [class.example-tree-invisible]="!treeControl.isExpanded(node)">
+                      <ng-container matTreeNodeOutlet></ng-container>
+                    </div>
+                    <pre class="list-title content list-tag" *ngIf="node.OrderedList == true"><b>]</b></pre>
+                    <pre class="list-title content list-tag" *ngIf="node.UnorderedList == true"><b>}}</b></pre>
+                  </mat-expansion-panel>
+                </div>
+              </mat-nested-tree-node>
+            </mat-tree>
+            <pre class="list-title content list-tag" *ngIf="data.Derived.Tag === 'ORDEREDLIST'"><b>]</b></pre>
+            <pre class="list-title content list-tag" *ngIf="data.Derived.Tag === 'UNORDEREDLIST'"><b>}}</b></pre>
+            <p class="anon-note">Note: italicized = anonymous type</p>
+        </div>
+        <div *ngIf="data.MetadataType === 'index'">
+            <pre class="content"><b>Dataverse: </b>{{data.DataverseName}}</pre>
+            <pre class="content"><b>Index Name: </b>{{data.IndexName}}</pre>
+
+            <pre class="content" *ngIf="data.IsPrimary == true"><b>Index Type: </b>Primary</pre>
+            <pre class="content" *ngIf="data.IsPrimary == false"><b>Index Type: </b>Not Primary</pre>
+
+            <div *ngIf="data.SearchKey.length > 0">
+              <pre class="list-title content"><b>Search Key(s):</b></pre>
+                <li class="content" *ngFor="let skey of data.SearchKey">{{skey}}</li>
+            </div>
+        </div>
+        <div *ngIf="data.MetadataType === 'function'">
+          <pre class="content"><b>Dataverse: </b>{{data.DataverseName}}</pre>
+          <pre class="content"><b>Function Name: </b>{{data.Name}}</pre>
+          <pre class="content"><b>Arity: </b>{{data.Arity}}</pre>
+          <div *ngIf="data.Params.length > 0">
+            <pre class="content"><b>Parameters: </b></pre>
+            <li class="content" *ngFor="let param of data.Params">{{param}}</li>
+          </div>
+          <pre class="content"><b>Defintion: </b></pre>
+          <pre class="content">{{data.Definition}}</pre>
+
+        </div>
+    </mat-dialog-content>
+    <mat-dialog-content *ngIf="showGuts">
+        <pre class="content">{{data.guts}}</pre>
     </mat-dialog-content>
+    <div class="spacer"></div>
     <mat-action-row>
-        <button mat-button class='input-button' (click)="onClickClose()">CLOSE</button>
+        <button mat-button class="input-button" (click)="onClickParsed()" *ngIf="hideJSONButton">SUMMARY</button>
+        <button mat-button class="input-button" (click)="onClickJSON()" *ngIf="hideJSONButton == false">JSON</button>
     </mat-action-row>
-</div>
\ No newline at end of file
+</div>
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata-inspector.component.scss b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata-inspector.component.scss
index 0ca0461..72afdf9 100644
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata-inspector.component.scss
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata-inspector.component.scss
@@ -12,19 +12,91 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
+::ng-deep.mat-dialog-container {
+    resize: both !important;
+    overflow: auto;
+}
+
 .inspector-dialog {
     font-size: 0.80rem;
     font-weight: 500;
 }
 
 .header {
-    font-size: 1.0rem;
+    font-size: 1.5rem;
     font-weight: 500;
     color: blue;
-    border-bottom: 1px solid rgb(145, 152, 158);
 }
 
 .content {
     margin-left: auto !important;
     margin-right: auto !important;
-}
\ No newline at end of file
+}
+
+.list-title {
+    margin-bottom: 1px;
+}
+
+.metadata-inspector {
+    font-size: 1.0rem;
+}
+
+.metadata-inspector b {
+    color: blue !important;
+}
+
+.spacer {
+    margin-bottom: 30px;
+}
+
+.mat-expansion-panel {
+    margin-bottom: 1px;
+}
+
+.tree-node {
+    width: 100%;
+}
+
+.not-nested-title {
+    color: black !important;
+}
+
+.not-nested-options {
+    color: rgba(0, 0, 0, 0.54) !important;
+}
+
+.mat-tree-node {
+    min-height: 25px;
+}
+
+.mat-expansion-panel-header-description {
+    display: flex;
+    justify-content: flex-end;
+    flex: 0 0 auto;
+    padding-right: 5px;
+}
+
+.anon-note {
+    font-size: 15px;
+    color: #9e9e9e;
+}
+
+.sample-card {
+    max-height: 250px;
+    overflow:auto;
+    padding-bottom: 15px;
+}
+
+.list-tag {
+    margin-top: 0px !important;
+}
+
+.top-row {
+  display: flex;
+  flex-flow: row;
+  justify-content: space-between;
+  align-items: baseline;
+  border-bottom: 1px solid rgb(145, 152, 158);
+  cursor: move;
+  text-align: center;
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata.component.html b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata.component.html
index 386520b..b50027e 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata.component.html
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata.component.html
@@ -13,51 +13,64 @@ limitations under the License.
 */ -->
 <div class="wrapper">
     <mat-expansion-panel hideToggle [expanded]="true">
-        <mat-expansion-panel-header class="header-dataverse">
-        <mat-icon>developer_board</mat-icon>
-        <mat-panel-title>DATAVERSES</mat-panel-title>
-        <mat-panel-description></mat-panel-description>
-        </mat-expansion-panel-header>
-        <section class="section">
-        <li *ngFor="let dataverse of dataverses; index as i">
-            <mat-checkbox [labelPosition]="before" class="margin" (change)="generateFilter(dataverse.DataverseName, $event, value, i)" [(ngModel)]="dataverse.active">{{dataverse.DataverseName}}</mat-checkbox>
-        </li>
-        <div class="refresh">
-            <button mat-button class="refresh-button" (click)="refreshMetadata()" matTooltip="Click to refresh changes in AsterixDB"><mat-icon class="list-icon">cached</mat-icon>REFRESH</button>
-        </div>
-        </section>
-    </mat-expansion-panel>
-    <mat-expansion-panel hideToggle [expanded]="panelOpenState">
-        <mat-expansion-panel-header #datasetsPanel class="header">
-            <mat-icon>developer_board</mat-icon>
+      <mat-expansion-panel-header class="metadata-inspector-header">
+        <mat-panel-title>METADATA INSPECTOR</mat-panel-title>
+      </mat-expansion-panel-header>
+      <section>
+        <mat-expansion-panel hideToggle [expanded]="true">
+          <mat-expansion-panel-header class="header-dataverse">
+            <mat-panel-title class="title">DATAVERSES</mat-panel-title>
+          </mat-expansion-panel-header>
+          <section class="section">
+            <li *ngFor="let dataverse of dataverses; index as i">
+              <mat-checkbox [labelPosition]="'after'" class="margin" (change)="generateFilter(dataverse.DataverseName, $event, i)" [(ngModel)]="dataverse.active">{{dataverse.DataverseName}}</mat-checkbox>
+            </li>
+            <div class="refresh">
+              <button mat-button class="refresh-button" (click)="refreshMetadata()" matTooltip="Click to refresh changes in AsterixDB"><mat-icon class="list-icon">cached</mat-icon>REFRESH</button>
+            </div>
+          </section>
+        </mat-expansion-panel>
+        <mat-expansion-panel hideToggle [expanded]="panelOpenState">
+          <mat-expansion-panel-header #datasetsPanel class="header">
             <mat-panel-title class="title">DATASETS</mat-panel-title>
             <mat-panel-description></mat-panel-description>
-        </mat-expansion-panel-header>
-        <section class="section">
-        <li *ngFor="let dataset of datasetsFiltered" (click)="openMetadataInspectorDialog(dataset)">
-            {{dataset.DatasetName}}</li>
-        </section>
-    </mat-expansion-panel>
-    <mat-expansion-panel hideToggle [expanded]="panelOpenState">
-        <mat-expansion-panel-header class="header">
-            <mat-icon>developer_board</mat-icon>
+          </mat-expansion-panel-header>
+          <section class="section">
+            <li *ngFor="let dataset of datasetsFiltered" (click)="openMetadataInspectorDialog(dataset, 'dataset')">
+              {{dataset.DatasetName}}</li>
+          </section>
+        </mat-expansion-panel>
+        <mat-expansion-panel hideToggle [expanded]="panelOpenState">
+          <mat-expansion-panel-header class="header">
             <mat-panel-title class="title">DATATYPES</mat-panel-title>
             <mat-panel-description></mat-panel-description>
-        </mat-expansion-panel-header>
-        <section class="section">
-        <li *ngFor="let datatype of datatypesFiltered" (click)="openMetadataInspectorDialog(datatype)">
-            {{datatype.DatatypeName}}</li>
-        </section>
-    </mat-expansion-panel>
-    <mat-expansion-panel hideToggle [expanded]="panelOpenState">
-        <mat-expansion-panel-header class="header">
-            <mat-icon>developer_board</mat-icon>
+          </mat-expansion-panel-header>
+          <section class="section">
+            <li *ngFor="let datatype of datatypesFiltered" (click)="openMetadataInspectorDialog(datatype, 'datatype')">
+              {{datatype.DatatypeName}}</li>
+          </section>
+        </mat-expansion-panel>
+        <mat-expansion-panel hideToggle [expanded]="panelOpenState">
+          <mat-expansion-panel-header class="header">
             <mat-panel-title class="title">INDEX</mat-panel-title>
             <mat-panel-description></mat-panel-description>
-        </mat-expansion-panel-header>
-        <section class="section">
-        <li *ngFor="let index of indexesFiltered" (click)="openMetadataInspectorDialog(index)">
-            {{index.IndexName}}</li>
-        </section>
+          </mat-expansion-panel-header>
+          <section class="section">
+            <li *ngFor="let index of indexesFiltered" (click)="openMetadataInspectorDialog(index, 'index')">
+              {{index.IndexName}}</li>
+          </section>
+        </mat-expansion-panel>
+        <mat-expansion-panel hideToggle [expanded]="panelOpenState">
+          <mat-expansion-panel-header class="header">
+            <mat-panel-title class="title">USER DEFINED FUNCTIONS</mat-panel-title>
+            <mat-panel-description></mat-panel-description>
+          </mat-expansion-panel-header>
+          <section class="section">
+            <li *ngFor="let function of functionsFiltered" (click)="openMetadataInspectorDialog(function, 'function')">
+              {{function.Name}}
+            </li>
+          </section>
+        </mat-expansion-panel>
+      </section>
     </mat-expansion-panel>
-</div>
\ No newline at end of file
+</div>
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata.component.scss b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata.component.scss
index b79d774..3728cdb 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata.component.scss
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata.component.scss
@@ -42,8 +42,7 @@ mat-checkbox .mat-icon {
     padding-right: 10px;
     padding-bottom: 5px;
     padding-top: 5px;
-    //background-color: gainsboro;
-    background-color: black;
+    background-color: gainsboro;
     margin-bottom: 50px;
     mat-expansion-panel {
         border: none !important;
@@ -70,6 +69,15 @@ mat-checkbox .mat-icon {
     color: blue;
 }
 
+.metadata-inspector-header {
+  font-size: 0.80rem;
+  font-weight: 500;
+  max-height: 42px;
+  min-height: 42px;
+  border-bottom: 1px solid gray;
+  margin-bottom: 5px;
+}
+
 .title {
     color:blue;
 }
@@ -87,4 +95,4 @@ mat-checkbox .mat-icon {
     display: flex;
     flex-flow: row;
     align-items: center;
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata.component.ts b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata.component.ts
index 99c470c..835eeb8 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata.component.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/metadata.component.ts
@@ -18,8 +18,41 @@ import * as dataverseActions from '../../shared/actions/dataverse.actions';
 import * as datasetActions from '../../shared/actions/dataset.actions';
 import * as datatypesActions from '../../shared/actions/datatype.actions';
 import * as indexesActions from '../../shared/actions/index.actions';
+import * as functionActions from '../../shared/actions/function.actions';
 import { ViewChild} from '@angular/core';
-import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
+import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import {MatTreeNestedDataSource} from "@angular/material/tree";
+import {NestedTreeControl} from "@angular/cdk/tree";
+import {Data} from "@angular/router";
+
+interface DataTypeNode {
+  DatatypeName: string;
+  DatatypeType?: string;
+  fields?: DataTypeNode[];
+  primitive: boolean;
+  isNullable?: boolean;
+  isMissable?: boolean;
+  OrderedList?: boolean;
+  UnorderedList?: boolean;
+  anonymous: boolean;
+}
+
+enum MetadataTypes {
+  record = "RECORD",
+  orderedList = "ORDEREDLIST",
+  unorderedList = "UNORDEREDLIST"
+}
+
+enum MetadataTypesNames {
+  record = "Record",
+  orderedList = "Ordered List",
+  unorderedList = "Unordered List"
+}
+
+enum MetadataListTypes {
+  orderedList = "OrderedList",
+  unorderedList = "UnorderedList"
+}
 
 @Component({
     moduleId: module.id,
@@ -40,9 +73,28 @@ export class MetadataComponent {
     indexesFiltered: any;
     indexes$: Observable<any>;
     indexes: any;
+    functionsFiltered: any;
+    functions$: Observable<any>;
+    functions: any;
+    curr_dialogRef: any;
+    dialogActive: boolean;
+
+    //added variables for sample
+    sampleDataset$: Observable<any>;
+    sampleDataset: any;
+
+    //variables for counting
+    countDataset$:Observable<any>;
+    countDataset: number;
+
+    //added variables for flattening
+    datatypesDict: Object;
+
+    dialogSamples = {};
 
     constructor(private store: Store<any>, public dialog: MatDialog) {
         this.refreshMetadata();
+        this.dialogActive = false;
     }
 
     ngOnInit() {
@@ -66,6 +118,7 @@ export class MetadataComponent {
          this.datatypes$.subscribe((data: any[]) => {
             this.datatypes = data;
             this.datatypesFiltered = this.filter(this.datatypes);
+            this.datatypesDict = this.createDatatypeDict(this.datatypes);
          });
 
          // Watching for indexes
@@ -74,6 +127,16 @@ export class MetadataComponent {
             this.indexes = data;
             this.indexesFiltered = this.filter(this.indexes);
          });
+
+         //Watching for functions
+        this.functions$ = this.store.select(s => s.functions.functions.results);
+        this.functions$.subscribe((data: any[]) => {
+          this.functions = data;
+          this.functionsFiltered = this.filter(this.functions);
+        });
+
+         // Watching for samples
+        this.sampleDataset$ = this.store.select(s => s.dataset.sample);
     }
 
     refreshMetadata() {
@@ -81,11 +144,24 @@ export class MetadataComponent {
         this.store.dispatch(new datasetActions.SelectDatasets('-'));
         this.store.dispatch(new datatypesActions.SelectDatatypes('-'));
         this.store.dispatch(new indexesActions.SelectIndexes('-'));
+        this.store.dispatch(new functionActions.SelectFunctions('-'));
     }
 
     dataverseFilter = {}
     dataverseFilterMap = new Map();
 
+    createDatatypeDict(data) {
+        let newDict = new Object();
+
+        if (data) {
+            for (let i=0; i < data.length; i++) {
+                newDict[data[i].DataverseName + "." + data[i].DatatypeName] = i;
+            }
+        }
+
+        return newDict;
+    }
+
     filter(data){
         let results = [];
 
@@ -125,6 +201,7 @@ export class MetadataComponent {
         this.datasetsFiltered = this.filter(this.datasets);
         this.datatypesFiltered = this.filter(this.datatypes);
         this.indexesFiltered = this.filter(this.indexes);
+        this.functionsFiltered = this.filter(this.functions);
 
         /* Open the dataset expansion panel if there is anything to show */
         if (this.datasetsFiltered.length > 0) {
@@ -135,16 +212,213 @@ export class MetadataComponent {
     }
 
     /*
+    * Traverse Metadata recursively, handles Metadata dataverse as well as regular dataverses
+     */
+    recursiveMetadataTraverse(data, toCreate): Object {
+      toCreate.DatatypeName = data.DatatypeName;
+      //primitive == no Derived field or Derived == undefined
+      if (data.Derived == undefined) {
+        //if primitive
+        toCreate.DatatypeName = data.DatatypeName;
+        toCreate.fields = [];
+        toCreate.primitive = true;
+      } else {
+        //if not primitive, need to check .Derived exists every time, or else handle primitive type
+        toCreate.DatatypeType = data.Derived.Tag;
+
+        //determine what type it is (Record, Ordered List or Unordered List). Ordered list and unordered list are handled the same
+        let list_type = "";
+
+        switch(toCreate.DatatypeType) {
+          case MetadataTypes.record:
+            toCreate.DatatypeType = MetadataTypesNames.record;
+            break;
+          case MetadataTypes.orderedList:
+            toCreate.DatatypeType = MetadataTypesNames.orderedList;
+            list_type = MetadataListTypes.orderedList;
+            break;
+          case MetadataTypes.unorderedList:
+            toCreate.DatatypeType = MetadataTypesNames.unorderedList;
+            list_type = MetadataListTypes.unorderedList;
+            break;
+          default:
+            break;
+        }
+
+        toCreate.fields = [];
+
+        if (data.Derived.Tag == "RECORD") {
+          // if it is a record, we must iterate over the fields and may have to recurse if there is a non primitive type
+          for (let field of data.Derived.Record.Fields) {
+            //if it is NOT a primitive type
+            if ((data.DataverseName + "." + field.FieldType) in this.datatypesDict &&
+              this.datatypes[this.datatypesDict[data.DataverseName + "." + field.FieldType]].Derived != undefined) {
+              field.Nested = true;
+
+              //get the nested object from datatypesDict
+              let nestedName = this.datatypesDict[data.DataverseName + "." + field.FieldType];
+              let nestedObject = this.datatypes[nestedName];
+
+              let nested_field = {
+                DatatypeName: field.FieldName,
+                DatatypeType: field.FieldType,
+                primitive: false,
+                fields: [],
+                isNullable: field.IsNullable,
+                isMissable: field.IsMissable,
+                OrderedList: false,
+                UnorderedList: false,
+                anonymous: nestedObject.Derived.IsAnonymous,
+              }
+
+              if (nestedObject.Derived.Tag == "RECORD") {
+                //object and should iterate over fields
+                field.NestedType = "Record";
+                field.NestedTypeType = nestedObject.DatatypeName;
+
+                let recurse_result = this.recursiveMetadataTraverse(nestedObject, {})
+
+                field.NestedRecord = recurse_result[0];
+
+                let toAdd = recurse_result[1];
+                toAdd.DatatypeType = "Record";
+                toAdd.primitive = false;
+                toAdd.anonymous = nestedObject.Derived.IsAnonymous;
+
+                nested_field.fields.push(toAdd);
+              }
+              else {
+                let listObject;
+                let nestedListType = "";
+                let nestedListTypeName = "";
+
+                //determine the type of list of the nested object
+                if (nestedObject.Derived.Tag == MetadataTypes.orderedList) {
+                  nestedListType = MetadataListTypes.orderedList;
+                  nestedListTypeName = MetadataTypesNames.orderedList;
+                } else {
+                  nestedListType = MetadataListTypes.unorderedList;
+                  nestedListTypeName = MetadataTypesNames.unorderedList;
+                }
+
+                nested_field[nestedListType] = true;
+                field.NestedType = nestedListTypeName;
+
+                if (data.DataverseName + "." + nestedObject.Derived[nestedListType] in this.datatypesDict) {
+                  field.primitive = false;
+                  listObject = this.datatypes[this.datatypesDict[data.DataverseName + "." + nestedObject.Derived[nestedListType]]];
+
+                  let recurse_result = this.recursiveMetadataTraverse(listObject, {});
+
+                  field.NestedRecord = recurse_result[0];
+
+                  let toAdd = recurse_result[1];
+
+                  if (toAdd.DatatypeType == nestedListTypeName) {
+                    toAdd[nestedListType] = true;
+                  } else {
+                    toAdd[nestedListType] = false;
+                  }
+
+                  toAdd.primitive = false;
+                  if (listObject.Derived != undefined)
+                    toAdd.anonymous = listObject.Derived.IsAnonymous;
+
+                  nested_field.fields.push(toAdd);
+                } else {
+                  field.primitive = true;
+                  nested_field.fields.push({
+                    DatatypeName: nestedObject.Derived[nestedListType],
+                    [nestedListType]: true,
+                    primitive: true,
+                  });
+                }
+              }
+
+              toCreate.fields.push(nested_field);
+            }
+            else {
+              field.Nested = false;
+              toCreate.fields.push({
+                DatatypeName: field.FieldName,
+                DatatypeType: field.FieldType,
+                primitive: true,
+                isMissable: field.IsMissable,
+                isNullable: field.IsNullable,
+                anonymous: false,
+              });
+            }
+          }
+        } else {
+          let listItem = this.datatypes[this.datatypesDict[data.DataverseName + "." + data.Derived[list_type]]];
+
+          toCreate[list_type] = true;
+
+          if (listItem == undefined) {
+            toCreate.fields.push({
+              DatatypeName: data.Derived[list_type],
+              [list_type]: true,
+              primitive: true,
+              anonymous: data.Derived.IsAnonymous,
+            })
+          } else {
+            let recurse_result = this.recursiveMetadataTraverse(listItem, {});
+
+            let toAdd = recurse_result[1];
+            toAdd.primitive = false;
+            if (listItem.Derived != undefined)
+              toAdd.anonymous = listItem.Derived.IsAnonymous;
+            toCreate.fields.push(toAdd);
+          }
+        }
+      }
+
+      return [data, toCreate];
+    }
+
+    /*
     * opens the metadata inspector
     */
-    openMetadataInspectorDialog(data): void {
+    openMetadataInspectorDialog(data, metadata_type): void {
         let metadata = JSON.stringify(data, null, 8);
         metadata = metadata.replace(/^{/, '');
         metadata = metadata.replace(/^\n/, '');
         metadata = metadata.replace(/}$/, '');
-        let dialogRef = this.dialog.open(DialogMetadataInspector, {
-            width: '500px',
-            data: metadata,
+
+        //if metadata_type is dataset, sample said dataset and add to data to be displayed.
+
+        data.guts = metadata;
+
+        data.MetadataType = metadata_type
+        if (metadata_type == 'dataset') {
+            let dataset = "`" + data.DataverseName + "`" + "." + "`" + data.DatasetName + "`";
+            this.store.dispatch(new datasetActions.SampleDataset({dataset: dataset}));
+
+            this.sampleDataset$.subscribe((resp: any[]) => {
+                if (resp) {
+                  this.dialogSamples[dataset] = JSON.stringify(resp[dataset], null, 2);
+                  data.sample = this.dialogSamples[dataset];
+                } else {
+                  data.sample = undefined;
+                }
+            });
+        }
+
+        //flatten data if datatype
+        if (metadata_type == 'datatype') {
+            let new_datatype = new Object();
+
+            let recurseResults = this.recursiveMetadataTraverse(data, new_datatype);
+            let converted = recurseResults[1];
+
+            data = recurseResults[0];
+
+            data.DataTypeTree = converted;
+        }
+
+        this.curr_dialogRef = this.dialog.open(DialogMetadataInspector, {
+            minWidth: '500px',
+            data: data,
             hasBackdrop: false
         });
     }
@@ -158,9 +432,30 @@ export class MetadataComponent {
 
 export class DialogMetadataInspector {
     constructor(  public dialogCreateDsRef: MatDialogRef<DialogMetadataInspector>,
-                  @Inject(MAT_DIALOG_DATA) public data: any) { }
+                  @Inject(MAT_DIALOG_DATA) public data: any) {
+        if (data.MetadataType == "datatype") {
+            this.dataSource.data = data.DataTypeTree.fields;
+        }
+    }
 
     onClickClose() {
-        this.dialogCreateDsRef.close();
+        this.dialogCreateDsRef.close(this.data['dialogID']);
     }
-}
\ No newline at end of file
+
+    onClickJSON() {
+        this.showGuts = true;
+        this.hideJSONButton = true;
+    }
+
+    onClickParsed() {
+        this.showGuts = false;
+        this.hideJSONButton = false;
+    }
+
+    showGuts = false;
+    hideJSONButton = false
+
+    treeControl = new NestedTreeControl<DataTypeNode>(node => node.fields);
+    dataSource = new MatTreeNestedDataSource<DataTypeNode>();
+    hasChild = (_: number, node: DataTypeNode) => !!node.fields && node.fields.length > 0;
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/output.component.html b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/output.component.html
index e95752e..89ed701 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/output.component.html
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/output.component.html
@@ -14,15 +14,16 @@ limitations under the License.
 <mat-card class="sql-results-card">
     <mat-card-content class="content-area">
         <div class="divider">
-        <div *ngIf='data.length != 0'>
-            <tree-view [data]="data" [queryId]="queryId"></tree-view>
-        </div>
-        <div *ngIf='queryOptimizedLogicalPlan != ""'>
-            <plan-view [planFormat]="planFormat" [jsonPlan]="queryOptimizedLogicalPlan" [plan]="optimalLogicalPlan" [planName]="'OPTIMIZED PLAN'"></plan-view>
+        <div *ngIf="isError == false && hideOutput == false">
+            <tree-view [data]="data" [queryId]="queryId" [planFormat]="planFormat" [jsonPlan]="queryOptimizedLogicalPlan" [plan]="optimalLogicalPlan" [planName]="'OPTIMIZED PLAN'" [inputToOutput]="inputToOutput"></tree-view>
         </div>
+        <!--
+        Ignore Logical Plan for now...if users want back can add back in. However, current JSON->graph code does not support sub plans
+
         <div *ngIf='queryLogicalPlan != ""'>
             <plan-view [planFormat]="planFormat" [jsonPlan]="queryLogicalPlan" [plan]="logicalPlan" [planName]="'LOGICAL PLAN'"></plan-view>
         </div>
+        -->
         </div>
     </mat-card-content>
-</mat-card>
\ No newline at end of file
+</mat-card>
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/output.component.scss b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/output.component.scss
index 67431c8..4949ded 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/output.component.scss
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/output.component.scss
@@ -13,13 +13,14 @@ limitations under the License.
 */
 
 $results-spacing-unit: 5px;
+
 .sql-results-card {
+    display: block;
+    margin: 0 0px 0 0px;
+    padding: 0;
     margin: ($results-spacing-unit);
-    display: flex;
-    flex-flow: column;
+    border-radius: 4px;
     width: 100%;
-    padding: 0px;
-    border: none !important;
 }
 
 .content-area {
@@ -31,4 +32,4 @@ $results-spacing-unit: 5px;
 
 .divider {
     width: 100%;
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/output.component.ts b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/output.component.ts
index 592e9b5..cd0abef 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/output.component.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/output.component.ts
@@ -11,7 +11,7 @@ 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 } from '@angular/core';
+import {Component, Input, OnChanges, SimpleChange} from '@angular/core';
 import { Observable } from 'rxjs';
 import { Store } from '@ngrx/store';
 
@@ -24,6 +24,10 @@ import { Store } from '@ngrx/store';
 
 
 export class QueryOutputComponent {
+    @Input('inputToOutput') inputToOutput: Object;
+    @Input('isError') isError: boolean;
+    @Input('hideOutput') hideOutput: boolean;
+
     data: any[];
     currentQueryActive$: Observable < any > ;
     currentQueryActive: string;
@@ -37,7 +41,14 @@ export class QueryOutputComponent {
     SQLresults: any;
     queryId: any = "";
     observedPlanFormat = "";
+    queryError: boolean;
+    currentQuery: any = 0;
 
+    ngOnChanges(changes: SimpleChange) {
+      this.inputToOutput = this.inputToOutput;
+      this.isError = this.isError;
+      this.hideOutput = this.hideOutput;
+    }
 
     constructor(private store: Store <any>) {
         let key = '1';
@@ -54,7 +65,7 @@ export class QueryOutputComponent {
                 this.currentQueryActive = "0";
             }
         })
-        /* this is the output when the quey runs for the first time */
+        /* this is the output when the query runs for the first time */
         this.results$ = this.store.select(s => s.sqlQuery.sqlQueryResultHash);
         this.results$.subscribe((data: any) => {
             if (Object.keys(data).length !== 0 && data[this.currentQueryActive]) {
@@ -104,4 +115,4 @@ export class QueryOutputComponent {
               this.data = [];
         }
     }
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-node-svg.component.html b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-node-svg.component.html
deleted file mode 100644
index 5aa58d6..0000000
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-node-svg.component.html
+++ /dev/null
@@ -1,79 +0,0 @@
-<!--/*
-Licensed 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.
-*/-->
-<svg id='node{{level}}{{item}}{{subplan}}{{planName}}' xmlns="http://www.w3.org/2000/svg" xml:lang="en" xmlns:xlink="http://www.w3.org/1999/xlink"
-    width="200px" height="160px" class="plan-node" (click)="seeDetails(viewParams_)">
-    <title>{{details}}</title>
-    <style>
-        @keyframes cycle {
-        33.3% {
-            visibility: visible;
-        }
-        100% {
-            visibility: hidden
-        }
-        }
-
-        .lit {
-        animation: cycle 9s step-start infinite;
-        }
-
-        .red .lit {
-        animation-delay: -3s;
-        }
-
-        .yellow .lit {
-        animation-delay: -6s;
-        }
-
-        .green .lit {
-        animation-delay: 0;
-        }
-
-        .operation-text {
-        font-size: 12px;
-        fill: black;
-        }
-
-        .operation-see-more {
-        font-size: 12px;
-        fill: black;
-        cursor: pointer;
-        }
-
-        .card {
-        cursor: pointer;
-        }
-
-        .card:hover {
-        stroke: blue;
-        }
-
-        .operation-details {
-        visibility: none;
-        transition: opacity 1s ease-in-out;
-        opacity: 0;
-        }
-
-    </style>
-    <text class="operation-text" x="50%" y="50%" text-anchor="middle">{{getNodeOperatorId()}} : {{getNodeName()}}</text>
-</svg>
-
-<div class="branch" *ngIf="node.inputs">
-    <li *ngIf="checkSubPlan()" class="li sub">
-        <plan-node-svg class="sub" [planName]="planName" [node]="node.inputs[item].subplan[0]" [level]="0" [item]="0" [subplan]="level+item+subplan+1"></plan-node-svg>
-    </li>
-    <li class="li" *ngFor="let subNode of node.inputs; let i = index">
-        <plan-node-svg class="" [planName]="planName" [node]="subNode" [level]="level+1" [item]="i" [subplan]="subplan" [viewParams]="viewParams"></plan-node-svg>
-    </li>
-</div>
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-node-svg.component.scss b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-node-svg.component.scss
deleted file mode 100644
index f1af051..0000000
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-node-svg.component.scss
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
-Licensed 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.
-*/
-
-//vars
-$page-width: 1000px;
-$padding-base: 12px;
-$padding-sm: 5px;
-$padding-lg: 15px;
-$padding-xl: 25px;
-$font-size-base: 12px;
-$font-size-xs: round($font-size-base * 0.7);
-$font-size-sm: round($font-size-base * 0.9);
-$font-size-lg: round($font-size-base * 1.3);
-$font-size-xl: round($font-size-base * 1.7);
-$font-family-sans-serif: 'noto';
-$font-family-mono: 'source code';
-$line-height-base: 1.3;
-$gray-lightest: #f7f7f7;
-$gray-light: darken($gray-lightest, 10%);
-$gray: darken(#f7f7f7, 30%);
-$gray-dark: darken(#f7f7f7, 50%);
-$gray-darkest: darken($gray-lightest, 70%);
-$blue: #00B5E2;
-$dark-blue: #008CAF;
-$light-blue: #65DDFB;
-$red: #AF2F11;
-$dark-red: #7C210C;
-$light-red: #FB8165;
-$green: #279404;
-$yellow: #F8E400;
-$bg-color: $gray-lightest;
-$text-color: #4d525a;
-$text-color-light: lighten($text-color, 30%);
-$line-color: $gray-light;
-$line-color-light: lighten($gray-light, 10%);
-$link-color: $blue;
-$border-radius-base: 3px;
-$border-radius-lg: 6px;
-$main-color: $blue;
-$main-color-dark: $blue;
-$highlight-color: $blue;
-$highlight-color-dark: $dark-blue;
-$alert-color: #FB4418;
-$connector-height: 20px;
-$connector-line-small: 1px solid darken($line-color, 10%);
-$connector-line-big: 2px solid darken($line-color, 10%);
-.plan-nodea {
-    display: table;
-    position: relative;
-    float: left;
-}
-
-.view-icon {
-    font-size: 14px;
-}
-
-.flex-spacer {
-    flex: 1 1 10%;
-}
-
-.dot {
-    display: flex;
-    flex-flow: column;
-    margin-left: auto;
-    margin-right: auto;
-    padding: 0;
-}
-
-.plan-node {
-    display: flex;
-    flex-flow: column;
-    justify-content: flex-start;
-    color: $text-color;
-    transition: hidden 0.8s;
-    padding: 0px;
-    font-size: 10px;
-    border: 1px solid $line-color;
-    margin: 0px;
-    border-radius: $border-radius-base;
-    width: 200px;
-    height: 60px;
-    box-shadow: 1px 1px 3px 0px rgba(0, 0, 0, 0.1);
-    margin-left: auto;
-    margin-right: auto;
-    padding-left: auto;
-    padding-right: auto;
-    transition: height 0.3s ease-in-out;
-    &:hover {
-        border-color: $highlight-color;
-    }
-}
-
-.plan {
-    list-style: none !important;
-    padding-bottom: $padding-lg * 3;
-    margin-top: 0;
-    padding-top: 0;
-    padding-left: 0;
-
-    .merge {
-        display: flex;
-        flex-flow: column;
-        margin-top: 0 !important;
-        list-style: none;
-        padding-left: 0;
-        color: #00B5E2 !important;
-        border: 1px dashed #00B5E2;
-        justify-content: center;
-        align-items: center;
-    }
-
-    .branch {
-        display: flex;
-        flex-flow: row;
-        margin-top: 0;
-        list-style: none;
-        padding-top: $connector-height;
-        position: relative;
-        transition: all 1s;
-        padding-left: 0;
-        color: black;
-        margin-left: auto;
-        margin-right: auto; // vertical
-        &:before {
-            content: '';
-            position: absolute;
-            top: 0;
-            left: 50%;
-            border-left: $connector-line-small;
-            height: $connector-height;
-            width: 0;
-            color: black;
-            margin-top: 0;
-        }
-        &:first-child {
-            margin-top: 0;
-            &:before {
-                border: none;
-            }
-        }
-        .branch {
-            display: flex;
-            flex-flow: row;
-            margin-top: 0 !important;
-            list-style: none;
-            padding-left: 0;
-        }
-
-        li {
-            display: inline;
-            list-style-type: none;
-            position: relative;
-            padding: $connector-height $padding-sm 0 $padding-sm;
-            transition: all 1s;
-            margin-left: auto;
-            margin-right: auto; // connectors
-            &:before,
-            &:after {
-                content: '';
-                position: absolute;
-                top: 0;
-                right: 50%;
-                border-top: $connector-line-small;
-                width: 50%;
-                height: $connector-height;
-            }
-            &:after {
-                right: auto;
-                left: 50%;
-                border-left: $connector-line-small;
-            }
-            &:only-child {
-                padding-top: 0;
-                &:after,
-                &:before {
-                display: none;
-                }
-            }
-            &:first-child::before,
-            &:last-child::after {
-                border: 0 none;
-            }
-            &:last-child::before {
-                border-right: $connector-line-small;
-                border-radius: 0 $border-radius-lg 0 0;
-            }
-            &:first-child::after {
-                border-radius: $border-radius-lg 0 0 0;
-            }
-        }
-    }
-}
-
-.sub {
-    .plan-node {
-        background-color: rgb(230, 230, 230);
-    }
-}
-
-.viewMe {
-    display: inline-block;
-    position: relative;
-    cursor: pointer;
-}
-
-.node-summary {
-    display: block;
-    margin: 10px 0 10px 0;
-    text-align: left;
-}
-
-.node-details {
-    display: block;
-    text-align: left;
-}
-
-.node-subplan {
-    float: right;
-}
\ No newline at end of file
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-node-svg.component.ts b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-node-svg.component.ts
deleted file mode 100644
index a8d1987..0000000
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-node-svg.component.ts
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
-Licensed 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.
-*/
-/*
-Licensed 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 { Renderer2, ViewEncapsulation, Component, Input } from '@angular/core';
-
-export interface ViewParams {
-    viewMode: string,
-    width: string,
-    height: string,
-    visible: string,
-    display: string,
-    opacity: number,
-    border: string
-}
-
-export const FULL:ViewParams = {
-    viewMode: 'FULL',
-    width: '350px',
-    height: '180px',
-    visible: 'visible',
-    display: 'block',
-    opacity: 1,
-    border: "2px solid #0000FF"
-};
-
-export const NORMAL:ViewParams = {
-    viewMode: 'NORMAL',
-    width: '200px',
-    height: '60px',
-    visible: 'hidden',
-    display: 'none',
-    opacity: 0,
-    border: "none"
-};
-
-@Component({
-    moduleId: module.id,
-    selector: 'plan-node-svg',
-    templateUrl: 'plan-node-svg.component.html',
-    styleUrls: ['plan-node-svg.component.scss'],
-    encapsulation: ViewEncapsulation.None,
-})
-
-export class PlanNodeSVGComponent {
-    @Input() node: any;
-    @Input() level;
-    @Input() item = 0;
-    @Input() subplan = 0;
-    @Input() planName = "";
-    @Input() viewParams;
-
-    details: any;
-    viewParams_: any;
-
-    constructor(private renderer: Renderer2) {}
-
-    numberOfInputs: 0;
-    selected = false;
-
-    ngOnInit() {
-
-        this.viewParams_ = NORMAL;
-
-        /* Some preprocessing to show explanation details */
-        if (this.node.inputs){
-            this.numberOfInputs = this.node.inputs.length;
-        } else {
-            this.numberOfInputs = 0;
-        }
-
-        if (this.node) {
-            let node_=  JSON.parse(JSON.stringify(this.node));
-
-            if (node_.inputs) {
-                delete node_['inputs'];
-            }
-
-            if (node_.subplan) {
-                delete node_['subplan'];
-            }
-
-            if (node_.visible != undefined ) {
-                delete node_['visible'];
-            }
-
-            if (node_.viewDetails != undefined) {
-                delete node_['viewDetails'];
-            }
-
-            if (node_.operator) {
-                delete node_['operator'];
-            }
-
-            if (node_.operatorId) {
-                delete node_['operatorId'];
-            }
-
-            this.details = JSON.stringify(node_, null, 8);
-
-            this.details = this.details.replace(/^{/, '');
-            this.details = this.details.replace(/^\n/, '');
-            this.details = this.details.replace(/}$/, '');
-        }
-    }
-
-    getNodeName() {
-        if(this.node) {
-            if (this.node.operator) {
-                return (this.node.operator).toUpperCase();
-            } else {
-                return "NA";
-            }
-        }
-    }
-
-    getNodeOperatorId() {
-        if(this.node) {
-            if (this.node.operatorId) {
-                return (this.node.operatorId).toUpperCase();
-            } else {
-                return "NA";
-            }
-        }
-    }
-
-    getNodeSubPlan() {
-        if(this.node) {
-            if (this.node['inputs']) {
-                if (this.node['inputs'][this.item]) {
-                    if (this.node['inputs'][this.item]['subplan']) {
-                        return "Subplan";
-                    } else {
-                        return "";
-                    }
-                } else {
-                    return "";
-                }
-            }
-        }
-    }
-
-    seeDetails(me) {
-        // Future Implementation
-    }
-
-    checkSubPlan() {
-        if(this.node) {
-            if (this.node['inputs']) {
-                if (this.node['inputs'][this.item]) {
-                    if (this.node['inputs'][this.item]['subplan']) {
-                        return true;
-                    } else {
-                        return false;
-                    }
-                } else {
-                    return false;
-                }
-            } else {
-                return false;
-            }
-        }
-    }
-
-    checkMerge() {
-        if(this.node) {
-            if (this.node['mergeWith']) {
-                return true;
-            } else {
-                return false;
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-view.component.html b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-view.component.html
deleted file mode 100644
index 6eaee72..0000000
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-view.component.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!--/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/ -->
-<div *ngIf="plan_" class="plan-graph" id={{planName}}>
-    <mat-expansion-panel hideToggle [expanded]="true">
-        <mat-expansion-panel-header class="plan-header header-centered-v">
-        <mat-icon>assessment</mat-icon>
-        <mat-panel-title>{{planName}}</mat-panel-title>
-        <mat-panel-description></mat-panel-description>
-        </mat-expansion-panel-header>
-        <mat-panel-description class='content'>
-        <div *ngIf="!jsonButtonDisabled" class='panel'>
-            <button id='jsonButton' mat-button class='button' (click)="showJSON()" matTooltip="Toggle JSON or Graphic View">JSON</button>
-        </div>
-        <div class="divider">
-            <div *ngIf="!jsonVisible" class="plan">
-              <plan-node-svg [planName]="planName" [node]="plan_" [level]="0" [item]="0" [subplan]="0"></plan-node-svg>
-            </div>
-            <div *ngIf="jsonVisible" class="json">
-            <div class='center'>
-                <pre class="json-content">{{jsonPlan}}</pre>
-            </div>
-            </div>
-        </div>
-        </mat-panel-description>
-    </mat-expansion-panel>
-</div>
\ No newline at end of file
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-view.component.scss b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-view.component.scss
deleted file mode 100644
index 28dd380..0000000
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-view.component.scss
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
-Licensed 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.
-*/
-
-.plan-graph {
-    display: block;
-    margin: 0 0px 0 0px;
-    padding: 0;
-    mat-expansion-panel {
-        border: none !important;
-    }
-}
-
-.panel {
-    display: flex;
-    flex-flow: row;
-    justify-content: flex-start;
-    border-bottom: 1px dashed gray;
-    margin-bottom: 15px;
-}
-
-.plan-header {
-    max-height: 42px;
-    min-height: 42px;
-    font-size: 0.80rem;
-    font-weight: 500;
-    border-bottom: 1px solid gray;
-}
-
-.divider {
-    display: flex;
-    flex-flow: row;
-    align-items: flex-start;
-}
-
-.plan {
-    margin: 0px;
-    padding: 20px;
-    padding-left: 50px;
-    padding-right: 50px;
-    margin-right: auto;
-    margin-left: auto;
-    padding-left: 50px;
-    overflow: visible;
-}
-
-.plan1 {
-    display: flow;
-    flex-flow: row;
-}
-
-.content {
-    margin-top: 20px;
-    display: block;
-    font-size: 0.80rem;
-    font-weight: 500;
-}
-
-.json {
-    //padding: 20px;
-    //padding-right: 50px;
-    //margin-right: 25px;
-    min-width: 100%;
-    max-width: 100%;
-}
-
-.json-content {
-    //border-left: 1px solid gray;
-    padding-left: 25px; //display: inline-block;
-    //margin: auto;
-}
-
-.button {
-    font-size: 12px !important;
-    float: right;
-    color: blue !important;
-    margin-bottom: 15px;
-}
-
-#wrapper {
-    position: relative;
-}
-
-.center {
-    margin-left: auto;
-    margin-right: auto;
-    width: 50%;
-}
\ No newline at end of file
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-view.component.ts b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-view.component.ts
deleted file mode 100644
index dd0ee6b..0000000
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-view.component.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import { Component, Input, SimpleChange } from '@angular/core';
-
-export  interface planCount {
-    nodesCnt: number,
-    levelsCnt: number
-}
-
-@Component({
-    selector: 'plan-view',
-    templateUrl: 'plan-view.component.html',
-    styleUrls: ['plan-view.component.scss'],
-})
-
-export class PlanViewComponent {
-
-    @Input() planFormat: any;
-    @Input() plan: any;
-    @Input() planName: any;
-    @Input() jsonPlan: any;
-
-    plan_: any;
-    numberOfLevels: number = 0;
-    numberOfNodes: number = 0;
-    jsonVisible = false;
-    jsonButtonDisabled = false;
-
-    constructor() {}
-
-    ngOnInit() {}
-
-    ngOnChanges() {
-        this.plan_ = this.plan;
-        /* If plan format is JSON analyze and augment for visualization */
-        if (this.planFormat === 'JSON') {
-            let summary : planCount = {nodesCnt:0, levelsCnt:0}
-            summary = this.analyzePlan(this.plan_, summary);
-            this.numberOfLevels = summary.levelsCnt;
-            this.numberOfNodes = summary.nodesCnt;
-            this.jsonVisible = false;
-            this.jsonButtonDisabled = false;
-        } else {
-            this.jsonVisible = true;
-            this.jsonButtonDisabled = true;
-        }
-	}
-
-    /*
-    * See the JSON contents inside of each node
-    */
-    showJSON() {
-        this.jsonVisible = !this.jsonVisible;
-    }
-
-    /*
-    * Check the merge paths, from operation ID
-    */
-    operation = [];
-    checkOperationId(operationId, levelsCnt){
-        console.log('LEVEL:' + levelsCnt + 'OP' + operationId)
-       // console.log(this.operation)
-        if (this.operation.length > 0) {
-            for (let i = 0; i < this.operation.length; i++) {
-                if (this.operation[i] === operationId) {
-                    console.log('found')
-                    console.log('BREAK')
-                    this.operation = [];
-                    return true;
-                }
-            }
-        }
-        this.operation.push(operationId);
-        console.log('not found')
-        return false;
-    }
-
-    /*
-    * Counts the number of nodes/operations in the tree
-    */
-    analyzePlan(plan, planCounter) {
-        planCounter.nodesCnt += 1;
-        planCounter.levelsCnt += 1;
-        let nodes = {}
-        nodes = plan;
-        // augment
-        if (nodes) {
-            nodes['visible'] = true;
-            nodes['viewDetails'] = false;
-            if (nodes['inputs']) {
-                for (let i = 0; i< nodes['inputs'].length; i++)
-                {
-                    planCounter = this.analyzePlan(nodes['inputs'][i], planCounter);
-                }
-            }
-        }
-        return planCounter;
-    }
-
-    /*
-    * See the JSON contents inside of each node, with pre-format
-    * Not used in this version
-    */
-    toggleViewDetails(plan) {
-        let nodes = {}
-        nodes = plan;
-        // augment
-        nodes['visible'] = true;
-        nodes['viewDetails'] = !nodes['viewDetails'];
-        if (nodes['inputs']) {
-            for (let i = 0; i< nodes['inputs'].length; i++)
-            {
-                this.toggleViewDetails(nodes['inputs'][i]);
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-viewer.component.html b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-viewer.component.html
new file mode 100644
index 0000000..a131353
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-viewer.component.html
@@ -0,0 +1,184 @@
+<!--/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/ -->
+<div *ngIf="plan_" class="plan-graph" id={{planName}}>
+  <div *ngIf="planFormat=='JSON'" class='panel' #navBar [class.sticky]="sticky" [class.collapsed]="collapsed">
+    <div class="plan-explorer-title">
+      <h2 class="heading-title">Plan Explorer</h2>
+    </div>
+    <div class="orientation">
+      <mat-form-field>
+        <h3 class="heading-title">View:</h3>
+        <mat-select [(ngModel)]="planOrientation" (ngModelChange)="setOrientation($event)">
+          <mat-option *ngFor="let orientation of orientations" [value]="orientation.value">
+            {{orientation.label}}
+          </mat-option>
+        </mat-select>
+      </mat-form-field>
+    </div>
+    <button mat-raised-button class="zoomFit-button other-buttons" (click)="fitGraph()" matTooltip="Zoom out to Fit Whole Plan">Zoom To Fit</button>
+    <h3 class="heading-title">Navigation:</h3>
+    <mat-checkbox class="detailed-checkbox" [checked]="detailed" (change)="setDetail($event.checked)">Detailed</mat-checkbox>
+    <h4 class="heading-title">Node:</h4>
+    <div>
+      <div class="node-nav">
+        <mat-form-field>
+          <mat-select [(ngModel)]="selectedNode" (ngModelChange)="panToNode($event)">
+            <mat-option *ngFor="let node of nodeIdsArr" [value]="node.value">
+              {{node.label}}
+            </mat-option>
+          </mat-select>
+        </mat-form-field>
+        <div class="node-mover">
+          <button mat-icon-button class="inc-dec" (click)="decrementNode()" matTooltip="Move Up a Node" [disabled]="nodeIdx == 0"><mat-icon>keyboard_arrow_up</mat-icon></button>
+          <button mat-icon-button class="inc-dec" (click)="incrementNode()" matTooltip="Move Down a Node" [disabled]="nodeIdx == nodesArr.length - 1"><mat-icon>keyboard_arrow_down</mat-icon></button>
+        </div>
+      </div>
+    </div>
+    <h4 class="heading-title" *ngIf="detailed">Variable:</h4>
+    <div class="variable-nav" *ngIf="detailed">
+      <div class="variable-select-move">
+        <mat-form-field class="variable-select">
+          <mat-label *ngIf="selectedVariableOccurrences == undefined">See Variable Occurrences</mat-label>
+          <mat-label *ngIf="selectedVariableOccurrences">Variable Occurrences ({{this.occurrenceArrayIdx+1}}/{{this.selectedVariableOccurrencesArray.length}})</mat-label>
+          <mat-select [(ngModel)]="selectedVariableOccurrences" (ngModelChange)="setSelectedVariableOccurrences($event)">
+            <mat-option *ngFor="let variable of variables" [value]="variable">
+              {{variable}}
+            </mat-option>
+          </mat-select>
+        </mat-form-field>
+        <div class="node-mover">
+          <button mat-icon-button class="inc-dec" [disabled]="!selectedVariableOccurrences" (click)="decrementOccurrence()" matTooltip="Move Up an Occurrence"><mat-icon>keyboard_arrow_up</mat-icon></button>
+          <button mat-icon-button class="inc-dec" [disabled]="!selectedVariableOccurrences" (click)="incrementOccurrence()" matTooltip="Move Down an Occurrence"><mat-icon>keyboard_arrow_down</mat-icon></button>
+        </div>
+      </div>
+      <div class="declaration-back-btns">
+        <button mat-raised-button class="other-buttons" [disabled]="!selectedVariableOccurrences"(click)="jumpToDeclaration()" matTooltip="Skip to Declaration">DECLARATION</button>
+        <button mat-raised-button class="undo-buttons" matTooltip="Jump back to Previous Node" [disabled]="previousNodeId == undefined" (click)="jumpBack()">BACK</button>
+      </div>
+    </div>
+    <h4 class="heading-title" *ngIf="detailed">Search:</h4>
+    <div class="search" *ngIf="detailed">
+      <div class="search-nav">
+        <mat-form-field>
+          <mat-label *ngIf="!matchesFound">Search</mat-label>
+          <mat-label *ngIf="matchesFound">Matches ({{this.matchIdx+1}}/{{this.searchMatches.length}})</mat-label>
+          <input matInput [(ngModel)]="searchRegex">
+        </mat-form-field>
+        <div class="node-mover">
+          <button mat-icon-button class="inc-dec" [disabled]="!matchesFound" (click)="decrementMatch()" matTooltip="Move up a Match"><mat-icon>keyboard_arrow_up</mat-icon></button>
+          <button mat-icon-button class="inc-dec" [disabled]="!matchesFound" (click)="incrementMatch()" matTooltip="Move down a Match"><mat-icon>keyboard_arrow_down</mat-icon></button>
+        </div>
+      </div>
+      <button class="search-btn" mat-raised-button (click)="onClickSearch()" [disabled]="searchRegex == ''">SEARCH</button>
+    </div>
+    <button class="clear-btn" mat-raised-button (click)="clearSelections()">CLEAR</button>
+  </div>
+  <div class="divider">
+    <div *ngIf="!jsonVisible" class="plan">
+      <ngx-graph
+        layout="dagre"
+        [view]="[850,800]"
+        [showMiniMap]="true"
+        [zoomToFit$]="zoomToFit$"
+        [center$]="center$"
+        [panToNode$]="panToNode$"
+        [enableZoom]="true"
+        [links]="edgesArr"
+        [nodes]="nodesArr"
+        [draggingEnabled]="false"
+        [zoomSpeed]="0.025"
+        [update$]="update$"
+        [layoutSettings]="{
+                orientation: planOrientation,
+                alignment: 'C',
+                nodePadding: 250,
+                rankPadding: 50
+              }">
+        <ng-template #defsTemplate>
+          <svg:marker id="arrow" viewBox="0 -5 10 10" refX="8" refY="0" markerWidth="5" markerHeight="5" orient="auto">
+            <svg:path d="M0,-5L10,0L0,5" class="arrow-head" />
+          </svg:marker>
+        </ng-template>
+
+        <ng-template #nodeTemplate let-node>
+          <svg:g class="node">
+            <svg:rect *ngIf="!node.selected"
+                      [attr.width]="node.dimension.width"
+                      [attr.height]="node.dimension.height"
+                      [attr.rx] = "15"
+                      [attr.fill]="node.color"
+                      fill-opacity="0.2"
+                      stroke="black"
+                      stroke-width="1.5"
+            />
+            <svg:rect *ngIf="node.selected"
+                      [attr.width]="node.dimension.width"
+                      [attr.height]="node.dimension.height"
+                      [attr.rx] = "15"
+                      [attr.fill] = "node.color"
+                      fill-opacity="0.0"
+                      stroke="black"
+                      stroke-width="4.5"
+            />
+            <svg:rect *ngIf="node.selected"
+                      [attr.width]="node.dimension.width"
+                      [attr.height]="node.dimension.height"
+                      [attr.rx] = "15"
+                      [attr.fill]="node.color"
+                      fill-opacity="0.2"
+                      stroke="red"
+                      stroke-width="3"
+            />
+            <svg:text *ngIf="!detailed" dominant-baseline="middle" text-anchor="middle" [attr.y]="node.dimension.height / 2" [attr.x]="node.dimension.width / 2" >
+              {{node.label}}
+            </svg:text>
+
+            <svg:text *ngIf="detailed" alignment-baseline="top" text-anchor="middle">
+              <svg:tspan [attr.x]="node.dimension.width / 2" dy="1.5em">
+
+              </svg:tspan>
+              <svg:tspan [attr.x]="node.dimension.width / 2" dy="1.5em">
+                {{node.detailed_label}}
+              </svg:tspan>
+              <svg:tspan [attr.x]="node.dimension.width / 2" dy="1.5em">
+                {{node.physical_operator}}
+              </svg:tspan>
+              <svg:tspan [attr.x]="node.dimension.width / 2" dy="1.5em">
+                {{node.execution_mode}}
+              </svg:tspan>
+              <ng-container *ngFor="let details of node.details | keyvalue">
+                <svg:tspan [attr.x]="node.dimension.width / 2" dy="1.5em">
+                  {{details.value}}
+                </svg:tspan>
+              </ng-container>
+              <svg:tspan [attr.x]="node.dimension.width / 2" dy="1.5em">
+                _
+              </svg:tspan>
+            </svg:text>
+          </svg:g>
+        </ng-template>
+        <ng-template #linkTemplate let-link>
+          <svg:g class="edge">
+            <svg:path class="line" stroke-width="2" marker-end="url(#arrow)"></svg:path>
+          </svg:g>
+        </ng-template>
+      </ngx-graph>
+    </div>
+    <div *ngIf="jsonVisible" class="json">
+      <div class='center'>
+        <pre class="json-content">{{jsonPlan}}</pre>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-viewer.component.scss b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-viewer.component.scss
new file mode 100644
index 0000000..6c40a68
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-viewer.component.scss
@@ -0,0 +1,231 @@
+/*
+Licensed 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.
+*/
+
+/*
+.plan-graph {
+  display: block;
+  margin: 0 0px 0 0px;
+  padding: 0;
+  width: 100%;
+  mat-expansion-panel {
+    border: none !important;
+  }
+}
+ */
+
+.plan-graph {
+  display: flex;
+  flex-flow: row;
+  margin: 0 0px 0 0px;
+  padding: 0;
+  width: 100%;
+  mat-expansion-panel {
+    border: none !important;
+  }
+}
+
+.panel {
+  order: 2;
+  display: flex;
+  flex-flow: column;
+  justify-content: stretch;
+  align-items: flex-start;
+  border: 1.5px solid grey;
+  border-radius: 15px;
+  margin-left: auto;
+  height: 100%;
+}
+
+.panel > * {
+  margin-left: 10px;
+  margin-right: 10px;
+}
+
+.plan-explorer-title {
+  width: 100%;
+  text-align: center;
+  margin-left: 0px !important;
+  margin-right: 0px !important;
+}
+
+.heading-title {
+  color: blue;
+  margin-bottom: 5px;
+  margin-top: 5px;
+}
+
+.mat-icon-button.inc-dec {
+  width: 25px;
+  height: 25px;
+  line-height: 25px;
+
+  .mat-icon {
+    font-size: 25px;
+    width: 25px;
+    height: 25px;
+    line-height: 25px;
+  }
+}
+
+.plan-header {
+  max-height: 42px;
+  min-height: 42px;
+  font-size: 0.80rem;
+  font-weight: 500;
+  border-bottom: 1px solid gray;
+}
+
+.divider {
+  display: flex;
+  flex-flow: row;
+  align-items: flex-start;
+}
+
+.plan {
+  margin: 0px;
+  padding: 20px;
+  padding-left: 50px;
+  padding-right: 50px;
+  margin-right: auto;
+  margin-left: auto;
+  padding-left: 50px;
+  min-height: 800px;
+  max-height: 1500px;
+  border: 1.5px solid grey;
+  border-radius: 15px;
+}
+
+.variable-nav {
+  display: flex;
+  flex-flow: column;
+}
+
+.declaration-back-btns {
+  display: flex;
+  flex-flow: row;
+  justify-content: space-between;
+}
+
+.variable-select-move {
+  display: flex;
+  flex-flow: row;
+  justify-content: flex-start;
+}
+
+.search-nav {
+  display: flex;
+  flex-flow: row;
+  justify-content: flex-start;
+}
+
+.search {
+  display: flex;
+  flex-flow: column;
+  align-items: flex-start;
+}
+
+.search-btn {
+  align-self: center;
+  width: 100%;
+}
+
+.node-nav {
+  display: flex;
+  flex-float: row;
+}
+
+.node-mover {
+  display: flex;
+  flex-flow: column;
+  overflow: auto;
+}
+
+
+.plan1 {
+  display: flow;
+  flex-flow: row;
+}
+
+.content {
+  margin-top: 20px;
+  display: block;
+  font-size: 0.80rem;
+  font-weight: 500;
+}
+
+.json {
+  //padding: 20px;
+  //padding-right: 50px;
+  //margin-right: 25px;
+  min-width: 100%;
+  max-width: 100%;
+}
+
+.json-content {
+  //border-left: 1px solid gray;
+  padding-left: 25px; //display: inline-block;
+  //margin: auto;
+}
+
+.button {
+  font-size: 12px !important;
+  float: right;
+  color: blue !important;
+  margin-bottom: 15px;
+}
+
+#wrapper {
+  position: relative;
+}
+
+.center {
+  margin-left: auto;
+  margin-right: auto;
+  width: 50%;
+}
+
+.sticky {
+  position: fixed;
+  top: 10px;
+  left: 50%;
+  background-color: white;
+  border: 1px solid grey;
+  border-radius: 5px;
+  width: auto;
+  transform: translateX(-50%);
+}
+
+
+.collapsed {
+  width: auto;
+}
+
+.minimize-btn {
+  align-self: flex-start;
+}
+
+.clear-btn {
+  margin-top: 10px;
+  margin-bottom: 10px;
+  align-self: center;
+}
+
+.orientation {
+  display: flex;
+  flex-flow: column;
+}
+
+.zoomFit-button {
+  align-self: center;
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-viewer.component.ts b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-viewer.component.ts
new file mode 100644
index 0000000..4dcee8a
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/plan-viewer.component.ts
@@ -0,0 +1,681 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+import {Component, Input, SimpleChange, HostListener, ViewChild, ElementRef} from '@angular/core';
+import { Subject } from "rxjs";
+
+export  interface planCount {
+  nodesCnt: number,
+  levelsCnt: number
+}
+
+@Component({
+  selector: 'plan-viewer',
+  templateUrl: 'plan-viewer.component.html',
+  styleUrls: ['plan-viewer.component.scss'],
+})
+
+export class PlanViewerComponent {
+
+  @Input() planFormat: any;
+  @Input() plan: any;
+  @Input() planName: any;
+  @Input() jsonPlan: any;
+
+  plan_: any;
+  jsonVisible = false;
+  detailed: boolean = false;
+  nodesArr: any[];
+  nodeIdsArr: any[] = [];
+  ids: any[] = [];
+  edgesArr: any[];
+
+  //search variables
+  flatJSONGraph: any = {};
+  searchRegex: string = "";
+  searchMatches: any[] = [];
+  matchesFound: boolean = false;
+  matchIdx: number = 0;
+
+  //variables for ngx-graph
+  zoomToFit$: Subject<boolean> = new Subject();
+  center$: Subject<boolean> = new Subject();
+  update$: Subject<boolean> = new Subject();
+  panToNode$: Subject<any> = new Subject();
+
+  //drop down variables
+  planOrientation = "BT";
+  selectedNode = "n11";
+
+  previousNodeId: any;
+  previouseOccurrenceArrayIdx: number;
+
+  selectedVariableOccurrences: any;
+  selectedVariableOccurrencesArray: any[];
+  occurrenceArrayIdx: number = 0;
+
+  nodeIdx = 0;
+  orientations: any[] = [
+    {
+      label: "Bottom to Top",
+      value: "BT"
+    },
+    {
+      label: "Top to Bottom",
+      value: "TB"
+    }
+    /*
+    Left to Right or Right to Left do not look right yet
+
+    {
+      label: "Left to Right",
+      value: "LR"
+    },
+    {
+      label: "Right to Left",
+      value: "RL"
+    }
+    */
+  ];
+
+  colors = [
+    "#00ff00",
+    "#0000ff",
+    "#ffff00",
+    "#8b008b",
+    "#ffa500",
+    "#ee82ee",
+    "#ff0000",
+    "#9acd32",
+    "#20b2aa",
+    "#00fa9a",
+    "#db7093",
+    "#eee8aa",
+    "#6495ed",
+    "#ff1493",
+    "#ffa07a",
+    "#2f4f4f",
+    "#8b4513",
+    "#006400",
+    "#808000",
+    "#483d8b",
+    "#000080"
+  ]
+
+  coloredNodes: any = {};
+
+  variablesOccurrences: any = {};
+  variablesDeclarations: any = {};
+  variables: any[];
+
+  constructor() {}
+
+  ngOnInit() {}
+
+  ngAfterViewInit() {
+  }
+
+  ngOnChanges() {
+    this.plan_ = this.plan;
+
+    /* If plan format is JSON analyze and augment for visualization */
+    if (this.planFormat === 'JSON') {
+      //clear previous plans results
+      this.nodesArr = [];
+      this.edgesArr = [];
+      this.nodeIdsArr = [];
+      this.ids = [];
+      this.selectedNode = 'n11';
+
+      this.previousNodeId = undefined;
+      this.previouseOccurrenceArrayIdx = undefined;
+
+      this.selectedVariableOccurrences = undefined;
+      this.selectedVariableOccurrencesArray = undefined;
+      this.occurrenceArrayIdx = 0;
+
+      this.nodeIdx = 0;
+
+      this.coloredNodes = {};
+
+      this.variablesOccurrences = {};
+      this.variablesDeclarations = {};
+      this.variables = undefined;
+
+      this.searchRegex = "";
+      this.searchMatches = [];
+      this.matchesFound = false;
+      this.matchIdx = 0;
+
+      let nodesSet = new Set();
+      let edgesSet = new Set();
+
+      let recurseResults = this.createLinksEdgesArray(this.plan_, this.nodesArr, this.edgesArr, nodesSet, edgesSet);
+
+      this.nodesArr = recurseResults[0];
+      this.edgesArr = recurseResults[1];
+
+      this.variables = Object.keys(this.variablesOccurrences);
+
+      //get declarations from variableOccurrences
+      for (let variable of this.variables) {
+        //extract first occurrence of variable (last because we parse from bottom->down)
+        this.variablesDeclarations[variable] = this.variablesOccurrences[variable][this.variablesOccurrences[variable].length-1];
+      }
+
+      this.jsonVisible = false;
+    } else {
+      this.jsonVisible = true;
+    }
+  }
+
+  /*
+  Function that makes the entire graph to fit the view
+   */
+  fitGraph() {
+    this.zoomToFit$.next(true);
+    this.center$.next(true);
+  }
+
+  /*
+  * Create links array and edges array for NgxGraphModule
+   */
+  createLinksEdgesArray(plan, nodesArr, edgesArr, nodesSet, edgesSet) {
+    let nodes = {};
+    nodes = plan;
+
+    let nodeToAdd = {};
+
+    if (nodes) {
+      if (!nodesSet.has(nodes['operatorId'])) {
+        nodesSet.add(nodes['operatorId']);
+        nodeToAdd['id'] = "n" + nodes['operatorId'];
+        nodeToAdd['id'] = nodeToAdd['id'].replace(/\./g, '');
+
+        //read variables and expressions from node and add to this.nodeOccurrences
+        this.storeVariablesExpressions(nodes, nodeToAdd['id']);
+
+        this.flatJSONGraph[nodeToAdd['id']] = "";
+
+        //logic for label
+        nodeToAdd['label'] = nodes['operatorId'] + " : " + nodes['operator'];
+
+        nodeToAdd['detailed_label'] = nodes['operatorId'] + " : " + nodes['operator'];
+        nodeToAdd['physical_operator'] = nodes['physical-operator'];
+        nodeToAdd['execution_mode'] = "[" + nodes['execution-mode'] + "]"
+
+        nodeToAdd["details"] = {};
+
+        nodeToAdd['selected'] = false;
+        nodeToAdd['operator'] = nodes['operator'];
+
+        //case for having both expressions and variables
+        if (nodes['expressions'] && nodes['variables']) {
+          nodeToAdd['detailed_label'] += `${this.variableExpressionStringify(nodes['expressions'])} <- ${this.variableExpressionStringify(nodes['variables'])}`;
+        }
+        //case for having only expressions
+        if (nodes['expressions'] && nodes['variables'] == undefined) {
+          nodeToAdd['detailed_label'] += this.variableExpressionStringify(nodes['expressions']);
+        }
+
+        //case for having only variables
+        if (nodes['variables'] && nodes['expressions'] == undefined) {
+          //if data scan, different
+          if (nodes['data-source']) {
+            nodeToAdd['details']['data-scan'] = `[]<-${this.variableExpressionStringify(nodes['variables'])}<-${nodes['data-source']}`;
+          }
+          //else
+          else
+            nodeToAdd['detailed_label'] += `(${this.variableExpressionStringify(nodes['variables'])})`;
+        }
+
+        //limit value
+        if (nodes['value']) {
+          nodeToAdd['detailed_label'] += ` ${nodes['value']}`;
+        }
+
+        //group by operator group-by list
+        if (nodes['group-by-list'])
+          nodeToAdd['details']['group_by_list'] = `group by ([${this.groupByListStringify(nodes['group-by-list'])}])`;
+
+        //group by operator decor-list
+        if (nodes['decor-list'])
+          nodeToAdd['details']['decor_list'] = `decor ([${this.groupByListStringify(nodes['decor-list'])}])`;
+
+        //join operator condition
+        if (nodes['condition']) {
+          nodeToAdd['details']['condition'] = `join condition (${nodes['condition']})`;
+        }
+
+        let nodeDropDown = {};
+        nodeDropDown['label'] = nodeToAdd['label'];
+        nodeDropDown['value'] = nodeToAdd['id'];
+        this.nodeIdsArr.push(nodeDropDown);
+
+        for (let val of Object.values(nodeToAdd)) {
+          this.flatJSONGraph[nodeToAdd['id']] += String(val).toLowerCase() + " ";
+        }
+
+        //Dynamic node coloring
+        if (nodeToAdd['operator'] in this.coloredNodes) {
+          nodeToAdd['color'] = this.coloredNodes[nodeToAdd['operator']];
+        } else {
+          if (this.colors.length > 1) {
+            let nodeColor = this.colors[0];
+            this.colors.splice(0, 1);
+            nodeToAdd['color'] = nodeColor;
+
+            this.coloredNodes[nodeToAdd['operator']] = nodeColor;
+          } else {
+            let nodeColor = "#ffffff";
+            nodeToAdd['color'] = nodeColor;
+
+            this.coloredNodes[nodeToAdd['operator']] = nodeColor;
+          }
+        }
+
+        this.ids.push(nodeToAdd['id']);
+
+        nodesArr.push(nodeToAdd);
+      }
+
+      if (nodes['inputs']) {
+        for (let i = 0; i < nodes['inputs'].length; i++) {
+          let edge = nodes['operatorId'].slice() + "to" + nodes['inputs'][i]['operatorId'].slice();
+          edge = edge.replace(/\./g, '');
+
+          if (!edgesSet.has(edge)) {
+            edgesSet.add(edge);
+
+            //create the edge
+            let edgeToAdd = {};
+
+            edgeToAdd['id'] = "e" + edge;
+            edgeToAdd['source'] = "n" + nodes['inputs'][i]['operatorId'];
+            edgeToAdd['source'] = edgeToAdd['source'].replace(/\./g, '');
+            edgeToAdd['target'] = "n" + nodes['operatorId'];
+            edgeToAdd['target'] = edgeToAdd['target'].replace(/\./g, '');
+
+            edgesArr.push(Object.assign(edgeToAdd, {}));
+          }
+
+
+          let recurseResult = this.createLinksEdgesArray(nodes['inputs'][i], nodesArr, edgesArr, nodesSet, edgesSet);
+
+          nodesArr = recurseResult[0];
+          edgesArr = recurseResult[1];
+          nodesSet = recurseResult[2];
+          edgesSet = recurseResult[3];
+
+        }
+      }
+    }
+
+    return [nodesArr, edgesArr, nodesSet, edgesSet];
+  }
+
+  /*
+  * Extracts variables and expressions and stores occurences in this.variablesOccurrences
+   */
+  storeVariablesExpressions(node, id) {
+    if (node['expressions']) {
+      if (node['operator'] == 'assign') {
+        for (let expression of node['expressions']) {
+          let matches = expression.match(/\$\$.*?(?=,|\])/g)
+
+          if (matches) {
+            for (let match of matches) {
+              this.addVariableExpression(match, id);
+            }
+          }
+        }
+      } else {
+        for (let expression of node['expressions']) {
+          this.addVariableExpression(expression, id);
+        }
+      }
+    }
+    if (node['variables']) {
+      for (let variable of node['variables']) {
+        this.addVariableExpression(variable, id);
+      }
+    }
+    if (node['group-by-list']) {
+      for (let item of node['group-by-list']) {
+        this.addVariableExpression(item.variable, id);
+        this.addVariableExpression(item.expression, id);
+      }
+    }
+    if (node['decor-list']) {
+      for (let item of node['decor-list']) {
+        this.addVariableExpression(item.variable, id);
+        this.addVariableExpression(item.expression, id);
+      }
+    }
+    if (node['condition'] || node['operator'] == 'exchange') {
+      //does this for joins or exchanges (HASH_PARTITION_EXCHANGE contains variables/expressions)
+      //regex extracts variables / expressions ($$ match until a ',' ']', or '('
+      let matches = node['physical-operator'].match(/\$\$.*?(?=,|\]|\()/g)
+
+      if (matches) {
+        for (let match of matches) {
+          this.addVariableExpression(match, id);
+        }
+      }
+    }
+  }
+
+  /*
+  * Helper function that creates a set if var/exp not in this.variableOccurrences, then stores the id of the node
+   */
+  addVariableExpression(varExp, id) {
+    if (!(varExp in this.variablesOccurrences)) {
+      this.variablesOccurrences[varExp] = [];
+    }
+    if (!(this.variablesOccurrences[varExp].includes(varExp)))
+      this.variablesOccurrences[varExp].push(id);
+  }
+
+  /*
+  * Conducts the string match in query plan viewer
+   */
+  onClickSearch() {
+    if (this.searchRegex != "") {
+      this.searchMatches = [];
+
+      for (let searchId in this.flatJSONGraph) {
+        if (this.flatJSONGraph[searchId].includes(String(this.searchRegex).toLowerCase())) {
+          this.searchMatches.push(searchId);
+        }
+      }
+
+      if (this.searchMatches.length > 0) {
+        //matches found
+        this.matchesFound = true;
+        this.matchIdx = 0;
+
+        let currentIdx = this.ids.indexOf(this.selectedNode);
+        let nextIdx = this.ids.indexOf(this.searchMatches[this.matchIdx]);
+
+        this.nodesArr[currentIdx]['selected'] = false;
+        this.nodesArr[nextIdx]['selected'] = true;
+
+        this.panToNode(this.ids[nextIdx]);
+        this.nodeIdx = nextIdx;
+      }
+    }
+  }
+
+  /*
+  * Function that resets all the navigation variables
+   */
+  clearSelections() {
+    //clear main selection variables
+    this.nodeIdx = 0;
+    this.selectedNode = "n11";
+
+    //clear variable occurrences variables
+    this.selectedVariableOccurrences = undefined;
+    this.selectedVariableOccurrencesArray = [];
+    this.occurrenceArrayIdx = 0;
+
+    //clear search variables
+    this.searchRegex = "";
+    this.searchMatches = [];
+    this.matchesFound = false;
+    this.matchIdx = 0;
+
+    for (let node of this.nodesArr) {
+      node['selected'] = false;
+    }
+
+    this.panToNode(this.selectedNode);
+  }
+
+  /*
+  * Select variable from variable occurrences drop down
+   */
+  setSelectedVariableOccurrences(variable) {
+    this.selectedVariableOccurrences = variable;
+
+    //set the array
+    this.selectedVariableOccurrencesArray = this.variablesOccurrences[this.selectedVariableOccurrences];
+
+    //set the selected node to the first in the array
+    this.panToNode(this.selectedVariableOccurrencesArray[0]);
+    this.occurrenceArrayIdx = 0;
+  }
+
+  /*
+  * Jumps to selected variable's declaration (first occurrence in a DFS)
+   */
+  jumpToDeclaration() {
+    this.previousNodeId = this.selectedNode;
+    this.previouseOccurrenceArrayIdx = this.occurrenceArrayIdx;
+
+    this.panToNode(this.variablesDeclarations[this.selectedVariableOccurrences])
+
+    this.occurrenceArrayIdx = this.selectedVariableOccurrencesArray.length - 1;
+  }
+
+  /*
+  * Jump back to previous node after a call to jumpToDeclaration()
+   */
+  jumpBack() {
+    this.panToNode(this.previousNodeId);
+
+    this.occurrenceArrayIdx = this.previouseOccurrenceArrayIdx;
+
+    this.previousNodeId = undefined;
+    this.previouseOccurrenceArrayIdx = undefined;
+  }
+
+  /*
+  * Sets the orientation of the graph (top to bottom, left to right, etc.)
+   */
+  setOrientation(orientation: string): void {
+    this.planOrientation = orientation;
+    this.update$.next(true);
+  }
+
+  setDetail(checked: boolean) {
+    this.detailed = checked;
+    this.update$.next(true);
+  }
+
+  /*
+  * Pans to node in the graph. Jumps to specified node and updates selection variables
+   */
+  panToNode(id: any) {
+    this.selectedNode = id;
+
+    this.nodesArr[this.nodeIdx]['selected'] = false;
+
+    let nodeIdx = this.nodesArr.map(function(e) { return e.id}).indexOf(id);
+    this.nodeIdx = nodeIdx;
+    this.nodesArr[nodeIdx]['selected'] = true;
+
+    this.panToNode$.next(id);
+    this.update$.next(true);
+  }
+
+  /*
+  * Increments current node in graph (going "down" the graph in a DFS)
+   */
+  incrementNode() {
+    let currentIdx = this.ids.indexOf(this.selectedNode);
+    if (currentIdx + 1 < this.nodesArr.length) {
+      this.nodesArr[currentIdx]['selected'] = false;
+      this.nodesArr[currentIdx + 1]['selected'] = true;
+      this.panToNode(this.ids[currentIdx + 1]);
+      this.nodeIdx = currentIdx + 1;
+    }
+  }
+
+  /*
+  * Decrements current node in graph (going "up" the graph in a DFS)
+   */
+  decrementNode() {
+    let currentIdx = this.ids.indexOf(this.selectedNode);
+
+    if (currentIdx - 1 >= 0) {
+      this.nodesArr[currentIdx]['selected'] = false;
+      this.nodesArr[currentIdx-1]['selected'] = true;
+      this.panToNode(this.ids[currentIdx - 1]);
+      this.nodeIdx = currentIdx - 1;
+    }
+  }
+
+  /*
+  * Increments current node but in occurrence (Jumping to the next occurrence of the selected variable)
+   */
+  incrementOccurrence() {
+    let currentIdx = this.ids.indexOf(this.selectedNode);
+    if (this.occurrenceArrayIdx + 1 < this.selectedVariableOccurrencesArray.length) {
+      let nextIdx = this.ids.indexOf(this.selectedVariableOccurrencesArray[this.occurrenceArrayIdx + 1]);
+
+      this.nodesArr[currentIdx]['selected'] = false;
+      this.nodesArr[nextIdx]['selected'] = true;
+
+      this.panToNode(this.ids[nextIdx]);
+      this.nodeIdx = nextIdx;
+      this.occurrenceArrayIdx += 1;
+    } else {
+      //wrap around to first item
+      let nextIdx = this.ids.indexOf(this.selectedVariableOccurrencesArray[0]);
+
+      this.nodesArr[currentIdx]['selected'] = false;
+      this.nodesArr[nextIdx]['selected'] = true;
+
+      this.panToNode(this.ids[nextIdx]);
+      this.nodeIdx = nextIdx;
+      this.occurrenceArrayIdx = 0;
+    }
+  }
+
+  /*
+  * Decrements current node but in occurrence (Jumping to the previous occurrence of the selected variable)
+   */
+  decrementOccurrence() {
+    let currentIdx = this.ids.indexOf(this.selectedNode);
+    if (this.occurrenceArrayIdx - 1 >= 0) {
+      let nextIdx = this.ids.indexOf(this.selectedVariableOccurrencesArray[this.occurrenceArrayIdx - 1]);
+
+      this.nodesArr[currentIdx]['selected'] = false;
+      this.nodesArr[nextIdx]['selected'] = true;
+
+      this.panToNode(this.ids[nextIdx]);
+      this.nodeIdx = nextIdx;
+      this.occurrenceArrayIdx -= 1;
+    } else {
+      //wrap around to last item
+      let nextIdx = this.ids.indexOf(this.selectedVariableOccurrencesArray[this.selectedVariableOccurrencesArray.length - 1]);
+
+      this.nodesArr[currentIdx]['selected'] = false;
+      this.nodesArr[nextIdx]['selected'] = true;
+
+      this.panToNode(this.ids[nextIdx]);
+      this.nodeIdx = nextIdx;
+      this.occurrenceArrayIdx = this.selectedVariableOccurrencesArray.length - 1;
+    }
+  }
+
+  /*
+  * Increments current node but in search match (Jumping to the next occurrence of the search results)
+   */
+  incrementMatch() {
+    let currentIdx = this.ids.indexOf(this.selectedNode);
+    if (this.matchIdx + 1 < this.searchMatches.length) {
+      let nextIdx = this.ids.indexOf(this.searchMatches[this.matchIdx + 1]);
+
+      this.nodesArr[currentIdx]['selected'] = false;
+      this.nodesArr[nextIdx]['selected'] = true;
+
+      this.panToNode(this.ids[nextIdx]);
+      this.nodeIdx = nextIdx;
+      this.matchIdx += 1;
+    } else {
+      //wrap around to first item
+      let nextIdx = this.ids.indexOf(this.searchMatches[0]);
+
+      this.nodesArr[currentIdx]['selected'] = false;
+      this.nodesArr[nextIdx]['selected'] = true;
+
+      this.panToNode(this.ids[nextIdx]);
+      this.nodeIdx = nextIdx;
+      this.matchIdx = 0;
+    }
+  }
+
+  /*
+  * Decrements current node but in search match (Jumping to the previous occurrence of the search results)
+   */
+  decrementMatch() {
+    let currentIdx = this.ids.indexOf(this.selectedNode);
+    if (this.matchIdx - 1 >= 0) {
+      let nextIdx = this.ids.indexOf(this.searchMatches[this.matchIdx - 1]);
+
+      this.nodesArr[currentIdx]['selected'] = false;
+      this.nodesArr[nextIdx]['selected'] = true;
+
+      this.panToNode(this.ids[nextIdx]);
+      this.nodeIdx = nextIdx;
+      this.matchIdx -= 1;
+    } else {
+      //wrap around to last item
+      let nextIdx = this.ids.indexOf(this.searchMatches[this.searchMatches.length - 1]);
+
+      this.nodesArr[currentIdx]['selected'] = false;
+      this.nodesArr[nextIdx]['selected'] = true;
+
+      this.panToNode(this.ids[nextIdx]);
+      this.nodeIdx = nextIdx;
+      this.matchIdx = this.searchMatches.length - 1;
+    }
+  }
+
+  /*
+  * Function takes in array of objects and stringifies
+   */
+  groupByListStringify(variables: any[]) {
+    let buildString = "";
+
+    let listSize = variables.length;
+    let counter = 0;
+
+    for (let variable of variables) {
+      if (counter < listSize - 1) {
+        buildString += variable['variable'] + " := " + variable['expression'] + "; ";
+      } else {
+        buildString += variable['variable'] + " := " + variable['expression'];
+      }
+
+      counter++;
+    }
+
+    return buildString;
+  }
+
+  /*
+  * Function that stringifys variables / objects array
+   */
+  variableExpressionStringify(arr: any[]) {
+    if (arr.length == 1) {
+      return "[" + arr[0] + "]";
+    } else {
+      return "[" + arr.toString() + "]";
+    }
+  }
+}
+
+
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/query-container.component.html b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/query-container.component.html
index a2e0d3e..ad40a2f 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/query-container.component.html
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/query-container.component.html
@@ -11,12 +11,14 @@ 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="query-container">
-    <div class="content">
-        <awc-query class="input-card"></awc-query>
-        <awc-results class="output-card"></awc-results>
-    </div>
-    <div *ngIf="visible" class="drawer">
-        <awc-metadata></awc-metadata>
-    </div>
-</div>
\ No newline at end of file
+  <div class="content">
+    <awc-query (inputToOutputEmitter)="sendInputToOutput($event)" (isErrorEmitter)="sendIsError($event)" (hideOutputEmitter)="sendHideOutput($event)" class="input-card"></awc-query>
+    <awc-results [inputToOutput]="inputToOutput" [isError]="isError" [hideOutput]="hideOutput" class="output-card"></awc-results>
+  </div>
+  <div *ngIf="visible" class="drawer">
+    <awc-metadata></awc-metadata>
+  </div>
+</div>
+
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/query-container.component.scss b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/query-container.component.scss
index 137721e..cdc901b 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/query-container.component.scss
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/query-container.component.scss
@@ -33,7 +33,10 @@
 }
 
 .content {
+    display: flex;
+    flex-flow: column;
     width: 100%;
+    overflow: auto;
 }
 
 .query-card {
@@ -52,4 +55,4 @@
     width: 100%;
     margin: 0;
     padding: 0;
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/query-container.component.ts b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/query-container.component.ts
index aff5e50..236a5ae 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/query-container.component.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/query-container.component.ts
@@ -28,18 +28,29 @@ import { Observable } from 'rxjs';
 
 export class QueryContainerComponent implements AfterViewInit {
     sideMenuVisible$: Observable<any>;
-    visible = false;
+    visible = true;
+    inputToOutput: Object;
+    isError: boolean;
+    hideOutput: boolean;
 
     constructor(private store: Store<any>) {}
 
     ngAfterViewInit() {
         this.sideMenuVisible$ = this.store.select(s => s.app.sideMenuVisible);
         this.sideMenuVisible$.subscribe((data: any) => {
-            if (data === true) {
-                this.visible = true;
-            } else {
-                this.visible = false;
-            }
+          this.visible = true;
         })
     }
-}
\ No newline at end of file
+
+    sendInputToOutput(inputToOutputData) {
+      this.inputToOutput = inputToOutputData;
+    }
+
+    sendIsError(isErrorData) {
+      this.isError = isErrorData;
+    }
+
+    sendHideOutput(hideOutputData) {
+      this.hideOutput = hideOutputData;
+    }
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/tree-view.component.html b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/tree-view.component.html
index b037a11..f6b0533 100644
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/tree-view.component.html
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/tree-view.component.html
@@ -14,22 +14,23 @@ limitations under the License.
 <div id='top' *ngIf='treeData' class='tree-card'>
     <mat-expansion-panel hideToggle [expanded]="true">
         <mat-expansion-panel-header class='header header-centered-v'>
-            <mat-panel-title>OUTPUT DATA</mat-panel-title>
+            <mat-panel-title>QUERY OUTPUT</mat-panel-title>
             <mat-icon>format_list_numbered_rtl</mat-icon>
         </mat-expansion-panel-header>
         <mat-panel-description class='content'>
             <div class='panel'>
-                <!--<span class='summary' >Items: {{metrics.resultCount}} Size: {{metrics.resultSizeKb}} Kb</span>-->
-                <mat-paginator [showFirstLastButtons]="true" [length]='metrics.resultCount' [pageSize]='pagedefaults.pageSize' [pageSizeOptions]='pageSizeOptions' (page)='showResults($event, false)'>
-                    </mat-paginator>
                 <span class='options'>
-                    <button mat-button class='button' (click)='dataExpand()' [disabled]= 'checkView()' matTooltip="Expand Data"><mat-icon>add_circle</mat-icon></button>
-                    <button mat-button class='button' (click)='dataCollapse()' [disabled]= 'checkView()' matTooltip="Collapse Data"><mat-icon>remove_circle_outline</mat-icon></button>
-                    <button mat-button class='button button-json' (click)='showTable()' matTooltip="Show Table View">TABLE</button>
-                    <button mat-button class='button button-json' (click)='showTree()' matTooltip="Show Tree View">TREE</button>
-                    <button mat-button class='button button-json' (click)='showJSON()' matTooltip="Show JSON View">JSON</button>
-                    <button id='export' mat-button class='button' (click)='exportToText()' matTooltip="Export JSON file to Computer">EXPORT</button>
+                    <button mat-button [ngClass]="this.tableVisible === true ? 'button button-json selected' : 'button button-json'" (click)='showTable()' matTooltip="Show Table View">TABLE</button>
+                    <button mat-button *ngIf="isCSV == false;" [ngClass]="this.jsonVisible === true ? 'button button-json selected' : 'button button-json'" (click)='showJSON()' matTooltip="Show JSON View">JSON</button>
+                    <button mat-button *ngIf="isCSV == false;" [ngClass]="this.treeVisible === true ? 'button button-json selected' : 'button button-json'" (click)='showTree()' matTooltip="Show Tree View">TREE</button>
+
+                    <button mat-button [ngClass]="this.planVisible === true ? 'button button-json selected' : 'button button-json'" (click)='showPlan()' matTooltip="Show Plan Viewer">PLAN</button>
+
+                    <button *ngIf="isCSV" mat-button class='button export' (click)='openJSONExportPicker()' matTooltip="Export CSV file to Computer">EXPORT</button>
+                    <button *ngIf="isCSV == false" mat-button class='button export' (click)='openJSONExportPicker()' matTooltip="Export JSON/JSONL file to Computer">EXPORT</button>
                 </span>
+                <mat-paginator *ngIf="this.planVisible === false" [showFirstLastButtons]="true" [length]='metrics.resultCount' [pageSize]='pagedefaults.pageSize' [pageSizeOptions]='pageSizeOptions' (page)='showResults($event, false)'>
+                </mat-paginator>
             </div>
             <div *ngIf='treeVisible' class='navi-data' class='navi-data'>
                 <mat-icon class='navi-path'>link</mat-icon>
@@ -37,7 +38,7 @@ limitations under the License.
             </div>
             <div class='divider'>
                 <div *ngIf='tableVisible'>
-                    <table mat-table [dataSource]="dataSource" class='items-table table-responsive'>
+                    <table mat-table *ngIf='dataSource' [dataSource]="dataSource" class='items-table table-responsive'>
                         <ng-container matColumnDef="{{col}}" *ngFor="let col of displayedColumns">
                             <th mat-header-cell *matHeaderCellDef class='cell'>{{col}}</th>
                             <td mat-cell *matCellDef="let element"class='cell' >{{element[col]}}</td>
@@ -52,6 +53,9 @@ limitations under the License.
                 <div *ngIf='jsonVisible' class='json'>
                     <pre>{{jsonData}}</pre>
                 </div>
+                <div *ngIf="planVisible" class="plan">
+                  <plan-viewer [planFormat]="planFormat" [jsonPlan]="jsonPlan" [plan]="plan" [planName]="planName"></plan-viewer>
+                </div>
                 <div id='bottom'></div>
             </div>
             <button *ngIf='showGoTop' mat-fab color='primary' class='button back-button' (click)='gotoTop()'>
@@ -59,4 +63,4 @@ limitations under the License.
             </button>
         </mat-panel-description>
     </mat-expansion-panel>
-</div>
\ No newline at end of file
+</div>
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/tree-view.component.scss b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/tree-view.component.scss
index 5c2546f..6cc1283 100644
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/tree-view.component.scss
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/tree-view.component.scss
@@ -23,6 +23,7 @@ limitations under the License.
     display: flex;
     flex-flow: row;
     align-items: center;
+    justify-content: space-between;
     font-size: 12px;
     padding-top: 15px;
     border-bottom: 1px dashed gray;
@@ -53,6 +54,7 @@ limitations under the License.
     border: 1px dashed gainsboro;
     padding: 20px;
     overflow: auto;
+    min-width: 0;
 }
 
 .content {
@@ -82,6 +84,10 @@ limitations under the License.
     color: blue !important;
 }
 
+.selected {
+    border: 2px solid blue !important;
+}
+
 .navi-data {
     display: flex;
     flex-flow: row;
@@ -110,6 +116,10 @@ limitations under the License.
     font-size: 14px;
 }
 
+.export {
+  align-self: flex-end !important;
+}
+
 .summary {
     float: left;
     margin-right: 15px;
@@ -169,4 +179,12 @@ tr.example-element-row:not(.example-expanded-row):active {
         overflow: hidden;
         vertical-align: middle;
     }
-}
\ No newline at end of file
+}
+
+.plan {
+  width: 100%;
+}
+
+.plan-new-tab {
+  margin-left: auto;
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/tree-view.component.ts b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/tree-view.component.ts
index f1437d9..9818973 100644
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/tree-view.component.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/tree-view.component.ts
@@ -11,11 +11,17 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
-import { Component, Input, NgZone, SimpleChange, ViewChild } from '@angular/core';
-import { MatTableDataSource } from '@angular/material/table';
+import { Component, Input, NgZone, SimpleChange, ViewChild, Inject } from '@angular/core';
+import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
+import {MatFooterCell, MatTableDataSource} from '@angular/material/table';
 import { saveAs } from 'file-saver';
 import * as cloneDeep from 'lodash/cloneDeep';
 
+export interface DialogData {
+  exportFormat: string;
+  fileName: string;
+}
+
 @Component({
     selector: 'tree-view',
     templateUrl: 'tree-view.component.html',
@@ -25,10 +31,23 @@ import * as cloneDeep from 'lodash/cloneDeep';
 export class TreeViewComponent {
     @Input() data: any;
     @Input() queryId: any;
-
-    jsonVisible: any = false;
-    tableVisible: any = true;
+    @Input() planFormat: any;
+    @Input() plan: any;
+    @Input() planName: any;
+    @Input() jsonPlan: any;
+    @Input() inputToOutput: Object;
+
+    isExplain: boolean = false;
+    outputFormat: string = 'json';
+    isCSV: boolean = false;
+    hasHeader: boolean = false;
+    exportFormat: string = 'json';
+    exportFileName: string = 'asterixdb-query-results';
+
+    jsonVisible: any = true;
+    tableVisible: any = false;
     treeVisible: any = false;
+    planVisible: any = false;
     jsonData: any;
     jsonPath_: any = ': < JSON PATH >';
     rawData: any;
@@ -54,7 +73,7 @@ export class TreeViewComponent {
 
     private eventOptions: boolean|{capture?: boolean, passive?: boolean};
 
-    constructor( private ngZone: NgZone) {}
+    constructor( private ngZone: NgZone, public dialog: MatDialog) {}
 
     ngOnInit() {
         this.ngZone.runOutsideAngular(() => {
@@ -63,9 +82,48 @@ export class TreeViewComponent {
     }
 
     ngOnChanges(changes: SimpleChange) {
-        this.rawData = this.data['results'];
-        if (this.rawData) {
-            this.showResults(this.pagedefaults, this.EXPANDED);
+        if (this.inputToOutput) {
+            this.jsonVisible = true;
+            this.planVisible = false;
+            this.tableVisible = false;
+            this.treeVisible = false;
+            this.viewMode = 'JSON';
+
+            this.isExplain = this.inputToOutput['isExplain'];
+            this.outputFormat = this.inputToOutput['outputFormat'];
+
+            if (this.outputFormat == 'CSV_header') {
+              this.hasHeader = true;
+              this.outputFormat = 'CSV';
+            }
+
+            if (this.outputFormat == 'CSV' && !this.isExplain) {
+              this.exportFileName = 'asterixdb-query-results';
+              this.exportFormat = 'csv'
+              this.isCSV = true;
+              this.jsonVisible = false;
+              this.tableVisible = true;
+              this.viewMode = "TABLE";
+            }
+
+            if (this.data.length == 0) {
+              //case where 0 objects are returned
+              this.rawData = [];
+              this.showResults(this.pagedefaults, this.EXPANDED);
+            } else {
+              this.rawData = this.data['results'];
+              if (this.rawData) {
+                this.showResults(this.pagedefaults, this.EXPANDED);
+              }
+            }
+
+            if (this.isExplain) {
+              this.jsonVisible = false;
+              this.planVisible = true;
+              this.tableVisible = false;
+              this.treeVisible = false;
+              this.viewMode = "PLAN";
+            }
         }
     }
 
@@ -81,27 +139,48 @@ export class TreeViewComponent {
     showResults(range, expanded) {
         this.currentRange = range;
         this.currentIndex = this.currentRange.pageIndex;
-        this.treeData = this.rawData.filter(this.filter, this.currentRange);
-        // Build the dynamic table column names
-        // Flat the results to display in a table
-        this.BuildTableFlatData(this.treeData);
 
-        if (this.treeData.length > 0) {
+        if (this.rawData.length > 0) {
+          this.treeData = this.rawData.filter(this.filter, this.currentRange);
+          // Build the dynamic table column names
+          // Flat the results to display in a table
+          this.BuildTableFlatData(this.treeData);
+
+          if (this.treeData.length > 0) {
             this.metrics = this.data['metrics'];
             this.metrics['resultSizeKb'] = (this.metrics.resultSize/1024).toFixed(2);
             var myData_ = [];
             for (let i = 0; i < this.treeData.length; i++) {
-                // mat-paginator start counting from 1, thats why the i+1 trick
-                myData_.push(this.generateTree(this.treeData[i], '/', {}, (this.currentRange.pageSize * this.currentRange.pageIndex) + (i + 1), 0, expanded));
+              // mat-paginator start counting from 1, thats why the i+1 trick
+              myData_.push(this.generateTree(this.treeData[i], '/', {}, (this.currentRange.pageSize * this.currentRange.pageIndex) + (i + 1), 0, expanded));
             }
 
             this.treeData_ = myData_;
             /* Prepare the JSON view */
             this.jsonData = JSON.stringify(this.treeData, null, 8)
-        } else {
+          } else {
             console.log('no data')
             this.treeData = [];
+          }
+        } else {
+          this.treeData = [];
+          this.jsonData = JSON.stringify([ ], null, 8);
+          this.metrics = {"resultCount": 0};
+
+          //clear tree data
+          var myData_ = [];
+          for (let i = 0; i < this.treeData.length; i++) {
+            // mat-paginator start counting from 1, thats why the i+1 trick
+            myData_.push(this.generateTree(this.treeData[i], '/', {}, (this.currentRange.pageSize * this.currentRange.pageIndex) + (i + 1), 0, expanded));
+          }
+
+          this.treeData_ = myData_;
+
+          //clear table data
+          this.dataSource.data = [];
+          this.displayedColumns = [];
         }
+
     }
 
     /*
@@ -111,6 +190,8 @@ export class TreeViewComponent {
         this.jsonVisible = true;
         this.treeVisible = false;
         this.tableVisible = false;
+        this.planVisible = false;
+        this.viewMode = 'JSON'
     }
 
     /*
@@ -120,6 +201,7 @@ export class TreeViewComponent {
         this.jsonVisible = false;
         this.treeVisible = false;
         this.tableVisible = true;
+        this.planVisible = false;
         this.viewMode = 'TABLE';
     }
 
@@ -130,25 +212,63 @@ export class TreeViewComponent {
         this.jsonVisible = false;
         this.treeVisible = true;
         this.tableVisible = false;
+        this.planVisible = false;
         this.viewMode = 'TREE';
     }
 
     /*
+    * Shows Plan Viewer Mode
+     */
+    showPlan() {
+      this.jsonVisible = false;
+      this.treeVisible = false;
+      this.tableVisible = false;
+      this.planVisible = true;
+      this.viewMode = 'PLAN';
+    }
+
+    /*
     * Export to CSV
     */
     exportToCSV(){
-        var exportOutput = JSON.stringify(this.rawData, null, 4);
-        var blob = new Blob([this.jsonData], {type: "text/csv;charset=utf-8"});
-        saveAs(blob, "Asterix-results.csv");
+      var csvJoin = this.rawData.join("");
+      var blob = new Blob([csvJoin], {type: "text/csv;charset=utf=8"});
+      if (this.exportFileName == "") {
+        saveAs(blob, "asterixdb-query-results.csv");
+        this.exportFileName = "asterixdb-query-results";
+      }
+      else {
+        saveAs(blob, this.exportFileName + ".csv");
+      }
     }
 
     /*
-    *  Export to plain text
+    *  Export to JSON
     */
-    exportToText(){
+    exportToJSON(){
         var exportOutput = JSON.stringify(this.rawData, null, 4);
         var blob = new Blob([exportOutput], {type: "text/json;charset=utf-8"});
-        saveAs(blob, "Asterix-results.json");
+        if (this.exportFileName == "") {
+          saveAs(blob, "asterixdb-query-results.json");
+          this.exportFileName = "asterixdb-query-results";
+        }
+        else
+          saveAs(blob, this.exportFileName + ".json");
+    }
+
+    /*
+    * Export to JSON Lines (JSONL)
+    */
+    exportToJSONLines() {
+      var exportOutput = this.jsonlinesTransform(this.rawData);
+      var blob = new Blob([exportOutput], {type: "text/json;charset=utf-8"});
+      if (this.exportFileName == "") {
+        saveAs(blob, "asterixdb-query-results.jsonl");
+        this.exportFileName = "asterixdb-query-results";
+      }
+
+      else
+        saveAs(blob, this.exportFileName + ".jsonl");
     }
 
     /*
@@ -174,38 +294,42 @@ export class TreeViewComponent {
         // Going through all the keys in a node looking for objects or array of key values
         // and create a sub menu if is an object.
         let nodeArray = [];
-        Object.keys(node).map((k) => {
-            if (typeof node[k] === 'object') {
-                let nodeObject = { nested: true, item: '', label: '', key: '', value: '', type: '', link: '/', visible: expanded, children: [], level: level };
-                nodeObject.item = index;
-                nodeObject.label = k;
-                nodeObject.key = k;
-                nodeObject.value = node[k];
-                nodeObject.link = nodeLink + '/' + k;
-                nodeObject.level = level;
-                level = level + 1;
-                if(Array.isArray(node[k]) ){
-                    nodeObject.type = 'ARRAY';
-                } else {
-                    nodeObject.type = 'OBJECT';
-                }
-                var newNodeObject = this.generateTree(node[k], nodeObject.link, nodeObject, index, level, expanded);
-                if (nodeRoot.children) {
-                    nodeRoot.children.push(newNodeObject)
-                }
+
+        if (node != null) {
+          Object.keys(node).map((k) => {
+            if (typeof node[k] === 'object' && (node[k] != null || node[k] != undefined)) {
+              let nodeObject = { nested: true, item: '', label: '', key: '', value: '', type: '', link: '/', visible: expanded, children: [], level: level };
+              nodeObject.item = index;
+              nodeObject.label = k;
+              nodeObject.key = k;
+              nodeObject.value = node[k];
+              nodeObject.link = nodeLink + '/' + k;
+              nodeObject.level = level;
+              level = level + 1;
+              if(Array.isArray(node[k]) ){
+                nodeObject.type = 'ARRAY';
+              } else {
+                nodeObject.type = 'OBJECT';
+              }
+              var newNodeObject = this.generateTree(node[k], nodeObject.link, nodeObject, index, level, expanded);
+              if (nodeRoot.children) {
+                nodeRoot.children.push(newNodeObject)
+              }
             }
             else {
-                // key values converted into a unique string with a : separator
-                let nodeKeyValue = { nested: false, item: '', label: '', key: '', value: '', type: 'KEYVALUE', link: '/', visible: expanded, children: [], level: level};
-                nodeKeyValue.item = index;
-                nodeKeyValue.label = k + " : " + node[k];
-                nodeKeyValue.key = k;
-                nodeKeyValue.value = node[k];
-                nodeKeyValue.link = nodeLink + '/' + k + '/' + node[k];
-                nodeKeyValue.level = level;
-                nodeArray.push(nodeKeyValue);
+              // key values converted into a unique string with a : separator
+              let nodeKeyValue = { nested: false, item: '', label: '', key: '', value: '', type: 'KEYVALUE', link: '/', visible: expanded, children: [], level: level};
+              nodeKeyValue.item = index;
+              nodeKeyValue.label = k + " : " + node[k];
+              nodeKeyValue.key = k;
+              nodeKeyValue.value = node[k];
+              nodeKeyValue.link = nodeLink + '/' + k + '/' + node[k];
+              nodeKeyValue.level = level;
+              nodeArray.push(nodeKeyValue);
             }
-        })
+          })
+        }
+
         // The array will be added as value to a parent key.
         if (nodeArray.length > 0) {
             nodeRoot.children = nodeArray.concat(nodeRoot.children)
@@ -258,55 +382,125 @@ export class TreeViewComponent {
         this.jsonPath_ = event.link;
     }
 
-    dataExpand() {
-        this.showResults(this.currentRange, this.EXPANDED);
-    }
-
-    dataCollapse() {
-        this.showResults(this.currentRange, this.COLLAPSED);
-    }
-
     /*
     * Build the table column names from result data
     * Flat the result data for Table display
     */
     BuildTableFlatData(data) {
+      if (this.isCSV)
+        this.buildTableFlatCSVData(data);
+      else
+        this.buildTableFlatJSONData(data);
+    }
+
+    /*
+    * Reads JSON and creates data for table display
+     */
+    buildTableFlatJSONData(data) {
+      this.flattenData = [];
+      this.displayedColumns = []
 
-        this.flattenData = [];
-        this.displayedColumns = []
+      const replacer = (key, value) => typeof value === 'undefined' ? null : value;
 
-        for (let i = 0; i < data.length; i++) {
-            if (data[i] instanceof Object) {
+      for (let i = 0; i < data.length; i++) {
+        if (data[i] instanceof Object) {
 
-                var itemsKeyList = Object.keys(data[i]);
-                var objectNode = cloneDeep(data[i]);
+          var itemsKeyList = Object.keys(data[i]);
+          var objectNode = cloneDeep(data[i]);
 
-                for (let j = 0; j < itemsKeyList.length; j++) {
+          for (let j = 0; j < itemsKeyList.length; j++) {
 
-                    var itemsKey: string = itemsKeyList[j];
-                    if (data[i][itemsKey] instanceof Object) {
-                        objectNode[itemsKey] = JSON.stringify(data[i][itemsKey], null, '\n');
-                    } else {
-                        objectNode[itemsKey] = data[i][itemsKey]
-                    }
-                    if (this.displayedColumns.indexOf(itemsKey) === -1) {
-                        this.displayedColumns.push(itemsKey)
-                    }
-                }
-                this.flattenData.push(objectNode)
+            var itemsKey: string = itemsKeyList[j];
+            if (data[i][itemsKey] instanceof Object) {
+              objectNode[itemsKey] = JSON.stringify(data[i][itemsKey], replacer, '\n');
+            } else {
+              objectNode[itemsKey] = data[i][itemsKey]
+            }
+            if (this.displayedColumns.indexOf(itemsKey) === -1) {
+              this.displayedColumns.push(itemsKey)
+            }
+          }
+          this.flattenData.push(objectNode)
+        } else {
+          //only create one column called "value" if you are doing a select value
+          if (i == 0)
+            this.displayedColumns.push('value')
+          this.flattenData.push({ 'value': data[i] })
+        }
+      }
+
+      this.dataSource.data = this.flattenData;
+    }
+
+    /*
+    * Reads CSV data and creates data for table display
+     */
+    buildTableFlatCSVData(data) {
+      this.flattenData = [];
+      this.displayedColumns = [];
+
+      for (let i = 0; i < data.length; i++) {
+        //split the string using the comma delimiter
+        let items = this.readCSV(data[i]);
+
+        //iterate over these elements
+        let item_idx = 0;
+        let row_obj = {};
+
+        for (let item of items) {
+          //if it is the header row
+          if (this.hasHeader && i == 0) {
+            this.displayedColumns.push(item);
+          } else if (!this.hasHeader && i == 0) {
+            this.displayedColumns.push("Column " + (item_idx + 1).toString());
+            row_obj["Column " + (item_idx + 1).toString()] = item;
+          } else {
+            if (this.displayedColumns.length > 0) {
+              //has header
+              row_obj[this.displayedColumns[item_idx]] = item;
             } else {
-                this.displayedColumns.push('value')
-                this.flattenData.push({ 'value': data[0] })
+              //does not have header
+              row_obj["Column " + (item_idx + 1).toString()] = item;
             }
+          }
+
+          item_idx++;
         }
 
-        this.dataSource.data = this.flattenData;
+        if (Object.keys(row_obj).length > 0)
+          this.flattenData.push(row_obj);
+      }
+
+      this.dataSource.data = this.flattenData;
     }
 
     jsonTransform(item) {
         return JSON.stringify(item, null, 4);
     }
 
+    /*
+    * function transformers json item into a string of JSONL
+     */
+    jsonlinesTransform(item) {
+      let buildString = "";
+
+      let counter = 0;
+
+      let newLineRE = /\r?\n|\r/g;
+
+      for (let obj of item) {
+        buildString += JSON.stringify(obj).replace(newLineRE, "");
+
+        //new line delimeter
+        if (counter < item.length - 1)
+          buildString += "\n";
+
+        counter++;
+      }
+
+      return buildString;
+    }
+
     checkView() {
         if (!this.treeVisible) {
             return true;
@@ -314,4 +508,76 @@ export class TreeViewComponent {
             return false
         }
     }
-}
\ No newline at end of file
+
+    /*
+    * Function opens dialog to pick between JSON and JSONL
+     */
+    openJSONExportPicker() {
+      const dialogRef = this.dialog.open(DialogExportPicker, {
+        width: '350px',
+        data: {exportFormat: this.exportFormat, fileName: this.exportFileName}
+      });
+
+      dialogRef.afterClosed().subscribe(result => {
+        if (result[0] != 'cancel') {
+          this.exportFormat = result[0];
+          this.exportFileName = result[1];
+
+          if (this.exportFormat == 'json')
+            this.exportToJSON();
+          else if (this.exportFormat == 'jsonl')
+            this.exportToJSONLines();
+          else if (this.exportFormat == 'csv')
+            this.exportToCSV();
+
+          if (this.exportFormat == 'jsonl')
+            this.exportFormat = 'json';
+        }
+      });
+    }
+
+    /*
+    * Function reads row of CSV and returns the list of items
+     */
+    readCSV(row: string) {
+      let items = []
+      let escapeMode = false;
+      let currentItem = "";
+
+      for (let i = 0; i < row.length; i++) {
+        let curr_char = row.charAt(i);
+
+        if (curr_char == '"' && escapeMode)
+          //turn off escape mode
+          escapeMode = false;
+        else if (curr_char == '"' && !escapeMode)
+          //turn on escape mode
+          escapeMode = true;
+        else {
+          //if char is a comma and not in escape mode
+          if ((curr_char == ',' && !escapeMode) || i == row.length - 1) {
+            //push current item and reset for next item
+            items.push(currentItem);
+            currentItem = "";
+          } else {
+            currentItem += curr_char;
+          }
+        }
+      }
+
+      return items;
+    }
+}
+
+@Component({
+  selector: 'dialog-export-picker',
+  templateUrl: 'dialog-export-picker.html',
+  styleUrls:  ['dialog-export-picker.scss']
+})
+export class DialogExportPicker {
+
+  constructor(
+    public dialogRef: MatDialogRef<DialogExportPicker>,
+    @Inject(MAT_DIALOG_DATA) public data: DialogData) {}
+}
+
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/material.module.ts b/asterixdb/asterix-dashboard/src/node/src/app/material.module.ts
index d172bf8..756d758 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/material.module.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/material.module.ts
@@ -11,40 +11,39 @@ 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 {NgModule} from '@angular/core';
-import {
-    MatAutocompleteModule,
-    MatButtonModule,
-    MatButtonToggleModule,
-    MatCardModule,
-    MatCheckboxModule,
-    MatChipsModule,
-    MatDatepickerModule,
-    MatDialogModule,
-    MatExpansionModule,
-    MatFormFieldModule,
-    MatGridListModule,
-    MatIconModule,
-    MatInputModule,
-    MatListModule,
-    MatMenuModule,
-    MatPaginatorModule,
-    MatProgressBarModule,
-    MatProgressSpinnerModule,
-    MatRadioModule,
-    MatSelectModule,
-    MatSidenavModule,
-    MatSliderModule,
-    MatSlideToggleModule,
-    MatSnackBarModule,
-    MatSortModule,
-    MatTableModule,
-    MatTabsModule,
-    MatToolbarModule,
-    MatTooltipModule,
-    MatStepperModule,
-} from '@angular/material';
-import {MatNativeDateModule, MatRippleModule} from '@angular/material';
+import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
+import { MatAutocompleteModule } from '@angular/material/autocomplete';
+import { MatButtonModule } from "@angular/material/button";
+import { MatButtonToggleModule } from "@angular/material/button-toggle";
+import { MatCardModule } from "@angular/material/card";
+import { MatCheckboxModule } from "@angular/material/checkbox";
+import { MatChipsModule } from "@angular/material/chips";
+import { MatDatepickerModule } from "@angular/material/datepicker";
+import { MatDialogModule } from "@angular/material/dialog";
+import { MatExpansionModule } from "@angular/material/expansion";
+import { MatFormFieldModule } from "@angular/material/form-field";
+import { MatGridListModule } from "@angular/material/grid-list";
+import { MatIconModule } from "@angular/material/icon";
+import { MatInputModule } from "@angular/material/input";
+import { MatListModule } from "@angular/material/list";
+import { MatMenuModule } from "@angular/material/menu";
+import { MatPaginatorModule } from "@angular/material/paginator";
+import { MatProgressBarModule } from "@angular/material/progress-bar";
+import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
+import { MatRadioModule } from "@angular/material/radio";
+import { MatSelectModule } from "@angular/material/select";
+import { MatSidenavModule } from "@angular/material/sidenav";
+import { MatSliderModule } from "@angular/material/slider";
+import { MatSlideToggleModule } from "@angular/material/slide-toggle";
+import { MatSnackBarModule } from "@angular/material/snack-bar";
+import { MatSortModule } from "@angular/material/sort";
+import { MatTableModule } from "@angular/material/table";
+import { MatTabsModule } from "@angular/material/tabs";
+import { MatToolbarModule } from "@angular/material/toolbar";
+import { MatTooltipModule } from "@angular/material/tooltip";
+import { MatTreeModule } from "@angular/material/tree";
+import {MatStepperModule } from "@angular/material/stepper";
+import { MatNativeDateModule, MatRippleModule } from "@angular/material/core";
 import {CdkTableModule} from '@angular/cdk/table';
 //import {CdkAccordionModule} from '@angular/cdk/accordion';
 import {A11yModule} from '@angular/cdk/a11y';
@@ -53,6 +52,8 @@ import {OverlayModule} from '@angular/cdk/overlay';
 import {PlatformModule} from '@angular/cdk/platform';
 import {ObserversModule} from '@angular/cdk/observers';
 import {PortalModule} from '@angular/cdk/portal';
+import {DragDropModule} from "@angular/cdk/drag-drop";
+import {MatBadgeModule} from "@angular/material/badge";
 
 /*
 * NgModule that includes all Material modules that are required to
@@ -92,6 +93,7 @@ import {PortalModule} from '@angular/cdk/portal';
         MatTableModule,
         MatToolbarModule,
         MatTooltipModule,
+        MatTreeModule,
         MatNativeDateModule,
         CdkTableModule,
         A11yModule,
@@ -101,6 +103,9 @@ import {PortalModule} from '@angular/cdk/portal';
         OverlayModule,
         PlatformModule,
         PortalModule,
-    ]
+        DragDropModule,
+        MatBadgeModule,
+    ],
+  schemas: [CUSTOM_ELEMENTS_SCHEMA]
 })
-export class MaterialModule {}
\ No newline at end of file
+export class MaterialModule {}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/cancel.actions.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/cancel.actions.ts
new file mode 100644
index 0000000..a38e106
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/cancel.actions.ts
@@ -0,0 +1,43 @@
+/*
+Licensed 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 { Action } from '@ngrx/store';
+
+export const CANCEL_QUERY                       = '[Query] Cancel SQL++ Query';
+export const CANCEL_QUERY_SUCCESS               = '[Query] Cancel SQL++ Query Success';
+export const CANCEL_QUERY_FAIL                  = '[Query] Cancel SQL++ Query Fail';
+
+/*
+* Cancel SQL++ Query
+ */
+export class CancelQuery implements Action {
+  readonly type = CANCEL_QUERY;
+  constructor(public payload: any) {}
+}
+
+export class CancelQuerySuccess implements Action {
+  readonly type = CANCEL_QUERY_SUCCESS;
+  constructor(public payload: any) {}
+}
+
+export class CancelQueryFail implements Action {
+  readonly type = CANCEL_QUERY_FAIL;
+  constructor(public payload: any) {}
+}
+
+/*
+* Exports of SQL++ actions
+*/
+export type All = CancelQuery |
+  CancelQuerySuccess |
+  CancelQueryFail;
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/dataset.actions.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/dataset.actions.ts
index 61259bb..40214d9 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/dataset.actions.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/dataset.actions.ts
@@ -29,7 +29,9 @@ export const DROP_DATASET             = '[Dataset Collection] Drop Dataset';
 export const DROP_DATASET_SUCCESS     = '[Dataset Collection] Drop Dataset Success';
 export const DROP_DATASET_FAIL        = '[Dataset Collection] Drop Dataset Fail';
 export const GUIDE_SELECT_DATASET     = '[Dataset Collection] Guide Select Dataset';
-
+export const SAMPLE_DATASET           = '[Dataset Collection] Sample Dataset';
+export const SAMPLE_DATASET_SUCCESS   = '[Dataset Collection] Sample Dataset Success';
+export const SAMPLE_DATASET_FAIL      = '[Dataset Collection] Sample Dataset Fail';
 
 /*
 * Guide Select Datasets for UI Helpers
@@ -112,6 +114,24 @@ export class DropDatasetFail implements Action {
 }
 
 /*
+* Sample Dataset
+ */
+export class SampleDataset implements Action {
+    readonly type = SAMPLE_DATASET;
+    constructor(public payload: any) {}
+}
+
+export class SampleDatasetSuccess implements Action {
+    readonly type = SAMPLE_DATASET_SUCCESS;
+    constructor(public payload: any) {}
+}
+
+export class SampleDatasetFail implements Action {
+    readonly type = SAMPLE_DATASET_FAIL;
+    constructor(public payload: any) {}
+}
+
+/*
 * Exports of datasets actions
 */
 export type All = SelectDatasets |
@@ -126,4 +146,7 @@ export type All = SelectDatasets |
     DropDataset |
     DropDatasetSuccess |
     DropDatasetFail |
-    GuideSelectDatasets;
\ No newline at end of file
+    SampleDataset |
+    SampleDatasetSuccess |
+    SampleDatasetFail |
+    GuideSelectDatasets;
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/function.actions.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/function.actions.ts
new file mode 100644
index 0000000..0f10153
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/function.actions.ts
@@ -0,0 +1,46 @@
+/*
+Licensed 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 { Action } from '@ngrx/store';
+
+/*
+* Definition of Function Actions
+*/
+export const SELECT_FUNCTIONS         = '[Function Collection] Select Functions';
+export const SELECT_FUNCTIONS_SUCCESS = '[Function Collection] Select Functions Success';
+export const SELECT_FUNCTIONS_FAIL    = '[Function Collection] Select Functions Fail';
+
+/*
+* Select Functions
+*/
+export class SelectFunctions implements Action {
+  readonly type = SELECT_FUNCTIONS;
+  constructor(public payload: string) {}
+}
+
+export class SelectFunctionsSuccess implements Action {
+  readonly type = SELECT_FUNCTIONS_SUCCESS;
+  constructor(public payload: any[]) {}
+}
+
+export class SelectFunctionsFail implements Action {
+  readonly type = SELECT_FUNCTIONS_FAIL;
+  constructor(public payload: any[]) {}
+}
+
+/*
+* Exports of functions actions
+*/
+export type All = SelectFunctions |
+  SelectFunctionsSuccess |
+  SelectFunctionsFail;
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/query.actions.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/query.actions.ts
index e18a89e..59366f6 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/query.actions.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/actions/query.actions.ts
@@ -87,4 +87,4 @@ export type All = PrepareQuery |
     ExecuteQueryFail |
     ExecuteMetadataQuery |
     ExecuteMetadataQuerySuccess |
-    ExecuteMetadataQueryFail;
\ No newline at end of file
+    ExecuteMetadataQueryFail;
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/app.effects.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/app.effects.ts
index 891191b..80864e8 100644
--- a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/app.effects.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/app.effects.ts
@@ -16,9 +16,9 @@ import { Action } from '@ngrx/store';
 import { Actions } from '@ngrx/effects';
 import * as appActions from '../actions/app.actions';
 
-export type Action = appActions.All
+export type Action_type = appActions.All
 
 @Injectable()
 export class AppEffects {
     constructor(private actions: Actions) {}
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/cancel.effects.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/cancel.effects.ts
new file mode 100644
index 0000000..a025c8b
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/cancel.effects.ts
@@ -0,0 +1,43 @@
+/*
+Licensed 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 { Action } from '@ngrx/store';
+import { Actions, Effect, ofType } from '@ngrx/effects';
+import { Observable ,  of } from 'rxjs';
+import { map, switchMap, catchError } from 'rxjs/operators';
+import { SQLService } from '../services/async-query.service';
+import * as sqlCancelActions from '../actions/cancel.actions';
+
+export type Action_type = sqlCancelActions.All;
+
+@Injectable()
+export class SQLCancelEffects {
+  constructor(private actions: Actions,
+              private sqlService: SQLService) {
+  }
+
+  /*
+   * Effect to Cancel a SQL++ Query against AsterixDB
+   */
+  @Effect()
+  cancelQuery$: Observable<Action_type> = this.actions.pipe(
+    ofType(sqlCancelActions.CANCEL_QUERY),
+    switchMap(query => {
+      return this.sqlService.cancelSQLQuery((query as any).payload.requestId).pipe(
+        map(sqlCancelQueryResult => new sqlCancelActions.CancelQuerySuccess(sqlCancelQueryResult)),
+        catchError(sqlCancelQueryError => of(new sqlCancelActions.CancelQueryFail(sqlCancelQueryError)))
+      )
+    })
+  )
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/dataset.effects.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/dataset.effects.ts
index 3ca0da4..0a5bb5a 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/dataset.effects.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/dataset.effects.ts
@@ -13,13 +13,13 @@ limitations under the License.
 */
 import { Injectable } from '@angular/core';
 import { Action } from '@ngrx/store';
-import { Effect, Actions } from '@ngrx/effects';
+import { Effect, Actions, ofType} from '@ngrx/effects';
 import { Observable ,  of } from 'rxjs';
+import { switchMap, map, catchError } from "rxjs/operators";
 import * as datasetActions from '../actions/dataset.actions';
 import { SQLService } from '../services/async-query.service';
-import 'rxjs/add/operator/switchMap'
 
-export type Action = datasetActions.All
+export type ActionType = datasetActions.All
 
 @Injectable()
 export class DatasetEffects {
@@ -29,33 +29,52 @@ export class DatasetEffects {
     /* Effect to load a collection of all Datasets from AsterixDB
     */
     @Effect()
-    selectDatasets$: Observable<Action> = this.actions
-        .ofType(datasetActions.SELECT_DATASETS)
-        .switchMap(query => {
-            return this.sqlService.selectDatasets()
-                .map(dataset => new datasetActions.SelectDatasetsSuccess(dataset))
-                .catch(err => of(new datasetActions.SelectDatasetsFail(err)));
-    });
+    selectDatasets$: Observable<ActionType> = this.actions.pipe(
+      ofType(datasetActions.SELECT_DATASETS),
+      switchMap(query => {
+        return this.sqlService.selectDatasets().pipe(
+          map(dataset => new datasetActions.SelectDatasetsSuccess(dataset)),
+          catchError(err => of(new datasetActions.SelectDatasetsFail(err)))
+        )
+      })
+    );
 
     /* Effect to create a Datasets from AsterixDB
     */
     @Effect()
-    createDatasets$: Observable<Action> = this.actions
-        .ofType(datasetActions.CREATE_DATASET)
-        .switchMap(dataset => {
-            return this.sqlService.createDataset((dataset as any).payload)
-                .map(dataset => new datasetActions.CreateDatasetSuccess(dataset))
-                .catch(err => of(new datasetActions.CreateDatasetFail(err)));
-    });
+    createDatasets$: Observable<ActionType> = this.actions.pipe(
+      ofType(datasetActions.CREATE_DATASET),
+      switchMap(dataset => {
+        return this.sqlService.createDataset((dataset as any).payload).pipe(
+          map(dataset => new datasetActions.CreateDatasetSuccess(dataset)),
+          catchError(err => of(new datasetActions.CreateDatasetFail(err)))
+        )
+      })
+    );
 
     /* Effect to drop a Datasets from AsterixDB
     */
     @Effect()
-    dropDatasets$: Observable<Action> = this.actions
-        .ofType(datasetActions.DROP_DATASET)
-        .switchMap(dataset => {
-            return this.sqlService.dropDataset((dataset as any).payload)
-                .map(dataset => new datasetActions.DropDatasetSuccess(dataset))
-                .catch(err => of(new datasetActions.DropDatasetFail(err)));
-    });
-}
\ No newline at end of file
+    dropDatasets$: Observable<ActionType> = this.actions.pipe(
+      ofType(datasetActions.DROP_DATASET),
+      switchMap(dataset => {
+        return this.sqlService.dropDataset((dataset as any).payload).pipe(
+          map(dataset => new datasetActions.DropDatasetSuccess(dataset)),
+          catchError(err => of(new datasetActions.DropDatasetFail(err)))
+        )
+      })
+    );
+
+    /* Effect of sampling a datasets from AsterixDB
+    */
+    @Effect()
+    sampleDataset$: Observable<ActionType> = this.actions.pipe(
+      ofType(datasetActions.SAMPLE_DATASET),
+      switchMap(dataset => {
+        return this.sqlService.sampleDataset((dataset as any).payload.dataset).pipe(
+          map(dataset => new datasetActions.SampleDatasetSuccess(dataset)),
+          catchError(err => of(new datasetActions.SampleDatasetFail(err)))
+        )
+      })
+    );
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/datatype.effects.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/datatype.effects.ts
index 5e06f89..50daa1b 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/datatype.effects.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/datatype.effects.ts
@@ -12,8 +12,9 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 import { Injectable } from '@angular/core';
-import { Effect, Actions } from '@ngrx/effects';
+import { Effect, Actions, ofType } from '@ngrx/effects';
 import { Observable ,  of } from 'rxjs';
+import { map, switchMap, catchError } from "rxjs/operators";
 import * as datatypeActions from '../actions/datatype.actions';
 import { SQLService } from '../services/async-query.service';
 
@@ -26,33 +27,38 @@ export class DatatypeEffects {
 
     /* Effect to load a collection of all Datatypes from AsterixDB */
     @Effect()
-    selectDatatypes$: Observable<Action> = this.actions
-        .ofType(datatypeActions.SELECT_DATATYPES)
-        .switchMap(query => {
-            return this.sqlService.selectDatatypes()
-                .map(datatype => new datatypeActions.SelectDatatypesSuccess(datatype))
-                .catch(err => of(new datatypeActions.SelectDatatypesFail(err)));
-    });
+    selectDatatypes$: Observable<Action> = this.actions.pipe(
+      ofType(datatypeActions.SELECT_DATATYPES),
+      switchMap(query => {
+        return this.sqlService.selectDatatypes().pipe(
+          map(datatype => new datatypeActions.SelectDatatypesSuccess(datatype)),
+          catchError(err => of(new datatypeActions.SelectDatatypesFail(err)))
+        )
+      })
+    );
 
     /* Effect to create a Datatype from AsterixDB
     */
     @Effect()
-    createDatatypes$: Observable<Action> = this.actions
-        .ofType(datatypeActions.CREATE_DATATYPE)
-        .switchMap(datatype => {
-            return this.sqlService.createDatatype((datatype as any).payload)
-                .map(datatype => new datatypeActions.CreateDatatypeSuccess(datatype))
-                .catch(err => of(new datatypeActions.CreateDatatypeFail(err)));
-    });
-
+    createDatatypes$: Observable<Action> = this.actions.pipe(
+      ofType(datatypeActions.CREATE_DATATYPE),
+      switchMap(datatype => {
+        return this.sqlService.createDatatype((datatype as any).payload).pipe(
+          map(datatype => new datatypeActions.CreateDatatypeSuccess(datatype)),
+          catchError(err => of(new datatypeActions.CreateDatatypeFail(err)))
+        )
+      })
+    );
     /* Effect to drop a Datatype from AsterixDB
     */
     @Effect()
-    dropDatatypes$: Observable<Action> = this.actions
-        .ofType(datatypeActions.DROP_DATATYPE)
-        .switchMap(datatype => {
-            return this.sqlService.dropDatatype((datatype as any).payload)
-                .map(datatype => new datatypeActions.DropDatatypeSuccess(datatype))
-                .catch(err => of(new datatypeActions.DropDatatypeFail(err)));
-    });
-}
\ No newline at end of file
+    dropDatatypes$: Observable<Action> = this.actions.pipe(
+      ofType(datatypeActions.DROP_DATATYPE),
+      switchMap(datatype => {
+        return this.sqlService.dropDatatype((datatype as any).payload).pipe(
+          map(datatype => new datatypeActions.DropDatatypeSuccess(datatype)),
+          catchError(err => of(new datatypeActions.DropDatatypeFail(err)))
+        )
+      })
+    );
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/dataverse.effects.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/dataverse.effects.ts
index e8e86af..a37f4d2 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/dataverse.effects.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/dataverse.effects.ts
@@ -12,8 +12,9 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 import { Injectable } from '@angular/core';
-import { Effect, Actions } from '@ngrx/effects';
+import { Effect, Actions, ofType } from '@ngrx/effects';
 import { Observable ,  of } from 'rxjs';
+import { map, switchMap, catchError } from "rxjs/operators";
 import * as dataverseActions from '../actions/dataverse.actions';
 import { SQLService } from '../services/async-query.service';
 
@@ -22,44 +23,50 @@ export type Action = dataverseActions.All
 @Injectable()
 export class DataverseEffects {
   constructor(private actions: Actions, private sqlService: SQLService) {}
-
     /* Effect to set the default Dataverse */
     @Effect()
-    setDefaultDataverse$: Observable<Action> = this.actions
-        .ofType(dataverseActions.SET_DEFAULT_DATAVERSE)
-        .switchMap(query => {
-            return new Observable().map(dataverse => new dataverseActions.SetDefaultDataverse('Default'))
-    });
+    setDefaultDataverse$: Observable<Action> = this.actions.pipe(
+      ofType(dataverseActions.SET_DEFAULT_DATAVERSE),
+      switchMap(query => {
+        return new Observable().pipe(map(dataverse => new dataverseActions.SetDefaultDataverse('Default')))
+      })
+    );
 
     /* Effect to load a collection of all Dataverses from AsterixDB */
     @Effect()
-    selectDataverses$: Observable<Action> = this.actions
-        .ofType(dataverseActions.SELECT_DATAVERSES)
-        .switchMap(query => {
-            return this.sqlService.selectDataverses()
-                .map(dataverse => new dataverseActions.SelectDataversesSuccess(dataverse))
-                .catch(err => of(new dataverseActions.SelectDataversesFail(err)));
-    });
+    selectDataverses$: Observable<Action> = this.actions.pipe(
+      ofType(dataverseActions.SELECT_DATAVERSES),
+      switchMap(query => {
+        return this.sqlService.selectDataverses().pipe(
+          map(dataverse => new dataverseActions.SelectDataversesSuccess(dataverse)),
+          catchError(err => of(new dataverseActions.SelectDataversesFail(err)))
+        )
+      })
+    );
 
     /* Effect to create Dataverse from AsterixDB
     */
     @Effect()
-    createDataverses$: Observable<Action> = this.actions
-        .ofType(dataverseActions.CREATE_DATAVERSE)
-        .switchMap(dataverseName => {
-            return this.sqlService.createDataverse((dataverseName as any).payload)
-                .map(dataverse => new dataverseActions.CreateDataverseSuccess(dataverse))
-                .catch(err => of(new dataverseActions.CreateDataverseFail(err)));
-    });
+    createDataverses$: Observable<Action> = this.actions.pipe(
+      ofType(dataverseActions.CREATE_DATAVERSE),
+      switchMap(dataverseName => {
+        return this.sqlService.createDataverse((dataverseName as any).payload).pipe(
+          map(dataverse => new dataverseActions.CreateDataverseSuccess(dataverse)),
+          catchError(err => of(new dataverseActions.CreateDataverseFail(err)))
+        )
+      })
+    );
 
     /* Effect to drop a Dataverse from AsterixDB
     */
     @Effect()
-    dropDataverses$: Observable<Action> = this.actions
-        .ofType(dataverseActions.DROP_DATAVERSE)
-        .switchMap(dataverseName => {
-            return this.sqlService.dropDataverse((dataverseName as any).payload)
-                .map(dataverse => new dataverseActions.DropDataverseSuccess(dataverse))
-                .catch(err => of(new dataverseActions.DropDataverseFail(err)));
-    });
+    dropDataverses$: Observable<Action> = this.actions.pipe(
+      ofType(dataverseActions.DROP_DATAVERSE),
+      switchMap(dataverseName => {
+        return this.sqlService.dropDataverse((dataverseName as any).payload).pipe(
+          map(dataverse => new dataverseActions.DropDataverseSuccess(dataverse)),
+          catchError(err => of(new dataverseActions.DropDataverseFail(err)))
+        )
+      })
+    );
 }
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/function.effects.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/function.effects.ts
new file mode 100644
index 0000000..de0a102
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/function.effects.ts
@@ -0,0 +1,40 @@
+/*
+Licensed 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 { Effect, Actions, ofType } from '@ngrx/effects';
+import { Observable ,  of } from 'rxjs';
+import { map, switchMap, catchError } from 'rxjs/operators';
+import * as functionActions from '../actions/function.actions';
+import { SQLService } from "../services/async-query.service";
+
+export type Action = functionActions.All;
+
+@Injectable()
+export class FunctionEffects {
+  constructor(private actions: Actions,
+              private sqlService: SQLService) {}
+
+  /* Effect to load a collection of all functions from AsterixDB */
+  @Effect()
+  selectFunctions$: Observable<Action> = this.actions.pipe(
+    ofType(functionActions.SELECT_FUNCTIONS),
+    switchMap(query => {
+      return this.sqlService.selectFunctions().pipe(
+        map(fn => new functionActions.SelectFunctionsSuccess(fn)),
+        catchError(err => of(new functionActions.SelectFunctionsFail(err)))
+      )
+    })
+  );
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/index.effects.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/index.effects.ts
index a1c0b07..7a70a06 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/index.effects.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/index.effects.ts
@@ -12,8 +12,9 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 import { Injectable } from '@angular/core';
-import { Effect, Actions } from '@ngrx/effects';
+import { Effect, Actions, ofType } from '@ngrx/effects';
 import { Observable ,  of } from 'rxjs';
+import { map, switchMap, catchError } from 'rxjs/operators';
 import * as indexActions from '../actions/index.actions';
 import { SQLService } from '../services/async-query.service';
 
@@ -26,33 +27,39 @@ export class IndexEffects {
 
     /* Effect to load a collection of all Index from AsterixDB */
     @Effect()
-    selectIndexes$: Observable<Action> = this.actions
-        .ofType(indexActions.SELECT_INDEXES)
-        .switchMap(query => {
-            return this.sqlService.selectIndexes()
-                .map(index => new indexActions.SelectIndexesSuccess(index))
-                .catch(err => of(new indexActions.SelectIndexesFail(err)));
-    });
+    selectIndexes$: Observable<Action> = this.actions.pipe(
+      ofType(indexActions.SELECT_INDEXES),
+      switchMap(query => {
+        return this.sqlService.selectIndexes().pipe(
+          map(index => new indexActions.SelectIndexesSuccess(index)),
+          catchError(err => of(new indexActions.SelectIndexesFail(err)))
+        )
+      })
+    );
 
     /* Effect to create a Index
     */
     @Effect()
-    createIndexes$: Observable<Action> = this.actions
-        .ofType(indexActions.CREATE_INDEX)
-        .switchMap(index => {
-            return this.sqlService.createIndex((index as any).payload)
-                .map(index => new indexActions.CreateIndexSuccess(index))
-                .catch(err => of(new indexActions.CreateIndexFail(err)));
-    });
+    createIndexes$: Observable<Action> = this.actions.pipe(
+      ofType(indexActions.CREATE_INDEX),
+      switchMap(index => {
+        return this.sqlService.createIndex((index as any).payload).pipe(
+          map(index => new indexActions.CreateIndexSuccess(index)),
+          catchError(err => of(new indexActions.CreateIndexFail(err)))
+        )
+      })
+    );
 
     /* Effect to drop a Index
     */
     @Effect()
-    dropIndexes$: Observable<Action> = this.actions
-        .ofType(indexActions.DROP_INDEX)
-        .switchMap(index => {
-            return this.sqlService.dropIndex((index as any).payload)
-                .map(index => new indexActions.DropIndexSuccess(index))
-                .catch(err => of(new indexActions.DropIndexFail(err)));
-    });
+    dropIndexes$: Observable<Action> = this.actions.pipe(
+      ofType(indexActions.DROP_INDEX),
+      switchMap(index => {
+        return this.sqlService.dropIndex((index as any).payload).pipe(
+          map(index => new indexActions.DropIndexSuccess(index)),
+          catchError(err => of(new indexActions.DropIndexFail(err)))
+        )
+      })
+    );
 }
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/query.effects.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/query.effects.ts
index 317211f..a5f8127 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/query.effects.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/effects/query.effects.ts
@@ -15,10 +15,11 @@ import { Injectable } from '@angular/core';
 import { Action } from '@ngrx/store';
 import { Actions, Effect, ofType } from '@ngrx/effects';
 import { Observable ,  of } from 'rxjs';
+import { map, switchMap, catchError } from 'rxjs/operators';
 import { SQLService } from '../services/async-query.service';
 import * as sqlQueryActions from '../actions/query.actions';
 
-export type Action = sqlQueryActions.All
+export type Action_type = sqlQueryActions.All
 
 @Injectable()
 export class SQLQueryEffects {
@@ -27,22 +28,26 @@ export class SQLQueryEffects {
 
     /* Effect to Execute an SQL++ Query against the AsterixDB */
     @Effect()
-    executeQuery$: Observable<Action> = this.actions
-        .ofType(sqlQueryActions.EXECUTE_QUERY)
-        .switchMap(query => {
-            return this.sqlService.executeSQLQuery((query as any).payload.queryString, (query as any).payload.planFormat)
-                .map(sqlQueryResult => new sqlQueryActions.ExecuteQuerySuccess(sqlQueryResult))
-                .catch(sqlQueryError => of(new sqlQueryActions.ExecuteQueryFail(sqlQueryError)));
-    });
+    executeQuery$: Observable<Action_type> = this.actions.pipe(
+      ofType(sqlQueryActions.EXECUTE_QUERY),
+      switchMap(query => {
+        return this.sqlService.executeSQLQuery((query as any).payload.queryString, (query as any).payload.planFormat, (query as any).payload.format, (query as any).payload.requestId).pipe(
+          map(sqlQueryResult => new sqlQueryActions.ExecuteQuerySuccess(sqlQueryResult)),
+          catchError(sqlQueryError => of(new sqlQueryActions.ExecuteQueryFail(sqlQueryError)))
+        )
+      })
+    );
 
     /* Effect to Execute an SQL++ Metadata Query against the AsterixDB
     */
     @Effect()
-    executeMetadataQuery$: Observable<Action> = this.actions
-        .ofType(sqlQueryActions.EXECUTE_METADATA_QUERY)
-        .switchMap(query => {
-            return this.sqlService.executeSQLQuery((query as any).payload, (query as any).payload.planFormat)
-                .map(sqlMetadataQueryResult => new sqlQueryActions.ExecuteMetadataQuerySuccess(sqlMetadataQueryResult))
-                .catch(sqlMetadataQueryError => of(new sqlQueryActions.ExecuteMetadataQueryFail(sqlMetadataQueryError)));
-    });
-}
\ No newline at end of file
+    executeMetadataQuery$: Observable<Action_type> = this.actions.pipe(
+      ofType(sqlQueryActions.EXECUTE_METADATA_QUERY),
+      switchMap(query => {
+        return this.sqlService.executeSQLQuery((query as any).payload, (query as any).payload.planFormat, (query as any).payload.format, 'default').pipe(
+          map(sqlMetadataQueryResult => new sqlQueryActions.ExecuteMetadataQuerySuccess(sqlMetadataQueryResult)),
+          catchError(sqlMetadataQueryError => of(new sqlQueryActions.ExecuteMetadataQueryFail(sqlMetadataQueryError)))
+        )
+      })
+    );
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/cancel.reducer.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/cancel.reducer.ts
new file mode 100644
index 0000000..2738139
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/cancel.reducer.ts
@@ -0,0 +1,56 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+import * as sqlCancelActions from '../actions/cancel.actions';
+
+export type Action_type = sqlCancelActions.All;
+
+export interface State {
+  currentRequestId: string,
+  success: boolean
+}
+
+const initialState: State = {
+  currentRequestId: "",
+  success: false
+}
+
+/*
+** Reducer function for sql++ queries in store/state
+*/
+export function cancelReducer(state = initialState, action: Action_type) {
+  switch (action.type) {
+    case sqlCancelActions.CANCEL_QUERY: {
+      return Object.assign({}, state, {
+        currentRequsetId: action.payload.requestId,
+        success: false
+      })
+    }
+
+    case sqlCancelActions.CANCEL_QUERY_SUCCESS: {
+      return Object.assign({}, state, {
+        success: true
+      })
+    }
+
+    case sqlCancelActions.CANCEL_QUERY_FAIL: {
+      return Object.assign({}, state, {
+        success: false
+      })
+    }
+
+    default: {
+      return state;
+    }
+  }
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/dataset.reducer.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/dataset.reducer.ts
index deaceb3..6edc206 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/dataset.reducer.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/dataset.reducer.ts
@@ -30,7 +30,9 @@ export interface State {
     dropDatasetError: any[],
     dropDatasetSuccess: boolean,
     dropDatasetFailed: boolean,
-    guideSelectsDataset: string
+    guideSelectsDataset: string,
+    sample: {},
+    dataset: string
 };
 
 const initialState: State = {
@@ -45,7 +47,9 @@ const initialState: State = {
     dropDatasetError: [],
     dropDatasetSuccess: false,
     dropDatasetFailed: false,
-    guideSelectsDataset: ""
+    guideSelectsDataset: "",
+    sample: {},
+    dataset: ""
 };
 
 /*
@@ -84,6 +88,26 @@ export function datasetReducer(state = initialState, action: Action) {
       }
 
       /*
+      * Change the load state to true to signal that a SELECT Query is ongoing
+       */
+      case DatasetAction.SAMPLE_DATASET: {
+          return Object.assign({}, state, { loading: true, dataset: action.payload.dataset });
+      }
+
+      /*
+      * Change the load state to false, and loaded to true to signal
+      * that a SELECT Query is successful and there is a sample available
+      * in the store.
+       */
+      case DatasetAction.SAMPLE_DATASET_SUCCESS: {
+          return Object.assign({}, state, {
+              loaded: true,
+              loading: false,
+              sample: {...state.sample, [state.dataset]: action.payload.results[0]}
+          })
+      }
+
+      /*
       * Change the load state to true to signaling
       * that a CREATE a Dataset Query is ongoing
       */
@@ -173,4 +197,4 @@ export function datasetReducer(state = initialState, action: Action) {
       default:
           return state;
     }
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/function.reducer.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/function.reducer.ts
new file mode 100644
index 0000000..ea0db87
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/function.reducer.ts
@@ -0,0 +1,68 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import * as FunctionAction from '../actions/function.actions';
+
+export type Action = FunctionAction.All;
+
+/*
+** Interfaces for functions in store/state
+*/
+export interface State {
+  loaded: boolean,
+  loading: boolean,
+  functions: any[]
+}
+
+const initialState: State = {
+  loaded: false,
+  loading: false,
+  functions: []
+}
+
+/*
+** Reducer function for functions in store/state
+*/
+export function functionReducer(state = initialState, action: Action) {
+  switch (action.type) {
+    /*
+    * Change the load state to true to signaling
+    * that a SELECT Query is ongoing
+    */
+    case FunctionAction.SELECT_FUNCTIONS: {
+      return Object.assign({}, state, {
+        loading: true
+      });
+    }
+
+    /*
+     * Change the load state to false, and loaded to true to signaling
+     * that a SELECT Query is success and there are functions available in the
+     * store
+     */
+    case FunctionAction.SELECT_FUNCTIONS_SUCCESS: {
+      return Object.assign({}, state, {
+        loaded: true,
+        loading: false,
+        functions: action.payload
+      });
+    }
+
+    /*
+    * Just returns the current store/state object
+     */
+    default:
+      return state;
+  }
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/index.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/index.ts
index 77b678c..1a7109c 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/index.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/index.ts
@@ -15,9 +15,11 @@ import * as fromDataverse from './dataverse.reducer';
 import * as fromDataset from './dataset.reducer';
 import * as fromDatatype from './datatype.reducer';
 import * as fromIndex from './index.reducer';
+import * as fromFunction from './function.reducer';
 import * as fromQuery from './query.reducer';
 import * as fromQueryMetadata from './query-metadata.reducer';
 import * as fromAppState from './app.reducer';
+import * as fromCancel from './cancel.reducer';
 
 /*
 ** Global Interfaces store/state
@@ -27,6 +29,7 @@ export interface ModelState {
     dataset: fromDataset.State,
     datatype: fromDatatype.State,
     index: fromIndex.State,
+    functions: fromFunction.State,
     sqlQuery: fromQuery.State,
     sqlMetadataQuery: fromQueryMetadata.State,
     appState: fromAppState.State,
@@ -40,7 +43,9 @@ export const reducers = {
     dataset: fromDataset.datasetReducer,
     datatype: fromDatatype.datatypeReducer,
     index: fromIndex.indexReducer,
+    functions: fromFunction.functionReducer,
     sqlQuery: fromQuery.sqlReducer,
+    cancelQuery: fromCancel.cancelReducer,
     sqlMetadataQuery: fromQueryMetadata.sqlMetadataReducer,
     app: fromAppState.appReducer
-};
\ No newline at end of file
+};
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/query.reducer.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/query.reducer.ts
index 408172b..1d2226c 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/query.reducer.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/reducers/query.reducer.ts
@@ -26,12 +26,14 @@ export interface State {
     errorHash: {},
     sqlQueryString: string,
     sqlQueryPlanFormat: string,
+    sqlQueryOutputFormat: string,
     sqlQueryStringHash: {},
     sqlQueryPlanFormatHash: {},
     sqlQueryResultHash: {},
     sqlQueryErrorHash: {},
     sqlQueryPrepared: {},
-    sqlQueryMetrics: {}
+    sqlQueryMetrics: {},
+    sqlQueryWarnings: {}
 };
 
 const initialState: State = {
@@ -42,12 +44,14 @@ const initialState: State = {
     errorHash: {},
     sqlQueryString: "",
     sqlQueryPlanFormat: "",
+    sqlQueryOutputFormat: "",
     sqlQueryStringHash: {},
     sqlQueryPlanFormatHash: {},
     sqlQueryResultHash: {},
     sqlQueryErrorHash: {},
     sqlQueryPrepared: {},
-    sqlQueryMetrics: {}
+    sqlQueryMetrics: {},
+    sqlQueryWarnings: {}
 };
 
 /*
@@ -66,6 +70,7 @@ export function sqlReducer(state = initialState, action: Action) {
                 sqlQueryStringHash: state.sqlQueryStringHash,
                 sqlQueryPlanFormatHash: { ...state.sqlQueryPlanFormatHash, [action.payload.editorId]: action.payload.planFormat },
                 sqlQueryMetrics: state.sqlQueryMetrics,
+                sqlQueryWarnings: state.sqlQueryWarnings,
                 currentRequestId: state.currentRequestId
             });
         }
@@ -81,6 +86,7 @@ export function sqlReducer(state = initialState, action: Action) {
                 sqlQueryResultHash: { ...state.sqlQueryResultHash, [action.payload.editorId]: {} },
                 sqlQueryErrorHash: { ...state.sqlQueryErrorHash, [action.payload.editorId]: [] },
                 sqlQueryMetrics: { ...state.sqlQueryMetrics, [action.payload.editorId]: {} },
+                sqlQueryWarnings: { ...state.sqlQueryWarnings, [action.payload.editorId]: []},
                 currentRequestId: action.payload.editorId
             });
         }
@@ -98,11 +104,13 @@ export function sqlReducer(state = initialState, action: Action) {
                 errorHash: { ...state.errorHash, [action.payload.requestId]: false },
                 sqlQueryString: action.payload.queryString,
                 sqlQueryPlanFormat: action.payload.planFormat,
+                sqlQueryOutputFormat: action.payload.format,
                 sqlQueryStringHash: { ...state.sqlQueryStringHash, [action.payload.requestId]: action.payload.queryString },
                 sqlQueryPlanFormatHash: { ...state.sqlQueryPlanFormatHash, [action.payload.requestId]: action.payload.planFormat },
                 sqlQueryResultHash: { ...state.sqlQueryResultHash, [action.payload.requestId]: [] },
                 sqlQueryErrorHash: { ...state.sqlQueryErrorHash, [action.payload.requestId]: [] },
                 sqlQueryMetrics: { ...state.sqlQueryMetrics, [action.payload.requestId]: [] },
+                sqlQueryWarnings: { ...state.sqlQueryWarnings, [action.payload.requestId]: []},
                });
         }
 
@@ -122,7 +130,8 @@ export function sqlReducer(state = initialState, action: Action) {
                 sqlQueryStringHash: { ...state.sqlQueryStringHash, [state.currentRequestId]: state.sqlQueryString },
                 sqlQueryResultHash: { ...state.sqlQueryResultHash, [state.currentRequestId]: action.payload },
                 sqlQueryErrorHash: { ...state.sqlQueryErrorHash, [state.currentRequestId]: [] },
-                sqlQueryMetrics: { ...state.sqlQueryMetrics, [state.currentRequestId]: action.payload.metrics }
+                sqlQueryMetrics: { ...state.sqlQueryMetrics, [state.currentRequestId]: action.payload.metrics },
+                sqlQueryWarnings: { ...state.sqlQueryWarnings, [state.currentRequestId]: action.payload.warnings},
             })
         }
 
@@ -143,6 +152,7 @@ export function sqlReducer(state = initialState, action: Action) {
                 sqlQueryResultHash: { ...state.sqlQueryResultHash, [state.currentRequestId]: [] },
                 sqlQueryErrorHash: { ...state.sqlQueryErrorHash, [state.currentRequestId]: action.payload.errors },
                 sqlQueryMetrics: { ...state.sqlQueryMetrics, [state.currentRequestId]: [] },
+                sqlQueryWarnings: { ...state.sqlQueryWarnings, [action.payload.requestId]: []},
             })
         }
 
@@ -153,4 +163,4 @@ export function sqlReducer(state = initialState, action: Action) {
             return state;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/shared/services/async-query.service.ts b/asterixdb/asterix-dashboard/src/node/src/app/shared/services/async-query.service.ts
index 5366531..c7c815d 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/shared/services/async-query.service.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/app/shared/services/async-query.service.ts
@@ -14,8 +14,7 @@ limitations under the License.
 import { Injectable } from '@angular/core';
 import { HttpClient, HttpHeaders } from '@angular/common/http';
 import { Observable } from 'rxjs';
-import 'rxjs/add/operator/map'
-import 'rxjs/add/operator/catch'
+import { map, catchError } from 'rxjs/operators';
 import { environment } from '../../../environments/environment';
 
 /*
@@ -25,9 +24,12 @@ import { environment } from '../../../environments/environment';
 /* Using local proxy if webpack and development */
 var AsterixRestApiUrl = '/query-service';
 
+var AsterixDeleteApiUrl = '/admin/requests/running';
+
 if (environment.production) {
     var locationHost =  self.location.host.split(':')
-    var AsterixRestApiUrl = 'http://' + locationHost[0] + ':19002/query/service';
+    AsterixRestApiUrl = 'http://' + locationHost[0] + ':19002/query/service';
+    AsterixDeleteApiUrl = 'http://' + locationHost[0] + ':19002/admin/requests/running';
 }
 
 /*
@@ -36,6 +38,9 @@ if (environment.production) {
 @Injectable()
 export class SQLService {
     defaultPlanFormat='JSON';
+    defaultOutputFormat='JSON';
+    defaultClientContextID='default';
+
     /*
     * SQLQueryService constructor using
     * HttpClient from Angular 5
@@ -48,7 +53,7 @@ export class SQLService {
      */
     selectDataverses() : Observable<any> {
          let query = "SELECT VALUE dv FROM Metadata.`Dataverse` dv"
-         return this.executeSQLQuery(query, this.defaultPlanFormat);
+         return this.executeSQLQuery(query, this.defaultPlanFormat, this.defaultOutputFormat, this.defaultClientContextID);
     }
 
     /*
@@ -57,7 +62,15 @@ export class SQLService {
     */
     selectDatasets() : Observable<any> {
         let query = "SELECT VALUE ds FROM Metadata.`Dataset` ds"
-        return this.executeSQLQuery(query, this.defaultPlanFormat);
+        return this.executeSQLQuery(query, this.defaultPlanFormat, this.defaultOutputFormat, this.defaultClientContextID);
+    }
+
+    /*
+    * sends a select sql++ query to sample the passed in dataset.
+     */
+    sampleDataset(dataset: string) : Observable<any> {
+        let query = "SELECT * FROM " + dataset + " LIMIT 1;"
+        return this.executeSQLQuery(query, this.defaultPlanFormat, this.defaultOutputFormat, this.defaultClientContextID);
     }
 
     /*
@@ -66,7 +79,7 @@ export class SQLService {
     */
     selectDatatypes() : Observable<any> {
         let query = "SELECT VALUE dt FROM Metadata.`Datatype` dt"
-        return this.executeSQLQuery(query, this.defaultPlanFormat);
+        return this.executeSQLQuery(query, this.defaultPlanFormat, this.defaultOutputFormat, this.defaultClientContextID);
     }
 
     /*
@@ -75,7 +88,12 @@ export class SQLService {
     */
     selectIndexes() : Observable<any> {
         let query = "SELECT VALUE ix FROM Metadata.`Index` ix"
-        return this.executeSQLQuery(query, this.defaultPlanFormat);
+        return this.executeSQLQuery(query, this.defaultPlanFormat, this.defaultOutputFormat, this.defaultClientContextID);
+    }
+
+    selectFunctions() : Observable<any> {
+      let query = "SELECT VALUE fn FROM Metadata.`Function` fn"
+      return this.executeSQLQuery(query, this.defaultPlanFormat, this.defaultOutputFormat, this.defaultClientContextID);
     }
 
     /*
@@ -175,19 +193,59 @@ export class SQLService {
           status: string;
           login(username: string, password: string): Observable<boolean>
     */
-    executeSQLQuery(query: string, planFormat: string): Observable<any> {
+    executeSQLQuery(query: string, planFormat: string, outputFormat: string, clientContextID: string): Observable<any> {
         const apiUrl = AsterixRestApiUrl;
-        const headers = new HttpHeaders();
-        headers.append('Content-Type', 'application/json');
+        let headers = new HttpHeaders();
+
+        if (outputFormat == 'CSV_header') {
+          headers = new HttpHeaders({"Accept": "text/csv; header=present", "Content-Type": "application/json"});
+          outputFormat = 'CSV';
+        }
+        else if (outputFormat == 'CSV') {
+          headers = new HttpHeaders({"Accept": "text/csv; header=absent", "Content-Type": "application/json"});
+        }
+        else
+          headers = headers.append('Content-Type', 'application/json');
+
         let options = ({ headers: headers });
 
-        return this.http.post(apiUrl, {statement: query,
-            'logical-plan': true,
-            'optimized-logical-plan': true,
-            'plan-format': planFormat }, options)
-                .map((response: Response) => { return response; })
-                .catch((error: any) => this.handleExecuteQueryError(error))
+        let body = {
+          statement: query,
+          'logical-plan': true,
+          'optimized-logical-plan': true,
+          'plan-format': planFormat,
+          'max-warnings': Number.MAX_SAFE_INTEGER
+        }
+
+        if (clientContextID != 'default') {
+          body['client_context_id'] = clientContextID;
+        }
+
+        return this.http.post(apiUrl, body, options)
+            .pipe(map((response: Response) => { return response; }),
+                  catchError((error: any) => this.handleExecuteQueryError(error)))
+
+
+    }
+
+    /*
+    * Cancels a sql++ query
+     */
+    cancelSQLQuery(clientContextID: string): Observable<any> {
+      let url = AsterixDeleteApiUrl;
+      let headers = new HttpHeaders();
+
+      url += `?client_context_id=${clientContextID}`;
+      headers = headers.append('Content-Type', 'application/json');
+
+      let options = ({ headers: headers });
+
+      return this.http.delete(url, options)
+        .pipe(map((response: Response) => {return response; }),
+          catchError((error: any) => this.handleDeleteQueryError(error))
+        )
     }
+
     /*
     * AsterixDB query-service API raises HTTP errors if the sql++ query has some
     * syntax error, or some elements in the query are not found
@@ -204,4 +262,10 @@ export class SQLService {
         console.log(error);
         return Promise.reject(error.error || error);
     }
-}
\ No newline at end of file
+
+    private handleDeleteQueryError(error: any): Promise<any> {
+      console.log('deleteQueryError:')
+      console.log(error);
+      return Promise.reject(error.error || error);
+    }
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/index.html b/asterixdb/asterix-dashboard/src/node/src/index.html
index 0d264bc..e790e8c 100755
--- a/asterixdb/asterix-dashboard/src/node/src/index.html
+++ b/asterixdb/asterix-dashboard/src/node/src/index.html
@@ -15,7 +15,7 @@ limitations under the License.
 <html>
 <head>
     <meta charset="utf-8">
-    <title>AsterixDb Administration Console</title>
+    <title>AsterixDB Admin Console</title>
     <base href="/">
     <link rel="icon" type="image/x-icon" href="favicon.ico">
 </head>
diff --git a/asterixdb/asterix-dashboard/src/node/src/main.scss b/asterixdb/asterix-dashboard/src/node/src/main.scss
index 0a67dd2..c18bdb4 100755
--- a/asterixdb/asterix-dashboard/src/node/src/main.scss
+++ b/asterixdb/asterix-dashboard/src/node/src/main.scss
@@ -15,6 +15,8 @@ limitations under the License.
 // Include material core styles.
 @import '~@angular/material/theming';
 @include mat-core();
+// Include codemirror styles
+@import '~codemirror/addon/hint/show-hint';
 // Define the light theme.
 $primary: mat-palette($mat-grey);
 $accent : mat-palette($mat-orange, A200, A100, A400);
diff --git a/asterixdb/asterix-dashboard/src/node/src/main.ts b/asterixdb/asterix-dashboard/src/node/src/main.ts
index ac7552b..848666d 100755
--- a/asterixdb/asterix-dashboard/src/node/src/main.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/main.ts
@@ -23,4 +23,4 @@ if (environment.production) {
 }
 
 platformBrowserDynamic().bootstrapModule(AppModule)
-    .catch(err => console.log(err));
\ No newline at end of file
+    .catch(err => console.log(err));
diff --git a/asterixdb/asterix-dashboard/src/node/src/polyfills.ts b/asterixdb/asterix-dashboard/src/node/src/polyfills.ts
index b7ac983..b8fb265 100755
--- a/asterixdb/asterix-dashboard/src/node/src/polyfills.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/polyfills.ts
@@ -43,7 +43,7 @@
 
 /** Evergreen browsers require these. **/
 // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
-import 'core-js/es7/reflect';
+//import 'core-js/es7/reflect';
 
 
 /**
@@ -73,4 +73,4 @@ import 'zone.js/dist/zone';  // Included with Angular CLI.
 /**
  * Need to import at least one locale-data with intl.
  */
-// import 'intl/locale-data/jsonp/en';
\ No newline at end of file
+// import 'intl/locale-data/jsonp/en';
diff --git a/asterixdb/asterix-dashboard/src/node/src/styles/_general.scss b/asterixdb/asterix-dashboard/src/node/src/styles/_general.scss
index ef2d7a2..fa4c40c 100755
--- a/asterixdb/asterix-dashboard/src/node/src/styles/_general.scss
+++ b/asterixdb/asterix-dashboard/src/node/src/styles/_general.scss
@@ -81,9 +81,9 @@ textarea {
   top: 0 !important;
 }
 
-.CodeMirror {
-  font-family: Arial, monospace;
-  font-size: 14px !important;
+.CodeMirror * {
+  font-family: "Courier New", monospace !important;
+  font-size: 16px !important;
   color: black;
 }
 
@@ -93,4 +93,4 @@ textarea {
 
 .mat-expansion-panel-header-description {
   margin-right: 0px !important;
-}
\ No newline at end of file
+}
diff --git a/asterixdb/asterix-dashboard/src/node/src/test.ts b/asterixdb/asterix-dashboard/src/node/src/test.ts
index 06c7693..4d9f2f5 100755
--- a/asterixdb/asterix-dashboard/src/node/src/test.ts
+++ b/asterixdb/asterix-dashboard/src/node/src/test.ts
@@ -13,34 +13,26 @@ limitations under the License.
 */
 
 // This file is required by karma.conf.js and loads recursively all the .spec and framework files
-
-import 'zone.js/dist/long-stack-trace-zone';
-import 'zone.js/dist/proxy.js';
-import 'zone.js/dist/sync-test';
-import 'zone.js/dist/jasmine-patch';
-import 'zone.js/dist/async-test';
-import 'zone.js/dist/fake-async-test';
+import 'zone.js/dist/zone-testing';
 import { getTestBed } from '@angular/core/testing';
 import {
   BrowserDynamicTestingModule,
   platformBrowserDynamicTesting
 } from '@angular/platform-browser-dynamic/testing';
 
-// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
-declare const __karma__: any;
-declare const require: any;
-
-// Prevent Karma from running prematurely.
-__karma__.loaded = function () {};
+declare const require: {
+  context(path: string, deep?: boolean, filter?: RegExp): {
+    keys(): string[];
+    <T>(id: string): T;
+  };
+};
 
 // First, initialize the Angular testing environment.
 getTestBed().initTestEnvironment(
-    BrowserDynamicTestingModule,
-    platformBrowserDynamicTesting()
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting()
 );
 // Then we find all the tests.
 const context = require.context('./', true, /\.spec\.ts$/);
 // And load the modules.
 context.keys().map(context);
-// Finally, start Karma to run the tests.
-__karma__.start();
\ No newline at end of file
diff --git a/asterixdb/asterix-dashboard/src/node/src/tests/appbar.component.spec.ts b/asterixdb/asterix-dashboard/src/node/src/tests/appbar.component.spec.ts
new file mode 100644
index 0000000..a62aa4e
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/tests/appbar.component.spec.ts
@@ -0,0 +1,68 @@
+/*
+Licensed 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 { AppBarComponent } from "../app/dashboard/appbar.component";
+import {TestBed, waitForAsync} from "@angular/core/testing";
+import {StoreModule} from "@ngrx/store";
+import {CUSTOM_ELEMENTS_SCHEMA} from "@angular/core";
+
+describe('AppBarComponent', () => {
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      declarations: [
+        AppBarComponent
+      ],
+      imports: [
+        StoreModule.forRoot({})
+      ],
+      schemas: [
+        CUSTOM_ELEMENTS_SCHEMA
+      ]
+    }).compileComponents();
+  }));
+
+  it('should render link titled "WEBSITE"', waitForAsync(() => {
+    const fixture = TestBed.createComponent(AppBarComponent);
+    const compiled = fixture.debugElement.nativeElement;
+    expect(compiled.querySelector('.menu:nth-child(1)').textContent).toContain('WEBSITE');
+  }));
+
+  it('should render link titled "FILE ISSUES"', waitForAsync(() => {
+    const fixture = TestBed.createComponent(AppBarComponent);
+    const compiled = fixture.debugElement.nativeElement;
+    expect(compiled.querySelector('.menu:nth-child(2)').textContent).toContain('FILE ISSUES');
+  }));
+
+  it('should render link titled "DOCUMENTATION"', waitForAsync(() => {
+    const fixture = TestBed.createComponent(AppBarComponent);
+    const compiled = fixture.debugElement.nativeElement;
+    expect(compiled.querySelector('.menu:nth-child(3)').textContent).toContain('DOCUMENTATION');
+  }));
+
+  it('should render link titled "CONTACT"', waitForAsync(() => {
+    const fixture = TestBed.createComponent(AppBarComponent);
+    const compiled = fixture.debugElement.nativeElement;
+    expect(compiled.querySelector('.menu:nth-child(4)').textContent).toContain('CONTACT');
+  }));
+
+  it('should render link titled "GITHUB"', waitForAsync(() => {
+    const fixture = TestBed.createComponent(AppBarComponent);
+    const compiled = fixture.debugElement.nativeElement;
+    expect(compiled.querySelector('.menu:nth-child(5)').textContent).toContain('GITHUB');
+  }));
+
+  it('should render link titled "METADATA"', waitForAsync(() => {
+    const fixture = TestBed.createComponent(AppBarComponent);
+    const compiled = fixture.debugElement.nativeElement;
+    expect(compiled.querySelector('.menu:nth-child(6)').textContent).toContain('METADATA');
+  }));
+});
diff --git a/asterixdb/asterix-dashboard/src/node/src/tests/input.component.spec.ts b/asterixdb/asterix-dashboard/src/node/src/tests/input.component.spec.ts
new file mode 100644
index 0000000..d006b44
--- /dev/null
+++ b/asterixdb/asterix-dashboard/src/node/src/tests/input.component.spec.ts
@@ -0,0 +1,213 @@
+/*
+Licensed 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 {InputQueryComponent} from "../app/dashboard/query/input.component";
+import {ComponentFixture, TestBed, waitForAsync, async} from "@angular/core/testing";
+import {provideMockStore, MockStore} from "@ngrx/store/testing";
+import {CUSTOM_ELEMENTS_SCHEMA, DebugElement} from "@angular/core";
+import {By} from "@angular/platform-browser";
+import {HarnessLoader} from "@angular/cdk/testing";
+import {TestbedHarnessEnvironment} from "@angular/cdk/testing/testbed";
+
+describe('InputQueryComponent: unit test', () => {
+  let component: InputQueryComponent;
+  let fixture: ComponentFixture<InputQueryComponent>;
+  let de: DebugElement;
+  let el: HTMLElement;
+  let loader: HarnessLoader;
+
+  let store: MockStore;
+  const initialState = {
+    sqlQuery: {
+      currentRequestId: '',
+      loadingHash: {},
+      loadedHash: {},
+      successHash:{},
+      errorHash: {},
+      sqlQueryString: "",
+      sqlQueryPlanFormat: "",
+      sqlQueryOutputFormat: "",
+      sqlQueryStringHash: {},
+      sqlQueryPlanFormatHash: {},
+      sqlQueryResultHash: {},
+      sqlQueryErrorHash: {},
+      sqlQueryPrepared: {},
+      sqlQueryMetrics: {},
+      sqlQueryWarnings: {}
+    },
+    cancelQuery: {
+      currentRequestId: "",
+      success: false
+    },
+    dataverse: {
+      loaded: false,
+      loading: false,
+      dataverses: [],
+      createDataverse: [],
+      createDataverseName: "",
+      createDataverseError: [],
+      createDataverseSuccess: false,
+      createDataverseFailed: false,
+      dropDataverse: [],
+      dropDataverseName: "",
+      dropDataverseError: [],
+      dropDataverseSuccess: false,
+      dropDataverseFailed: false,
+      defaultDataverseName: 'Default'
+    }
+  }
+
+  beforeEach(async() => {
+    await TestBed.configureTestingModule({
+      declarations: [
+        InputQueryComponent
+      ],
+      providers: [
+        provideMockStore({initialState})
+      ],
+      schemas: [CUSTOM_ELEMENTS_SCHEMA]
+    }).compileComponents();
+
+    store = TestBed.inject(MockStore);
+    spyOn(store, 'dispatch');
+    jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(InputQueryComponent);
+    component = fixture.componentInstance;
+    de = fixture.debugElement;
+    fixture.detectChanges();
+    loader = TestbedHarnessEnvironment.loader(fixture);
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  })
+
+  //query navigation
+  describe('query navigation tests', () => {
+    it('running query should call store.dispatch 3 times', () => {
+      expect(store.dispatch).toHaveBeenCalledTimes(3);
+
+      component.queryString = "select 1";
+      component.onClickRun();
+      fixture.detectChanges();
+      expect(store.dispatch).toHaveBeenCalledTimes(6);
+
+      component.queryString = "select 2";
+      component.onClickRun();
+      fixture.detectChanges();
+      expect(store.dispatch).toHaveBeenCalledTimes(9);
+
+      component.queryString = "select 3";
+      component.onClickRun();
+      fixture.detectChanges();
+      expect(store.dispatch).toHaveBeenCalledTimes(12);
+    });
+  });
+
+  //outputs
+  describe('test outputs', () => {
+    it('emits isExplain is false on click run', () => {
+      let actualInputToOutput = {};
+
+      component.inputToOutputEmitter.subscribe((inputToOutput) => {
+        actualInputToOutput = inputToOutput;
+      })
+
+      let button = de.query(By.css('.run'));
+      button.nativeElement.click();
+
+      expect(actualInputToOutput['isExplain']).toBe(false);
+    });
+
+    it('emits isExplain is true on click explain', () => {
+      let actualInputToOutput: boolean = undefined;
+
+      component.inputToOutputEmitter.subscribe((inputToOutput) => {
+        actualInputToOutput = inputToOutput['isExplain'];
+      })
+
+      let button = de.query(By.css('.explain'));
+      button.nativeElement.click();
+
+      expect(actualInputToOutput).toBe(true);
+    });
+
+    it('emits JSON output format when selected from dropdown', async()=> {
+      let outputFormat: string = undefined;
+
+      component.inputToOutputEmitter.subscribe((inputToOutput) => {
+        outputFormat = inputToOutput['outputFormat'];
+      })
+
+      component.outputOptions = 'JSON';
+
+      let button = de.query(By.css('.run'));
+      button.nativeElement.click();
+
+      expect(outputFormat).toEqual('JSON');
+    });
+
+    it('emits CSV (no header) output format when selected from dropdown', async() => {
+      let outputFormat: string = undefined;
+
+      component.inputToOutputEmitter.subscribe((inputToOutput) => {
+        outputFormat = inputToOutput['outputFormat'];
+      })
+
+      component.outputOptions = 'CSV';
+
+      let button = de.query(By.css('.run'));
+      button.nativeElement.click();
+
+      expect(outputFormat).toEqual('CSV');
+    });
+
+    it('emits CSV (header) output format when selected from dropdown', () => {
+      let outputFormat: string = undefined;
+
+
+      component.inputToOutputEmitter.subscribe((inputToOutput) => {
+        outputFormat = inputToOutput['outputFormat'];
+      })
+
+      component.outputOptions = 'CSV_header'
+
+      let button = de.query(By.css('.run'));
+      button.nativeElement.click();
+
+      expect(outputFormat).toEqual('CSV_header');
+    });
+  });
+
+  //clear tests
+  describe('clear() tests', () => {
+    it('clicking #clear() should clear query string', () => {
+      component.queryString = "select 1";
+      expect(component.queryString).toBe("select 1");
+      component.onClickClear();
+      expect(component.queryString).toBe("");
+    });
+
+    it('clicking clear should clear the editor on screen', async() => {
+      component.editor.getDoc().setValue("this is a test text");
+
+      let button = de.query(By.css('.clear'));
+      button.nativeElement.click();
+
+      expect(component.editor.getDoc().getValue()).toEqual("");
+      expect(component.queryString).toEqual("");
+    });
+
+  });
+});
diff --git a/asterixdb/asterix-doc/src/site/markdown/dashboard.md b/asterixdb/asterix-doc/src/site/markdown/dashboard.md
new file mode 100644
index 0000000..2d6a519
--- /dev/null
+++ b/asterixdb/asterix-doc/src/site/markdown/dashboard.md
@@ -0,0 +1,236 @@
+<!--
+ ! 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.
+ !-->
+ 
+ # AsterixDB Administration Console #
+ 
+ ## <a id="toc">Table of Contents</a>
+ 
+ * [Basics](#basics)
+ * [Query Navigation](#qnav)
+ * [Metadata Inspector](#metadatainspector)
+ * [Interactive Plan Viewer](#planviewer)
+ * [Exporting Data](#exporting)
+ * [Development](#development)
+ 
+## <a id="basics">Basic Usage</a><font size="4"> <a href="#toc">[Back to TOC]</a></font>
+
+Executing a query on this console is easy. First, select from the input options and then select your execution mode.
+
+__Input Options__
+
+* `Dataverse` -  the dataverse that the query will use. The default is the `Default` dataverse. This is not required to
+run a query and the console will try and autodetect the dataverse used in the query.
+* `Plan Format` - specifies what format of the resulting query plan. 
+    * `JSON` - results in the showing the interactive query plan viewer. 
+    * `STRING` - results in the text/string format of the query plan. Equivalent to the text format from the old 19001 
+    console.
+* `Output Format` - the format of the result of the query.
+    * `JSON` - the default and will return the results in JSON. Can also view in `Tree` and `Table` views in the output
+    section.
+    * `CSV (no header)` - will return CSV format but without the header. Can only view this in `Table` view in the output
+    section.
+    * `CSV (header)` - will return CSV format with the header. Can only view this in `Table` view in the output
+    section. See the [Exporting Data](#exporting) section for more information and examples.
+
+To execute the query click the green triangle in the bottom right. Users may also choose to click the `Explain` button.
+This will not actually run the query (no results returned) and will only return the query plan. The console will default
+the view in the output section to `Plan`.
+
+To cancel the query click the red stop button in the bottom right. This will send a `DELETE` request to the server and cancel the previous
+request.
+
+The dashboard now supports autocomplete for SQL++ keywords. Use `CTRL+Space` to activate the autocomplete.
+
+## <a id="qnav">Query Navigation</a><font size="4"> <a href="#toc">[Back to TOC]</a></font>
+
+This console supports query history and has two different ways of navigating the query history. On the input bar there is a section
+for `QUERY HISTORY` and there are also two arrows `<` and `>`.
+
+Utilizing the arrows will let you traverse the queries one by one. However, if the console is already at the most recent query
+in the history and the user clicks the `>` or forward arrow, it will create a new empty query. 
+
+The `QUERY HISTORY` dropdown allows users to jump through the history without having to step through it with the arrows.
+
+When executing a query, this query will be counted as a new query if it is different (purely the text of the query, not 
+the results) from the most recent query. It will subsequently be added to the query history. 
+
+## <a id="metadatainspector">Metadata Inspector</a><font size="4"> <a href="#toc">[Back to TOC]</a></font>
+
+The metadata inspector is the column on the rightside of the console. The `Refresh` button is used to update the current metadata.
+When a user creates or drops a Dataverse, Dataset, Datatype, or Index the changes will not be automatically reflected. User must
+click the `Refresh` button to get the most up to date data. 
+
+The console supports multiple dialogs/windows open at once. All of these are resizable and draggable as well.
+
+Users can also click the `JSON` / `SUMMARY` button to toggle from the raw and parsed views. `SUMMARY` is the default.
+
+#### Dataverse
+
+Clicking a dataverse will add it to the shown metadata in this inspector. Users can select as many dataverses as desired.
+The corresponding datasets, datatypes, and indices will appear.
+
+#### Datasets
+
+Clicking on a dataset will open a draggable and expandable window that contains information about the dataset.
+
+* `Dataverse` - which dataverse the dataset belongs to.
+* `Dataset` - the name of the dataset.
+* `Datatype Name` - the name of the datatype of the dataset.
+* `Primary Keys` - the primary keys of the dataset.
+* `Sample` - if there is data inserted into the dataset, this is a section where viewers can see a sample of the dataset.
+It is a `SELECT * FROM {dataset} LIMIT 1` query.
+
+#### Datatypes
+
+Clicking on a datatypes will open a draggable and expandable window that contains information about the datatype. This console
+does support nested datatypes.
+
+* `Dataverse` - which dataverse the datatype belongs to.
+* `Datatype Name` - the name of the datatype.
+* `Fields` - a list of the fields in the dataset. Each field has information on whether it is nullable or required. If the
+field is nested / not a primitive type, click on it to see the information of that type. If the field is wrapped in `[ ]` or `{{ }}`,
+then it is an ordered list or unordered of that type respectively. If a field is italicized, it is an anonymous type.
+
+NOTE: the `JSON` view does not support nested like `SUMMARY` does.
+
+#### Index
+
+Clicking on a dataset will open a draggable and expandable window that contains information about the index.
+
+* `Dataverse` - which dataverse the index belongs to.
+* `Index Name` - the name of the index.
+* `Index Type` - the type of the index.
+* `Search Key(s)` - the key(s) of the index.
+
+
+
+## <a id="planviewer">Interactive Plan Viewer</a><font size="4"> <a href="#toc">[Back to TOC]</a></font>
+
+To view the interactive plan viewer, execute a query and switch to the `PLAN` tab in the output section. Alternatively,
+users can click explain the query by clicking `EXPLAIN` instead of execute and the output section will default to the 
+`PLAN` tab.
+
+To interact with the query plan, drag to move the view of the graph. Use the scroll wheel or scroll movement to zoom in and out
+of the plan. 
+
+The default plan orientation is `Bottom to Top` and can be swapped for `Top to Bottom` if so desired.
+
+The default view of the plan is not detailed (just operator IDs and operator names). To look at a more detailed plan, check
+the `Detailed` checkbox and the plan will reload with more detail per node.
+
+#### Traversing
+
+There are multiple ways to traverse the query plan. the `Go to Node` dropdown will keep track of the currently selected
+node. Using the arrows next to the `Go to Node` dropdown will traverse the plan node by node in a Depth First Search (DFS) 
+fashion. Selecting nodes on the `Go to Node` dropdown will jump the plan to the selected node. 
+
+Utilizing both the arrows and the `Go to Node` dropdown, it is easy to trace through a plan.
+
+#### Search (Detailed mode only)
+
+The `Search` function appears when the plan is in `Detailed` mode. Search for specific string occurrences in the plan. When
+the search icon is clicked, the first mathc will be selected (if there is a match). Use the arrows that appear next to it
+to iterate through every match. 
+
+Must click `Clear Selections` after done with the search. 
+
+Unfortunately, at this time regular expression search is not supported.
+
+#### Variables (Detailed mode only)
+
+The `See Variable Occurences` dropdown will appear when the plan is in `Detailed` mode. Users can select from any variable
+that appears in the plan. Selecting a variable will jump to the node of last occurrence. The user can see how many occurence there
+are by the `See Variable Occurences` dropdown title (it will now include a fraction). 
+
+The arrows that appear can iterate through the occurences. 
+
+Often, it is useful to be able to skip right to the declaration of a variable. By clicking on the skip button, the plan
+will select the node where that variable was declared. To jump back to whatever node before, click the undo button.
+
+#### Clear Selections
+
+Clicking `Clear Selections` will reset the graph and focus on the first node in the plan.
+
+## <a id="exporting">Exporting Data</a><font size="4"> <a href="#toc">[Back to TOC]</a></font>
+
+### JSON/JSONL:
+
+1. Select `JSON` in the input `Output Format` option and run the query that you want to export.
+2. Click `Export` in the output section.
+3. Select between `JSON` and `JSONL` (JSON Lines). Adjust the filename to the desired name.
+4. Click `Export` to start the download. 
+
+### CSV (no header):
+
+1. Select `CSV (no header)` in the input `Output Format` option and run the query that you want to export.
+2. Click `Export` in the output section.
+3. Adjust the filename to the desired name.
+4. Click `Export` to start the download.
+
+### CSV (header):
+
+1. Create a type that supports the query you want to run.
+2. Set the `output-record-type` before your query
+3. Select `CSV (no header)` in the input `Output Format` option and run the query that you want to export.
+4. Click `Export` in the output section.
+5. Adjust the filename to the desired name.
+6. Click `Export` to start the download.
+
+This one is trickier. In order to get the header in the CSV format, we need to set the `output-record-type` in the query
+in order to get the headers. To explain further, here is an example using the TinySocial dataset from the Using SQL++ Primer.
+
+    CREATE TYPE GleambookMessageType AS {
+        messageId: int,
+        authorId: int,
+        inResponseTo: int?,
+        senderLocation: point?,
+        message: string
+    };
+
+    CREATE DATASET GleambookMessages(GleambookMessageType)
+        PRIMARY KEY messageId;
+
+If we wanted to export `messageId`, `authorId`, and `senderLocation` in CSV format with headers, we would have to create
+an additional type to support this export.
+
+    CREATE TYPE GleambookMessages_exportCSV AS {
+        messageId: int,
+        authorId: int,
+        senderLocation: point
+    };
+    
+The query would then look something like this:
+    
+    USE TinySocial;
+    
+    SET `output-record-type` "GleambookMessages_exportCSV";
+    
+    SELECT messageId, authorId, senderLocation
+    FROM GleambookMessages;
+    
+Now run the query with the `CSV (header)` input option and the result will contain the hedaer `messageId`, `authorId`,
+and `senderLocation`.
+
+## <a id="development">Development</a><font size="4"> <a href="#toc">[Back to TOC]</a></font>
+
+To start the development server, run `ng serve` or `npm start`. Navigate to `http://localhost:4200/`. 
+The app will automatically reload if you change any of the source files.
+
+To add a debugger, add a new `Javascrip Debug` configuration in the IntelliJ `Run Configurations` and set the URL to
+`http://localhost:4200/`. Additionally, you can set the file directory to asterix-dashboard.
\ No newline at end of file
diff --git a/asterixdb/src/main/licenses/templates/3rdpartylicenses.txt b/asterixdb/src/main/licenses/templates/3rdpartylicenses.txt
index 8c67061..5195add 100644
--- a/asterixdb/src/main/licenses/templates/3rdpartylicenses.txt
+++ b/asterixdb/src/main/licenses/templates/3rdpartylicenses.txt
@@ -1,8 +1,36 @@
-codemirror@5.39.0
+@angular-devkit/build-angular
 MIT
-MIT License
+The MIT License
+
+Copyright (c) 2017 Google, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
 
-Copyright (C) 2017 by Marijn Haverbeke <ma...@gmail.com> and others
+
+@angular/animations
+MIT
+
+@angular/cdk
+MIT
+The MIT License
+
+Copyright (c) 2020 Google LLC.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -22,9 +50,21 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 
-core-js@2.5.7
+
+@angular/common
+MIT
+
+@angular/core
 MIT
-Copyright (c) 2014-2018 Denis Pushkarev
+
+@angular/forms
+MIT
+
+@angular/material
+MIT
+The MIT License
+
+Copyright (c) 2020 Google LLC.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -44,11 +84,100 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 
-zone.js@0.8.26
+
+@angular/platform-browser
 MIT
-The MIT License
 
-Copyright (c) 2016-2018 Google, Inc.
+@babel/runtime
+MIT
+MIT License
+
+Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+@ngrx/effects
+MIT
+
+@ngrx/store
+MIT
+
+@ngrx/store-devtools
+MIT
+
+@swimlane/ngx-charts
+MIT
+MIT License
+
+Copyright (c) 2017 Swimlane
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+@swimlane/ngx-graph
+MIT
+(The MIT License)
+
+Copyright (c) 2016 Swimlane <in...@swimlane.com>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+codemirror
+MIT
+MIT License
+
+Copyright (C) 2017 by Marijn Haverbeke <ma...@gmail.com> and others
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -68,11 +197,708 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 
-hammerjs@2.0.8
+
+core-js
 MIT
-The MIT License (MIT)
+Copyright (c) 2014-2020 Denis Pushkarev
 
-Copyright (C) 2011-2014 by Jorik Tangelder (Eight Media)
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+css-loader
+MIT
+Copyright JS Foundation and other contributors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+d3-array
+BSD-3-Clause
+Copyright 2010-2020 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-brush
+BSD-3-Clause
+Copyright 2010-2016 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-collection
+BSD-3-Clause
+Copyright 2010-2016, Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-color
+BSD-3-Clause
+Copyright 2010-2016 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-dispatch
+BSD-3-Clause
+Copyright 2010-2016 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-drag
+BSD-3-Clause
+Copyright 2010-2016 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-ease
+BSD-3-Clause
+Copyright 2010-2016 Mike Bostock
+Copyright 2001 Robert Penner
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-force
+BSD-3-Clause
+Copyright 2010-2016 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-format
+BSD-3-Clause
+Copyright 2010-2015 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-hierarchy
+BSD-3-Clause
+Copyright 2010-2016 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-interpolate
+BSD-3-Clause
+Copyright 2010-2016 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-path
+BSD-3-Clause
+Copyright 2015-2016 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-quadtree
+BSD-3-Clause
+Copyright 2010-2016 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-scale
+BSD-3-Clause
+Copyright 2010-2015 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-selection
+BSD-3-Clause
+Copyright (c) 2010-2018, Michael Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* The name Michael Bostock may not be used to endorse or promote products
+  derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-shape
+BSD-3-Clause
+Copyright 2010-2015 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-time
+BSD-3-Clause
+Copyright 2010-2016 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-time-format
+BSD-3-Clause
+Copyright 2010-2017 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-timer
+BSD-3-Clause
+Copyright 2010-2016 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+d3-transition
+BSD-3-Clause
+Copyright (c) 2010-2015, Michael Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* The name Michael Bostock may not be used to endorse or promote products
+  derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+TERMS OF USE - EASING EQUATIONS
+
+Open source under the BSD License.
+
+Copyright 2001 Robert Penner
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+- Neither the name of the author nor the names of contributors may be used to
+  endorse or promote products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+dagre
+MIT
+Copyright (c) 2012-2014 Chris Pettitt
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -92,71 +918,354 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 
-@angular/core@6.0.7
+
+file-saver
 MIT
+The MIT License
+
+Copyright © 2016 [Eli Grey][1].
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+  [1]: http://eligrey.com
+
+
+graphlib
 MIT
+Copyright (c) 2012-2014 Chris Pettitt
 
-tslib@1.9.3
-Apache-2.0
-Apache License
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
 
-Version 2.0, January 2004
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
 
-http://www.apache.org/licenses/ 
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
 
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 
-1. Definitions.
+hammerjs
+MIT
+The MIT License (MIT)
 
-"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
+Copyright (C) 2011-2014 by Jorik Tangelder (Eight Media)
 
-"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
 
-"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
 
-"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
 
-"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
 
-"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
+lodash
+MIT
+Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
 
-"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
+Based on Underscore.js, copyright Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
 
-"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
+This software consists of voluntary contributions made by many
+individuals. For exact contribution history, see the revision history
+available at https://github.com/lodash/lodash
 
-"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or it [...]
+The following license applies to all parts of this software except as
+documented below:
 
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
+====
 
-2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
 
-3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combin [...]
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
 
-4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-You must give any other recipients of the Work or Derivative Works a copy of this License; and
+====
 
-You must cause any modified files to carry prominent notices stating that You changed the files; and
+Copyright and related rights for sample code are waived via CC0. Sample
+code is defined as all source code displayed within the prose of the
+documentation.
 
-You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
+CC0: http://creativecommons.org/publicdomain/zero/1.0/
 
-If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, w [...]
+====
 
-5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
+Files located in the node_modules and vendor directories are externally
+maintained libraries used by this software which have their own
+licenses; we recommend you read them, as their terms may differ from the
+terms above.
 
-6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
 
-7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the [...]
+regenerator-runtime
+MIT
+MIT License
 
-8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limit [...]
+Copyright (c) 2014-present, Facebook, Inc.
 
-9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor h [...]
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
 
-END OF TERMS AND CONDITIONS
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
 
-rxjs@6.2.1
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+roboto-fontface
+Apache-2.0
+                                Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2013 Christian Hoffmeister
+
+   Licensed 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.
+
+
+rxjs
 Apache-2.0
-Apache License
+                               Apache License
                          Version 2.0, January 2004
                       http://www.apache.org/licenses/
 
@@ -357,26 +1466,74 @@ Apache License
  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.
+ 
 
-@ngrx/store-devtools@6.0.1
-MIT
-MIT
 
-file-saver@1.3.8
+transformation-matrix
 MIT
-The MIT License
+MIT License
 
-Copyright © 2016 [Eli Grey][1].
+Copyright (c) 2017 https://github.com/chrvadala
 
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
 
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
 
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
 
-  [1]: http://eligrey.com
 
-webpack@4.8.3
+tslib
+0BSD
+Copyright (c) Microsoft Corporation.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
+webcola
+MIT
+The MIT License (MIT)
+
+Copyright (c) 2013 Tim Dwyer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+webpack
 MIT
 Copyright JS Foundation and other contributors
 
@@ -399,6 +1556,27 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-@ngrx/store@6.0.1
+
+zone.js
 MIT
-MIT
\ No newline at end of file
+The MIT License
+
+Copyright (c) 2010-2020 Google LLC. http://angular.io/license
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.