blob: cb5af5a2e272da2f77458cc4c3825294a7f8de14 [file] [log] [blame]
/*
* 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.
*/
'use strict';
KylinApp
.controller('QueryCtrl', function ($scope, storage, $base64, $q, $location, $anchorScroll, $routeParams, QueryService, $modal, MessageService, $domUtilityService, $timeout, TableService, SweetAlert, VdmUtil) {
$scope.mainPanel = 'query';
if ($routeParams.queryPanel) {
$scope.mainPanel = $routeParams.queryPanel;
}
$scope.rowsPerPage = 50000;
$scope.hasLimit = true;
$scope.base64 = $base64;
$scope.queryString = "";
$scope.queries = [];
$scope.curQuery = null;
$scope.exportSql = null;
$scope.dateTypes = [91, 92, 93];
$scope.stringTypes = [-1, 1, 12];
$scope.numberTypes = [-7, -6, -5, 3, 4, 5, 6, 7, 8];
$scope.ui = {fullScreen: false};
$scope.chartTypes = [
{name: "Line Chart", value: "line", dimension: { types: ['date'], multiple: false }, metrics: { multiple: false }},
{name: "Bar Chart", value: "bar", dimension: { types: ['date', 'string'], multiple: false }, metrics: { multiple: false }},
{name: "Pie Chart", value: "pie", dimension: { types: ['date', 'string'], multiple: false }, metrics: { multiple: false }}
];
$scope.statusList = [
{name: 'All', value: ''},
{name: 'Executing', value: 'executing'},
{name: 'Success', value: 'success'},
{name: 'Failed', value: 'failed'}
];
$scope.statusFilter = null;
$scope.savedQueries = null;
$scope.srcTables = [];
$scope.srcColumns = [];
$scope.curProject = null;
$scope.state = {
selectedProject: null
};
var Query = {
createNew: function (sql, project) {
var query = {
originSql: sql,
sql: sql,
project: (!!project)? project:$scope.projectModel.getSelectedProject(),
status: 'executing',
acceptPartial: true,
result: {
isResponsePartial: false
},
graph: {
meta: {
dimensions: [], //Keys metadata
metrics: [] //Values metadata
},
state: {
dimensions: [], // User selected dimensions
metrics: [] // User selected metrics
},
type: {}, //Graph type
show: false
},
startTime: new Date()
};
return query;
},
resetQuery: function (query) {
query.status = 'executing';
query.result = {
isResponsePartial: false
};
query.acceptPartial = true;
query.graph = {
meta: {
dimensions: [], //Keys metadata
metrics: [] //Values metadata
},
state: {
dimensions: [], // User selected dimensions
metrics: [] // User selected metrics
},
type: {}, //Graph type
show: false
};
query.startTime = new Date();
}
}
$scope.checkLimit = function () {
if (!$scope.rowsPerPage) {
$scope.rowsPerPage = 50000;
}
}
$scope.changeLimit = function () {
if ($scope.hasLimit) {
$scope.rowsPerPage = 50000;
} else {
$scope.rowsPerPage = 0
}
}
function getQuery(queries, query) {
for (var i = 0; i < queries.length; i++) {
if (queries[i].sql === query.sql && queries[i].project === query.project) {
return queries[i];
}
}
return null;
}
function makeSuffixRegExp(suffix, caseInsensitive) {
return new RegExp(
String(suffix).replace(/[$%()*+.?\[\\\]{|}]/g, "\\$&") + "$",
caseInsensitive ? "i" : "");
}
function justShowTable() {
var words = $scope.queryString.split(/\s+/);
words.splice(words.length - 1, words.length);
return makeSuffixRegExp("from", true).test(words.toString()) || makeSuffixRegExp("join", true).test(words.toString());
}
$scope.setQueryString = function (queryString) {
$scope.queryString = queryString;
}
$scope.aceLoaded = function (_editor) {
var langTools = ace.require("ace/ext/language_tools");
if (!langTools.tableAdded) {
langTools.addCompleter({
getCompletions: function (editor, session, pos, prefix, callback) {
if (prefix.length === 0) {
callback(null, []);
} else {
callback(null, $scope.srcTables);
}
}
});
langTools.tableAdded = true;
}
if (!langTools.columnAdded) {
langTools.addCompleter({
getCompletions: function (editor, session, pos, prefix, callback) {
if (prefix.length === 0 || justShowTable()) {
callback(null, []);
} else {
callback(null, $scope.srcColumns);
}
}
});
langTools.columnAdded = true;
}
_editor.commands.bindKey("Command-Option-Space", "startAutocomplete")
_editor.setOptions({
enableBasicAutocompletion: true
});
}
$scope.parseQueryResult = function (oneQuery, result, status) {
oneQuery.status = status;
var data = [];
angular.forEach(result.results, function (row, index) {
var oneRow = {};
angular.forEach(result.columnMetas, function (meta, metaIndex) {
oneRow[meta.label] = row[metaIndex];
});
data.push(oneRow);
});
var columnDefs = [];
angular.forEach(result.columnMetas, function (meta, metaIndex) {
columnDefs.push({field: meta.label, width: 120});
});
if (oneQuery.result.results) {
oneQuery.result.results = oneQuery.result.results.concat(result.results);
} else {
oneQuery.result = result;
}
if (oneQuery.status == 'success') {
if (oneQuery.result.data) {
oneQuery.result.data = oneQuery.result.data.concat(data);
} else {
oneQuery.result.data = data;
}
angular.forEach(oneQuery.result.data,function(row,index){
angular.forEach(row,function(column,value){
var float =VdmUtil.SCToFloat(column);
if (float!=""){
oneQuery.result.data[index][value]=parseFloat(float);
}
});
});
$scope.curQuery.result.isResponsePartial = result.partial;
}
oneQuery.result.gridOptions = {
data: 'curQuery.result.data',
showGroupPanel: true,
enablePinning: true,
columnDefs: columnDefs
};
oneQuery.completeTime = new Date();
}
$scope.refreshUi = function () {
$scope.ui.fullScreen = !$scope.ui.fullScreen;
$timeout(function () {
if ($scope.curQuery.result.gridOptions) {
$domUtilityService.RebuildGrid($scope.curQuery.result.gridOptions.$gridScope, $scope.curQuery.result.gridOptions.ngGrid);
}
});
}
$scope.sanitate = function (sql) {
return encodeURIComponent(sql.replace(/\n/g, " "));
}
$scope.query = function (query) {
scrollToButton();
QueryService.query({}, {sql: query.sql, offset: 0, limit: $scope.rowsPerPage, acceptPartial: query.acceptPartial, project: query.project}, function (result) {
scrollToButton();
$scope.parseQueryResult(query, result, (!result || result.isException) ? 'failed' : 'success');
$scope.curQuery.result.hasMore = (query.result.results && query.result.results.length == $scope.rowsPerPage);
}, function (result) {
scrollToButton();
$scope.parseQueryResult(query, result, 'failed');
});
}
function scrollToButton() {
$timeout(function () {
$location.hash('query_content_results');
$anchorScroll();
});
}
$scope.submitQuery = function (queryString, project) {
if (!queryString && !$scope.queryString) {
return;
}
var sql = (!!queryString) ? queryString : $scope.queryString;
var newQuery = Query.createNew(sql, project);
$scope.queries.push(newQuery);
$scope.curQuery = newQuery;
$scope.query($scope.curQuery);
$scope.cacheQuery($scope.curQuery);
$scope.exportSql = $scope.sanitate(sql);
}
$scope.reset = function (query) {
Query.resetQuery(query);
}
$scope.rerun = function () {
Query.resetQuery($scope.curQuery);
$scope.query($scope.curQuery);
}
$scope.loadMore = function (query) {
query.result.loading = true;
var query = query;
QueryService.query({}, {sql: query.originSql, offset: query.result.results.length, limit: $scope.rowsPerPage, acceptPartial: query.acceptPartial, project: query.project}, function (result) {
if (result && !result.isException) {
$scope.parseQueryResult($scope.curQuery, result, (!result || result.isException) ? 'failed' : 'success');
}
else {
query.status = 'failed';
}
query.result.hasMore = (result.results && result.results.length == $scope.rowsPerPage);
query.result.loading = false;
}, function (result) {
query.status = 'failed';
});
}
$scope.removeResult = function (queryInstance) {
if ($scope.curQuery == queryInstance) {
$scope.curQuery = null;
}
var index = $scope.queries.indexOf(queryInstance);
$scope.queries.splice(index, 1);
$scope.curQuery = ($scope.queries.length > 0) ? $scope.queries[0] : null;
}
$scope.removeQuery = function (queryInstance) {
var queryToRemove = getQuery($scope.cachedQueries, this.query);
if (queryToRemove) {
var index = $scope.cachedQueries.indexOf(queryToRemove);
$scope.cachedQueries.splice(index, 1);
var indexFilter = $scope.cachedFilterQueries.indexOf(queryToRemove);
$scope.cachedFilterQueries.splice(indexFilter, 1);
storage.set("saved_queries", $scope.cachedQueries);
}
}
$scope.cacheQuery = function (query) {
if (!getQuery($scope.cachedQueries, query)) {
if ($scope.cachedQueries.length >= 99) {
delete $scope.cachedQueries.splice(0, 1);
}
$scope.cachedQueries.push({
sql: query.sql,
savedAt: new Date(),
project: query.project
});
storage.set("saved_queries", $scope.cachedQueries);
}
}
$scope.listSavedQueries = function () {
QueryService.list({project: $scope.projectModel.selectedProject}, function (queries) {
$scope.savedQueries = queries;
$scope.savedQueries.curPage = 1;
$scope.savedQueries.perPage = 3;
});
}
$scope.listCachedQueries = function () {
$scope.cachedQueries = storage.get("saved_queries") || [];
$scope.cachedFilterQueries = $scope.cachedQueries.filter(function (query) {
return query.project === $scope.projectModel.selectedProject;
});
if (!$scope.cachedFilterQueries) {
$scope.cachedFilterQueries = [];
}
$scope.cachedFilterQueries.curPage = 1;
$scope.cachedFilterQueries.perPage = 3;
}
$scope.removeSavedQuery = function (id) {
QueryService.delete({subject_id: id}, function () {
$scope.listSavedQueries();
});
}
$scope.refreshCurQuery = function () {
$scope.curQuery = this.query;
$scope.curQuery.result = {
isResponsePartial: false
};
$scope.query($scope.curQuery);
};
$scope.showSavePanel = function () {
var modalInstance = $modal.open({
templateUrl: 'saveQueryModal.html',
controller: saveQueryController,
resolve: {
curQuery: function () {
return $scope.curQuery;
}
}
});
modalInstance.result.then( function (result) {
$scope.listSavedQueries();
}, function (reason) {
$scope.listSavedQueries();
});
}
var saveQueryController = function ($scope, $modalInstance, curQuery, QueryService) {
$scope.curQuery = curQuery;
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
}
$scope.saveQuery = function (query) {
QueryService.save({}, {name: query.name, project: query.project, sql: query.sql, description: query.description}, function () {
SweetAlert.swal('Success!', 'New query saved..', 'success');
$modalInstance.dismiss('cancel');
});
}
}
$scope.$watch('projectModel.selectedProject', function (newValue, oldValue) {
$scope.listCachedQueries();
$scope.listSavedQueries();
});
$scope.$on('$locationChangeStart', function (event, next, current) {
var isExecuting = false;
angular.forEach($scope.queries, function (query, index) {
if (query.status == "executing") {
isExecuting = true;
}
});
if (isExecuting && (next.replace(current, "").indexOf("#") != 0)) {
SweetAlert.swal({
title: '',
text: "You've executing query in current page, are you sure to leave this page?",
type: '',
showCancelButton: true,
confirmButtonColor: '#DD6B55',
confirmButtonText: "Yes",
closeOnConfirm: true
}, function(isConfirm) {
if(!isConfirm){
event.preventDefault();
}
});
}
});
})
.controller('QueryResultCtrl', function ($scope, storage, $base64, $q, $location, $anchorScroll, $routeParams, QueryService, kylinConfig, queryConfig) {
$scope.isAdminExportAllowed = kylinConfig.isAdminExportAllowed();
$scope.isNonAdminExportAllowed = kylinConfig.isNonAdminExportAllowed();
$scope.buildGraphMetadata = function (query) {
if (!query.graph.show) {
return;
}
// Build graph metadata
query.graph.meta.dimensions = [];
query.graph.meta.metrics = [];
var datePattern = /_date|_dt/i;
query.graph.type = $scope.chartTypes[1];
angular.forEach(query.result.columnMetas, function (meta, index) {
if (($scope.dateTypes.indexOf(meta.columnType) > -1 || datePattern.test(meta.name))) {
query.graph.type = $scope.chartTypes[0];
query.graph.meta.dimensions.push({
column: meta,
index: index,
type: 'date'
});
return;
}
if ($scope.stringTypes.indexOf(meta.columnType) > -1) {
query.graph.meta.dimensions.push({
column: meta,
index: index,
type: 'string'
});
return;
}
if ($scope.numberTypes.indexOf(meta.columnType) > -1) {
query.graph.meta.metrics.push({
column: meta,
index: index
});
return;
}
});
}
$scope.mappingToChartype = function (dimension) {
return $scope.curQuery.graph.type.dimension.types.indexOf(dimension.type) > -1;
}
$scope.resetGraph = function (query) {
var dimension = (query.graph.meta.dimensions && query.graph.meta.dimensions.length > 0) ? query.graph.meta.dimensions[0] : null;
var metrics = (query.graph.meta.metrics && query.graph.meta.metrics.length > 0) ? query.graph.meta.metrics[0] : null;
query.graph.state = {
dimensions: dimension,
metrics: ((query.graph.type.metrics.multiple) ? [metrics] : metrics)
};
$scope.refreshGraphData(query);
}
$scope.refreshGraphData = function (query) {
if (query.graph.show) {
$scope.chart = undefined;
var selectedDimension = query.graph.state.dimensions;
if (selectedDimension && query.graph.type.dimension.types.indexOf(selectedDimension.type) > -1) {
$scope.chart = {};
var chartType = query.graph.type.value;
var selectedMetric = query.graph.state.metrics;
var dataValues = [];
angular.forEach(query.result.results, function(result, ind) {
var data = {
label: result[selectedDimension.index],
value: parseFloat(result[selectedMetric.index])
};
if (selectedDimension.type === 'date' && chartType === 'line') {
data.label = parseInt(moment(data.label).format('X'));
}
dataValues.push(data);
});
dataValues = _.sortBy(dataValues, 'label');
var oldLabel = dataValues[0].label;
var groupValues = [{label: dataValues[0].label, value: 0}];
angular.forEach(dataValues, function(data) {
if (data.label === oldLabel) {
groupValues[groupValues.length-1].value += data.value;
} else {
groupValues.push(data);
oldLabel = data.label;
}
});
$scope.chart.data = [{
key: selectedMetric.column.label,
values: groupValues
}];
if (chartType === 'line') {
$scope.chart.options = angular.copy(queryConfig.lineChartOptions);
if (selectedDimension.type === 'date') {
$scope.chart.options.chart.xAxis.tickFormat = function (d) {
return d3.time.format('%Y-%m-%d')(moment.unix(d).toDate());
};
}
} else if (chartType === 'bar') {
$scope.chart.options = angular.copy(queryConfig.barChartOptions);
if (groupValues.length > 15) {
$scope.chart.options.chart.showLegend = false;
$scope.chart.options.chart.xAxis.height = 100;
$scope.chart.options.chart.margin.bottom = 150;
$scope.chart.options.chart.xAxis.rotateLabels = -90;
if (groupValues.length > 50) {
$scope.chart.options.chart.showXAxis = false;
}
}
} else if (chartType === 'pie') {
$scope.chart.options = angular.copy(queryConfig.pieChartOptions);
$scope.chart.data = groupValues;
if (groupValues.length > 15) {
$scope.chart.options.chart.showLegend = false;
$scope.chart.options.chart.showLabels = false;
}
}
}
} else {
$scope.chart.data = [];
}
}
$scope.getMissInfo = function(query) {
if (query.status != 'success') {
return '';
}
var missMeasureInfo = query.result.missMeasureMessage;
if (missMeasureInfo) {
var retMsg = "";
for (var i = 0; i < missMeasureInfo.length; i++) {
retMsg += (i+1) + ". " + missMeasureInfo[i] + "\n";
}
return retMsg;
}
return "empty";
}
});