datasource.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import { map as _map } from 'lodash';
  2. import { lastValueFrom, of } from 'rxjs';
  3. import { catchError, map, mapTo } from 'rxjs/operators';
  4. import { AnnotationEvent, DataSourceInstanceSettings, MetricFindValue, ScopedVars } from '@grafana/data';
  5. import { BackendDataSourceResponse, DataSourceWithBackend, FetchResponse, getBackendSrv } from '@grafana/runtime';
  6. import { toTestingStatus } from '@grafana/runtime/src/utils/queryResponse';
  7. import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
  8. import MySQLQueryModel from 'app/plugins/datasource/mysql/mysql_query_model';
  9. import { getSearchFilterScopedVar } from '../../../features/variables/utils';
  10. import ResponseParser from './response_parser';
  11. import { MySQLOptions, MySQLQuery, MysqlQueryForInterpolation } from './types';
  12. export class MysqlDatasource extends DataSourceWithBackend<MySQLQuery, MySQLOptions> {
  13. id: any;
  14. name: any;
  15. responseParser: ResponseParser;
  16. queryModel: MySQLQueryModel;
  17. interval: string;
  18. constructor(
  19. instanceSettings: DataSourceInstanceSettings<MySQLOptions>,
  20. private readonly templateSrv: TemplateSrv = getTemplateSrv()
  21. ) {
  22. super(instanceSettings);
  23. this.name = instanceSettings.name;
  24. this.id = instanceSettings.id;
  25. this.responseParser = new ResponseParser();
  26. this.queryModel = new MySQLQueryModel({});
  27. const settingsData = instanceSettings.jsonData || ({} as MySQLOptions);
  28. this.interval = settingsData.timeInterval || '1m';
  29. }
  30. interpolateVariable = (value: string | string[] | number, variable: any) => {
  31. if (typeof value === 'string') {
  32. if (variable.multi || variable.includeAll) {
  33. const result = this.queryModel.quoteLiteral(value);
  34. return result;
  35. } else {
  36. return value;
  37. }
  38. }
  39. if (typeof value === 'number') {
  40. return value;
  41. }
  42. const quotedValues = _map(value, (v: any) => {
  43. return this.queryModel.quoteLiteral(v);
  44. });
  45. return quotedValues.join(',');
  46. };
  47. interpolateVariablesInQueries(
  48. queries: MysqlQueryForInterpolation[],
  49. scopedVars: ScopedVars
  50. ): MysqlQueryForInterpolation[] {
  51. let expandedQueries = queries;
  52. if (queries && queries.length > 0) {
  53. expandedQueries = queries.map((query) => {
  54. const expandedQuery = {
  55. ...query,
  56. datasource: this.getRef(),
  57. rawSql: this.templateSrv.replace(query.rawSql, scopedVars, this.interpolateVariable),
  58. rawQuery: true,
  59. };
  60. return expandedQuery;
  61. });
  62. }
  63. return expandedQueries;
  64. }
  65. filterQuery(query: MySQLQuery): boolean {
  66. return !query.hide;
  67. }
  68. applyTemplateVariables(target: MySQLQuery, scopedVars: ScopedVars): Record<string, any> {
  69. const queryModel = new MySQLQueryModel(target, this.templateSrv, scopedVars);
  70. return {
  71. refId: target.refId,
  72. datasource: this.getRef(),
  73. rawSql: queryModel.render(this.interpolateVariable as any),
  74. format: target.format,
  75. };
  76. }
  77. async annotationQuery(options: any): Promise<AnnotationEvent[]> {
  78. if (!options.annotation.rawQuery) {
  79. return Promise.reject({
  80. message: 'Query missing in annotation definition',
  81. });
  82. }
  83. const query = {
  84. refId: options.annotation.name,
  85. datasource: this.getRef(),
  86. rawSql: this.templateSrv.replace(options.annotation.rawQuery, options.scopedVars, this.interpolateVariable),
  87. format: 'table',
  88. };
  89. return lastValueFrom(
  90. getBackendSrv()
  91. .fetch<BackendDataSourceResponse>({
  92. url: '/api/ds/query',
  93. method: 'POST',
  94. data: {
  95. from: options.range.from.valueOf().toString(),
  96. to: options.range.to.valueOf().toString(),
  97. queries: [query],
  98. },
  99. requestId: options.annotation.name,
  100. })
  101. .pipe(
  102. map(
  103. async (res: FetchResponse<BackendDataSourceResponse>) =>
  104. await this.responseParser.transformAnnotationResponse(options, res.data)
  105. )
  106. )
  107. );
  108. }
  109. metricFindQuery(query: string, optionalOptions: any): Promise<MetricFindValue[]> {
  110. let refId = 'tempvar';
  111. if (optionalOptions && optionalOptions.variable && optionalOptions.variable.name) {
  112. refId = optionalOptions.variable.name;
  113. }
  114. const rawSql = this.templateSrv.replace(
  115. query,
  116. getSearchFilterScopedVar({ query, wildcardChar: '%', options: optionalOptions }),
  117. this.interpolateVariable
  118. );
  119. const interpolatedQuery = {
  120. refId: refId,
  121. datasource: this.getRef(),
  122. rawSql,
  123. format: 'table',
  124. };
  125. const range = optionalOptions?.range;
  126. return lastValueFrom(
  127. getBackendSrv()
  128. .fetch<BackendDataSourceResponse>({
  129. url: '/api/ds/query',
  130. method: 'POST',
  131. data: {
  132. from: range?.from?.valueOf()?.toString(),
  133. to: range?.to?.valueOf()?.toString(),
  134. queries: [interpolatedQuery],
  135. },
  136. requestId: refId,
  137. })
  138. .pipe(
  139. map((rsp) => {
  140. return this.responseParser.transformMetricFindResponse(rsp);
  141. }),
  142. catchError((err) => {
  143. return of([]);
  144. })
  145. )
  146. );
  147. }
  148. testDatasource(): Promise<any> {
  149. return lastValueFrom(
  150. getBackendSrv()
  151. .fetch({
  152. url: '/api/ds/query',
  153. method: 'POST',
  154. data: {
  155. from: '5m',
  156. to: 'now',
  157. queries: [
  158. {
  159. refId: 'A',
  160. intervalMs: 1,
  161. maxDataPoints: 1,
  162. datasource: this.getRef(),
  163. rawSql: 'SELECT 1',
  164. format: 'table',
  165. },
  166. ],
  167. },
  168. })
  169. .pipe(
  170. mapTo({ status: 'success', message: 'Database Connection OK' }),
  171. catchError((err) => {
  172. return of(toTestingStatus(err));
  173. })
  174. )
  175. );
  176. }
  177. targetContainsTemplate(target: any) {
  178. let rawSql = '';
  179. if (target.rawQuery) {
  180. rawSql = target.rawSql;
  181. } else {
  182. const query = new MySQLQueryModel(target);
  183. rawSql = query.buildQuery();
  184. }
  185. rawSql = rawSql.replace('$__', '');
  186. return this.templateSrv.containsTemplate(rawSql);
  187. }
  188. }