query_ctrl.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. import _ from 'lodash';
  2. import { QueryCtrl } from 'app/plugins/sdk';
  3. import queryPart from './query_part';
  4. import PostgresQuery from './postgres_query';
  5. export interface QueryMeta {
  6. sql: string;
  7. }
  8. const defaultQuery = `SELECT
  9. $__time(time_column),
  10. value1
  11. FROM
  12. metric_table
  13. WHERE
  14. $__timeFilter(time_column)
  15. `;
  16. export class PostgresQueryCtrl extends QueryCtrl {
  17. static templateUrl = 'partials/query.editor.html';
  18. showLastQuerySQL: boolean;
  19. formats: any[];
  20. queryModel: PostgresQuery;
  21. lastQueryMeta: QueryMeta;
  22. lastQueryError: string;
  23. showHelp: boolean;
  24. schemaSegment: any;
  25. tableSegment: any;
  26. whereSegment: any;
  27. timeColumnSegment: any;
  28. metricColumnSegment: any;
  29. selectMenu: any;
  30. groupBySegment: any;
  31. /** @ngInject **/
  32. constructor($scope, $injector, private templateSrv, private $q, private uiSegmentSrv) {
  33. super($scope, $injector);
  34. this.target = this.target;
  35. this.queryModel = new PostgresQuery(this.target, templateSrv, this.panel.scopedVars);
  36. this.formats = [{ text: 'Time series', value: 'time_series' }, { text: 'Table', value: 'table' }];
  37. if (!this.target.rawSql) {
  38. // special handling when in table panel
  39. if (this.panelCtrl.panel.type === 'table') {
  40. this.target.format = 'table';
  41. this.target.rawSql = 'SELECT 1';
  42. } else {
  43. this.target.rawSql = defaultQuery;
  44. }
  45. }
  46. this.schemaSegment= uiSegmentSrv.newSegment(this.target.schema);
  47. if (!this.target.table) {
  48. this.tableSegment = uiSegmentSrv.newSegment({value: 'select table',fake: true});
  49. } else {
  50. this.tableSegment= uiSegmentSrv.newSegment(this.target.table);
  51. }
  52. this.timeColumnSegment = uiSegmentSrv.newSegment(this.target.timeColumn);
  53. this.metricColumnSegment = uiSegmentSrv.newSegment(this.target.metricColumn);
  54. this.buildSelectMenu();
  55. this.groupBySegment = this.uiSegmentSrv.newPlusButton();
  56. this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope);
  57. this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope);
  58. }
  59. buildSelectMenu() {
  60. var categories = queryPart.getCategories();
  61. this.selectMenu = _.reduce(
  62. categories,
  63. function(memo, cat, key) {
  64. var menu = {
  65. text: key,
  66. submenu: cat.map(item => {
  67. return { text: item.type, value: item.type };
  68. }),
  69. };
  70. memo.push(menu);
  71. return memo;
  72. },
  73. []
  74. );
  75. }
  76. toggleEditorMode() {
  77. try {
  78. // this.target.query = this.queryModel.render(false);
  79. } catch (err) {
  80. console.log('query render error');
  81. }
  82. this.target.rawQuery = !this.target.rawQuery;
  83. }
  84. getSchemaSegments() {
  85. var schemaQuery = "SELECT schema_name FROM information_schema.schemata WHERE";
  86. schemaQuery += " schema_name NOT LIKE 'pg_%' AND schema_name <> 'information_schema';";
  87. return this.datasource
  88. .metricFindQuery(schemaQuery)
  89. .then(this.transformToSegments(true))
  90. .catch(this.handleQueryError.bind(this));
  91. }
  92. getTableSegments() {
  93. var tableQuery = "SELECT table_name FROM information_schema.tables WHERE table_schema = '" + this.target.schema + "';";
  94. return this.datasource
  95. .metricFindQuery(tableQuery)
  96. .then(this.transformToSegments(true))
  97. .catch(this.handleQueryError.bind(this));
  98. }
  99. getTimeColumnSegments() {
  100. var columnQuery = "SELECT column_name FROM information_schema.columns WHERE ";
  101. columnQuery += " table_schema = '" + this.target.schema + "'";
  102. columnQuery += " AND table_name = '" + this.target.table + "'";
  103. columnQuery += " AND data_type IN ('timestamp without time zone','timestamp with time zone','bigint','integer','double precision','real');";
  104. return this.datasource
  105. .metricFindQuery(columnQuery)
  106. .then(this.transformToSegments(true))
  107. .catch(this.handleQueryError.bind(this));
  108. }
  109. getMetricColumnSegments() {
  110. var columnQuery = "SELECT column_name FROM information_schema.columns WHERE ";
  111. columnQuery += " table_schema = '" + this.target.schema + "'";
  112. columnQuery += " AND table_name = '" + this.target.table + "'";
  113. columnQuery += " AND data_type IN ('text','char','varchar');";
  114. return this.datasource
  115. .metricFindQuery(columnQuery)
  116. .then(this.transformToSegments(true))
  117. .catch(this.handleQueryError.bind(this));
  118. }
  119. tableChanged() {
  120. this.target.table = this.tableSegment.value;
  121. this.panelCtrl.refresh();
  122. }
  123. schemaChanged() {
  124. this.target.schema = this.schemaSegment.value;
  125. this.panelCtrl.refresh();
  126. }
  127. timeColumnChanged() {
  128. this.target.timeColumn = this.timeColumnSegment.value;
  129. this.panelCtrl.refresh();
  130. }
  131. metricColumnChanged() {
  132. this.target.metricColumn = this.metricColumnSegment.value;
  133. this.panelCtrl.refresh();
  134. }
  135. onDataReceived(dataList) {
  136. this.lastQueryMeta = null;
  137. this.lastQueryError = null;
  138. let anySeriesFromQuery = _.find(dataList, { refId: this.target.refId });
  139. if (anySeriesFromQuery) {
  140. this.lastQueryMeta = anySeriesFromQuery.meta;
  141. }
  142. }
  143. onDataError(err) {
  144. if (err.data && err.data.results) {
  145. let queryRes = err.data.results[this.target.refId];
  146. if (queryRes) {
  147. this.lastQueryMeta = queryRes.meta;
  148. this.lastQueryError = queryRes.error;
  149. }
  150. }
  151. }
  152. transformToSegments(addTemplateVars) {
  153. return results => {
  154. var segments = _.map(results, segment => {
  155. return this.uiSegmentSrv.newSegment({
  156. value: segment.text,
  157. expandable: segment.expandable,
  158. });
  159. });
  160. if (addTemplateVars) {
  161. for (let variable of this.templateSrv.variables) {
  162. segments.unshift(
  163. this.uiSegmentSrv.newSegment({
  164. type: 'template',
  165. value: '/^$' + variable.name + '$/',
  166. expandable: true,
  167. })
  168. );
  169. }
  170. }
  171. return segments;
  172. };
  173. }
  174. addSelectPart(selectParts, cat, subitem) {
  175. this.queryModel.addSelectPart(selectParts, subitem.value);
  176. this.panelCtrl.refresh();
  177. }
  178. handleSelectPartEvent(selectParts, part, evt) {
  179. switch (evt.name) {
  180. case 'get-param-options': {
  181. var columnQuery = "SELECT column_name FROM information_schema.columns WHERE ";
  182. columnQuery += " table_schema = '" + this.target.schema + "'";
  183. columnQuery += " AND table_name = '" + this.target.table + "'";
  184. columnQuery += " AND data_type IN ('bigint','integer','double precision','real');";
  185. return this.datasource
  186. .metricFindQuery(columnQuery)
  187. .then(this.transformToSegments(true))
  188. .catch(this.handleQueryError.bind(this));
  189. }
  190. case 'part-param-changed': {
  191. this.panelCtrl.refresh();
  192. break;
  193. }
  194. case 'action': {
  195. this.queryModel.removeSelectPart(selectParts, part);
  196. this.panelCtrl.refresh();
  197. break;
  198. }
  199. case 'get-part-actions': {
  200. return this.$q.when([{ text: 'Remove', value: 'remove-part' }]);
  201. }
  202. }
  203. }
  204. handleGroupByPartEvent(part, index, evt) {
  205. switch (evt.name) {
  206. case 'get-param-options': {
  207. var columnQuery = "SELECT column_name FROM information_schema.columns WHERE ";
  208. columnQuery += " table_schema = '" + this.target.schema + "'";
  209. columnQuery += " AND table_name = '" + this.target.table + "'";
  210. return this.datasource
  211. .metricFindQuery(columnQuery)
  212. .then(this.transformToSegments(true))
  213. .catch(this.handleQueryError.bind(this));
  214. }
  215. case 'part-param-changed': {
  216. this.panelCtrl.refresh();
  217. break;
  218. }
  219. case 'action': {
  220. this.queryModel.removeGroupByPart(part, index);
  221. this.panelCtrl.refresh();
  222. break;
  223. }
  224. case 'get-part-actions': {
  225. return this.$q.when([{ text: 'Remove', value: 'remove-part' }]);
  226. }
  227. }
  228. }
  229. getGroupByOptions() {
  230. var columnQuery = "SELECT column_name FROM information_schema.columns WHERE ";
  231. columnQuery += " table_schema = '" + this.target.schema + "'";
  232. columnQuery += " AND table_name = '" + this.target.table + "'";
  233. return this.datasource
  234. .metricFindQuery(columnQuery)
  235. .then(tags => {
  236. var options = [];
  237. if (!this.queryModel.hasFill()) {
  238. options.push(this.uiSegmentSrv.newSegment({ value: 'fill(null)' }));
  239. }
  240. if (!this.target.limit) {
  241. options.push(this.uiSegmentSrv.newSegment({ value: 'LIMIT' }));
  242. }
  243. if (!this.target.slimit) {
  244. options.push(this.uiSegmentSrv.newSegment({ value: 'SLIMIT' }));
  245. }
  246. if (this.target.orderByTime === 'ASC') {
  247. options.push(this.uiSegmentSrv.newSegment({ value: 'ORDER BY time DESC' }));
  248. }
  249. if (!this.queryModel.hasGroupByTime()) {
  250. options.push(this.uiSegmentSrv.newSegment({ value: 'time($interval)' }));
  251. }
  252. for (let tag of tags) {
  253. options.push(this.uiSegmentSrv.newSegment({ value: 'tag(' + tag.text + ')' }));
  254. }
  255. return options;
  256. })
  257. .catch(this.handleQueryError.bind(this));
  258. }
  259. groupByAction() {
  260. switch (this.groupBySegment.value) {
  261. case 'LIMIT': {
  262. this.target.limit = 10;
  263. break;
  264. }
  265. case 'ORDER BY time DESC': {
  266. this.target.orderByTime = 'DESC';
  267. break;
  268. }
  269. default: {
  270. this.queryModel.addGroupBy(this.groupBySegment.value);
  271. }
  272. }
  273. var plusButton = this.uiSegmentSrv.newPlusButton();
  274. this.groupBySegment.value = plusButton.value;
  275. this.groupBySegment.html = plusButton.html;
  276. this.panelCtrl.refresh();
  277. }
  278. handleQueryError(err) {
  279. this.error = err.message || 'Failed to issue metric query';
  280. return [];
  281. }
  282. }