ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From vkuliche...@apache.org
Subject [23/50] [abbrv] ignite git commit: Ignite Web Console beta2.
Date Mon, 04 Jul 2016 19:02:59 GMT
http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/controllers/sql-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/controllers/sql-controller.js b/modules/web-console/src/main/js/controllers/sql-controller.js
index 4bc39e2..a8058ff 100644
--- a/modules/web-console/src/main/js/controllers/sql-controller.js
+++ b/modules/web-console/src/main/js/controllers/sql-controller.js
@@ -19,18 +19,26 @@
 import consoleModule from 'controllers/common-module';
 
 consoleModule.controller('sqlController', [
-    '$rootScope', '$scope', '$http', '$q', '$timeout', '$interval', '$animate', '$location', '$anchorScroll', '$state', '$modal', '$popover', '$loading', '$common', '$confirm', 'IgniteAgentMonitor', 'IgniteChartColors', 'QueryNotebooks', 'uiGridExporterConstants',
-    function ($root, $scope, $http, $q, $timeout, $interval, $animate, $location, $anchorScroll, $state, $modal, $popover, $loading, $common, $confirm, agentMonitor, IgniteChartColors, QueryNotebooks, uiGridExporterConstants) {
-        var stopTopology = null;
+    '$rootScope', '$scope', '$http', '$q', '$timeout', '$interval', '$animate', '$location', '$anchorScroll', '$state', '$modal', '$popover', '$loading', '$common', '$confirm', 'IgniteAgentMonitor', 'IgniteChartColors', 'QueryNotebooks', 'uiGridConstants', 'uiGridExporterConstants',
+    function($root, $scope, $http, $q, $timeout, $interval, $animate, $location, $anchorScroll, $state, $modal, $popover, $loading, $common, $confirm, agentMonitor, IgniteChartColors, QueryNotebooks, uiGridConstants, uiGridExporterConstants) {
+        let stopTopology = null;
 
-        $scope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
+        const _tryStopRefresh = function(paragraph) {
+            if (paragraph.rate && paragraph.rate.stopTime) {
+                $interval.cancel(paragraph.rate.stopTime);
+
+                delete paragraph.rate.stopTime;
+            }
+        };
+
+        const _stopTopologyRefresh = () => {
             $interval.cancel(stopTopology);
 
             if ($scope.notebook && $scope.notebook.paragraphs)
-                $scope.notebook.paragraphs.forEach(function (paragraph) {
-                    _tryStopRefresh(paragraph);
-                });
-        });
+                $scope.notebook.paragraphs.forEach((paragraph) => _tryStopRefresh(paragraph));
+        };
+
+        $scope.$on('$stateChangeStart', _stopTopologyRefresh);
 
         $scope.caches = [];
 
@@ -51,14 +59,14 @@ consoleModule.controller('sqlController', [
         ];
 
         $scope.exportDropdown = [
-            { 'text': 'Export all', 'click': 'exportCsvAll(paragraph)' }
-            //{ 'text': 'Export all to CSV', 'click': 'exportCsvAll(paragraph)' },
-            //{ 'text': 'Export all to PDF', 'click': 'exportPdfAll(paragraph)' }
+            { text: 'Export all', click: 'exportCsvAll(paragraph)' }
+            // { 'text': 'Export all to CSV', 'click': 'exportCsvAll(paragraph)' },
+            // { 'text': 'Export all to PDF', 'click': 'exportPdfAll(paragraph)' }
         ];
 
         $scope.metadata = [];
 
-        $scope.metaFilter = "";
+        $scope.metaFilter = '';
 
         $scope.metaOptions = {
             nodeChildren: 'children',
@@ -69,1429 +77,1451 @@ consoleModule.controller('sqlController', [
             }
         };
 
-        $scope.maskCacheName = (cacheName) => _.isEmpty(cacheName) ? "<default>" : cacheName;
+        $scope.maskCacheName = (cacheName) => _.isEmpty(cacheName) ? '<default>' : cacheName;
 
-        var _handleException = function(err) {
+        const _handleException = function(err) {
             $common.showError(err);
         };
 
         // Time line X axis descriptor.
-        var TIME_LINE = {value: -1, type: 'java.sql.Date', label: 'TIME_LINE'};
+        const TIME_LINE = {value: -1, type: 'java.sql.Date', label: 'TIME_LINE'};
 
         // Row index X axis descriptor.
-        var ROW_IDX = {value: -2, type: 'java.lang.Integer', label: 'ROW_IDX'};
+        const ROW_IDX = {value: -2, type: 'java.lang.Integer', label: 'ROW_IDX'};
 
         // We need max 1800 items to hold history for 30 mins in case of refresh every second.
-        var HISTORY_LENGTH = 1800;
+        const HISTORY_LENGTH = 1800;
 
-        var MAX_VAL_COLS = IgniteChartColors.length;
+        const MAX_VAL_COLS = IgniteChartColors.length;
 
         $anchorScroll.yOffset = 55;
 
         $scope.chartColor = function(index) {
-            return {"color": "white", "background-color": IgniteChartColors[index]};
+            return {color: 'white', 'background-color': IgniteChartColors[index]};
         };
 
-        $scope.chartRemoveKeyColumn = function (paragraph, index) {
-            paragraph.chartKeyCols.splice(index, 1);
+        function _chartNumber(arr, idx, dflt) {
+            if (idx >= 0 && arr && arr.length > idx && _.isNumber(arr[idx]))
+                return arr[idx];
 
-            _chartApplySettings(paragraph, true);
-        };
+            return dflt;
+        }
 
-        $scope.chartRemoveValColumn = function (paragraph, index) {
-            paragraph.chartValCols.splice(index, 1);
+        function _min(rows, idx, dflt) {
+            let min = _chartNumber(rows[0], idx, dflt);
 
-            _chartApplySettings(paragraph, true);
-        };
+            _.forEach(rows, (row) => {
+                const v = _chartNumber(row, idx, dflt);
 
-        $scope.chartAcceptKeyColumn = function(paragraph, item) {
-            var accepted = _.findIndex(paragraph.chartKeyCols, item) < 0;
+                if (v < min)
+                    min = v;
+            });
 
-            if (accepted) {
-                paragraph.chartKeyCols = [item];
+            return min;
+        }
 
-                _chartApplySettings(paragraph, true);
-            }
+        function _max(rows, idx, dflt) {
+            let max = _chartNumber(rows[0], idx, dflt);
 
-            return false;
-        };
+            _.forEach(rows, (row) => {
+                const v = _chartNumber(row, idx, dflt);
 
-        $scope.chartAcceptValColumn = function(paragraph, item) {
-            var valCols = paragraph.chartValCols;
+                if (v > max)
+                    max = v;
+            });
 
-            var accepted = _.findIndex(valCols, item) < 0 && item.value >= 0 && _numberType(item.type);
+            return max;
+        }
 
-            if (accepted) {
-                if (valCols.length == MAX_VAL_COLS - 1)
-                    valCols.shift();
+        function _sum(rows, idx) {
+            let sum = 0;
 
-                valCols.push(item);
+            _.forEach(rows, (row) => sum += _chartNumber(row, idx, 0));
 
-                _chartApplySettings(paragraph, true);
-            }
+            return sum;
+        }
 
-            return false;
-        };
+        function _aggregate(rows, aggFx, idx, dflt) {
+            const len = rows.length;
 
-        $scope.scrollParagraphs = [];
+            switch (aggFx) {
+                case 'FIRST':
+                    return _chartNumber(rows[0], idx, dflt);
 
-        $scope.rebuildScrollParagraphs = function () {
-            $scope.scrollParagraphs = $scope.notebook.paragraphs.map(function (paragraph) {
-                return {
-                    "text": paragraph.name,
-                    "click": 'scrollToParagraph("' + paragraph.id + '")'
-                };
-            });
-        };
+                case 'LAST':
+                    return _chartNumber(rows[len - 1], idx, dflt);
 
-        $scope.scrollToParagraph = function (paragraphId) {
-            var idx = _.findIndex($scope.notebook.paragraphs, {id: paragraphId});
+                case 'MIN':
+                    return _min(rows, idx, dflt);
 
-            if (idx >= 0) {
-                if (!_.includes($scope.notebook.expandedParagraphs, idx))
-                    $scope.notebook.expandedParagraphs.push(idx);
+                case 'MAX':
+                    return _max(rows, idx, dflt);
 
-                setTimeout(function () {
-                    $scope.notebook.paragraphs[idx].ace.focus();
-                });
-            }
+                case 'SUM':
+                    return _sum(rows, idx);
 
-            $location.hash(paragraphId);
+                case 'AVG':
+                    return len > 0 ? _sum(rows, idx) / len : 0;
 
-            $anchorScroll();
-        };
+                case 'COUNT':
+                    return len;
 
-        const _hideColumn = (col) => col.fieldName !== '_KEY' && col.fieldName !== '_VAL';
+                default:
+            }
 
-        const _allColumn = () => true;
+            return 0;
+        }
 
-        var paragraphId = 0;
+        function _chartLabel(arr, idx, dflt) {
+            if (arr && arr.length > idx && _.isString(arr[idx]))
+                return arr[idx];
 
-        function enhanceParagraph(paragraph) {
-            paragraph.nonEmpty = function () {
-                return this.rows && this.rows.length > 0;
-            };
+            return dflt;
+        }
 
-            paragraph.chart = function () {
-                return this.result != 'table' && this.result != 'none';
-            };
+        function _chartDatum(paragraph) {
+            let datum = [];
 
-            paragraph.queryExecuted = () =>
-                paragraph.queryArgs && paragraph.queryArgs.query && !paragraph.queryArgs.query.startsWith('EXPLAIN ');
+            if (paragraph.chartColumnsConfigured()) {
+                paragraph.chartValCols.forEach(function(valCol) {
+                    let index = 0;
+                    let values = [];
+                    const colIdx = valCol.value;
 
-            paragraph.table = function () {
-                return this.result == 'table';
-            };
+                    if (paragraph.chartTimeLineEnabled()) {
+                        const aggFx = valCol.aggFx;
+                        const colLbl = valCol.label + ' [' + aggFx + ']';
 
-            paragraph.chartColumnsConfigured = function () {
-                return !_.isEmpty(this.chartKeyCols) && !_.isEmpty(this.chartValCols);
-            };
+                        if (paragraph.charts && paragraph.charts.length === 1)
+                            datum = paragraph.charts[0].data;
 
-            paragraph.chartTimeLineEnabled = function () {
-                return !_.isEmpty(this.chartKeyCols) && angular.equals(this.chartKeyCols[0], TIME_LINE);
-            };
+                        const chartData = _.find(datum, {series: valCol.label});
 
-            paragraph.timeLineSupported = function () {
-                return this.result != 'pie';
-            };
+                        const leftBound = new Date();
+                        leftBound.setMinutes(leftBound.getMinutes() - parseInt(paragraph.timeLineSpan, 10));
 
-            paragraph.refreshExecuting = function () {
-                return paragraph.rate && paragraph.rate.stopTime
-            };
+                        if (chartData) {
+                            const lastItem = _.last(paragraph.chartHistory);
 
-            Object.defineProperty(paragraph, 'gridOptions', { value: {
-                onRegisterApi: function(api) {
-                    $animate.enabled(api.grid.element, false);
+                            values = chartData.values;
 
-                    this.api = api;
-                },
-                enableGridMenu: false,
-                enableColumnMenus: false,
-                setRows: function(rows) {
-                    this.height = Math.min(rows.length, 15) * 30 + 42 + 'px';
+                            values.push({
+                                x: lastItem.tm,
+                                y: _aggregate(lastItem.rows, aggFx, colIdx, index++)
+                            });
 
-                    this.data = rows;
-                }
-            }});
+                            while (values.length > 0 && values[0].x < leftBound)
+                                values.shift();
+                        }
+                        else {
+                            _.forEach(paragraph.chartHistory, (history) => {
+                                if (history.tm >= leftBound) {
+                                    values.push({
+                                        x: history.tm,
+                                        y: _aggregate(history.rows, aggFx, colIdx, index++)
+                                    });
+                                }
+                            });
 
-            Object.defineProperty(paragraph, 'chartHistory', {value: []});
-        }
+                            datum.push({series: valCol.label, key: colLbl, values});
+                        }
+                    }
+                    else {
+                        index = paragraph.total;
 
-        $scope.aceInit = function (paragraph) {
-            return function (editor) {
-                editor.setAutoScrollEditorIntoView(true);
-                editor.$blockScrolling = Infinity;
+                        values = _.map(paragraph.rows, function(row) {
+                            const xCol = paragraph.chartKeyCols[0].value;
 
-                var renderer = editor.renderer;
+                            const v = {
+                                x: _chartNumber(row, xCol, index),
+                                xLbl: _chartLabel(row, xCol, null),
+                                y: _chartNumber(row, colIdx, index)
+                            };
 
-                renderer.setHighlightGutterLine(false);
-                renderer.setShowPrintMargin(false);
-                renderer.setOption('fontFamily', 'monospace');
-                renderer.setOption('fontSize', '14px');
-                renderer.setOption('minLines', '5');
-                renderer.setOption('maxLines', '15');
+                            index++;
 
-                editor.setTheme('ace/theme/chrome');
+                            return v;
+                        });
 
-                Object.defineProperty(paragraph, 'ace', { value: editor });
+                        datum.push({series: valCol.label, key: valCol.label, values});
+                    }
+                });
             }
-        };
 
-        var _setActiveCache = function () {
-            if ($scope.caches.length > 0)
-                _.forEach($scope.notebook.paragraphs, function (paragraph) {
-                    if (!_.find($scope.caches, {name: paragraph.cacheName}))
-                        paragraph.cacheName = $scope.caches[0].name;
-                });
-        };
+            return datum;
+        }
 
-        const _refreshFn = () =>
-            agentMonitor.topology()
-                .then((clusters) => {
-                    agentMonitor.checkModal();
+        function _xX(d) {
+            return d.x;
+        }
 
-                    const caches = _.flattenDeep(clusters.map((cluster) => cluster.caches));
+        function _yY(d) {
+            return d.y;
+        }
 
-                    $scope.caches = _.sortBy(_.uniqBy(_.reject(caches, { mode: 'LOCAL' }), 'name'), 'name');
+        function _xAxisTimeFormat(d) {
+            return d3.time.format('%X')(new Date(d));
+        }
 
-                    _setActiveCache();
-                })
-                .catch((err) => {
-                    if (err.code === 2)
-                        return agentMonitor.showNodeError('Agent is failed to authenticate in grid. Please check agent\'s login and password.');
+        const _intClasses = ['java.lang.Byte', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Short'];
 
-                    agentMonitor.showNodeError(err.message)}
-                );
+        function _intType(cls) {
+            return _.includes(_intClasses, cls);
+        }
 
-        var loadNotebook = function (notebook) {
-            $scope.notebook = notebook;
+        const _xAxisWithLabelFormat = function(paragraph) {
+            return function(d) {
+                const values = paragraph.charts[0].data[0].values;
 
-            $scope.notebook_name = notebook.name;
+                const fmt = _intType(paragraph.chartKeyCols[0].type) ? 'd' : ',.2f';
 
-            if (!$scope.notebook.expandedParagraphs)
-                $scope.notebook.expandedParagraphs = [];
+                const dx = values[d];
 
-            if (!$scope.notebook.paragraphs)
-                $scope.notebook.paragraphs = [];
+                if (!dx)
+                    return d3.format(fmt)(d);
 
-            _.forEach(notebook.paragraphs, function (paragraph) {
-                paragraph.id = 'paragraph-' + paragraphId++;
+                const lbl = dx.xLbl;
 
-                enhanceParagraph(paragraph);
-            });
+                return lbl ? lbl : d3.format(fmt)(d);
+            };
+        };
 
-            if (!notebook.paragraphs || notebook.paragraphs.length == 0)
-                $scope.addParagraph();
-            else
-                $scope.rebuildScrollParagraphs();
+        function _xAxisLabel(paragraph) {
+            return _.isEmpty(paragraph.chartKeyCols) ? 'X' : paragraph.chartKeyCols[0].label;
+        }
 
-            agentMonitor.startWatch({
-                    state: 'base.configuration.clusters',
-                    text: 'Back to Configuration',
-                    goal: 'execute sql statements'
-                })
-                .then(() => {
-                    $loading.start('sqlLoading');
+        const _yAxisFormat = function(d) {
+            const fmt = d < 1000 ? ',.2f' : '.3s';
 
-                    _refreshFn()
-                        .finally(() => {
-                            if ($root.IgniteDemoMode)
-                                _.forEach($scope.notebook.paragraphs, $scope.execute);
+            return d3.format(fmt)(d);
+        };
 
-                            $loading.finish('sqlLoading');
+        function _updateCharts(paragraph) {
+            $timeout(() => _.forEach(paragraph.charts, (chart) => chart.api.update()), 100);
+        }
 
-                            stopTopology = $interval(_refreshFn, 5000, 0, false);
-                        });
-                });
-        };
+        function _updateChartsWithData(paragraph, newDatum) {
+            $timeout(() => {
+                if (!paragraph.chartTimeLineEnabled()) {
+                    const chartDatum = paragraph.charts[0].data;
 
-        QueryNotebooks.read($state.params.noteId)
-            .then(loadNotebook)
-            .catch(function() {
-                $scope.notebookLoadFailed = true;
+                    chartDatum.length = 0;
 
-                $loading.finish('sqlLoading');
+                    _.forEach(newDatum, (series) => chartDatum.push(series));
+                }
+
+                paragraph.charts[0].api.update();
             });
+        }
 
-        $scope.renameNotebook = function (name) {
-            if (!name)
-                return;
+        function _yAxisLabel(paragraph) {
+            const cols = paragraph.chartValCols;
 
-            if ($scope.notebook.name != name) {
-                $scope.notebook.name = name;
+            const tml = paragraph.chartTimeLineEnabled();
 
-                QueryNotebooks.save($scope.notebook)
-                    .then(function() {
-                        var idx = _.findIndex($root.notebooks, function (item) {
-                            return item._id == $scope.notebook._id;
-                        });
+            return _.isEmpty(cols) ? 'Y' : _.map(cols, function(col) {
+                let lbl = col.label;
 
-                        if (idx >= 0) {
-                            $root.notebooks[idx].name = name;
+                if (tml)
+                    lbl += ' [' + col.aggFx + ']';
 
-                            $root.rebuildDropdown();
-                        }
+                return lbl;
+            }).join(', ');
+        }
 
-                        $scope.notebook.edit = false;
-                    })
-                    .catch(_handleException);
-            }
-            else
-                $scope.notebook.edit = false
-        };
+        function _barChart(paragraph) {
+            const datum = _chartDatum(paragraph);
 
-        $scope.removeNotebook = function () {
-            $confirm.confirm('Are you sure you want to remove: "' + $scope.notebook.name + '"?')
-                .then(function () {
-                    return QueryNotebooks.remove($scope.notebook);
-                })
-                .then(function (notebook) {
-                    if (notebook)
-                        $state.go('base.sql.notebook', {noteId: notebook._id});
-                    else
-                        $state.go('base.configuration.clusters');
-                })
-                .catch(_handleException);
-        };
-
-        $scope.renameParagraph = function (paragraph, newName) {
-            if (!newName)
-                return;
+            if (_.isEmpty(paragraph.charts)) {
+                const stacked = paragraph.chartsOptions && paragraph.chartsOptions.barChart
+                    ? paragraph.chartsOptions.barChart.stacked
+                    : true;
 
-            if (paragraph.name != newName) {
-                paragraph.name = newName;
+                const options = {
+                    chart: {
+                        type: 'multiBarChart',
+                        height: 400,
+                        margin: {left: 70},
+                        duration: 0,
+                        x: _xX,
+                        y: _yY,
+                        xAxis: {
+                            axisLabel: _xAxisLabel(paragraph),
+                            tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(paragraph),
+                            showMaxMin: false
+                        },
+                        yAxis: {
+                            axisLabel: _yAxisLabel(paragraph),
+                            tickFormat: _yAxisFormat
+                        },
+                        color: IgniteChartColors,
+                        stacked,
+                        showControls: true,
+                        legend: {
+                            vers: 'furious',
+                            margin: {right: -25}
+                        }
+                    }
+                };
 
-                $scope.rebuildScrollParagraphs();
+                paragraph.charts = [{options, data: datum}];
 
-                QueryNotebooks.save($scope.notebook)
-                    .then(function () { paragraph.edit = false; })
-                    .catch(_handleException);
+                _updateCharts(paragraph);
             }
             else
-                paragraph.edit = false
-        };
+                _updateChartsWithData(paragraph, datum);
+        }
 
-        $scope.addParagraph = function () {
-            var sz = $scope.notebook.paragraphs.length;
+        function _pieChartDatum(paragraph) {
+            const datum = [];
 
-            var paragraph = {
-                id: 'paragraph-' + paragraphId++,
-                name: 'Query' + (sz ==0 ? '' : sz),
-                query: '',
-                pageSize: $scope.pageSizes[0],
-                timeLineSpan: $scope.timeLineSpans[0],
-                result: 'none',
-                rate: {
-                    value: 1,
-                    unit: 60000,
-                    installed: false
-                }
-            };
+            if (paragraph.chartColumnsConfigured() && !paragraph.chartTimeLineEnabled()) {
+                paragraph.chartValCols.forEach(function(valCol) {
+                    let index = paragraph.total;
 
-            enhanceParagraph(paragraph);
+                    const values = _.map(paragraph.rows, (row) => {
+                        const xCol = paragraph.chartKeyCols[0].value;
 
-            if ($scope.caches && $scope.caches.length > 0)
-                paragraph.cacheName = $scope.caches[0].name;
+                        const v = {
+                            x: xCol < 0 ? index : row[xCol],
+                            y: _chartNumber(row, valCol.value, index)
+                        };
 
-            $scope.notebook.paragraphs.push(paragraph);
+                        // Workaround for known problem with zero values on Pie chart.
+                        if (v.y === 0)
+                            v.y = 0.0001;
 
-            $scope.notebook.expandedParagraphs.push(sz);
+                        index++;
 
-            $scope.rebuildScrollParagraphs();
+                        return v;
+                    });
 
-            $location.hash(paragraph.id);
+                    datum.push({series: paragraph.chartKeyCols[0].label, key: valCol.label, values});
+                });
+            }
 
-            $anchorScroll();
+            return datum;
+        }
 
-            setTimeout(function () {
-                paragraph.ace.focus();
+        function _pieChart(paragraph) {
+            let datum = _pieChartDatum(paragraph);
+
+            if (datum.length === 0)
+                datum = [{values: []}];
+
+            paragraph.charts = _.map(datum, function(data) {
+                return {
+                    options: {
+                        chart: {
+                            type: 'pieChart',
+                            height: 400,
+                            duration: 0,
+                            x: _xX,
+                            y: _yY,
+                            showLabels: true,
+                            labelThreshold: 0.05,
+                            labelType: 'percent',
+                            donut: true,
+                            donutRatio: 0.35,
+                            legend: {
+                                vers: 'furious',
+                                margin: {
+                                    right: -25
+                                }
+                            }
+                        },
+                        title: {
+                            enable: true,
+                            text: data.key
+                        }
+                    },
+                    data: data.values
+                };
             });
-        };
 
-        $scope.setResult = function (paragraph, new_result) {
-            if (paragraph.result === new_result)
-                return;
+            _updateCharts(paragraph);
+        }
 
-            _saveChartSettings(paragraph);
+        function _lineChart(paragraph) {
+            const datum = _chartDatum(paragraph);
 
-            paragraph.result = new_result;
+            if (_.isEmpty(paragraph.charts)) {
+                const options = {
+                    chart: {
+                        type: 'lineChart',
+                        height: 400,
+                        margin: { left: 70 },
+                        duration: 0,
+                        x: _xX,
+                        y: _yY,
+                        xAxis: {
+                            axisLabel: _xAxisLabel(paragraph),
+                            tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(paragraph),
+                            showMaxMin: false
+                        },
+                        yAxis: {
+                            axisLabel: _yAxisLabel(paragraph),
+                            tickFormat: _yAxisFormat
+                        },
+                        color: IgniteChartColors,
+                        useInteractiveGuideline: true,
+                        legend: {
+                            vers: 'furious',
+                            margin: {
+                                right: -25
+                            }
+                        }
+                    }
+                };
 
-            if (paragraph.chart())
-                _chartApplySettings(paragraph, true);
-        };
+                paragraph.charts = [{options, data: datum}];
 
-        $scope.resultEq = function(paragraph, result) {
-            return (paragraph.result === result);
-        };
+                _updateCharts(paragraph);
+            }
+            else
+                _updateChartsWithData(paragraph, datum);
+        }
 
-        $scope.removeParagraph = function(paragraph) {
-            $confirm.confirm('Are you sure you want to remove: "' + paragraph.name + '"?')
-                .then(function () {
-                    $scope.stopRefresh(paragraph);
+        function _areaChart(paragraph) {
+            const datum = _chartDatum(paragraph);
 
-                    var paragraph_idx = _.findIndex($scope.notebook.paragraphs, function (item) {
-                        return paragraph == item;
-                    });
+            if (_.isEmpty(paragraph.charts)) {
+                const style = paragraph.chartsOptions && paragraph.chartsOptions.areaChart
+                    ? paragraph.chartsOptions.areaChart.style
+                    : 'stack';
 
-                    var panel_idx = _.findIndex($scope.expandedParagraphs, function (item) {
-                        return paragraph_idx == item;
-                    });
+                const options = {
+                    chart: {
+                        type: 'stackedAreaChart',
+                        height: 400,
+                        margin: {left: 70},
+                        duration: 0,
+                        x: _xX,
+                        y: _yY,
+                        xAxis: {
+                            axisLabel: _xAxisLabel(paragraph),
+                            tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(paragraph),
+                            showMaxMin: false
+                        },
+                        yAxis: {
+                            axisLabel: _yAxisLabel(paragraph),
+                            tickFormat: _yAxisFormat
+                        },
+                        color: IgniteChartColors,
+                        style,
+                        legend: {
+                            vers: 'furious',
+                            margin: {right: -25}
+                        }
+                    }
+                };
 
-                    if (panel_idx >= 0)
-                        $scope.expandedParagraphs.splice(panel_idx, 1);
+                paragraph.charts = [{options, data: datum}];
 
-                    $scope.notebook.paragraphs.splice(paragraph_idx, 1);
+                _updateCharts(paragraph);
+            }
+            else
+                _updateChartsWithData(paragraph, datum);
+        }
 
-                    $scope.rebuildScrollParagraphs();
+        function _chartApplySettings(paragraph, resetCharts) {
+            if (resetCharts)
+                paragraph.charts = [];
 
-                    QueryNotebooks.save($scope.notebook)
-                        .catch(_handleException);
-                });
-        };
+            if (paragraph.chart() && paragraph.nonEmpty()) {
+                switch (paragraph.result) {
+                    case 'bar':
+                        _barChart(paragraph);
+                        break;
 
-        $scope.paragraphExpanded = function(paragraph) {
-            var paragraph_idx = _.findIndex($scope.notebook.paragraphs, function (item) {
-                return paragraph == item;
-            });
+                    case 'pie':
+                        _pieChart(paragraph);
+                        break;
 
-            var panel_idx = _.findIndex($scope.notebook.expandedParagraphs, function (item) {
-                return paragraph_idx == item;
-            });
+                    case 'line':
+                        _lineChart(paragraph);
+                        break;
 
-            return panel_idx >= 0;
+                    case 'area':
+                        _areaChart(paragraph);
+                        break;
+
+                    default:
+                }
+            }
+        }
+
+        $scope.chartRemoveKeyColumn = function(paragraph, index) {
+            paragraph.chartKeyCols.splice(index, 1);
+
+            _chartApplySettings(paragraph, true);
         };
 
-        var _columnFilter = function(paragraph) {
-            return paragraph.disabledSystemColumns || paragraph.systemColumns ? _allColumn : _hideColumn;
+        $scope.chartRemoveValColumn = function(paragraph, index) {
+            paragraph.chartValCols.splice(index, 1);
+
+            _chartApplySettings(paragraph, true);
         };
 
-        var _notObjectType = function(cls) {
-            return $common.isJavaBuiltInClass(cls);
+        $scope.chartAcceptKeyColumn = function(paragraph, item) {
+            const accepted = _.findIndex(paragraph.chartKeyCols, item) < 0;
+
+            if (accepted) {
+                paragraph.chartKeyCols = [item];
+
+                _chartApplySettings(paragraph, true);
+            }
+
+            return false;
         };
 
-        var _numberClasses = ['java.math.BigDecimal', 'java.lang.Byte', 'java.lang.Double',
+        const _numberClasses = ['java.math.BigDecimal', 'java.lang.Byte', 'java.lang.Double',
             'java.lang.Float', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Short'];
 
-        var _numberType = function(cls) {
+        const _numberType = function(cls) {
             return _.includes(_numberClasses, cls);
         };
 
-        var _intClasses = ['java.lang.Byte', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Short'];
+        $scope.chartAcceptValColumn = function(paragraph, item) {
+            const valCols = paragraph.chartValCols;
 
-        function _intType(cls) {
-            return _.includes(_intClasses, cls);
-        }
+            const accepted = _.findIndex(valCols, item) < 0 && item.value >= 0 && _numberType(item.type);
 
-        var _rebuildColumns = function (paragraph) {
-            var columnDefs = [];
+            if (accepted) {
+                if (valCols.length === MAX_VAL_COLS - 1)
+                    valCols.shift();
 
-            _.forEach(_.groupBy(paragraph.meta, 'fieldName'), function (colsByName, fieldName) {
-                var colsByTypes = _.groupBy(colsByName, 'typeName');
+                valCols.push(item);
 
-                var needType = _.keys(colsByTypes).length > 1;
+                _chartApplySettings(paragraph, true);
+            }
 
-                _.forEach(colsByTypes, function(colsByType, typeName) {
-                    _.forEach(colsByType, function (col, ix) {
-                        col.fieldName = (needType && !$common.isEmptyString(typeName) ? typeName + '.' : '') + fieldName + (ix > 0 ? ix : '');
-                    })
-                });
-            });
+            return false;
+        };
 
-            _.forEach(paragraph.meta, function (col, idx) {
-                if (paragraph.columnFilter(col)) {
-                    if (_notObjectType(col.fieldTypeName))
-                        paragraph.chartColumns.push({value: idx, type: col.fieldTypeName, label: col.fieldName, aggFx: $scope.aggregateFxs[0]});
-
-                    columnDefs.push({
-                        displayName: col.fieldName,
-                        headerTooltip: _fullColName(col),
-                        field: paragraph.queryArgs.query ? '' + idx : col.fieldName,
-                        minWidth: 50
-                    });
-                }
+        $scope.scrollParagraphs = [];
+
+        $scope.rebuildScrollParagraphs = function() {
+            $scope.scrollParagraphs = $scope.notebook.paragraphs.map(function(paragraph) {
+                return {
+                    text: paragraph.name,
+                    click: 'scrollToParagraph("' + paragraph.id + '")'
+                };
             });
+        };
 
-            paragraph.gridOptions.columnDefs = columnDefs;
+        $scope.scrollToParagraph = function(paragraphId) {
+            const idx = _.findIndex($scope.notebook.paragraphs, {id: paragraphId});
 
-            if (paragraph.chartColumns.length > 0) {
-                paragraph.chartColumns.push(TIME_LINE);
-                paragraph.chartColumns.push(ROW_IDX);
+            if (idx >= 0) {
+                if (!_.includes($scope.notebook.expandedParagraphs, idx))
+                    $scope.notebook.expandedParagraphs.push(idx);
+
+                setTimeout(function() {
+                    $scope.notebook.paragraphs[idx].ace.focus();
+                });
             }
 
-            // We could accept onl not object columns for X axis.
-            paragraph.chartKeyCols = _retainColumns(paragraph.chartColumns, paragraph.chartKeyCols, _notObjectType, true);
+            $location.hash(paragraphId);
 
-            // We could accept only numeric columns for Y axis.
-            paragraph.chartValCols = _retainColumns(paragraph.chartColumns, paragraph.chartValCols, _numberType, false, paragraph.chartKeyCols);
+            $anchorScroll();
         };
 
-        $scope.toggleSystemColumns = function (paragraph) {
-            if (paragraph.disabledSystemColumns)
-                return;
-
-            paragraph.systemColumns = !paragraph.systemColumns;
-
-            paragraph.columnFilter = _columnFilter(paragraph);
+        const _hideColumn = (col) => col.fieldName !== '_KEY' && col.fieldName !== '_VAL';
 
-            paragraph.chartColumns = [];
+        const _allColumn = () => true;
 
-            _rebuildColumns(paragraph);
-        };
+        let paragraphId = 0;
 
-        function _retainColumns(allCols, curCols, acceptableType, xAxis, unwantedCols) {
-            var retainedCols = [];
+        const _fullColName = function(col) {
+            const res = [];
 
-            var availableCols = xAxis ? allCols : _.filter(allCols, function (col) {
-                return col.value >= 0;
-            });
+            if (col.schemaName)
+                res.push(col.schemaName);
 
-            if (availableCols.length > 0) {
-                curCols.forEach(function (curCol) {
-                    var col = _.find(availableCols, {label: curCol.label});
+            if (col.typeName)
+                res.push(col.typeName);
 
-                    if (col && acceptableType(col.type)) {
-                        col.aggFx = curCol.aggFx;
+            res.push(col.fieldName);
 
-                        retainedCols.push(col);
-                    }
-                });
+            return res.join('.');
+        };
 
-                // If nothing was restored, add first acceptable column.
-                if (_.isEmpty(retainedCols)) {
-                    var col;
+        function enhanceParagraph(paragraph) {
+            paragraph.nonEmpty = function() {
+                return this.rows && this.rows.length > 0;
+            };
 
-                    if (unwantedCols)
-                        col = _.find(availableCols, function (col) {
-                            return !_.find(unwantedCols, {label: col.label}) && acceptableType(col.type);
-                        });
+            paragraph.chart = function() {
+                return this.result !== 'table' && this.result !== 'none';
+            };
 
-                    if (!col)
-                        col = _.find(availableCols, function (col) {
-                            return acceptableType(col.type);
-                        });
+            paragraph.queryExecuted = () =>
+                paragraph.queryArgs && paragraph.queryArgs.query && !paragraph.queryArgs.query.startsWith('EXPLAIN ');
 
-                    if (col)
-                        retainedCols.push(col);
-                }
-            }
+            paragraph.table = function() {
+                return this.result === 'table';
+            };
 
-            return retainedCols;
-        }
+            paragraph.chartColumnsConfigured = function() {
+                return !_.isEmpty(this.chartKeyCols) && !_.isEmpty(this.chartValCols);
+            };
 
-        /**
-         * @param {Object} paragraph Query
-         * @param {{fieldsMetadata: Array, items: Array, queryId: int, last: Boolean}} res Query results.
-         * @private
-         */
-        var _processQueryResult = function (paragraph, res) {
-            var prevKeyCols = paragraph.chartKeyCols;
-            var prevValCols = paragraph.chartValCols;
+            paragraph.chartTimeLineEnabled = function() {
+                return !_.isEmpty(this.chartKeyCols) && angular.equals(this.chartKeyCols[0], TIME_LINE);
+            };
 
-            if (!_.eq(paragraph.meta, res.fieldsMetadata)) {
-                paragraph.meta = [];
+            paragraph.timeLineSupported = function() {
+                return this.result !== 'pie';
+            };
 
-                paragraph.chartColumns = [];
+            paragraph.refreshExecuting = function() {
+                return paragraph.rate && paragraph.rate.stopTime;
+            };
 
-                if (!$common.isDefined(paragraph.chartKeyCols))
-                    paragraph.chartKeyCols = [];
+            Object.defineProperty(paragraph, 'gridOptions', { value: {
+                enableGridMenu: false,
+                enableColumnMenus: false,
+                flatEntityAccess: true,
+                fastWatch: true,
+                updateColumns(cols) {
+                    this.columnDefs = _.map(cols, (col) => {
+                        return {
+                            displayName: col.fieldName,
+                            headerTooltip: _fullColName(col),
+                            field: col.field,
+                            minWidth: 50
+                        };
+                    });
 
-                if (!$common.isDefined(paragraph.chartValCols))
-                    paragraph.chartValCols = [];
+                    $timeout(() => this.api.core.notifyDataChange(uiGridConstants.dataChange.COLUMN));
+                },
+                updateRows(rows) {
+                    const sizeChanged = this.data.length !== rows.length;
 
-                if (res.fieldsMetadata.length <= 2) {
-                    var _key = _.find(res.fieldsMetadata, {fieldName: '_KEY'});
-                    var _val = _.find(res.fieldsMetadata, {fieldName: '_VAL'});
+                    this.data = rows;
 
-                    paragraph.disabledSystemColumns = (res.fieldsMetadata.length == 2 && _key && _val) ||
-                        (res.fieldsMetadata.length == 1 && (_key || _val));
-                }
+                    if (sizeChanged) {
+                        const height = Math.min(rows.length, 15) * 30 + 47;
 
-                paragraph.columnFilter = _columnFilter(paragraph);
+                        // Remove header height.
+                        this.api.grid.element.css('height', height + 'px');
 
-                paragraph.meta = res.fieldsMetadata;
+                        $timeout(() => this.api.core.handleWindowResize());
+                    }
+                },
+                onRegisterApi(api) {
+                    $animate.enabled(api.grid.element, false);
 
-                _rebuildColumns(paragraph);
-            }
+                    this.api = api;
+                }
+            }});
 
-            paragraph.page = 1;
+            Object.defineProperty(paragraph, 'chartHistory', {value: []});
+        }
 
-            paragraph.total = 0;
+        $scope.aceInit = function(paragraph) {
+            return function(editor) {
+                editor.setAutoScrollEditorIntoView(true);
+                editor.$blockScrolling = Infinity;
 
-            paragraph.queryId = res.last ? null : res.queryId;
+                const renderer = editor.renderer;
 
-            delete paragraph.errMsg;
+                renderer.setHighlightGutterLine(false);
+                renderer.setShowPrintMargin(false);
+                renderer.setOption('fontFamily', 'monospace');
+                renderer.setOption('fontSize', '14px');
+                renderer.setOption('minLines', '5');
+                renderer.setOption('maxLines', '15');
 
-            // Prepare explain results for display in table.
-            if (paragraph.queryArgs.query && paragraph.queryArgs.query.startsWith('EXPLAIN') && res.items) {
-                paragraph.rows = [];
+                editor.setTheme('ace/theme/chrome');
 
-                res.items.forEach(function (row, i) {
-                    var line = res.items.length - 1 == i ? row[0] : row[0] + '\n';
+                Object.defineProperty(paragraph, 'ace', { value: editor });
+            };
+        };
 
-                    line.replace(/\"/g, '').split('\n').forEach(function (line) {
-                        paragraph.rows.push([line]);
-                    });
+        const _setActiveCache = function() {
+            if ($scope.caches.length > 0) {
+                _.forEach($scope.notebook.paragraphs, (paragraph) => {
+                    if (!_.find($scope.caches, {name: paragraph.cacheName}))
+                        paragraph.cacheName = $scope.caches[0].name;
                 });
             }
-            else
-                paragraph.rows = res.items;
-
-            paragraph.gridOptions.setRows(paragraph.rows);
+        };
 
-            var chartHistory = paragraph.chartHistory;
+        const _updateTopology = () =>
+            agentMonitor.topology()
+                .then((clusters) => {
+                    agentMonitor.checkModal();
 
-            // Clear history on query change.
-            var queryChanged = paragraph.prevQuery != paragraph.query;
+                    const caches = _.flattenDeep(clusters.map((cluster) => cluster.caches));
 
-            if (queryChanged) {
-                paragraph.prevQuery = paragraph.query;
+                    $scope.caches = _.sortBy(_.map(_.uniqBy(_.reject(caches, {mode: 'LOCAL'}), 'name'), (cache) => {
+                        cache.label = $scope.maskCacheName(cache.name);
 
-                chartHistory.length = 0;
+                        return cache;
+                    }), 'label');
 
-                _.forEach(paragraph.charts, function (chart) {
-                    chart.data.length = 0;
+                    _setActiveCache();
                 })
-            }
-
-            // Add results to history.
-            chartHistory.push({tm: new Date(), rows: paragraph.rows});
-
-            // Keep history size no more than max length.
-            while (chartHistory.length > HISTORY_LENGTH)
-                chartHistory.shift();
+                .catch((err) => {
+                    if (err.code === 2)
+                        return agentMonitor.showNodeError('Agent is failed to authenticate in grid. Please check agent\'s login and password.');
 
-            _showLoading(paragraph, false);
+                    agentMonitor.showNodeError(err);
+                });
 
-            if (paragraph.result === 'none' || !paragraph.queryExecuted())
-                paragraph.result = 'table';
-            else if (paragraph.chart()) {
-                var resetCharts = queryChanged;
+        const _startTopologyRefresh = () => {
+            $loading.start('sqlLoading');
 
-                if (!resetCharts) {
-                    var curKeyCols = paragraph.chartKeyCols;
-                    var curValCols = paragraph.chartValCols;
+            agentMonitor.awaitAgent()
+                .then(_updateTopology)
+                .finally(() => {
+                    if ($root.IgniteDemoMode)
+                        _.forEach($scope.notebook.paragraphs, $scope.execute);
 
-                    resetCharts = !prevKeyCols || !prevValCols ||
-                        prevKeyCols.length != curKeyCols.length ||
-                        prevValCols.length != curValCols.length;
-                }
+                    $loading.finish('sqlLoading');
 
-                _chartApplySettings(paragraph, resetCharts);
-            }
+                    stopTopology = $interval(_updateTopology, 5000, 0, false);
+                });
         };
 
-        const _closeOldQuery = (paragraph) => {
-            const queryId = paragraph.queryArgs && paragraph.queryArgs.queryId;
+        const loadNotebook = function(notebook) {
+            $scope.notebook = notebook;
 
-            return queryId ? agentMonitor.queryClose(queryId) : $q.when();
-        };
+            $scope.notebook_name = notebook.name;
 
-        const _executeRefresh = (paragraph) => {
-            const args = paragraph.queryArgs;
+            if (!$scope.notebook.expandedParagraphs)
+                $scope.notebook.expandedParagraphs = [];
 
-            agentMonitor.awaitAgent()
-                .then(() => _closeOldQuery(paragraph))
-                .then(() => agentMonitor.query(args.cacheName, args.pageSize, args.query))
-                .then(_processQueryResult.bind(this, paragraph))
-                .catch((err) => {
-                    paragraph.errMsg = err.message;
-                });
-        };
+            if (!$scope.notebook.paragraphs)
+                $scope.notebook.paragraphs = [];
 
-        const _showLoading = (paragraph, enable) => paragraph.loading = enable;
+            _.forEach(notebook.paragraphs, (paragraph) => {
+                paragraph.id = 'paragraph-' + paragraphId++;
 
-        $scope.execute = function (paragraph) {
-            QueryNotebooks.save($scope.notebook)
-                .catch(_handleException);
+                enhanceParagraph(paragraph);
+            });
 
-            paragraph.prevQuery = paragraph.queryArgs ? paragraph.queryArgs.query : paragraph.query;
+            if (!notebook.paragraphs || notebook.paragraphs.length === 0)
+                $scope.addParagraph();
+            else
+                $scope.rebuildScrollParagraphs();
 
-            _showLoading(paragraph, true);
+            agentMonitor.startWatch({
+                state: 'base.configuration.clusters',
+                text: 'Back to Configuration',
+                goal: 'execute sql statements',
+                onDisconnect: () => {
+                    _stopTopologyRefresh();
 
-            _closeOldQuery(paragraph)
-                .then(function () {
-                    const args = paragraph.queryArgs = {
-                        cacheName: paragraph.cacheName,
-                        pageSize: paragraph.pageSize,
-                        query: paragraph.query
-                    };
+                    _startTopologyRefresh();
+                }
+            })
+            .then(_startTopologyRefresh);
+        };
 
-                    return agentMonitor.query(args.cacheName, args.pageSize, args.query);
-                })
-                .then(function (res) {
-                    _processQueryResult(paragraph, res);
+        QueryNotebooks.read($state.params.noteId)
+            .then(loadNotebook)
+            .catch(() => {
+                $scope.notebookLoadFailed = true;
 
-                    _tryStartRefresh(paragraph);
-                })
-                .catch((err) => {
-                    paragraph.errMsg = err.message;
+                $loading.finish('sqlLoading');
+            });
 
-                    _showLoading(paragraph, false);
+        $scope.renameNotebook = function(name) {
+            if (!name)
+                return;
 
-                    $scope.stopRefresh(paragraph);
-                })
-                .finally(function () {
-                    paragraph.ace.focus();
-                });
-        };
+            if ($scope.notebook.name !== name) {
+                const prevName = $scope.notebook.name;
 
-        $scope.queryExecuted = function(paragraph) {
-            return $common.isDefined(paragraph.queryArgs);
-        };
+                $scope.notebook.name = name;
 
-        $scope.explain = function (paragraph) {
-            QueryNotebooks.save($scope.notebook)
-                .catch(_handleException);
+                QueryNotebooks.save($scope.notebook)
+                    .then(function() {
+                        const idx = _.findIndex($root.notebooks, function(item) {
+                            return item._id === $scope.notebook._id;
+                        });
 
-            _cancelRefresh(paragraph);
+                        if (idx >= 0) {
+                            $root.notebooks[idx].name = name;
 
-            _showLoading(paragraph, true);
+                            $root.rebuildDropdown();
+                        }
 
-            _closeOldQuery(paragraph)
-                .then(function () {
-                    const args = paragraph.queryArgs = {
-                        cacheName: paragraph.cacheName,
-                        pageSize: paragraph.pageSize,
-                        query: 'EXPLAIN ' + paragraph.query
-                    };
+                        $scope.notebook.edit = false;
+                    })
+                    .catch((err) => {
+                        $scope.notebook.name = prevName;
 
-                    return agentMonitor.query(args.cacheName, args.pageSize, args.query);
-                })
-                .then(_processQueryResult.bind(this, paragraph))
-                .catch((err) => {
-                    paragraph.errMsg = err.message;
+                        _handleException(err);
+                    });
+            }
+            else
+                $scope.notebook.edit = false;
+        };
 
-                    _showLoading(paragraph, false);
+        $scope.removeNotebook = function() {
+            $confirm.confirm('Are you sure you want to remove: "' + $scope.notebook.name + '"?')
+                .then(function() {
+                    return QueryNotebooks.remove($scope.notebook);
+                })
+                .then(function(notebook) {
+                    if (notebook)
+                        $state.go('base.sql.notebook', {noteId: notebook._id});
+                    else
+                        $state.go('base.configuration.clusters');
                 })
-                .finally(function () {
-                    paragraph.ace.focus();
-                });
-        };
-
-        $scope.scan = function (paragraph) {
-            QueryNotebooks.save($scope.notebook)
                 .catch(_handleException);
+        };
 
-            _cancelRefresh(paragraph);
-
-            _showLoading(paragraph, true);
+        $scope.renameParagraph = function(paragraph, newName) {
+            if (!newName)
+                return;
 
-            _closeOldQuery(paragraph)
-                .then(() => {
-                    const args = paragraph.queryArgs = {
-                        cacheName: paragraph.cacheName,
-                        pageSize: paragraph.pageSize
-                    };
+            if (paragraph.name !== newName) {
+                paragraph.name = newName;
 
-                    return agentMonitor.query(args.cacheName, args.pageSize);
-                })
-                .then(_processQueryResult.bind(this, paragraph))
-                .catch((err) => {
-                    paragraph.errMsg = err.message;
+                $scope.rebuildScrollParagraphs();
 
-                    _showLoading(paragraph, false);
-                })
-                .finally(function () {
-                    paragraph.ace.focus();
-                });
+                QueryNotebooks.save($scope.notebook)
+                    .then(function() { paragraph.edit = false; })
+                    .catch(_handleException);
+            }
+            else
+                paragraph.edit = false;
         };
 
-        $scope.nextPage = function(paragraph) {
-            _showLoading(paragraph, true);
+        $scope.addParagraph = function() {
+            const sz = $scope.notebook.paragraphs.length;
 
-            paragraph.queryArgs.pageSize = paragraph.pageSize;
+            const paragraph = {
+                id: 'paragraph-' + paragraphId++,
+                name: 'Query' + (sz === 0 ? '' : sz),
+                query: '',
+                pageSize: $scope.pageSizes[0],
+                timeLineSpan: $scope.timeLineSpans[0],
+                result: 'none',
+                rate: {
+                    value: 1,
+                    unit: 60000,
+                    installed: false
+                }
+            };
 
-            agentMonitor.next(paragraph.queryId, paragraph.pageSize)
-                .then(function (res) {
-                    paragraph.page++;
+            enhanceParagraph(paragraph);
 
-                    paragraph.total += paragraph.rows.length;
+            if ($scope.caches && $scope.caches.length > 0)
+                paragraph.cacheName = $scope.caches[0].name;
 
-                    paragraph.rows = res.items;
+            $scope.notebook.paragraphs.push(paragraph);
 
-                    if (paragraph.chart()) {
-                        if (paragraph.result == 'pie')
-                            _updatePieChartsWithData(paragraph, _pieChartDatum(paragraph));
-                        else
-                            _updateChartsWithData(paragraph, _chartDatum(paragraph));
-                    }
+            $scope.notebook.expandedParagraphs.push(sz);
 
-                    paragraph.gridOptions.setRows(paragraph.rows);
+            $scope.rebuildScrollParagraphs();
 
-                    _showLoading(paragraph, false);
+            $location.hash(paragraph.id);
 
-                    if (res.last)
-                        delete paragraph.queryId;
-                })
-                .catch((err) => {
-                    paragraph.errMsg = err.message;
+            $anchorScroll();
 
-                    _showLoading(paragraph, false);
-                })
-                .finally(function () {
-                    paragraph.ace.focus();
-                });
+            setTimeout(function() {
+                paragraph.ace.focus();
+            });
         };
 
-        var _fullColName = function(col) {
-            var res = [];
-
-            if (col.schemaName)
-                res.push(col.schemaName);
-            if (col.typeName)
-                res.push(col.typeName);
+        function _saveChartSettings(paragraph) {
+            if (!_.isEmpty(paragraph.charts)) {
+                const chart = paragraph.charts[0].api.getScope().chart;
 
-            res.push(col.fieldName);
+                if (!$common.isDefined(paragraph.chartsOptions))
+                    paragraph.chartsOptions = {barChart: {stacked: true}, areaChart: {style: 'stack'}};
 
-            return res.join('.');
-        };
+                switch (paragraph.result) {
+                    case 'bar':
+                        paragraph.chartsOptions.barChart.stacked = chart.stacked();
 
-        const _export = (fileName, columnFilter, meta, rows) => {
-            let csvContent = '';
+                        break;
 
-            const cols = [];
-            const excludedCols = [];
+                    case 'area':
+                        paragraph.chartsOptions.areaChart.style = chart.style();
 
-            if (meta) {
-                _.forEach(meta, (col, idx) => {
-                    if (columnFilter(col))
-                        cols.push(_fullColName(col));
-                    else
-                        excludedCols.push(idx);
-                });
+                        break;
 
-                csvContent += cols.join(';') + '\n';
+                    default:
+                }
             }
+        }
 
-            _.forEach(rows, (row) => {
-                cols.length = 0;
+        $scope.setResult = function(paragraph, new_result) {
+            if (paragraph.result === new_result)
+                return;
 
-                if (Array.isArray(row)) {
-                    _.forEach(row, (elem, idx) => {
-                        if (_.includes(excludedCols, idx))
-                            return;
+            _saveChartSettings(paragraph);
 
-                        cols.push(_.isUndefined(elem) ? '' : JSON.stringify(elem));
+            paragraph.result = new_result;
+
+            if (paragraph.chart())
+                _chartApplySettings(paragraph, true);
+            else
+                $timeout(() => paragraph.gridOptions.api.core.handleWindowResize());
+        };
+
+        $scope.resultEq = function(paragraph, result) {
+            return (paragraph.result === result);
+        };
+
+        $scope.removeParagraph = function(paragraph) {
+            $confirm.confirm('Are you sure you want to remove: "' + paragraph.name + '"?')
+                .then(function() {
+                    $scope.stopRefresh(paragraph);
+
+                    const paragraph_idx = _.findIndex($scope.notebook.paragraphs, function(item) {
+                        return paragraph === item;
                     });
-                }
-                else {
-                    _.forEach(meta, (col) => {
-                        if (columnFilter(col)) {
-                            const elem = row[col.fieldName];
 
-                            cols.push(_.isUndefined(elem) ? '' : JSON.stringify(elem));
-                        }
+                    const panel_idx = _.findIndex($scope.expandedParagraphs, function(item) {
+                        return paragraph_idx === item;
                     });
-                }
 
-                csvContent += cols.join(';') + '\n';
-            });
+                    if (panel_idx >= 0)
+                        $scope.expandedParagraphs.splice(panel_idx, 1);
 
-            $common.download('application/octet-stream;charset=utf-8', fileName, escape(csvContent));
-        };
+                    $scope.notebook.paragraphs.splice(paragraph_idx, 1);
 
-        $scope.exportCsv = function(paragraph) {
-            _export(paragraph.name + '.csv', paragraph.columnFilter, paragraph.meta, paragraph.rows);
+                    $scope.rebuildScrollParagraphs();
 
-            //paragraph.gridOptions.api.exporter.csvExport(uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE);
+                    QueryNotebooks.save($scope.notebook)
+                        .catch(_handleException);
+                });
         };
 
-        $scope.exportPdf = function(paragraph) {
-            paragraph.gridOptions.api.exporter.pdfExport(uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE);
-        };
+        $scope.paragraphExpanded = function(paragraph) {
+            const paragraph_idx = _.findIndex($scope.notebook.paragraphs, function(item) {
+                return paragraph === item;
+            });
 
-        $scope.exportCsvAll = function (paragraph) {
-            const args = paragraph.queryArgs;
+            const panel_idx = _.findIndex($scope.notebook.expandedParagraphs, function(item) {
+                return paragraph_idx === item;
+            });
 
-            agentMonitor.queryGetAll(args.cacheName, args.query)
-                .then((res) => _export(paragraph.name + '-all.csv', paragraph.columnFilter, res.fieldsMetadata, res.items))
-                .finally(() => paragraph.ace.focus());
+            return panel_idx >= 0;
         };
 
-        $scope.exportPdfAll = function(paragraph) {
-            //$http.post('/api/v1/agent/query/getAll', {query: paragraph.query, cacheName: paragraph.cacheName})
-            //    .success(function (item) {
-            //        _export(paragraph.name + '-all.csv', item.meta, item.rows);
-            //    })
-            //    .error(function (errMsg) {
-            //        $common.showError(errMsg);
-            //    });
+        const _columnFilter = function(paragraph) {
+            return paragraph.disabledSystemColumns || paragraph.systemColumns ? _allColumn : _hideColumn;
         };
 
-        $scope.rateAsString = function (paragraph) {
-            if (paragraph.rate && paragraph.rate.installed) {
-                var idx = _.findIndex($scope.timeUnit, function (unit) {
-                    return unit.value == paragraph.rate.unit;
-                });
+        const _notObjectType = function(cls) {
+            return $common.isJavaBuiltInClass(cls);
+        };
 
-                if (idx >= 0)
-                    return ' ' + paragraph.rate.value + $scope.timeUnit[idx].short;
+        function _retainColumns(allCols, curCols, acceptableType, xAxis, unwantedCols) {
+            const retainedCols = [];
 
-                paragraph.rate.installed = false;
-            }
+            const availableCols = xAxis ? allCols : _.filter(allCols, function(col) {
+                return col.value >= 0;
+            });
 
-            return '';
-        };
+            if (availableCols.length > 0) {
+                curCols.forEach(function(curCol) {
+                    const col = _.find(availableCols, {label: curCol.label});
 
-        var _cancelRefresh = function (paragraph) {
-            if (paragraph.rate && paragraph.rate.stopTime) {
-                delete paragraph.queryArgs;
+                    if (col && acceptableType(col.type)) {
+                        col.aggFx = curCol.aggFx;
 
-                paragraph.rate.installed = false;
+                        retainedCols.push(col);
+                    }
+                });
 
-                $interval.cancel(paragraph.rate.stopTime);
+                // If nothing was restored, add first acceptable column.
+                if (_.isEmpty(retainedCols)) {
+                    let col;
 
-                delete paragraph.rate.stopTime;
-            }
-        };
+                    if (unwantedCols)
+                        col = _.find(availableCols, (avCol) => !_.find(unwantedCols, {label: avCol.label}) && acceptableType(avCol.type));
 
-        var _tryStopRefresh = function (paragraph) {
-            if (paragraph.rate && paragraph.rate.stopTime) {
-                $interval.cancel(paragraph.rate.stopTime);
+                    if (!col)
+                        col = _.find(availableCols, (avCol) => acceptableType(avCol.type));
 
-                delete paragraph.rate.stopTime;
+                    if (col)
+                        retainedCols.push(col);
+                }
             }
-        };
 
-        var _tryStartRefresh = function (paragraph) {
-            _tryStopRefresh(paragraph);
+            return retainedCols;
+        }
 
-            if (paragraph.rate && paragraph.rate.installed && paragraph.queryArgs) {
-                $scope.chartAcceptKeyColumn(paragraph, TIME_LINE);
+        const _rebuildColumns = function(paragraph) {
+            _.forEach(_.groupBy(paragraph.meta, 'fieldName'), function(colsByName, fieldName) {
+                const colsByTypes = _.groupBy(colsByName, 'typeName');
 
-                _executeRefresh(paragraph);
+                const needType = _.keys(colsByTypes).length > 1;
 
-                var delay = paragraph.rate.value * paragraph.rate.unit;
+                _.forEach(colsByTypes, function(colsByType, typeName) {
+                    _.forEach(colsByType, function(col, ix) {
+                        col.fieldName = (needType && !$common.isEmptyString(typeName) ? typeName + '.' : '') + fieldName + (ix > 0 ? ix : '');
+                    });
+                });
+            });
 
-                paragraph.rate.stopTime = $interval(_executeRefresh, delay, 0, false, paragraph);
-            }
-        };
+            const cols = [];
 
-        $scope.startRefresh = function (paragraph, value, unit) {
-            paragraph.rate.value = value;
-            paragraph.rate.unit = unit;
-            paragraph.rate.installed = true;
+            _.forEach(paragraph.meta, (col, idx) => {
+                if (paragraph.columnFilter(col)) {
+                    col.field = paragraph.queryArgs.query ? idx.toString() : col.fieldName;
 
-            if (paragraph.queryExecuted())
-                _tryStartRefresh(paragraph);
-        };
+                    cols.push(col);
+                }
+            });
 
-        $scope.stopRefresh = function (paragraph) {
-            paragraph.rate.installed = false;
+            paragraph.gridOptions.updateColumns(cols);
 
-            _tryStopRefresh(paragraph);
-        };
+            paragraph.chartColumns = _.reduce(cols, (acc, col) => {
+                if (_notObjectType(col.fieldTypeName)) {
+                    acc.push({
+                        label: col.fieldName,
+                        type: col.fieldTypeName,
+                        aggFx: $scope.aggregateFxs[0],
+                        value: col.field
+                    });
+                }
 
-        function _chartNumber(arr, idx, dflt) {
-            if (idx >= 0 && arr && arr.length > idx && _.isNumber(arr[idx]))
-                return arr[idx];
+                return acc;
+            }, []);
 
-            return dflt;
-        }
+            if (paragraph.chartColumns.length > 0) {
+                paragraph.chartColumns.push(TIME_LINE);
+                paragraph.chartColumns.push(ROW_IDX);
+            }
 
-        function _chartLabel(arr, idx, dflt) {
-            if (arr && arr.length > idx && _.isString(arr[idx]))
-                return arr[idx];
+            // We could accept onl not object columns for X axis.
+            paragraph.chartKeyCols = _retainColumns(paragraph.chartColumns, paragraph.chartKeyCols, _notObjectType, true);
+
+            // We could accept only numeric columns for Y axis.
+            paragraph.chartValCols = _retainColumns(paragraph.chartColumns, paragraph.chartValCols, _numberType, false, paragraph.chartKeyCols);
+        };
+
+        $scope.toggleSystemColumns = function(paragraph) {
+            if (paragraph.disabledSystemColumns)
+                return;
+
+            paragraph.systemColumns = !paragraph.systemColumns;
 
-            return dflt;
-        }
+            paragraph.columnFilter = _columnFilter(paragraph);
 
-        function _min(rows, idx, dflt) {
-            var min = _chartNumber(rows[0], idx, dflt);
+            paragraph.chartColumns = [];
 
-            _.forEach(rows, function (row) {
-                var v = _chartNumber(row, idx, dflt);
+            _rebuildColumns(paragraph);
+        };
 
-                if (v < min)
-                    min = v;
-            });
+        const _showLoading = (paragraph, enable) => paragraph.loading = enable;
 
-            return min;
-        }
+        /**
+         * @param {Object} paragraph Query
+         * @param {{fieldsMetadata: Array, items: Array, queryId: int, last: Boolean}} res Query results.
+         * @private
+         */
+        const _processQueryResult = function(paragraph, res) {
+            const prevKeyCols = paragraph.chartKeyCols;
+            const prevValCols = paragraph.chartValCols;
 
-        function _max(rows, idx, dflt) {
-            var max = _chartNumber(rows[0], idx, dflt);
+            if (!_.eq(paragraph.meta, res.fieldsMetadata)) {
+                paragraph.meta = [];
 
-            _.forEach(rows, function (row) {
-                var v = _chartNumber(row, idx, dflt);
+                paragraph.chartColumns = [];
 
-                if (v > max)
-                    max = v;
-            });
+                if (!$common.isDefined(paragraph.chartKeyCols))
+                    paragraph.chartKeyCols = [];
 
-            return max;
-        }
+                if (!$common.isDefined(paragraph.chartValCols))
+                    paragraph.chartValCols = [];
 
-        function _sum(rows, idx) {
-            var sum = 0;
+                if (res.fieldsMetadata.length <= 2) {
+                    const _key = _.find(res.fieldsMetadata, {fieldName: '_KEY'});
+                    const _val = _.find(res.fieldsMetadata, {fieldName: '_VAL'});
 
-            _.forEach(rows, function (row) {
-                sum += _chartNumber(row, idx, 0);
-            });
+                    paragraph.disabledSystemColumns = (res.fieldsMetadata.length === 2 && _key && _val) ||
+                        (res.fieldsMetadata.length === 1 && (_key || _val));
+                }
 
-            return sum;
-        }
+                paragraph.columnFilter = _columnFilter(paragraph);
 
-        function _aggregate(rows, aggFx, idx, dflt) {
-            var len = rows.length;
+                paragraph.meta = res.fieldsMetadata;
 
-            switch (aggFx) {
-                case  'FIRST':
-                    return _chartNumber(rows[0], idx, dflt);
+                _rebuildColumns(paragraph);
+            }
 
-                case 'LAST':
-                    return _chartNumber(rows[len - 1], idx, dflt);
+            paragraph.page = 1;
 
-                case 'MIN':
-                    return _min(rows, idx, dflt);
+            paragraph.total = 0;
 
-                case 'MAX':
-                    return _max(rows, idx, dflt);
+            paragraph.queryId = res.last ? null : res.queryId;
 
-                case 'SUM':
-                    return _sum(rows, idx);
+            delete paragraph.errMsg;
 
-                case 'AVG':
-                    return len > 0 ? _sum(rows, idx) / len : 0;
+            // Prepare explain results for display in table.
+            if (paragraph.queryArgs.query && paragraph.queryArgs.query.startsWith('EXPLAIN') && res.items) {
+                paragraph.rows = [];
 
-                case 'COUNT':
-                    return len;
+                res.items.forEach(function(row, i) {
+                    const line = res.items.length - 1 === i ? row[0] : row[0] + '\n';
+
+                    line.replace(/\"/g, '').split('\n').forEach((ln) => paragraph.rows.push([ln]));
+                });
             }
+            else
+                paragraph.rows = res.items;
 
-            return 0;
-        }
+            paragraph.gridOptions.updateRows(paragraph.rows);
 
-        function _chartDatum(paragraph) {
-            var datum = [];
+            const chartHistory = paragraph.chartHistory;
 
-            if (paragraph.chartColumnsConfigured()) {
-                paragraph.chartValCols.forEach(function (valCol) {
-                    var index = 0;
-                    var values = [];
-                    var colIdx = valCol.value;
+            // Clear history on query change.
+            const queryChanged = paragraph.prevQuery !== paragraph.query;
 
-                    if (paragraph.chartTimeLineEnabled()) {
-                        var aggFx = valCol.aggFx;
-                        var colLbl = valCol.label + ' [' + aggFx + ']';
+            if (queryChanged) {
+                paragraph.prevQuery = paragraph.query;
 
-                        if (paragraph.charts && paragraph.charts.length == 1)
-                            datum = paragraph.charts[0].data;
+                chartHistory.length = 0;
 
-                        var chartData = _.find(datum, {series: valCol.label});
+                _.forEach(paragraph.charts, (chart) => chart.data.length = 0);
+            }
 
-                        var leftBound = new Date();
-                        leftBound.setMinutes(leftBound.getMinutes() - parseInt(paragraph.timeLineSpan));
+            // Add results to history.
+            chartHistory.push({tm: new Date(), rows: paragraph.rows});
 
-                        if (chartData) {
-                            var lastItem = _.last(paragraph.chartHistory);
+            // Keep history size no more than max length.
+            while (chartHistory.length > HISTORY_LENGTH)
+                chartHistory.shift();
 
-                            values = chartData.values;
+            _showLoading(paragraph, false);
 
-                            values.push({
-                                x: lastItem.tm,
-                                y: _aggregate(lastItem.rows, aggFx, colIdx, index++)
-                            });
+            if (paragraph.result === 'none' || !paragraph.queryExecuted())
+                paragraph.result = 'table';
+            else if (paragraph.chart()) {
+                let resetCharts = queryChanged;
 
-                            while (values.length > 0 && values[0].x < leftBound)
-                                values.shift();
-                        }
-                        else {
-                            _.forEach(paragraph.chartHistory, function (history) {
-                                if (history.tm >= leftBound)
-                                    values.push({
-                                        x: history.tm,
-                                        y: _aggregate(history.rows, aggFx, colIdx, index++)
-                                    });
-                            });
+                if (!resetCharts) {
+                    const curKeyCols = paragraph.chartKeyCols;
+                    const curValCols = paragraph.chartValCols;
 
-                            datum.push({series: valCol.label, key: colLbl, values: values});
-                        }
-                    }
-                    else {
-                        index = paragraph.total;
+                    resetCharts = !prevKeyCols || !prevValCols ||
+                        prevKeyCols.length !== curKeyCols.length ||
+                        prevValCols.length !== curValCols.length;
+                }
 
-                        values = _.map(paragraph.rows, function (row) {
-                            var xCol = paragraph.chartKeyCols[0].value;
+                _chartApplySettings(paragraph, resetCharts);
+            }
+        };
 
-                            var v = {
-                                x: _chartNumber(row, xCol, index),
-                                xLbl: _chartLabel(row, xCol, undefined),
-                                y: _chartNumber(row, colIdx, index)
-                            };
+        const _closeOldQuery = (paragraph) => {
+            const queryId = paragraph.queryArgs && paragraph.queryArgs.queryId;
 
-                            index++;
+            return queryId ? agentMonitor.queryClose(queryId) : $q.when();
+        };
 
-                            return v;
-                        });
+        const _executeRefresh = (paragraph) => {
+            const args = paragraph.queryArgs;
 
-                        datum.push({series: valCol.label, key: valCol.label, values: values});
-                    }
-                });
-            }
+            agentMonitor.awaitAgent()
+                .then(() => _closeOldQuery(paragraph))
+                .then(() => agentMonitor.query(args.cacheName, args.pageSize, args.query))
+                .then(_processQueryResult.bind(this, paragraph))
+                .catch((err) => paragraph.errMsg = err.message);
+        };
 
-            return datum;
-        }
+        const _tryStartRefresh = function(paragraph) {
+            _tryStopRefresh(paragraph);
 
-        function _pieChartDatum(paragraph) {
-            var datum = [];
+            if (paragraph.rate && paragraph.rate.installed && paragraph.queryArgs) {
+                $scope.chartAcceptKeyColumn(paragraph, TIME_LINE);
 
-            if (paragraph.chartColumnsConfigured() && !paragraph.chartTimeLineEnabled()) {
-                paragraph.chartValCols.forEach(function (valCol) {
-                    var index = paragraph.total;
+                _executeRefresh(paragraph);
 
-                    var values = _.map(paragraph.rows, function (row) {
-                        var xCol = paragraph.chartKeyCols[0].value;
+                const delay = paragraph.rate.value * paragraph.rate.unit;
 
-                        var v = {
-                            x: xCol < 0 ? index : row[xCol],
-                            y: _chartNumber(row, valCol.value, index)
-                        };
+                paragraph.rate.stopTime = $interval(_executeRefresh, delay, 0, false, paragraph);
+            }
+        };
 
-                        index++;
+        $scope.execute = function(paragraph) {
+            QueryNotebooks.save($scope.notebook)
+                .catch(_handleException);
 
-                        return v;
-                    });
+            paragraph.prevQuery = paragraph.queryArgs ? paragraph.queryArgs.query : paragraph.query;
 
-                    datum.push({series: paragraph.chartKeyCols[0].label, key: valCol.label, values: values});
-                });
-            }
+            _showLoading(paragraph, true);
 
-            return datum;
-        }
+            _closeOldQuery(paragraph)
+                .then(function() {
+                    const args = paragraph.queryArgs = {
+                        cacheName: paragraph.cacheName,
+                        pageSize: paragraph.pageSize,
+                        query: paragraph.query
+                    };
 
-        $scope.paragraphTimeSpanVisible = function (paragraph) {
-            return paragraph.timeLineSupported() && paragraph.chartTimeLineEnabled();
-        };
+                    return agentMonitor.query(args.cacheName, args.pageSize, args.query);
+                })
+                .then(function(res) {
+                    _processQueryResult(paragraph, res);
 
-        $scope.paragraphTimeLineSpan = function (paragraph) {
-          if (paragraph && paragraph.timeLineSpan)
-            return paragraph.timeLineSpan.toString();
+                    _tryStartRefresh(paragraph);
+                })
+                .catch((err) => {
+                    paragraph.errMsg = err.message;
 
-            return '1';
-        };
+                    _showLoading(paragraph, false);
 
-        function _saveChartSettings(paragraph) {
-            if (!_.isEmpty(paragraph.charts)) {
-                var chart = paragraph.charts[0].api.getScope().chart;
+                    $scope.stopRefresh(paragraph);
+                })
+                .finally(() => paragraph.ace.focus());
+        };
 
-                if (!$common.isDefined(paragraph.chartsOptions))
-                    paragraph.chartsOptions = {barChart: {stacked: true}, areaChart: {style: 'stack'}};
+        $scope.queryExecuted = function(paragraph) {
+            return $common.isDefined(paragraph.queryArgs);
+        };
 
-                switch (paragraph.result) {
-                    case 'bar':
-                        paragraph.chartsOptions.barChart.stacked = chart.stacked();
+        const _cancelRefresh = function(paragraph) {
+            if (paragraph.rate && paragraph.rate.stopTime) {
+                delete paragraph.queryArgs;
 
-                        break;
+                paragraph.rate.installed = false;
 
-                    case 'area':
-                        paragraph.chartsOptions.areaChart.style = chart.style();
+                $interval.cancel(paragraph.rate.stopTime);
 
-                        break;
-                }
+                delete paragraph.rate.stopTime;
             }
-        }
+        };
 
-        function _chartApplySettings(paragraph, resetCharts) {
-            if (resetCharts)
-                paragraph.charts = [];
+        $scope.explain = function(paragraph) {
+            QueryNotebooks.save($scope.notebook)
+                .catch(_handleException);
 
-            if (paragraph.chart() && paragraph.nonEmpty()) {
-                switch (paragraph.result) {
-                    case 'bar':
-                        _barChart(paragraph);
-                        break;
+            _cancelRefresh(paragraph);
 
-                    case 'pie':
-                        _pieChart(paragraph);
-                        break;
+            _showLoading(paragraph, true);
 
-                    case 'line':
-                        _lineChart(paragraph);
-                        break;
+            _closeOldQuery(paragraph)
+                .then(function() {
+                    const args = paragraph.queryArgs = {
+                        cacheName: paragraph.cacheName,
+                        pageSize: paragraph.pageSize,
+                        query: 'EXPLAIN ' + paragraph.query
+                    };
 
-                    case 'area':
-                        _areaChart(paragraph);
-                        break;
-                }
-            }
-        }
+                    return agentMonitor.query(args.cacheName, args.pageSize, args.query);
+                })
+                .then(_processQueryResult.bind(this, paragraph))
+                .catch((err) => {
+                    paragraph.errMsg = err.message;
 
-        $scope.applyChartSettings = function (paragraph) {
-            _chartApplySettings(paragraph, true);
+                    _showLoading(paragraph, false);
+                })
+                .finally(() => paragraph.ace.focus());
         };
 
-        function _xAxisLabel(paragraph) {
-            return _.isEmpty(paragraph.chartKeyCols) ? 'X' : paragraph.chartKeyCols[0].label;
-        }
+        $scope.scan = function(paragraph) {
+            QueryNotebooks.save($scope.notebook)
+                .catch(_handleException);
+
+            _cancelRefresh(paragraph);
+
+            _showLoading(paragraph, true);
+
+            _closeOldQuery(paragraph)
+                .then(() => {
+                    const args = paragraph.queryArgs = {
+                        cacheName: paragraph.cacheName,
+                        pageSize: paragraph.pageSize
+                    };
 
-        function _yAxisLabel(paragraph) {
-            var cols = paragraph.chartValCols;
+                    return agentMonitor.query(args.cacheName, args.pageSize);
+                })
+                .then(_processQueryResult.bind(this, paragraph))
+                .catch((err) => {
+                    paragraph.errMsg = err.message;
 
-            var tml = paragraph.chartTimeLineEnabled();
+                    _showLoading(paragraph, false);
+                })
+                .finally(() => paragraph.ace.focus());
+        };
 
-            return _.isEmpty(cols) ? 'Y' : _.map(cols, function (col) {
-                var lbl = col.label;
+        function _updatePieChartsWithData(paragraph, newDatum) {
+            $timeout(() => {
+                _.forEach(paragraph.charts, function(chart) {
+                    const chartDatum = chart.data;
 
-                if (tml)
-                 lbl += ' [' + col.aggFx + ']';
+                    chartDatum.length = 0;
 
-                return lbl;
-            }).join(', ');
-        }
+                    _.forEach(newDatum, function(series) {
+                        if (chart.options.title.text === series.key)
+                            _.forEach(series.values, (v) => chartDatum.push(v));
+                    });
+                });
 
-        function _xX(d) {
-            return d.x;
+                _.forEach(paragraph.charts, (chart) => chart.api.update());
+            });
         }
 
-        function _yY(d) {
-            return d.y;
-        }
+        $scope.nextPage = function(paragraph) {
+            _showLoading(paragraph, true);
 
-        function _xAxisTimeFormat(d) {
-            return d3.time.format('%X')(new Date(d));
-        }
+            paragraph.queryArgs.pageSize = paragraph.pageSize;
 
-        var _xAxisWithLabelFormat = function(paragraph) {
-            return function (d) {
-                var values = paragraph.charts[0].data[0].values;
+            agentMonitor.next(paragraph.queryId, paragraph.pageSize)
+                .then(function(res) {
+                    paragraph.page++;
 
-                var fmt = _intType(paragraph.chartKeyCols[0].type) ? 'd' : ',.2f';
+                    paragraph.total += paragraph.rows.length;
 
-                var dx = values[d];
+                    paragraph.rows = res.items;
 
-                if (!dx)
-                    return d3.format(fmt)(d);
+                    if (paragraph.chart()) {
+                        if (paragraph.result === 'pie')
+                            _updatePieChartsWithData(paragraph, _pieChartDatum(paragraph));
+                        else
+                            _updateChartsWithData(paragraph, _chartDatum(paragraph));
+                    }
 
-                var lbl = dx.xLbl;
+                    paragraph.gridOptions.updateRows(paragraph.rows);
 
-                return lbl ? lbl : d3.format(fmt)(d);
-            }
-        };
+                    _showLoading(paragraph, false);
 
-        var _yAxisFormat = function(d) {
-            var fmt = d < 1000 ? ',.2f' : '.3s';
+                    if (res.last)
+                        delete paragraph.queryId;
+                })
+                .catch((err) => {
+                    paragraph.errMsg = err.message;
 
-            return d3.format(fmt)(d);
+                    _showLoading(paragraph, false);
+                })
+                .finally(() => paragraph.ace.focus());
         };
 
-        function _updateCharts(paragraph) {
-            $timeout(function () {
-                _.forEach(paragraph.charts, function (chart) {
-                    chart.api.update();
-                });
-            }, 100);
-        }
+        const _export = (fileName, columnFilter, meta, rows) =>

<TRUNCATED>

Mime
View raw message