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>
