mirror of
https://github.com/getredash/redash.git
synced 2025-12-23 03:13:08 -05:00
455 lines
12 KiB
JavaScript
455 lines
12 KiB
JavaScript
(function () {
|
|
var QueryResult = function ($resource, $timeout, $q) {
|
|
var QueryResultResource = $resource('/api/query_results/:id', {id: '@id'}, {'post': {'method': 'POST'}});
|
|
var Job = $resource('/api/jobs/:id', {id: '@id'});
|
|
|
|
var updateFunction = function (props) {
|
|
angular.extend(this, props);
|
|
if ('query_result' in props) {
|
|
this.status = "done";
|
|
this.filters = undefined;
|
|
this.filterFreeze = undefined;
|
|
|
|
var columnTypes = {};
|
|
|
|
_.each(this.query_result.data.rows, function (row) {
|
|
_.each(row, function (v, k) {
|
|
if (angular.isNumber(v)) {
|
|
columnTypes[k] = 'float';
|
|
} else if (_.isString(v) && v.match(/^\d{4}-\d{2}-\d{2}T/)) {
|
|
row[k] = moment(v);
|
|
columnTypes[k] = 'datetime';
|
|
} else if (_.isString(v) && v.match(/^\d{4}-\d{2}-\d{2}/)) {
|
|
row[k] = moment(v);
|
|
columnTypes[k] = 'date';
|
|
}
|
|
}, this);
|
|
}, this);
|
|
|
|
_.each(this.query_result.data.columns, function(column) {
|
|
if (columnTypes[column.name]) {
|
|
column.type = columnTypes[column.name];
|
|
}
|
|
});
|
|
|
|
this.deferred.resolve(this);
|
|
} else if (this.job.status == 3) {
|
|
this.status = "processing";
|
|
} else {
|
|
this.status = undefined;
|
|
}
|
|
}
|
|
|
|
function QueryResult(props) {
|
|
this.deferred = $q.defer();
|
|
this.job = {};
|
|
this.query_result = {};
|
|
this.status = "waiting";
|
|
this.filters = undefined;
|
|
this.filterFreeze = undefined;
|
|
|
|
this.updatedAt = moment();
|
|
|
|
if (props) {
|
|
updateFunction.apply(this, [props]);
|
|
}
|
|
}
|
|
|
|
var statuses = {
|
|
1: "waiting",
|
|
2: "processing",
|
|
3: "done",
|
|
4: "failed"
|
|
}
|
|
|
|
QueryResult.prototype.update = updateFunction;
|
|
|
|
QueryResult.prototype.getId = function () {
|
|
var id = null;
|
|
if ('query_result' in this) {
|
|
id = this.query_result.id;
|
|
}
|
|
return id;
|
|
}
|
|
|
|
QueryResult.prototype.cancelExecution = function () {
|
|
Job.delete({id: this.job.id});
|
|
}
|
|
|
|
QueryResult.prototype.getStatus = function () {
|
|
return this.status || statuses[this.job.status];
|
|
}
|
|
|
|
QueryResult.prototype.getError = function () {
|
|
// TODO: move this logic to the server...
|
|
if (this.job.error == "None") {
|
|
return undefined;
|
|
}
|
|
|
|
return this.job.error;
|
|
}
|
|
|
|
QueryResult.prototype.getUpdatedAt = function () {
|
|
return this.query_result.retrieved_at || this.job.updated_at * 1000.0 || this.updatedAt;
|
|
}
|
|
|
|
QueryResult.prototype.getRuntime = function () {
|
|
return this.query_result.runtime;
|
|
}
|
|
|
|
QueryResult.prototype.getRawData = function () {
|
|
if (!this.query_result.data) {
|
|
return null;
|
|
}
|
|
|
|
var data = this.query_result.data.rows;
|
|
|
|
return data;
|
|
}
|
|
|
|
QueryResult.prototype.getData = function () {
|
|
if (!this.query_result.data) {
|
|
return null;
|
|
}
|
|
|
|
var filterValues = function (filters) {
|
|
if (!filters) {
|
|
return null;
|
|
}
|
|
|
|
return _.reduce(filters, function (str, filter) {
|
|
return str + filter.current;
|
|
}, "")
|
|
}
|
|
|
|
var filters = this.getFilters();
|
|
var filterFreeze = filterValues(filters);
|
|
|
|
if (this.filterFreeze != filterFreeze) {
|
|
this.filterFreeze = filterFreeze;
|
|
|
|
if (filters) {
|
|
this.filteredData = _.filter(this.query_result.data.rows, function (row) {
|
|
return _.reduce(filters, function (memo, filter) {
|
|
if (!_.isArray(filter.current)) {
|
|
filter.current = [filter.current];
|
|
};
|
|
|
|
return (memo && _.some(filter.current, function(v) {
|
|
// We compare with either the value or the String representation of the value,
|
|
// because Select2 casts true/false to "true"/"false".
|
|
return v == row[filter.name] || String(row[filter.name]) == v
|
|
}));
|
|
}, true);
|
|
});
|
|
} else {
|
|
this.filteredData = this.query_result.data.rows;
|
|
}
|
|
}
|
|
|
|
return this.filteredData;
|
|
}
|
|
|
|
QueryResult.prototype.getChartData = function (mapping) {
|
|
var series = {};
|
|
|
|
_.each(this.getData(), function (row) {
|
|
var point = {};
|
|
var seriesName = undefined;
|
|
var xValue = 0;
|
|
var yValues = {};
|
|
|
|
_.each(row, function (value, definition) {
|
|
var name = definition.split("::")[0];
|
|
var type = definition.split("::")[1];
|
|
if (mapping) {
|
|
type = mapping[definition];
|
|
}
|
|
|
|
if (type == 'unused') {
|
|
return;
|
|
}
|
|
|
|
if (type == 'x') {
|
|
xValue = value;
|
|
point[type] = value;
|
|
}
|
|
if (type == 'y') {
|
|
if (value == null) {
|
|
value = 0;
|
|
}
|
|
yValues[name] = value;
|
|
point[type] = value;
|
|
}
|
|
|
|
if (type == 'series') {
|
|
seriesName = String(value);
|
|
}
|
|
|
|
if (type == 'multi-filter') {
|
|
seriesName = String(value);
|
|
}
|
|
});
|
|
|
|
var addPointToSeries = function (seriesName, point) {
|
|
if (series[seriesName] == undefined) {
|
|
series[seriesName] = {
|
|
name: seriesName,
|
|
type: 'column',
|
|
data: []
|
|
}
|
|
}
|
|
|
|
series[seriesName]['data'].push(point);
|
|
}
|
|
|
|
if (seriesName === undefined) {
|
|
_.each(yValues, function (yValue, seriesName) {
|
|
addPointToSeries(seriesName, {'x': xValue, 'y': yValue});
|
|
});
|
|
} else {
|
|
addPointToSeries(seriesName, point);
|
|
}
|
|
});
|
|
|
|
_.each(series, function (series) {
|
|
series.data = _.sortBy(series.data, 'x');
|
|
});
|
|
|
|
return _.values(series);
|
|
};
|
|
|
|
QueryResult.prototype.getColumns = function () {
|
|
if (this.columns == undefined && this.query_result.data) {
|
|
this.columns = this.query_result.data.columns;
|
|
}
|
|
|
|
return this.columns;
|
|
}
|
|
|
|
QueryResult.prototype.getColumnNames = function () {
|
|
if (this.columnNames == undefined && this.query_result.data) {
|
|
this.columnNames = _.map(this.query_result.data.columns, function (v) {
|
|
return v.name;
|
|
});
|
|
}
|
|
|
|
return this.columnNames;
|
|
}
|
|
|
|
QueryResult.prototype.getColumnNameWithoutType = function (column) {
|
|
var parts = column.split('::');
|
|
if (parts[0] == "" && parts.length == 2) {
|
|
return parts[1];
|
|
}
|
|
return parts[0];
|
|
};
|
|
|
|
var charConversionMap = {
|
|
'__pct': /%/g,
|
|
'_': / /g,
|
|
'__qm': /\?/g,
|
|
'__brkt': /[\(\)\[\]]/g,
|
|
'__dash': /-/g,
|
|
'__amp': /&/g,
|
|
'__sl': /\//g,
|
|
'__fsl': /\\/g,
|
|
};
|
|
|
|
QueryResult.prototype.getColumnCleanName = function (column) {
|
|
var name = this.getColumnNameWithoutType(column);
|
|
|
|
if (name != '') {
|
|
_.each(charConversionMap, function(regex, replacement) {
|
|
name = name.replace(regex, replacement);
|
|
});
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
QueryResult.prototype.getColumnFriendlyName = function (column) {
|
|
return this.getColumnNameWithoutType(column).replace(/(?:^|\s)\S/g, function (a) {
|
|
return a.toUpperCase();
|
|
});
|
|
}
|
|
|
|
QueryResult.prototype.getColumnCleanNames = function () {
|
|
return _.map(this.getColumnNames(), function (col) {
|
|
return this.getColumnCleanName(col);
|
|
}, this);
|
|
}
|
|
|
|
QueryResult.prototype.getColumnFriendlyNames = function () {
|
|
return _.map(this.getColumnNames(), function (col) {
|
|
return this.getColumnFriendlyName(col);
|
|
}, this);
|
|
}
|
|
|
|
QueryResult.prototype.getFilters = function () {
|
|
if (!this.filters) {
|
|
this.prepareFilters();
|
|
}
|
|
|
|
return this.filters;
|
|
};
|
|
|
|
QueryResult.prototype.prepareFilters = function () {
|
|
var filters = [];
|
|
var filterTypes = ['filter', 'multi-filter'];
|
|
_.each(this.getColumnNames(), function (col) {
|
|
var type = col.split('::')[1]
|
|
if (_.contains(filterTypes, type)) {
|
|
// filter found
|
|
var filter = {
|
|
name: col,
|
|
friendlyName: this.getColumnFriendlyName(col),
|
|
values: [],
|
|
multiple: (type=='multi-filter')
|
|
}
|
|
filters.push(filter);
|
|
}
|
|
}, this);
|
|
|
|
_.each(this.getRawData(), function (row) {
|
|
_.each(filters, function (filter) {
|
|
filter.values.push(row[filter.name]);
|
|
if (filter.values.length == 1) {
|
|
filter.current = row[filter.name];
|
|
}
|
|
})
|
|
});
|
|
|
|
_.each(filters, function(filter) {
|
|
filter.values = _.uniq(filter.values);
|
|
});
|
|
|
|
this.filters = filters;
|
|
}
|
|
|
|
var refreshStatus = function (queryResult, query, ttl) {
|
|
Job.get({'id': queryResult.job.id}, function (response) {
|
|
queryResult.update(response);
|
|
|
|
if (queryResult.getStatus() == "processing" && queryResult.job.query_result_id && queryResult.job.query_result_id != "None") {
|
|
QueryResultResource.get({'id': queryResult.job.query_result_id}, function (response) {
|
|
queryResult.update(response);
|
|
});
|
|
} else if (queryResult.getStatus() != "failed") {
|
|
$timeout(function () {
|
|
refreshStatus(queryResult, query, ttl);
|
|
}, 3000);
|
|
}
|
|
})
|
|
}
|
|
|
|
QueryResult.getById = function (id) {
|
|
var queryResult = new QueryResult();
|
|
|
|
QueryResultResource.get({'id': id}, function (response) {
|
|
queryResult.update(response);
|
|
});
|
|
|
|
return queryResult;
|
|
};
|
|
|
|
QueryResult.prototype.toPromise = function() {
|
|
return this.deferred.promise;
|
|
}
|
|
|
|
QueryResult.get = function (data_source_id, query, ttl) {
|
|
var queryResult = new QueryResult();
|
|
|
|
QueryResultResource.post({'data_source_id': data_source_id, 'query': query, 'ttl': ttl}, function (response) {
|
|
queryResult.update(response);
|
|
|
|
if ('job' in response) {
|
|
refreshStatus(queryResult, query, ttl);
|
|
}
|
|
});
|
|
|
|
return queryResult;
|
|
}
|
|
|
|
return QueryResult;
|
|
};
|
|
|
|
var Query = function ($resource, QueryResult, DataSource) {
|
|
var Query = $resource('/api/queries/:id', {id: '@id'});
|
|
|
|
Query.newQuery = function () {
|
|
return new Query({
|
|
query: "",
|
|
name: "New Query",
|
|
ttl: -1,
|
|
user: currentUser
|
|
});
|
|
};
|
|
|
|
Query.prototype.getSourceLink = function () {
|
|
return '/queries/' + this.id + '/source';
|
|
};
|
|
|
|
Query.prototype.getQueryResult = function (ttl) {
|
|
if (ttl == undefined) {
|
|
ttl = this.ttl;
|
|
}
|
|
|
|
if (this.latest_query_data && ttl != 0) {
|
|
if (!this.queryResult) {
|
|
this.queryResult = new QueryResult({'query_result': this.latest_query_data});
|
|
}
|
|
} else if (this.latest_query_data_id && ttl != 0) {
|
|
if (!this.queryResult) {
|
|
this.queryResult = QueryResult.getById(this.latest_query_data_id);
|
|
}
|
|
} else if (this.data_source_id) {
|
|
this.queryResult = QueryResult.get(this.data_source_id, this.query, ttl);
|
|
}
|
|
|
|
return this.queryResult;
|
|
};
|
|
|
|
Query.prototype.getQueryResultPromise = function() {
|
|
return this.getQueryResult().toPromise();
|
|
}
|
|
|
|
return Query;
|
|
};
|
|
|
|
|
|
|
|
var DataSource = function ($resource) {
|
|
var DataSourceResource = $resource('/api/data_sources/:id', {id: '@id'}, {'get': {'method': 'GET', 'cache': true, 'isArray': true}});
|
|
|
|
return DataSourceResource;
|
|
}
|
|
|
|
var Widget = function ($resource, Query) {
|
|
var WidgetResource = $resource('/api/widgets/:id', {id: '@id'});
|
|
|
|
WidgetResource.prototype.getQuery = function () {
|
|
if (!this.query && this.visualization) {
|
|
this.query = new Query(this.visualization.query);
|
|
}
|
|
|
|
return this.query;
|
|
};
|
|
|
|
WidgetResource.prototype.getName = function () {
|
|
if (this.visualization) {
|
|
return this.visualization.query.name + ' (' + this.visualization.name + ')';
|
|
}
|
|
return _.str.truncate(this.text, 20);
|
|
};
|
|
|
|
return WidgetResource;
|
|
}
|
|
|
|
angular.module('redash.services')
|
|
.factory('QueryResult', ['$resource', '$timeout', '$q', QueryResult])
|
|
.factory('Query', ['$resource', 'QueryResult', 'DataSource', Query])
|
|
.factory('DataSource', ['$resource', DataSource])
|
|
.factory('Widget', ['$resource', 'Query', Widget]);
|
|
})();
|