UNPKG

@grafana/runtime

Version:
323 lines (320 loc) • 11.2 kB
import { of, lastValueFrom, merge } from 'rxjs'; import { switchMap, catchError } from 'rxjs/operators'; import { makeClassES5Compatible, DataSourceApi, getDataSourceRef, parseLiveChannelAddress, dataFrameToJSON, StreamingFrameAction } from '@grafana/data'; import { reportInteraction } from '../analytics/utils.mjs'; import { config } from '../config.mjs'; import { getBackendSrv } from '../services/backendSrv.mjs'; import { getDataSourceSrv } from '../services/dataSourceSrv.mjs'; import { getGrafanaLiveSrv } from '../services/live.mjs'; import '../services/LocationService.mjs'; import '../services/appEvents.mjs'; import '../services/SidecarService_EXPERIMENTAL.mjs'; import '../services/SidecarContext_EXPERIMENTAL.mjs'; import 'react'; import '../services/ScopesContext.mjs'; import { publicDashboardQueryHandler } from './publicDashboardQueryHandler.mjs'; import { toDataQueryResponse } from './queryResponse.mjs'; const ExpressionDatasourceRef = Object.freeze({ type: "__expr__", uid: "__expr__", name: "Expression" }); function isExpressionReference(ref) { if (!ref) { return false; } const v = typeof ref === "string" ? ref : ref.type; return v === ExpressionDatasourceRef.type || v === ExpressionDatasourceRef.name || v === "-100"; } class HealthCheckError extends Error { constructor(message, details) { super(message); this.details = details; this.name = "HealthCheckError"; } } var HealthStatus = /* @__PURE__ */ ((HealthStatus2) => { HealthStatus2["Unknown"] = "UNKNOWN"; HealthStatus2["OK"] = "OK"; HealthStatus2["Error"] = "ERROR"; return HealthStatus2; })(HealthStatus || {}); class DataSourceWithBackend extends DataSourceApi { constructor(instanceSettings) { super(instanceSettings); /** * Optionally override the streaming behavior */ this.streamOptionsProvider = standardStreamOptionsProvider; } /** * Ideally final -- any other implementation may not work as expected */ query(request) { var _a; if (config.publicDashboardAccessToken) { return publicDashboardQueryHandler(request); } const { intervalMs, maxDataPoints, queryCachingTTL, range, requestId, hideFromInspector = false } = request; let targets = request.targets; let hasExpr = false; const pluginIDs = /* @__PURE__ */ new Set(); const dsUIDs = /* @__PURE__ */ new Set(); const queries = targets.map((q) => { var _a2, _b, _c; let datasource = this.getRef(); let datasourceId = this.id; let shouldApplyTemplateVariables = true; if (isExpressionReference(q.datasource)) { hasExpr = true; return { ...q, datasource: ExpressionDatasourceRef }; } if (q.datasource) { const ds = getDataSourceSrv().getInstanceSettings(q.datasource, request.scopedVars); if (!ds) { throw new Error(`Unknown Datasource: ${JSON.stringify(q.datasource)}`); } const dsRef = (_a2 = ds.rawRef) != null ? _a2 : getDataSourceRef(ds); const dsId = ds.id; if (dsRef.uid !== datasource.uid || datasourceId !== dsId) { datasource = dsRef; datasourceId = dsId; shouldApplyTemplateVariables = false; } } if ((_b = datasource.type) == null ? undefined : _b.length) { pluginIDs.add(datasource.type); } if ((_c = datasource.uid) == null ? undefined : _c.length) { dsUIDs.add(datasource.uid); } return { ...shouldApplyTemplateVariables ? this.applyTemplateVariables(q, request.scopedVars, request.filters) : q, datasource, datasourceId, // deprecated! intervalMs, maxDataPoints, queryCachingTTL }; }); if (!queries.length) { return of({ data: [] }); } const body = { queries, from: range == null ? undefined : range.from.valueOf().toString(), to: range == null ? undefined : range.to.valueOf().toString() }; if (config.featureToggles.queryOverLive) { return getGrafanaLiveSrv().getQueryData({ request, body }); } const headers = (_a = request.headers) != null ? _a : {}; headers["X-Plugin-Id" /* PluginID */] = Array.from(pluginIDs).join(", "); headers["X-Datasource-Uid" /* DatasourceUID */] = Array.from(dsUIDs).join(", "); let url = "/api/ds/query?ds_type=" + this.type; if (config.featureToggles.queryServiceFromUI) { if (!(config.featureToggles.queryService || config.featureToggles.grafanaAPIServerWithExperimentalAPIs)) { console.warn("feature toggle queryServiceFromUI also requires the queryService to be running"); } else { if (!hasExpr && dsUIDs.size === 1) ; url = `/apis/query.grafana.app/v0alpha1/namespaces/${config.namespace}/query?ds_type=' + this.type`; } } if (hasExpr) { headers["X-Grafana-From-Expr" /* FromExpression */] = "true"; url += "&expression=true"; } if (requestId) { url += `&requestId=${requestId}`; } if (request.dashboardUID) { headers["X-Dashboard-Uid" /* DashboardUID */] = request.dashboardUID; if (request.panelId) { headers["X-Panel-Id" /* PanelID */] = `${request.panelId}`; } } if (request.panelPluginId) { headers["X-Panel-Plugin-Id" /* PanelPluginId */] = `${request.panelPluginId}`; } if (request.queryGroupId) { headers["X-Query-Group-Id" /* QueryGroupID */] = `${request.queryGroupId}`; } if (request.skipQueryCache) { headers["X-Cache-Skip" /* SkipQueryCache */] = "true"; } return getBackendSrv().fetch({ url, method: "POST", data: body, requestId, hideFromInspector, headers }).pipe( switchMap((raw) => { var _a2; const rsp = toDataQueryResponse(raw, queries); if (((_a2 = rsp.data) == null ? undefined : _a2.length) && rsp.data.find((f) => { var _a3; return (_a3 = f.meta) == null ? undefined : _a3.channel; })) { return toStreamingDataResponse(rsp, request, this.streamOptionsProvider); } return of(rsp); }), catchError((err) => { return of(toDataQueryResponse(err)); }) ); } /** Get request headers with plugin ID+UID set */ getRequestHeaders() { const headers = {}; headers["X-Plugin-Id" /* PluginID */] = this.type; headers["X-Datasource-Uid" /* DatasourceUID */] = this.uid; return headers; } /** * Apply template variables for explore */ interpolateVariablesInQueries(queries, scopedVars, filters) { return queries.map((q) => this.applyTemplateVariables(q, scopedVars, filters)); } /** * Override to apply template variables and adhoc filters. The result is usually also `TQuery`, but sometimes this can * be used to modify the query structure before sending to the backend. * * NOTE: if you do modify the structure or use template variables, alerting queries may not work * as expected * * @virtual */ applyTemplateVariables(query, scopedVars, filters) { return query; } /** * Make a GET request to the datasource resource path */ async getResource(path, params, options) { const headers = this.getRequestHeaders(); const result = await lastValueFrom( getBackendSrv().fetch({ ...options, method: "GET", headers: (options == null ? undefined : options.headers) ? { ...options.headers, ...headers } : headers, params: params != null ? params : options == null ? undefined : options.params, url: `/api/datasources/uid/${this.uid}/resources/${path}` }) ); return result.data; } /** * Send a POST request to the datasource resource path */ async postResource(path, data, options) { const headers = this.getRequestHeaders(); const result = await lastValueFrom( getBackendSrv().fetch({ ...options, method: "POST", headers: (options == null ? undefined : options.headers) ? { ...options.headers, ...headers } : headers, data: data != null ? data : { ...data }, url: `/api/datasources/uid/${this.uid}/resources/${path}` }) ); return result.data; } /** * Run the datasource healthcheck */ async callHealthCheck() { return lastValueFrom( getBackendSrv().fetch({ method: "GET", url: `/api/datasources/uid/${this.uid}/health`, showErrorAlert: false, headers: this.getRequestHeaders() }) ).then((v) => v.data).catch((err) => { var _a, _b, _c, _d, _e; let properties = { plugin_id: ((_a = this.meta) == null ? undefined : _a.id) || "", plugin_version: ((_c = (_b = this.meta) == null ? undefined : _b.info) == null ? undefined : _c.version) || "", datasource_healthcheck_status: ((_d = err == null ? undefined : err.data) == null ? undefined : _d.status) || "error", datasource_healthcheck_message: ((_e = err == null ? undefined : err.data) == null ? undefined : _e.message) || "" }; reportInteraction("datasource_health_check_completed", properties); return err.data; }); } /** * Checks the plugin health * see public/app/features/datasources/state/actions.ts for what needs to be returned here */ async testDatasource() { return this.callHealthCheck().then((res) => { if (res.status === "OK" /* OK */) { return { status: "success", message: res.message }; } return Promise.reject({ status: "error", message: res.message, error: new HealthCheckError(res.message, res.details) }); }); } } function toStreamingDataResponse(rsp, req, getter) { var _a; const live = getGrafanaLiveSrv(); if (!live) { return of(rsp); } const staticdata = []; const streams = []; for (const f of rsp.data) { const addr = parseLiveChannelAddress((_a = f.meta) == null ? undefined : _a.channel); if (addr) { const frame = f; streams.push( live.getDataStream({ addr, buffer: getter(req, frame), frame: dataFrameToJSON(f) }) ); } else { staticdata.push(f); } } if (staticdata.length) { streams.push(of({ ...rsp, data: staticdata })); } if (streams.length === 1) { return streams[0]; } return merge(...streams); } const standardStreamOptionsProvider = (request, frame) => { var _a, _b; const opts = { maxLength: (_a = request.maxDataPoints) != null ? _a : 500, action: StreamingFrameAction.Append }; if (((_b = request.rangeRaw) == null ? undefined : _b.to) === "now") { opts.maxDelta = request.range.to.valueOf() - request.range.from.valueOf(); } return opts; }; DataSourceWithBackend = makeClassES5Compatible(DataSourceWithBackend); export { DataSourceWithBackend, ExpressionDatasourceRef, HealthCheckError, HealthStatus, isExpressionReference, standardStreamOptionsProvider, toStreamingDataResponse }; //# sourceMappingURL=DataSourceWithBackend.mjs.map