datasource.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. define([
  2. 'angular',
  3. 'lodash',
  4. 'config',
  5. 'kbn',
  6. 'moment',
  7. './queryBuilder',
  8. './directives'
  9. ],
  10. function (angular, _, config, kbn, moment, ElasticQueryBuilder) {
  11. 'use strict';
  12. var module = angular.module('grafana.services');
  13. module.factory('ElasticDatasource', function($q, backendSrv, templateSrv) {
  14. function ElasticDatasource(datasource) {
  15. this.type = 'elasticsearch';
  16. this.basicAuth = datasource.basicAuth;
  17. this.url = datasource.url;
  18. this.name = datasource.name;
  19. this.index = datasource.index;
  20. this.searchMaxResults = config.search.max_results || 20;
  21. this.saveTemp = _.isUndefined(datasource.save_temp) ? true : datasource.save_temp;
  22. this.saveTempTTL = _.isUndefined(datasource.save_temp_ttl) ? '30d' : datasource.save_temp_ttl;
  23. }
  24. ElasticDatasource.prototype._request = function(method, url, index, data) {
  25. var options = {
  26. url: this.url + "/" + index + url,
  27. method: method,
  28. data: data
  29. };
  30. if (this.basicAuth) {
  31. options.withCredentials = true;
  32. options.headers = {
  33. "Authorization": this.basicAuth
  34. };
  35. }
  36. return backendSrv.datasourceRequest(options);
  37. };
  38. ElasticDatasource.prototype._get = function(url) {
  39. return this._request('GET', url, this.index)
  40. .then(function(results) {
  41. return results.data;
  42. });
  43. };
  44. ElasticDatasource.prototype._post = function(url, data) {
  45. return this._request('POST', url, this.index, data)
  46. .then(function(results) {
  47. return results.data;
  48. });
  49. };
  50. ElasticDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) {
  51. var range = {};
  52. var timeField = annotation.timeField || '@timestamp';
  53. var queryString = annotation.query || '*';
  54. var tagsField = annotation.tagsField || 'tags';
  55. var titleField = annotation.titleField || 'desc';
  56. var textField = annotation.textField || null;
  57. range[timeField]= {
  58. from: rangeUnparsed.from,
  59. to: rangeUnparsed.to,
  60. };
  61. var queryInterpolated = templateSrv.replace(queryString);
  62. var filter = { "bool": { "must": [{ "range": range }] } };
  63. var query = { "bool": { "should": [{ "query_string": { "query": queryInterpolated } }] } };
  64. var data = {
  65. "fields": [timeField, "_source"],
  66. "query" : { "filtered": { "query" : query, "filter": filter } },
  67. "size": 10000
  68. };
  69. return this._request('POST', '/_search', annotation.index, data).then(function(results) {
  70. var list = [];
  71. var hits = results.data.hits.hits;
  72. var getFieldFromSource = function(source, fieldName) {
  73. if (!fieldName) { return; }
  74. var fieldNames = fieldName.split('.');
  75. var fieldValue = source;
  76. for (var i = 0; i < fieldNames.length; i++) {
  77. fieldValue = fieldValue[fieldNames[i]];
  78. if (!fieldValue) {
  79. console.log('could not find field in annotatation: ', fieldName);
  80. return '';
  81. }
  82. }
  83. if (_.isArray(fieldValue)) {
  84. fieldValue = fieldValue.join(', ');
  85. }
  86. return fieldValue;
  87. };
  88. for (var i = 0; i < hits.length; i++) {
  89. var source = hits[i]._source;
  90. var fields = hits[i].fields;
  91. var time = source[timeField];
  92. if (_.isString(fields[timeField]) || _.isNumber(fields[timeField])) {
  93. time = fields[timeField];
  94. }
  95. var event = {
  96. annotation: annotation,
  97. time: moment.utc(time).valueOf(),
  98. title: getFieldFromSource(source, titleField),
  99. tags: getFieldFromSource(source, tagsField),
  100. text: getFieldFromSource(source, textField)
  101. };
  102. list.push(event);
  103. }
  104. return list;
  105. });
  106. };
  107. ElasticDatasource.prototype._getDashboardWithSlug = function(id) {
  108. return this._get('/dashboard/' + kbn.slugifyForUrl(id))
  109. .then(function(result) {
  110. return angular.fromJson(result._source.dashboard);
  111. }, function() {
  112. throw "Dashboard not found";
  113. });
  114. };
  115. ElasticDatasource.prototype.getDashboard = function(id, isTemp) {
  116. var url = '/dashboard/' + id;
  117. if (isTemp) { url = '/temp/' + id; }
  118. var self = this;
  119. return this._get(url)
  120. .then(function(result) {
  121. return angular.fromJson(result._source.dashboard);
  122. }, function(data) {
  123. if(data.status === 0) {
  124. throw "Could not contact Elasticsearch. Please ensure that Elasticsearch is reachable from your browser.";
  125. } else {
  126. // backward compatible fallback
  127. return self._getDashboardWithSlug(id);
  128. }
  129. });
  130. };
  131. ElasticDatasource.prototype.saveDashboard = function(dashboard) {
  132. var title = dashboard.title;
  133. var temp = dashboard.temp;
  134. if (temp) { delete dashboard.temp; }
  135. var data = {
  136. user: 'guest',
  137. group: 'guest',
  138. title: title,
  139. tags: dashboard.tags,
  140. dashboard: angular.toJson(dashboard)
  141. };
  142. if (temp) {
  143. return this._saveTempDashboard(data);
  144. }
  145. else {
  146. var id = encodeURIComponent(kbn.slugifyForUrl(title));
  147. var self = this;
  148. return this._request('PUT', '/dashboard/' + id, this.index, data)
  149. .then(function(results) {
  150. self._removeUnslugifiedDashboard(results, title, id);
  151. return { title: title, url: '/dashboard/db/' + id };
  152. }, function() {
  153. throw 'Failed to save to elasticsearch';
  154. });
  155. }
  156. };
  157. ElasticDatasource.prototype._removeUnslugifiedDashboard = function(saveResult, title, id) {
  158. if (saveResult.statusText !== 'Created') { return; }
  159. if (title === id) { return; }
  160. var self = this;
  161. this._get('/dashboard/' + title).then(function() {
  162. self.deleteDashboard(title);
  163. });
  164. };
  165. ElasticDatasource.prototype._saveTempDashboard = function(data) {
  166. return this._request('POST', '/temp/?ttl=' + this.saveTempTTL, this.index, data)
  167. .then(function(result) {
  168. var baseUrl = window.location.href.replace(window.location.hash,'');
  169. var url = baseUrl + "#dashboard/temp/" + result.data._id;
  170. return { title: data.title, url: url };
  171. }, function(err) {
  172. throw "Failed to save to temp dashboard to elasticsearch " + err.data;
  173. });
  174. };
  175. ElasticDatasource.prototype.deleteDashboard = function(id) {
  176. return this._request('DELETE', '/dashboard/' + id, this.index)
  177. .then(function(result) {
  178. return result.data._id;
  179. }, function(err) {
  180. throw err.data;
  181. });
  182. };
  183. ElasticDatasource.prototype.searchDashboards = function(queryString) {
  184. var endsInOpen = function(string, opener, closer) {
  185. var character;
  186. var count = 0;
  187. for (var i = 0, len = string.length; i < len; i++) {
  188. character = string[i];
  189. if (character === opener) {
  190. count++;
  191. } else if (character === closer) {
  192. count--;
  193. }
  194. }
  195. return count > 0;
  196. };
  197. var tagsOnly = queryString.indexOf('tags!:') === 0;
  198. if (tagsOnly) {
  199. var tagsQuery = queryString.substring(6, queryString.length);
  200. queryString = 'tags:' + tagsQuery + '*';
  201. }
  202. else {
  203. if (queryString.length === 0) {
  204. queryString = 'title:';
  205. }
  206. // make this a partial search if we're not in some reserved portion of the language, comments on conditionals, in order:
  207. // 1. ends in reserved character, boosting, boolean operator ( -foo)
  208. // 2. typing a reserved word like AND, OR, NOT
  209. // 3. open parens (groupiing)
  210. // 4. open " (term phrase)
  211. // 5. open [ (range)
  212. // 6. open { (range)
  213. // see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax
  214. if (!queryString.match(/(\*|\]|}|~|\)|"|^\d+|\s[\-+]\w+)$/) &&
  215. !queryString.match(/[A-Z]$/) &&
  216. !endsInOpen(queryString, '(', ')') &&
  217. !endsInOpen(queryString, '"', '"') &&
  218. !endsInOpen(queryString, '[', ']') && !endsInOpen(queryString, '[', '}') &&
  219. !endsInOpen(queryString, '{', ']') && !endsInOpen(queryString, '{', '}')
  220. ){
  221. queryString += '*';
  222. }
  223. }
  224. var query = {
  225. query: { query_string: { query: queryString } },
  226. facets: { tags: { terms: { field: "tags", order: "term", size: 50 } } },
  227. size: 10000,
  228. sort: ["_uid"],
  229. };
  230. return this._post('/dashboard/_search', query)
  231. .then(function(results) {
  232. if(_.isUndefined(results.hits)) {
  233. return { dashboards: [], tags: [] };
  234. }
  235. var resultsHits = results.hits.hits;
  236. var displayHits = { dashboards: [], tags: results.facets.tags.terms || [] };
  237. for (var i = 0, len = resultsHits.length; i < len; i++) {
  238. var hit = resultsHits[i];
  239. displayHits.dashboards.push({
  240. id: hit._id,
  241. title: hit._source.title,
  242. tags: hit._source.tags
  243. });
  244. }
  245. displayHits.tagsOnly = tagsOnly;
  246. return displayHits;
  247. });
  248. };
  249. ElasticDatasource.prototype.query = function(options) {
  250. var self = this;
  251. var allQueries = _.map(options.targets, function(target) {
  252. if (target.hide) { return []; }
  253. var queryBuilder = new ElasticQueryBuilder(target);
  254. var query = queryBuilder.build();
  255. query = query.replace(/\$interval/g, target.interval || options.interval);
  256. query = query.replace(/\$rangeFrom/g, options.range.from);
  257. query = query.replace(/\$rangeTo/g, options.range.to);
  258. query = query.replace(/\$maxDataPoints/g, options.maxDataPoints);
  259. return query;
  260. }).join("\n");
  261. // replace templated variables
  262. // allQueries = templateSrv.replace(allQueries, options.scopedVars);
  263. return this._post('/_search?search_type=count', allQueries).then(function(results) {
  264. if (!results || !results.facets) {
  265. return { data: [] };
  266. }
  267. return { data: self._getTimeSeries(results.facets) };
  268. });
  269. };
  270. ElasticDatasource.prototype._getTimeSeries = function(facets) {
  271. var self = this;
  272. var targets = ['metric'];
  273. var data = targets.map(function(target) {
  274. var datapoints = facets[target].entries.map(self._getDatapoint);
  275. return { target: target, datapoints: datapoints };
  276. });
  277. return data;
  278. };
  279. ElasticDatasource.prototype._getDatapoint = function(entry) {
  280. return [entry.mean, entry.time];
  281. };
  282. return ElasticDatasource;
  283. });
  284. });