Files
redash/rd_ui/app/scripts/services/resources.js
2014-08-06 23:39:09 +03:00

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]);
})();