http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/controllers/profile-controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/controllers/profile-controller.js b/modules/web-console/src/main/js/controllers/profile-controller.js new file mode 100644 index 0000000..ffeffd4 --- /dev/null +++ b/modules/web-console/src/main/js/controllers/profile-controller.js @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Controller for Profile screen. +import consoleModule from 'controllers/common-module'; + +consoleModule.controller('profileController', [ + '$rootScope', '$scope', '$http', '$common', '$focus', '$confirm', 'IgniteCountries', + function ($root, $scope, $http, $common, $focus, $confirm, Countries) { + $scope.user = angular.copy($root.user); + + $scope.countries = Countries.getAll(); + + $scope.generateToken = () => { + $confirm.confirm('Are you sure you want to change security token?') + .then(() => $scope.user.token = $common.randomString(20)) + }; + + const _cleanup = () => { + const _user = $scope.user; + + if (!$scope.expandedToken) + _user.token = $root.user.token; + + if (!$scope.expandedPassword) { + delete _user.password; + + delete _user.confirm; + } + }; + + const _profileChanged = () => { + _cleanup(); + + const old = $root.user; + const cur = $scope.user; + + return !_.isEqual(old, cur); + }; + + $scope.profileCouldBeSaved = () => _profileChanged() && $scope.profileForm && $scope.profileForm.$valid; + + $scope.saveBtnTipText = () => { + if (!_profileChanged()) + return 'Nothing to save'; + + return $scope.profileForm && $scope.profileForm.$valid ? 'Save profile' : 'Invalid profile settings'; + }; + + $scope.saveUser = () => { + _cleanup(); + + $http.post('/api/v1/profile/save', $scope.user) + .success(() => { + $scope.expandedPassword = false; + + _cleanup(); + + $scope.expandedToken = false; + + $root.user = angular.copy($scope.user); + + $common.showInfo('Profile saved.'); + + $focus('profile-username'); + }) + .error((err) => $common.showError('Failed to save profile: ' + $common.errorMessage(err))); + }; + }] +);
http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/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 new file mode 100644 index 0000000..4bc39e2 --- /dev/null +++ b/modules/web-console/src/main/js/controllers/sql-controller.js @@ -0,0 +1,1568 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Controller for SQL notebook screen. +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; + + $scope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) { + $interval.cancel(stopTopology); + + if ($scope.notebook && $scope.notebook.paragraphs) + $scope.notebook.paragraphs.forEach(function (paragraph) { + _tryStopRefresh(paragraph); + }); + }); + + $scope.caches = []; + + $scope.pageSizes = [50, 100, 200, 400, 800, 1000]; + + $scope.timeLineSpans = ['1', '5', '10', '15', '30']; + + $scope.aggregateFxs = ['FIRST', 'LAST', 'MIN', 'MAX', 'SUM', 'AVG', 'COUNT']; + + $scope.modes = $common.mkOptions(['PARTITIONED', 'REPLICATED', 'LOCAL']); + + $scope.loadingText = $root.IgniteDemoMode ? 'Demo grid is starting. Please wait...' : 'Loading notebook screen...'; + + $scope.timeUnit = [ + {value: 1000, label: 'seconds', short: 's'}, + {value: 60000, label: 'minutes', short: 'm'}, + {value: 3600000, label: 'hours', short: 'h'} + ]; + + $scope.exportDropdown = [ + { '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.metaOptions = { + nodeChildren: 'children', + dirSelectable: true, + injectClasses: { + iExpanded: 'fa fa-minus-square-o', + iCollapsed: 'fa fa-plus-square-o' + } + }; + + $scope.maskCacheName = (cacheName) => _.isEmpty(cacheName) ? "<default>" : cacheName; + + var _handleException = function(err) { + $common.showError(err); + }; + + // Time line X axis descriptor. + var 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'}; + + // We need max 1800 items to hold history for 30 mins in case of refresh every second. + var HISTORY_LENGTH = 1800; + + var MAX_VAL_COLS = IgniteChartColors.length; + + $anchorScroll.yOffset = 55; + + $scope.chartColor = function(index) { + return {"color": "white", "background-color": IgniteChartColors[index]}; + }; + + $scope.chartRemoveKeyColumn = function (paragraph, index) { + paragraph.chartKeyCols.splice(index, 1); + + _chartApplySettings(paragraph, true); + }; + + $scope.chartRemoveValColumn = function (paragraph, index) { + paragraph.chartValCols.splice(index, 1); + + _chartApplySettings(paragraph, true); + }; + + $scope.chartAcceptKeyColumn = function(paragraph, item) { + var accepted = _.findIndex(paragraph.chartKeyCols, item) < 0; + + if (accepted) { + paragraph.chartKeyCols = [item]; + + _chartApplySettings(paragraph, true); + } + + return false; + }; + + $scope.chartAcceptValColumn = function(paragraph, item) { + var valCols = paragraph.chartValCols; + + var accepted = _.findIndex(valCols, item) < 0 && item.value >= 0 && _numberType(item.type); + + if (accepted) { + if (valCols.length == MAX_VAL_COLS - 1) + valCols.shift(); + + valCols.push(item); + + _chartApplySettings(paragraph, true); + } + + return false; + }; + + $scope.scrollParagraphs = []; + + $scope.rebuildScrollParagraphs = function () { + $scope.scrollParagraphs = $scope.notebook.paragraphs.map(function (paragraph) { + return { + "text": paragraph.name, + "click": 'scrollToParagraph("' + paragraph.id + '")' + }; + }); + }; + + $scope.scrollToParagraph = function (paragraphId) { + var idx = _.findIndex($scope.notebook.paragraphs, {id: paragraphId}); + + if (idx >= 0) { + if (!_.includes($scope.notebook.expandedParagraphs, idx)) + $scope.notebook.expandedParagraphs.push(idx); + + setTimeout(function () { + $scope.notebook.paragraphs[idx].ace.focus(); + }); + } + + $location.hash(paragraphId); + + $anchorScroll(); + }; + + const _hideColumn = (col) => col.fieldName !== '_KEY' && col.fieldName !== '_VAL'; + + const _allColumn = () => true; + + var paragraphId = 0; + + function enhanceParagraph(paragraph) { + paragraph.nonEmpty = function () { + return this.rows && this.rows.length > 0; + }; + + paragraph.chart = function () { + return this.result != 'table' && this.result != 'none'; + }; + + paragraph.queryExecuted = () => + paragraph.queryArgs && paragraph.queryArgs.query && !paragraph.queryArgs.query.startsWith('EXPLAIN '); + + paragraph.table = function () { + return this.result == 'table'; + }; + + paragraph.chartColumnsConfigured = function () { + return !_.isEmpty(this.chartKeyCols) && !_.isEmpty(this.chartValCols); + }; + + paragraph.chartTimeLineEnabled = function () { + return !_.isEmpty(this.chartKeyCols) && angular.equals(this.chartKeyCols[0], TIME_LINE); + }; + + paragraph.timeLineSupported = function () { + return this.result != 'pie'; + }; + + paragraph.refreshExecuting = function () { + return paragraph.rate && paragraph.rate.stopTime + }; + + Object.defineProperty(paragraph, 'gridOptions', { value: { + onRegisterApi: function(api) { + $animate.enabled(api.grid.element, false); + + this.api = api; + }, + enableGridMenu: false, + enableColumnMenus: false, + setRows: function(rows) { + this.height = Math.min(rows.length, 15) * 30 + 42 + 'px'; + + this.data = rows; + } + }}); + + Object.defineProperty(paragraph, 'chartHistory', {value: []}); + } + + $scope.aceInit = function (paragraph) { + return function (editor) { + editor.setAutoScrollEditorIntoView(true); + editor.$blockScrolling = Infinity; + + var renderer = editor.renderer; + + renderer.setHighlightGutterLine(false); + renderer.setShowPrintMargin(false); + renderer.setOption('fontFamily', 'monospace'); + renderer.setOption('fontSize', '14px'); + renderer.setOption('minLines', '5'); + renderer.setOption('maxLines', '15'); + + editor.setTheme('ace/theme/chrome'); + + Object.defineProperty(paragraph, 'ace', { value: editor }); + } + }; + + 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; + }); + }; + + const _refreshFn = () => + agentMonitor.topology() + .then((clusters) => { + agentMonitor.checkModal(); + + const caches = _.flattenDeep(clusters.map((cluster) => cluster.caches)); + + $scope.caches = _.sortBy(_.uniqBy(_.reject(caches, { mode: 'LOCAL' }), 'name'), 'name'); + + _setActiveCache(); + }) + .catch((err) => { + if (err.code === 2) + return agentMonitor.showNodeError('Agent is failed to authenticate in grid. Please check agent\'s login and password.'); + + agentMonitor.showNodeError(err.message)} + ); + + var loadNotebook = function (notebook) { + $scope.notebook = notebook; + + $scope.notebook_name = notebook.name; + + if (!$scope.notebook.expandedParagraphs) + $scope.notebook.expandedParagraphs = []; + + if (!$scope.notebook.paragraphs) + $scope.notebook.paragraphs = []; + + _.forEach(notebook.paragraphs, function (paragraph) { + paragraph.id = 'paragraph-' + paragraphId++; + + enhanceParagraph(paragraph); + }); + + if (!notebook.paragraphs || notebook.paragraphs.length == 0) + $scope.addParagraph(); + else + $scope.rebuildScrollParagraphs(); + + agentMonitor.startWatch({ + state: 'base.configuration.clusters', + text: 'Back to Configuration', + goal: 'execute sql statements' + }) + .then(() => { + $loading.start('sqlLoading'); + + _refreshFn() + .finally(() => { + if ($root.IgniteDemoMode) + _.forEach($scope.notebook.paragraphs, $scope.execute); + + $loading.finish('sqlLoading'); + + stopTopology = $interval(_refreshFn, 5000, 0, false); + }); + }); + }; + + QueryNotebooks.read($state.params.noteId) + .then(loadNotebook) + .catch(function() { + $scope.notebookLoadFailed = true; + + $loading.finish('sqlLoading'); + }); + + $scope.renameNotebook = function (name) { + if (!name) + return; + + if ($scope.notebook.name != name) { + $scope.notebook.name = name; + + QueryNotebooks.save($scope.notebook) + .then(function() { + var idx = _.findIndex($root.notebooks, function (item) { + return item._id == $scope.notebook._id; + }); + + if (idx >= 0) { + $root.notebooks[idx].name = name; + + $root.rebuildDropdown(); + } + + $scope.notebook.edit = false; + }) + .catch(_handleException); + } + else + $scope.notebook.edit = 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'); + }) + .catch(_handleException); + }; + + $scope.renameParagraph = function (paragraph, newName) { + if (!newName) + return; + + if (paragraph.name != newName) { + paragraph.name = newName; + + $scope.rebuildScrollParagraphs(); + + QueryNotebooks.save($scope.notebook) + .then(function () { paragraph.edit = false; }) + .catch(_handleException); + } + else + paragraph.edit = false + }; + + $scope.addParagraph = function () { + var sz = $scope.notebook.paragraphs.length; + + 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 + } + }; + + enhanceParagraph(paragraph); + + if ($scope.caches && $scope.caches.length > 0) + paragraph.cacheName = $scope.caches[0].name; + + $scope.notebook.paragraphs.push(paragraph); + + $scope.notebook.expandedParagraphs.push(sz); + + $scope.rebuildScrollParagraphs(); + + $location.hash(paragraph.id); + + $anchorScroll(); + + setTimeout(function () { + paragraph.ace.focus(); + }); + }; + + $scope.setResult = function (paragraph, new_result) { + if (paragraph.result === new_result) + return; + + _saveChartSettings(paragraph); + + paragraph.result = new_result; + + if (paragraph.chart()) + _chartApplySettings(paragraph, true); + }; + + $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); + + var paragraph_idx = _.findIndex($scope.notebook.paragraphs, function (item) { + return paragraph == item; + }); + + var panel_idx = _.findIndex($scope.expandedParagraphs, function (item) { + return paragraph_idx == item; + }); + + if (panel_idx >= 0) + $scope.expandedParagraphs.splice(panel_idx, 1); + + $scope.notebook.paragraphs.splice(paragraph_idx, 1); + + $scope.rebuildScrollParagraphs(); + + QueryNotebooks.save($scope.notebook) + .catch(_handleException); + }); + }; + + $scope.paragraphExpanded = function(paragraph) { + var paragraph_idx = _.findIndex($scope.notebook.paragraphs, function (item) { + return paragraph == item; + }); + + var panel_idx = _.findIndex($scope.notebook.expandedParagraphs, function (item) { + return paragraph_idx == item; + }); + + return panel_idx >= 0; + }; + + var _columnFilter = function(paragraph) { + return paragraph.disabledSystemColumns || paragraph.systemColumns ? _allColumn : _hideColumn; + }; + + var _notObjectType = function(cls) { + return $common.isJavaBuiltInClass(cls); + }; + + var _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) { + return _.includes(_numberClasses, cls); + }; + + var _intClasses = ['java.lang.Byte', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Short']; + + function _intType(cls) { + return _.includes(_intClasses, cls); + } + + var _rebuildColumns = function (paragraph) { + var columnDefs = []; + + _.forEach(_.groupBy(paragraph.meta, 'fieldName'), function (colsByName, fieldName) { + var colsByTypes = _.groupBy(colsByName, 'typeName'); + + var needType = _.keys(colsByTypes).length > 1; + + _.forEach(colsByTypes, function(colsByType, typeName) { + _.forEach(colsByType, function (col, ix) { + col.fieldName = (needType && !$common.isEmptyString(typeName) ? typeName + '.' : '') + fieldName + (ix > 0 ? ix : ''); + }) + }); + }); + + _.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 + }); + } + }); + + paragraph.gridOptions.columnDefs = columnDefs; + + if (paragraph.chartColumns.length > 0) { + paragraph.chartColumns.push(TIME_LINE); + paragraph.chartColumns.push(ROW_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; + + paragraph.columnFilter = _columnFilter(paragraph); + + paragraph.chartColumns = []; + + _rebuildColumns(paragraph); + }; + + function _retainColumns(allCols, curCols, acceptableType, xAxis, unwantedCols) { + var retainedCols = []; + + var availableCols = xAxis ? allCols : _.filter(allCols, function (col) { + return col.value >= 0; + }); + + if (availableCols.length > 0) { + curCols.forEach(function (curCol) { + var col = _.find(availableCols, {label: curCol.label}); + + if (col && acceptableType(col.type)) { + col.aggFx = curCol.aggFx; + + retainedCols.push(col); + } + }); + + // If nothing was restored, add first acceptable column. + if (_.isEmpty(retainedCols)) { + var col; + + if (unwantedCols) + col = _.find(availableCols, function (col) { + return !_.find(unwantedCols, {label: col.label}) && acceptableType(col.type); + }); + + if (!col) + col = _.find(availableCols, function (col) { + return acceptableType(col.type); + }); + + if (col) + retainedCols.push(col); + } + } + + return retainedCols; + } + + /** + * @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; + + if (!_.eq(paragraph.meta, res.fieldsMetadata)) { + paragraph.meta = []; + + paragraph.chartColumns = []; + + if (!$common.isDefined(paragraph.chartKeyCols)) + paragraph.chartKeyCols = []; + + if (!$common.isDefined(paragraph.chartValCols)) + paragraph.chartValCols = []; + + if (res.fieldsMetadata.length <= 2) { + var _key = _.find(res.fieldsMetadata, {fieldName: '_KEY'}); + var _val = _.find(res.fieldsMetadata, {fieldName: '_VAL'}); + + paragraph.disabledSystemColumns = (res.fieldsMetadata.length == 2 && _key && _val) || + (res.fieldsMetadata.length == 1 && (_key || _val)); + } + + paragraph.columnFilter = _columnFilter(paragraph); + + paragraph.meta = res.fieldsMetadata; + + _rebuildColumns(paragraph); + } + + paragraph.page = 1; + + paragraph.total = 0; + + paragraph.queryId = res.last ? null : res.queryId; + + delete paragraph.errMsg; + + // Prepare explain results for display in table. + if (paragraph.queryArgs.query && paragraph.queryArgs.query.startsWith('EXPLAIN') && res.items) { + paragraph.rows = []; + + res.items.forEach(function (row, i) { + var line = res.items.length - 1 == i ? row[0] : row[0] + '\n'; + + line.replace(/\"/g, '').split('\n').forEach(function (line) { + paragraph.rows.push([line]); + }); + }); + } + else + paragraph.rows = res.items; + + paragraph.gridOptions.setRows(paragraph.rows); + + var chartHistory = paragraph.chartHistory; + + // Clear history on query change. + var queryChanged = paragraph.prevQuery != paragraph.query; + + if (queryChanged) { + paragraph.prevQuery = paragraph.query; + + chartHistory.length = 0; + + _.forEach(paragraph.charts, function (chart) { + chart.data.length = 0; + }) + } + + // 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(); + + _showLoading(paragraph, false); + + if (paragraph.result === 'none' || !paragraph.queryExecuted()) + paragraph.result = 'table'; + else if (paragraph.chart()) { + var resetCharts = queryChanged; + + if (!resetCharts) { + var curKeyCols = paragraph.chartKeyCols; + var curValCols = paragraph.chartValCols; + + resetCharts = !prevKeyCols || !prevValCols || + prevKeyCols.length != curKeyCols.length || + prevValCols.length != curValCols.length; + } + + _chartApplySettings(paragraph, resetCharts); + } + }; + + const _closeOldQuery = (paragraph) => { + const queryId = paragraph.queryArgs && paragraph.queryArgs.queryId; + + return queryId ? agentMonitor.queryClose(queryId) : $q.when(); + }; + + const _executeRefresh = (paragraph) => { + const args = paragraph.queryArgs; + + 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; + }); + }; + + const _showLoading = (paragraph, enable) => paragraph.loading = enable; + + $scope.execute = function (paragraph) { + QueryNotebooks.save($scope.notebook) + .catch(_handleException); + + paragraph.prevQuery = paragraph.queryArgs ? paragraph.queryArgs.query : paragraph.query; + + _showLoading(paragraph, true); + + _closeOldQuery(paragraph) + .then(function () { + const args = paragraph.queryArgs = { + cacheName: paragraph.cacheName, + pageSize: paragraph.pageSize, + query: paragraph.query + }; + + return agentMonitor.query(args.cacheName, args.pageSize, args.query); + }) + .then(function (res) { + _processQueryResult(paragraph, res); + + _tryStartRefresh(paragraph); + }) + .catch((err) => { + paragraph.errMsg = err.message; + + _showLoading(paragraph, false); + + $scope.stopRefresh(paragraph); + }) + .finally(function () { + paragraph.ace.focus(); + }); + }; + + $scope.queryExecuted = function(paragraph) { + return $common.isDefined(paragraph.queryArgs); + }; + + $scope.explain = function (paragraph) { + QueryNotebooks.save($scope.notebook) + .catch(_handleException); + + _cancelRefresh(paragraph); + + _showLoading(paragraph, true); + + _closeOldQuery(paragraph) + .then(function () { + const args = paragraph.queryArgs = { + cacheName: paragraph.cacheName, + pageSize: paragraph.pageSize, + query: 'EXPLAIN ' + paragraph.query + }; + + return agentMonitor.query(args.cacheName, args.pageSize, args.query); + }) + .then(_processQueryResult.bind(this, paragraph)) + .catch((err) => { + paragraph.errMsg = err.message; + + _showLoading(paragraph, false); + }) + .finally(function () { + paragraph.ace.focus(); + }); + }; + + $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 + }; + + return agentMonitor.query(args.cacheName, args.pageSize); + }) + .then(_processQueryResult.bind(this, paragraph)) + .catch((err) => { + paragraph.errMsg = err.message; + + _showLoading(paragraph, false); + }) + .finally(function () { + paragraph.ace.focus(); + }); + }; + + $scope.nextPage = function(paragraph) { + _showLoading(paragraph, true); + + paragraph.queryArgs.pageSize = paragraph.pageSize; + + agentMonitor.next(paragraph.queryId, paragraph.pageSize) + .then(function (res) { + paragraph.page++; + + paragraph.total += paragraph.rows.length; + + paragraph.rows = res.items; + + if (paragraph.chart()) { + if (paragraph.result == 'pie') + _updatePieChartsWithData(paragraph, _pieChartDatum(paragraph)); + else + _updateChartsWithData(paragraph, _chartDatum(paragraph)); + } + + paragraph.gridOptions.setRows(paragraph.rows); + + _showLoading(paragraph, false); + + if (res.last) + delete paragraph.queryId; + }) + .catch((err) => { + paragraph.errMsg = err.message; + + _showLoading(paragraph, false); + }) + .finally(function () { + paragraph.ace.focus(); + }); + }; + + var _fullColName = function(col) { + var res = []; + + if (col.schemaName) + res.push(col.schemaName); + if (col.typeName) + res.push(col.typeName); + + res.push(col.fieldName); + + return res.join('.'); + }; + + const _export = (fileName, columnFilter, meta, rows) => { + let csvContent = ''; + + const cols = []; + const excludedCols = []; + + if (meta) { + _.forEach(meta, (col, idx) => { + if (columnFilter(col)) + cols.push(_fullColName(col)); + else + excludedCols.push(idx); + }); + + csvContent += cols.join(';') + '\n'; + } + + _.forEach(rows, (row) => { + cols.length = 0; + + if (Array.isArray(row)) { + _.forEach(row, (elem, idx) => { + if (_.includes(excludedCols, idx)) + return; + + cols.push(_.isUndefined(elem) ? '' : JSON.stringify(elem)); + }); + } + else { + _.forEach(meta, (col) => { + if (columnFilter(col)) { + const elem = row[col.fieldName]; + + cols.push(_.isUndefined(elem) ? '' : JSON.stringify(elem)); + } + }); + } + + csvContent += cols.join(';') + '\n'; + }); + + $common.download('application/octet-stream;charset=utf-8', fileName, escape(csvContent)); + }; + + $scope.exportCsv = function(paragraph) { + _export(paragraph.name + '.csv', paragraph.columnFilter, paragraph.meta, paragraph.rows); + + //paragraph.gridOptions.api.exporter.csvExport(uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE); + }; + + $scope.exportPdf = function(paragraph) { + paragraph.gridOptions.api.exporter.pdfExport(uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE); + }; + + $scope.exportCsvAll = function (paragraph) { + const args = paragraph.queryArgs; + + agentMonitor.queryGetAll(args.cacheName, args.query) + .then((res) => _export(paragraph.name + '-all.csv', paragraph.columnFilter, res.fieldsMetadata, res.items)) + .finally(() => paragraph.ace.focus()); + }; + + $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); + // }); + }; + + $scope.rateAsString = function (paragraph) { + if (paragraph.rate && paragraph.rate.installed) { + var idx = _.findIndex($scope.timeUnit, function (unit) { + return unit.value == paragraph.rate.unit; + }); + + if (idx >= 0) + return ' ' + paragraph.rate.value + $scope.timeUnit[idx].short; + + paragraph.rate.installed = false; + } + + return ''; + }; + + var _cancelRefresh = function (paragraph) { + if (paragraph.rate && paragraph.rate.stopTime) { + delete paragraph.queryArgs; + + paragraph.rate.installed = false; + + $interval.cancel(paragraph.rate.stopTime); + + delete paragraph.rate.stopTime; + } + }; + + var _tryStopRefresh = function (paragraph) { + if (paragraph.rate && paragraph.rate.stopTime) { + $interval.cancel(paragraph.rate.stopTime); + + delete paragraph.rate.stopTime; + } + }; + + var _tryStartRefresh = function (paragraph) { + _tryStopRefresh(paragraph); + + if (paragraph.rate && paragraph.rate.installed && paragraph.queryArgs) { + $scope.chartAcceptKeyColumn(paragraph, TIME_LINE); + + _executeRefresh(paragraph); + + var delay = paragraph.rate.value * paragraph.rate.unit; + + paragraph.rate.stopTime = $interval(_executeRefresh, delay, 0, false, paragraph); + } + }; + + $scope.startRefresh = function (paragraph, value, unit) { + paragraph.rate.value = value; + paragraph.rate.unit = unit; + paragraph.rate.installed = true; + + if (paragraph.queryExecuted()) + _tryStartRefresh(paragraph); + }; + + $scope.stopRefresh = function (paragraph) { + paragraph.rate.installed = false; + + _tryStopRefresh(paragraph); + }; + + function _chartNumber(arr, idx, dflt) { + if (idx >= 0 && arr && arr.length > idx && _.isNumber(arr[idx])) + return arr[idx]; + + return dflt; + } + + function _chartLabel(arr, idx, dflt) { + if (arr && arr.length > idx && _.isString(arr[idx])) + return arr[idx]; + + return dflt; + } + + function _min(rows, idx, dflt) { + var min = _chartNumber(rows[0], idx, dflt); + + _.forEach(rows, function (row) { + var v = _chartNumber(row, idx, dflt); + + if (v < min) + min = v; + }); + + return min; + } + + function _max(rows, idx, dflt) { + var max = _chartNumber(rows[0], idx, dflt); + + _.forEach(rows, function (row) { + var v = _chartNumber(row, idx, dflt); + + if (v > max) + max = v; + }); + + return max; + } + + function _sum(rows, idx) { + var sum = 0; + + _.forEach(rows, function (row) { + sum += _chartNumber(row, idx, 0); + }); + + return sum; + } + + function _aggregate(rows, aggFx, idx, dflt) { + var len = rows.length; + + switch (aggFx) { + case 'FIRST': + return _chartNumber(rows[0], idx, dflt); + + case 'LAST': + return _chartNumber(rows[len - 1], idx, dflt); + + case 'MIN': + return _min(rows, idx, dflt); + + case 'MAX': + return _max(rows, idx, dflt); + + case 'SUM': + return _sum(rows, idx); + + case 'AVG': + return len > 0 ? _sum(rows, idx) / len : 0; + + case 'COUNT': + return len; + } + + return 0; + } + + function _chartDatum(paragraph) { + var datum = []; + + if (paragraph.chartColumnsConfigured()) { + paragraph.chartValCols.forEach(function (valCol) { + var index = 0; + var values = []; + var colIdx = valCol.value; + + if (paragraph.chartTimeLineEnabled()) { + var aggFx = valCol.aggFx; + var colLbl = valCol.label + ' [' + aggFx + ']'; + + if (paragraph.charts && paragraph.charts.length == 1) + datum = paragraph.charts[0].data; + + var chartData = _.find(datum, {series: valCol.label}); + + var leftBound = new Date(); + leftBound.setMinutes(leftBound.getMinutes() - parseInt(paragraph.timeLineSpan)); + + if (chartData) { + var lastItem = _.last(paragraph.chartHistory); + + values = chartData.values; + + values.push({ + x: lastItem.tm, + y: _aggregate(lastItem.rows, aggFx, colIdx, index++) + }); + + 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++) + }); + }); + + datum.push({series: valCol.label, key: colLbl, values: values}); + } + } + else { + index = paragraph.total; + + values = _.map(paragraph.rows, function (row) { + var xCol = paragraph.chartKeyCols[0].value; + + var v = { + x: _chartNumber(row, xCol, index), + xLbl: _chartLabel(row, xCol, undefined), + y: _chartNumber(row, colIdx, index) + }; + + index++; + + return v; + }); + + datum.push({series: valCol.label, key: valCol.label, values: values}); + } + }); + } + + return datum; + } + + function _pieChartDatum(paragraph) { + var datum = []; + + if (paragraph.chartColumnsConfigured() && !paragraph.chartTimeLineEnabled()) { + paragraph.chartValCols.forEach(function (valCol) { + var index = paragraph.total; + + var values = _.map(paragraph.rows, function (row) { + var xCol = paragraph.chartKeyCols[0].value; + + var v = { + x: xCol < 0 ? index : row[xCol], + y: _chartNumber(row, valCol.value, index) + }; + + index++; + + return v; + }); + + datum.push({series: paragraph.chartKeyCols[0].label, key: valCol.label, values: values}); + }); + } + + return datum; + } + + $scope.paragraphTimeSpanVisible = function (paragraph) { + return paragraph.timeLineSupported() && paragraph.chartTimeLineEnabled(); + }; + + $scope.paragraphTimeLineSpan = function (paragraph) { + if (paragraph && paragraph.timeLineSpan) + return paragraph.timeLineSpan.toString(); + + return '1'; + }; + + function _saveChartSettings(paragraph) { + if (!_.isEmpty(paragraph.charts)) { + var chart = paragraph.charts[0].api.getScope().chart; + + if (!$common.isDefined(paragraph.chartsOptions)) + paragraph.chartsOptions = {barChart: {stacked: true}, areaChart: {style: 'stack'}}; + + switch (paragraph.result) { + case 'bar': + paragraph.chartsOptions.barChart.stacked = chart.stacked(); + + break; + + case 'area': + paragraph.chartsOptions.areaChart.style = chart.style(); + + break; + } + } + } + + function _chartApplySettings(paragraph, resetCharts) { + if (resetCharts) + paragraph.charts = []; + + if (paragraph.chart() && paragraph.nonEmpty()) { + switch (paragraph.result) { + case 'bar': + _barChart(paragraph); + break; + + case 'pie': + _pieChart(paragraph); + break; + + case 'line': + _lineChart(paragraph); + break; + + case 'area': + _areaChart(paragraph); + break; + } + } + } + + $scope.applyChartSettings = function (paragraph) { + _chartApplySettings(paragraph, true); + }; + + function _xAxisLabel(paragraph) { + return _.isEmpty(paragraph.chartKeyCols) ? 'X' : paragraph.chartKeyCols[0].label; + } + + function _yAxisLabel(paragraph) { + var cols = paragraph.chartValCols; + + var tml = paragraph.chartTimeLineEnabled(); + + return _.isEmpty(cols) ? 'Y' : _.map(cols, function (col) { + var lbl = col.label; + + if (tml) + lbl += ' [' + col.aggFx + ']'; + + return lbl; + }).join(', '); + } + + function _xX(d) { + return d.x; + } + + function _yY(d) { + return d.y; + } + + function _xAxisTimeFormat(d) { + return d3.time.format('%X')(new Date(d)); + } + + var _xAxisWithLabelFormat = function(paragraph) { + return function (d) { + var values = paragraph.charts[0].data[0].values; + + var fmt = _intType(paragraph.chartKeyCols[0].type) ? 'd' : ',.2f'; + + var dx = values[d]; + + if (!dx) + return d3.format(fmt)(d); + + var lbl = dx.xLbl; + + return lbl ? lbl : d3.format(fmt)(d); + } + }; + + var _yAxisFormat = function(d) { + var fmt = d < 1000 ? ',.2f' : '.3s'; + + return d3.format(fmt)(d); + }; + + function _updateCharts(paragraph) { + $timeout(function () { + _.forEach(paragraph.charts, function (chart) { + chart.api.update(); + }); + }, 100); + } + + function _updateChartsWithData(paragraph, newDatum) { + $timeout(function () { + if (!paragraph.chartTimeLineEnabled()) { + var chartDatum = paragraph.charts[0].data; + + chartDatum.length = 0; + + _.forEach(newDatum, function (series) { + chartDatum.push(series); + }) + } + + paragraph.charts[0].api.update(); + }); + } + + function _updatePieChartsWithData(paragraph, newDatum) { + $timeout(function () { + _.forEach(paragraph.charts, function (chart) { + var chartDatum = chart.data; + + chartDatum.length = 0; + + _.forEach(newDatum, function (series) { + if (chart.options.title.text == series.key) + _.forEach(series.values, function (v) { + chartDatum.push(v); + }); + }); + }); + + _.forEach(paragraph.charts, function (chart) { + chart.api.update(); + }); + }); + } + + function _barChart(paragraph) { + var datum = _chartDatum(paragraph); + + if (_.isEmpty(paragraph.charts)) { + var stacked = paragraph.chartsOptions && paragraph.chartsOptions.barChart + ? paragraph.chartsOptions.barChart.stacked + : true; + + var 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: stacked, + showControls: true, + legend: { + vers: 'furious', + margin: { + right: -25 + } + } + } + }; + + paragraph.charts = [{options: options, data: datum}]; + + _updateCharts(paragraph); + } + else + _updateChartsWithData(paragraph, datum); + } + + function _pieChart(paragraph) { + var 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 + } + }); + + _updateCharts(paragraph); + } + + function _lineChart(paragraph) { + var datum = _chartDatum(paragraph); + + if (_.isEmpty(paragraph.charts)) { + var 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 + } + } + } + }; + + paragraph.charts = [{options: options, data: datum}]; + + _updateCharts(paragraph); + } + else + _updateChartsWithData(paragraph, datum); + } + + function _areaChart(paragraph) { + var datum = _chartDatum(paragraph); + + if (_.isEmpty(paragraph.charts)) { + var style = paragraph.chartsOptions && paragraph.chartsOptions.areaChart + ? paragraph.chartsOptions.areaChart.style + : 'stack'; + + var 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: style, + legend: { + vers: 'furious', + margin: { + right: -25 + } + } + } + }; + + paragraph.charts = [{options: options, data: datum}]; + + _updateCharts(paragraph); + } + else + _updateChartsWithData(paragraph, datum); + } + + $scope.actionAvailable = function (paragraph, needQuery) { + return $scope.caches.length > 0 && (!needQuery || paragraph.query) && !paragraph.loading; + }; + + $scope.actionTooltip = function (paragraph, action, needQuery) { + if ($scope.actionAvailable(paragraph, needQuery)) + return; + + if (paragraph.loading) + return 'Waiting for server response'; + + return 'To ' + action + ' query select cache' + (needQuery ? ' and input query' : ''); + }; + + $scope.clickableMetadata = function (node) { + return node.type.slice(0, 5) != 'index'; + }; + + $scope.dblclickMetadata = function (paragraph, node) { + paragraph.ace.insert(node.name); + + setTimeout(function () { + paragraph.ace.focus(); + }, 1); + }; + + $scope.importMetadata = function () { + $loading.start('loadingCacheMetadata'); + + $scope.metadata = []; + + agentMonitor.metadata() + .then(function (metadata) { + $scope.metadata = _.sortBy(_.filter(metadata, function (meta) { + var cache = _.find($scope.caches, { name: meta.cacheName }); + + if (cache) { + meta.name = (cache.sqlSchema ? cache.sqlSchema : '"' + meta.cacheName + '"') + '.' + meta.typeName; + + meta.displayMame = $scope.maskCacheName(meta.cacheName) + '.' + meta.typeName; + + if (cache.sqlSchema) + meta.children.unshift({type: 'plain', name: 'sqlSchema: ' + cache.sqlSchema}); + + meta.children.unshift({type: 'plain', name: 'mode: ' + cache.mode}); + } + + return cache; + }), 'name'); + }) + .catch((err) => _handleException(err)) + .finally(function () { + $loading.finish('loadingCacheMetadata'); + }); + }; + + $scope.showResultQuery = function (paragraph) { + if ($common.isDefined(paragraph)) { + const scope = $scope.$new(); + + if (_.isNil(paragraph.queryArgs.query)) { + scope.title = 'SCAN query'; + scope.content = [`SCAN query for cache: <b>${$scope.maskCacheName(paragraph.queryArgs.cacheName)}</b>`]; + } + else if (paragraph.queryArgs.query .startsWith('EXPLAIN ')) { + scope.title = 'Explain query'; + scope.content = [paragraph.queryArgs.query]; + } + else { + scope.title = 'SQL query'; + scope.content = [paragraph.queryArgs.query]; + } + + // Show a basic modal from a controller + $modal({scope: scope, template: '/templates/message.html', placement:'center', show: true}); + } + } + }] +); http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/generator/generator-common.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/generator/generator-common.js b/modules/web-console/src/main/js/generator/generator-common.js new file mode 100644 index 0000000..812fa6a --- /dev/null +++ b/modules/web-console/src/main/js/generator/generator-common.js @@ -0,0 +1,570 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Entry point for common functions for code generation. +const $generatorCommon = {}; + +// Add leading zero. +$generatorCommon.addLeadingZero = function (numberStr, minSize) { + if (typeof (numberStr) !== 'string') + numberStr = '' + numberStr; + + while (numberStr.length < minSize) { + numberStr = '0' + numberStr; + } + + return numberStr; +}; + +// Format date to string. +$generatorCommon.formatDate = function (date) { + var dd = $generatorCommon.addLeadingZero(date.getDate(), 2); + var mm = $generatorCommon.addLeadingZero(date.getMonth() + 1, 2); + + var yyyy = date.getFullYear(); + + return mm + '/' + dd + '/' + yyyy + ' ' + $generatorCommon.addLeadingZero(date.getHours(), 2) + ':' + $generatorCommon.addLeadingZero(date.getMinutes(), 2); +}; + +// Generate comment for generated XML, Java, ... files. +$generatorCommon.mainComment = function mainComment() { + return 'This configuration was generated by Ignite Web Console (' + $generatorCommon.formatDate(new Date()) + ')'; +}; + +// Create result holder with service functions and properties for XML and java code generation. +$generatorCommon.builder = function (deep) { + if (_.isNil($generatorCommon.JavaTypes)) + $generatorCommon.JavaTypes = angular.element(document.getElementById('app')).injector().get('JavaTypes'); + + var res = []; + + res.deep = deep || 0; + res.needEmptyLine = false; + res.lineStart = true; + res.datasources = []; + res.imports = {}; + res.staticImports = {}; + res.vars = {}; + + res.safeDeep = 0; + res.safeNeedEmptyLine = false; + res.safeImports = {}; + res.safeDatasources = []; + res.safePoint = -1; + + res.mergeProps = function (fromRes) { + if ($generatorCommon.isDefinedAndNotEmpty(fromRes)) { + res.datasources = fromRes.datasources; + + angular.extend(res.imports, fromRes.imports); + angular.extend(res.staticImports, fromRes.staticImports); + angular.extend(res.vars, fromRes.vars); + } + }; + + res.mergeLines = function (fromRes) { + if ($generatorCommon.isDefinedAndNotEmpty(fromRes)) { + if (res.needEmptyLine) + res.push(''); + + _.forEach(fromRes, function (line) { + res.append(line); + }); + } + }; + + res.startSafeBlock = function () { + res.safeDeep = this.deep; + this.safeNeedEmptyLine = this.needEmptyLine; + this.safeImports = _.cloneDeep(this.imports); + this.safeStaticImports = _.cloneDeep(this.staticImports); + this.safeDatasources = this.datasources.slice(); + this.safePoint = this.length; + }; + + res.rollbackSafeBlock = function () { + if (this.safePoint >= 0) { + this.splice(this.safePoint, this.length - this.safePoint); + + this.deep = res.safeDeep; + this.needEmptyLine = this.safeNeedEmptyLine; + this.datasources = this.safeDatasources; + this.imports = this.safeImports; + this.staticImports = this.safeStaticImports; + this.safePoint = -1; + } + }; + + res.asString = function() { + return this.join('\n'); + }; + + res.append = function (s) { + this.push((this.lineStart ? _.repeat(' ', this.deep) : '') + s); + + return this; + }; + + res.line = function (s) { + if (s) { + if (res.needEmptyLine) + res.push(''); + + res.append(s); + } + + res.needEmptyLine = false; + + res.lineStart = true; + + return res; + }; + + res.startBlock = function (s) { + if (s) { + if (this.needEmptyLine) + this.push(''); + + this.append(s); + } + + this.needEmptyLine = false; + + this.lineStart = true; + + this.deep++; + + return this; + }; + + res.endBlock = function (s) { + this.deep--; + + if (s) + this.append(s); + + this.lineStart = true; + + return this; + }; + + res.softEmptyLine = function () { + this.needEmptyLine = this.length > 0; + }; + + res.emptyLineIfNeeded = function () { + if (this.needEmptyLine) { + this.push(''); + this.lineStart = true; + + this.needEmptyLine = false; + } + }; + + /** + * Add class to imports. + * + * @param clsName Full class name. + * @returns {String} Short class name or full class name in case of names conflict. + */ + res.importClass = function (clsName) { + if ($generatorCommon.JavaTypes.isJavaPrimitive(clsName)) + return clsName; + + var fullClassName = $generatorCommon.JavaTypes.fullClassName(clsName); + + var dotIdx = fullClassName.lastIndexOf('.'); + + var shortName = dotIdx > 0 ? fullClassName.substr(dotIdx + 1) : fullClassName; + + if (this.imports[shortName]) { + if (this.imports[shortName] !== fullClassName) + return fullClassName; // Short class names conflict. Return full name. + } + else + this.imports[shortName] = fullClassName; + + return shortName; + }; + + /** + * Add class to imports. + * + * @param member Static member. + * @returns {String} Short class name or full class name in case of names conflict. + */ + res.importStatic = function (member) { + var dotIdx = member.lastIndexOf('.'); + + var shortName = dotIdx > 0 ? member.substr(dotIdx + 1) : member; + + if (this.staticImports[shortName]) { + if (this.staticImports[shortName] !== member) + return member; // Short class names conflict. Return full name. + } + else + this.staticImports[shortName] = member; + + return shortName; + }; + + /** + * @returns String with "java imports" section. + */ + res.generateImports = function () { + var res = []; + + for (var clsName in this.imports) { + if (this.imports.hasOwnProperty(clsName) && this.imports[clsName].lastIndexOf('java.lang.', 0) !== 0) + res.push('import ' + this.imports[clsName] + ';'); + } + + res.sort(); + + return res.join('\n'); + }; + + /** + * @returns String with "java imports" section. + */ + res.generateStaticImports = function () { + var res = []; + + for (var clsName in this.staticImports) { + if (this.staticImports.hasOwnProperty(clsName) && this.staticImports[clsName].lastIndexOf('java.lang.', 0) !== 0) + res.push('import static ' + this.staticImports[clsName] + ';'); + } + + res.sort(); + + return res.join('\n'); + }; + + return res; +}; + +// Eviction policies code generation descriptors. +$generatorCommon.EVICTION_POLICIES = { + LRU: { + className: 'org.apache.ignite.cache.eviction.lru.LruEvictionPolicy', + fields: {batchSize: {dflt: 1}, maxMemorySize: null, maxSize: {dflt: 100000}} + }, + FIFO: { + className: 'org.apache.ignite.cache.eviction.fifo.FifoEvictionPolicy', + fields: {batchSize: {dflt: 1}, maxMemorySize: null, maxSize: {dflt: 100000}} + }, + SORTED: { + className: 'org.apache.ignite.cache.eviction.sorted.SortedEvictionPolicy', + fields: {batchSize: {dflt: 1}, maxMemorySize: null, maxSize: {dflt: 100000}} + } +}; + +// Marshaller code generation descriptors. +$generatorCommon.MARSHALLERS = { + OptimizedMarshaller: { + className: 'org.apache.ignite.marshaller.optimized.OptimizedMarshaller', + fields: {poolSize: null, requireSerializable: null } + }, + JdkMarshaller: { + className: 'org.apache.ignite.marshaller.jdk.JdkMarshaller', + fields: {} + } +}; + +// Pairs of supported databases and their JDBC dialects. +$generatorCommon.JDBC_DIALECTS = { + Generic: 'org.apache.ignite.cache.store.jdbc.dialect.BasicJdbcDialect', + Oracle: 'org.apache.ignite.cache.store.jdbc.dialect.OracleDialect', + DB2: 'org.apache.ignite.cache.store.jdbc.dialect.DB2Dialect', + SQLServer: 'org.apache.ignite.cache.store.jdbc.dialect.SQLServerDialect', + MySQL: 'org.apache.ignite.cache.store.jdbc.dialect.MySQLDialect', + PostgreSQL: 'org.apache.ignite.cache.store.jdbc.dialect.BasicJdbcDialect', + H2: 'org.apache.ignite.cache.store.jdbc.dialect.H2Dialect' +}; + +// Return JDBC dialect full class name for specified database. +$generatorCommon.jdbcDialectClassName = function(db) { + var dialectClsName = $generatorCommon.JDBC_DIALECTS[db]; + + return dialectClsName ? dialectClsName : 'Unknown database: ' + db; +}; + +// Generate default data cache for specified igfs instance. +$generatorCommon.igfsDataCache = function(igfs) { + return { + name: igfs.name + '-data', + cacheMode: 'PARTITIONED', + atomicityMode: 'TRANSACTIONAL', + writeSynchronizationMode: 'FULL_SYNC', + backups: 0, + igfsAffinnityGroupSize: igfs.affinnityGroupSize || 512 + }; +}; + +// Generate default meta cache for specified igfs instance. +$generatorCommon.igfsMetaCache = function(igfs) { + return { + name: igfs.name + '-meta', + cacheMode: 'REPLICATED', + atomicityMode: 'TRANSACTIONAL', + writeSynchronizationMode: 'FULL_SYNC' + }; +}; + +// Pairs of supported databases and their data sources. +$generatorCommon.DATA_SOURCES = { + Generic: 'com.mchange.v2.c3p0.ComboPooledDataSource', + Oracle: 'oracle.jdbc.pool.OracleDataSource', + DB2: 'com.ibm.db2.jcc.DB2DataSource', + SQLServer: 'com.microsoft.sqlserver.jdbc.SQLServerDataSource', + MySQL: 'com.mysql.jdbc.jdbc2.optional.MysqlDataSource', + PostgreSQL: 'org.postgresql.ds.PGPoolingDataSource', + H2: 'org.h2.jdbcx.JdbcDataSource' +}; + +// Return data source full class name for specified database. +$generatorCommon.dataSourceClassName = function(db) { + var dsClsName = $generatorCommon.DATA_SOURCES[db]; + + return dsClsName ? dsClsName : 'Unknown database: ' + db; +}; + +// Store factories code generation descriptors. +$generatorCommon.STORE_FACTORIES = { + CacheJdbcPojoStoreFactory: { + className: 'org.apache.ignite.cache.store.jdbc.CacheJdbcPojoStoreFactory', + suffix: 'JdbcPojo', + fields: { + configuration: {type: 'bean'} + } + }, + CacheJdbcBlobStoreFactory: { + className: 'org.apache.ignite.cache.store.jdbc.CacheJdbcBlobStoreFactory', + suffix: 'JdbcBlob', + fields: { + initSchema: null, + createTableQuery: null, + loadQuery: null, + insertQuery: null, + updateQuery: null, + deleteQuery: null + } + }, + CacheHibernateBlobStoreFactory: { + className: 'org.apache.ignite.cache.store.hibernate.CacheHibernateBlobStoreFactory', + suffix: 'Hibernate', + fields: {hibernateProperties: {type: 'propertiesAsList', propVarName: 'props'}} + } +}; + +// Swap space SPI code generation descriptor. +$generatorCommon.SWAP_SPACE_SPI = { + className: 'org.apache.ignite.spi.swapspace.file.FileSwapSpaceSpi', + fields: { + baseDirectory: {type: 'path'}, + readStripesNumber: null, + maximumSparsity: {type: 'float'}, + maxWriteQueueSize: null, + writeBufferSize: null + } +}; + +// Transaction configuration code generation descriptor. +$generatorCommon.TRANSACTION_CONFIGURATION = { + className: 'org.apache.ignite.configuration.TransactionConfiguration', + fields: { + defaultTxConcurrency: {type: 'enum', enumClass: 'org.apache.ignite.transactions.TransactionConcurrency', dflt: 'PESSIMISTIC'}, + defaultTxIsolation: {type: 'enum', enumClass: 'org.apache.ignite.transactions.TransactionIsolation', dflt: 'REPEATABLE_READ'}, + defaultTxTimeout: {dflt: 0}, + pessimisticTxLogLinger: {dflt: 10000}, + pessimisticTxLogSize: null, + txSerializableEnabled: null, + txManagerFactory: {type: 'bean'} + } +}; + +// SSL configuration code generation descriptor. +$generatorCommon.SSL_CONFIGURATION_TRUST_FILE_FACTORY = { + className: 'org.apache.ignite.ssl.SslContextFactory', + fields: { + keyAlgorithm: null, + keyStoreFilePath: {type: 'path'}, + keyStorePassword: {type: 'raw'}, + keyStoreType: null, + protocol: null, + trustStoreFilePath: {type: 'path'}, + trustStorePassword: {type: 'raw'}, + trustStoreType: null + } +}; + +// SSL configuration code generation descriptor. +$generatorCommon.SSL_CONFIGURATION_TRUST_MANAGER_FACTORY = { + className: 'org.apache.ignite.ssl.SslContextFactory', + fields: { + keyAlgorithm: null, + keyStoreFilePath: {type: 'path'}, + keyStorePassword: {type: 'raw'}, + keyStoreType: null, + protocol: null, + trustManagers: {type: 'array'} + } +}; + +// Communication configuration code generation descriptor. +$generatorCommon.CONNECTOR_CONFIGURATION = { + className: 'org.apache.ignite.configuration.ConnectorConfiguration', + fields: { + jettyPath: null, + host: null, + port: {dflt: 11211}, + portRange: {dflt: 100}, + idleTimeout: {dflt: 7000}, + idleQueryCursorTimeout: {dflt: 600000}, + idleQueryCursorCheckFrequency: {dflt: 60000}, + receiveBufferSize: {dflt: 32768}, + sendBufferSize: {dflt: 32768}, + sendQueueLimit: {dflt: 0}, + directBuffer: {dflt: false}, + noDelay: {dflt: true}, + selectorCount: null, + threadPoolSize: null, + messageInterceptor: {type: 'bean'}, + secretKey: null, + sslEnabled: {dflt: false} + } +}; + +// Communication configuration code generation descriptor. +$generatorCommon.COMMUNICATION_CONFIGURATION = { + className: 'org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi', + fields: { + listener: {type: 'bean'}, + localAddress: null, + localPort: {dflt: 47100}, + localPortRange: {dflt: 100}, + sharedMemoryPort: {dflt: 48100}, + directBuffer: {dflt: false}, + directSendBuffer: {dflt: false}, + idleConnectionTimeout: {dflt: 30000}, + connectTimeout: {dflt: 5000}, + maxConnectTimeout: {dflt: 600000}, + reconnectCount: {dflt: 10}, + socketSendBuffer: {dflt: 32768}, + socketReceiveBuffer: {dflt: 32768}, + messageQueueLimit: {dflt: 1024}, + slowClientQueueLimit: null, + tcpNoDelay: {dflt: true}, + ackSendThreshold: {dflt: 16}, + unacknowledgedMessagesBufferSize: {dflt: 0}, + socketWriteTimeout: {dflt: 2000}, + selectorsCount: null, + addressResolver: {type: 'bean'} + } +}; + +// Communication configuration code generation descriptor. +$generatorCommon.IGFS_IPC_CONFIGURATION = { + className: 'org.apache.ignite.igfs.IgfsIpcEndpointConfiguration', + fields: { + type: {type: 'enum', enumClass: 'org.apache.ignite.igfs.IgfsIpcEndpointType'}, + host: {dflt: '127.0.0.1'}, + port: {dflt: 10500}, + memorySize: {dflt: 262144}, + tokenDirectoryPath: {dflt: 'ipc/shmem'} + } +}; + +// Check that cache has datasource. +$generatorCommon.cacheHasDatasource = function (cache) { + if (cache.cacheStoreFactory && cache.cacheStoreFactory.kind) { + var storeFactory = cache.cacheStoreFactory[cache.cacheStoreFactory.kind]; + + return !!(storeFactory && (storeFactory.connectVia ? (storeFactory.connectVia === 'DataSource' ? storeFactory.dialect : false) : storeFactory.dialect)); + } + + return false; +}; + +$generatorCommon.secretPropertiesNeeded = function (cluster) { + return !_.isNil(_.find(cluster.caches, $generatorCommon.cacheHasDatasource)) || cluster.sslEnabled; +}; + +// Check that binary is configured. +$generatorCommon.binaryIsDefined = function (binary) { + return binary && ($generatorCommon.isDefinedAndNotEmpty(binary.idMapper) || $generatorCommon.isDefinedAndNotEmpty(binary.nameMapper) || + $generatorCommon.isDefinedAndNotEmpty(binary.serializer) || $generatorCommon.isDefinedAndNotEmpty(binary.typeConfigurations) || + (!_.isNil(binary.compactFooter) && !binary.compactFooter)); +}; + +// Extract domain model metadata location. +$generatorCommon.domainQueryMetadata = function(domain) { + return domain.queryMetadata ? domain.queryMetadata : 'Configuration'; +}; + +/** + * @param {Object} obj Object to check. + * @param {Array<String>} props Array of properties names. + * @returns {boolean} 'true' if + */ +$generatorCommon.hasAtLeastOneProperty = function (obj, props) { + return obj && props && _.findIndex(props, (prop) => !_.isNil(obj[prop])) >= 0; +}; + +/** + * Convert some name to valid java name. + * + * @param prefix To append to java name. + * @param name to convert. + * @returns {string} Valid java name. + */ +$generatorCommon.toJavaName = function (prefix, name) { + var javaName = name ? name.replace(/[^A-Za-z_0-9]+/g, '_') : 'dflt'; + + return prefix + javaName.charAt(0).toLocaleUpperCase() + javaName.slice(1); +}; + +/** + * @param v Value to check. + * @returns {boolean} 'true' if value defined and not empty string. + */ +$generatorCommon.isDefinedAndNotEmpty = function (v) { + var defined = !_.isNil(v); + + if (defined && (_.isString(v) || _.isArray(v))) + defined = v.length > 0; + + return defined; +}; + +/** + * @param {Object} obj Object to check. + * @param {Array<String>} props Properties names. + * @returns {boolean} 'true' if object contains at least one from specified properties. + */ +$generatorCommon.hasProperty = function (obj, props) { + for (var propName in props) { + if (props.hasOwnProperty(propName)) { + if (obj[propName]) + return true; + } + } + + return false; +}; + +export default $generatorCommon;
