datasource.ts 6.5 KB

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