Explorar o código

Merge pull request #14955 from grafana/datasource-typings

Datasource typings
Torkel Ödegaard %!s(int64=7) %!d(string=hai) anos
pai
achega
17aebf8462

+ 21 - 3
packages/grafana-ui/src/types/datasource.ts

@@ -7,15 +7,33 @@ export interface DataQueryResponse {
 }
 
 export interface DataQuery {
+  /**
+   * A - Z
+   */
   refId: string;
-  [key: string]: any;
+
+  /**
+   * true if query is disabled (ie not executed / sent to TSDB)
+   */
+  hide?: boolean;
+
+  /**
+   * Unique, guid like, string used in explore mode
+   */
+  key?: string;
+
+  /**
+   * For mixed data sources the selected datasource is on the query level.
+   * For non mixed scenarios this is undefined.
+   */
+  datasource?: string | null;
 }
 
-export interface DataQueryOptions {
+export interface DataQueryOptions<TQuery extends DataQuery = DataQuery> {
   timezone: string;
   range: TimeRange;
   rangeRaw: RawTimeRange;
-  targets: DataQuery[];
+  targets: TQuery[];
   panelId: number;
   dashboardId: number;
   cacheTimeout?: string;

+ 17 - 14
packages/grafana-ui/src/types/plugin.ts

@@ -2,11 +2,7 @@ import { ComponentClass } from 'react';
 import { PanelProps, PanelOptionsProps } from './panel';
 import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint } from './datasource';
 
-export interface DataSourceApi {
-  name: string;
-  meta: PluginMeta;
-  pluginExports: PluginExports;
-
+export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
   /**
    *  min interval range
    */
@@ -15,7 +11,7 @@ export interface DataSourceApi {
   /**
    * Imports queries from a different datasource
    */
-  importQueries?(queries: DataQuery[], originMeta: PluginMeta): Promise<DataQuery[]>;
+  importQueries?(queries: TQuery[], originMeta: PluginMeta): Promise<TQuery[]>;
 
   /**
    * Initializes a datasource after instantiation
@@ -25,7 +21,7 @@ export interface DataSourceApi {
   /**
    * Main metrics / data query action
    */
-  query(options: DataQueryOptions): Promise<DataQueryResponse>;
+  query(options: DataQueryOptions<TQuery>): Promise<DataQueryResponse>;
 
   /**
    * Test & verify datasource settings & connection details
@@ -35,20 +31,27 @@ export interface DataSourceApi {
   /**
    *  Get hints for query improvements
    */
-  getQueryHints(query: DataQuery, results: any[], ...rest: any): QueryHint[];
+  getQueryHints?(query: TQuery, results: any[], ...rest: any): QueryHint[];
+
+  /**
+   *  Set after constructor is called by Grafana
+   */
+  name?: string;
+  meta?: PluginMeta;
+  pluginExports?: PluginExports;
 }
 
-export interface QueryEditorProps {
-  datasource: DataSourceApi;
-  query: DataQuery;
+export interface QueryEditorProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
+  datasource: DSType;
+  query: TQuery;
   onExecuteQuery?: () => void;
-  onQueryChange?: (value: DataQuery) => void;
+  onQueryChange?: (value: TQuery) => void;
 }
 
 export interface PluginExports {
-  Datasource?: any;
+  Datasource?: DataSourceApi;
   QueryCtrl?: any;
-  QueryEditor?: ComponentClass<QueryEditorProps>;
+  QueryEditor?: ComponentClass<QueryEditorProps<DataSourceApi,DataQuery>>;
   ConfigCtrl?: any;
   AnnotationsQueryCtrl?: any;
   VariableQueryEditor?: any;

+ 6 - 2
public/app/core/utils/explore.ts

@@ -203,7 +203,7 @@ export function ensureQueries(queries?: DataQuery[]): DataQuery[] {
 /**
  * A target is non-empty when it has keys (with non-empty values) other than refId and key.
  */
-export function hasNonEmptyQuery(queries: DataQuery[]): boolean {
+export function hasNonEmptyQuery<TQuery extends DataQuery = any>(queries: TQuery[]): boolean {
   return (
     queries &&
     queries.some(
@@ -280,7 +280,11 @@ export function makeTimeSeriesList(dataList) {
 /**
  * Update the query history. Side-effect: store history in local storage
  */
-export function updateHistory(history: HistoryItem[], datasourceId: string, queries: DataQuery[]): HistoryItem[] {
+export function updateHistory<T extends DataQuery = any>(
+  history: Array<HistoryItem<T>>,
+  datasourceId: string,
+  queries: T[]
+): Array<HistoryItem<T>> {
   const ts = Date.now();
   queries.forEach(query => {
     history = [{ query, ts }, ...history];

+ 0 - 2
public/app/features/dashboard/panel_model.ts

@@ -243,8 +243,6 @@ export class PanelModel {
   addQuery(query?: Partial<DataQuery>) {
     query = query || { refId: 'A' };
     query.refId = this.getNextQueryLetter();
-    query.isNew = true;
-
     this.targets.push(query);
   }
 

+ 10 - 5
public/app/plugins/datasource/loki/components/LokiQueryField.tsx

@@ -1,16 +1,21 @@
+// Libraries
 import React from 'react';
 import Cascader from 'rc-cascader';
 import PluginPrism from 'slate-prism';
 import Prism from 'prismjs';
 
-import { DataQuery } from '@grafana/ui/src/types';
-import { TypeaheadOutput } from 'app/types/explore';
+// Components
+import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
 
+// Utils & Services
 // dom also includes Element polyfills
 import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom';
 import BracesPlugin from 'app/features/explore/slate-plugins/braces';
 import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
-import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
+
+// Types
+import { LokiQuery } from '../types';
+import { TypeaheadOutput } from 'app/types/explore';
 
 const PRISM_SYNTAX = 'promql';
 
@@ -63,10 +68,10 @@ interface LokiQueryFieldProps {
   error?: string | JSX.Element;
   hint?: any;
   history?: any[];
-  initialQuery?: DataQuery;
+  initialQuery?: LokiQuery;
   onClickHintFix?: (action: any) => void;
   onPressEnter?: () => void;
-  onQueryChange?: (value: DataQuery, override?: boolean) => void;
+  onQueryChange?: (value: LokiQuery, override?: boolean) => void;
 }
 
 interface LokiQueryFieldState {

+ 10 - 4
public/app/plugins/datasource/loki/datasource.test.ts

@@ -1,4 +1,6 @@
 import LokiDatasource from './datasource';
+import { LokiQuery } from './types';
+import { getQueryOptions } from 'test/helpers/getQueryOptions';
 
 describe('LokiDatasource', () => {
   const instanceSettings: any = {
@@ -13,12 +15,13 @@ describe('LokiDatasource', () => {
       replace: a => a,
     };
 
-    const range = { from: 'now-6h', to: 'now' };
-
     test('should use default max lines when no limit given', () => {
       const ds = new LokiDatasource(instanceSettings, backendSrvMock, templateSrvMock);
       backendSrvMock.datasourceRequest = jest.fn();
-      ds.query({ range, targets: [{ expr: 'foo' }] });
+      const options = getQueryOptions<LokiQuery>({ targets: [{ expr: 'foo', refId: 'B' }] });
+
+      ds.query(options);
+
       expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1);
       expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain('limit=1000');
     });
@@ -28,7 +31,10 @@ describe('LokiDatasource', () => {
       const customSettings = { ...instanceSettings, jsonData: customData };
       const ds = new LokiDatasource(customSettings, backendSrvMock, templateSrvMock);
       backendSrvMock.datasourceRequest = jest.fn();
-      ds.query({ range, targets: [{ expr: 'foo' }] });
+
+      const options = getQueryOptions<LokiQuery>({ targets: [{ expr: 'foo', refId: 'B' }] });
+      ds.query(options);
+
       expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1);
       expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain('limit=20');
     });

+ 12 - 7
public/app/plugins/datasource/loki/datasource.ts

@@ -1,13 +1,18 @@
+// Libraries
 import _ from 'lodash';
 
+// Services & Utils
 import * as dateMath from 'app/core/utils/datemath';
-import { LogsStream, LogsModel, makeSeriesForLogs } from 'app/core/logs_model';
-import { PluginMeta, DataQuery } from '@grafana/ui/src/types';
 import { addLabelToSelector } from 'app/plugins/datasource/prometheus/add_label_to_query';
-
 import LanguageProvider from './language_provider';
 import { mergeStreamsToLogs } from './result_transformer';
 import { formatQuery, parseQuery } from './query_utils';
+import { makeSeriesForLogs } from 'app/core/logs_model';
+
+// Types
+import { LogsStream, LogsModel } from 'app/core/logs_model';
+import { PluginMeta, DataQueryOptions } from '@grafana/ui/src/types';
+import { LokiQuery } from './types';
 
 export const DEFAULT_MAX_LINES = 1000;
 
@@ -68,7 +73,7 @@ export default class LokiDatasource {
     };
   }
 
-  query(options): Promise<{ data: LogsStream[] }> {
+  query(options: DataQueryOptions<LokiQuery>): Promise<{ data: LogsStream[] }> {
     const queryTargets = options.targets
       .filter(target => target.expr)
       .map(target => this.prepareQueryTarget(target, options));
@@ -96,7 +101,7 @@ export default class LokiDatasource {
     });
   }
 
-  async importQueries(queries: DataQuery[], originMeta: PluginMeta): Promise<DataQuery[]> {
+  async importQueries(queries: LokiQuery[], originMeta: PluginMeta): Promise<LokiQuery[]> {
     return this.languageProvider.importQueries(queries, originMeta.id);
   }
 
@@ -109,7 +114,7 @@ export default class LokiDatasource {
     });
   }
 
-  modifyQuery(query: DataQuery, action: any): DataQuery {
+  modifyQuery(query: LokiQuery, action: any): LokiQuery {
     const parsed = parseQuery(query.expr || '');
     let selector = parsed.query;
     switch (action.type) {
@@ -124,7 +129,7 @@ export default class LokiDatasource {
     return { ...query, expr: expression };
   }
 
-  getHighlighterExpression(query: DataQuery): string {
+  getHighlighterExpression(query: LokiQuery): string {
     return parseQuery(query.expr).regexp;
   }
 

+ 11 - 5
public/app/plugins/datasource/loki/language_provider.ts

@@ -1,6 +1,12 @@
+// Libraries
 import _ from 'lodash';
 import moment from 'moment';
 
+// Services & Utils
+import { parseSelector, labelRegexp, selectorRegexp } from 'app/plugins/datasource/prometheus/language_utils';
+import syntax from './syntax';
+
+// Types
 import {
   CompletionItem,
   CompletionItemGroup,
@@ -9,9 +15,7 @@ import {
   TypeaheadOutput,
   HistoryItem,
 } from 'app/types/explore';
-import { parseSelector, labelRegexp, selectorRegexp } from 'app/plugins/datasource/prometheus/language_utils';
-import syntax from './syntax';
-import { DataQuery } from '@grafana/ui/src/types';
+import { LokiQuery } from './types';
 
 const DEFAULT_KEYS = ['job', 'namespace'];
 const EMPTY_SELECTOR = '{}';
@@ -20,7 +24,9 @@ const HISTORY_COUNT_CUTOFF = 1000 * 60 * 60 * 24; // 24h
 
 const wrapLabel = (label: string) => ({ label });
 
-export function addHistoryMetadata(item: CompletionItem, history: HistoryItem[]): CompletionItem {
+type LokiHistoryItem = HistoryItem<LokiQuery>;
+
+export function addHistoryMetadata(item: CompletionItem, history: LokiHistoryItem[]): CompletionItem {
   const cutoffTs = Date.now() - HISTORY_COUNT_CUTOFF;
   const historyForItem = history.filter(h => h.ts > cutoffTs && (h.query.expr as string) === item.label);
   const count = historyForItem.length;
@@ -155,7 +161,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
     return { context, refresher, suggestions };
   }
 
-  async importQueries(queries: DataQuery[], datasourceType: string): Promise<DataQuery[]> {
+  async importQueries(queries: LokiQuery[], datasourceType: string): Promise<LokiQuery[]> {
     if (datasourceType === 'prometheus') {
       return Promise.all(
         queries.map(async query => {

+ 6 - 0
public/app/plugins/datasource/loki/types.ts

@@ -0,0 +1,6 @@
+import { DataQuery } from '@grafana/ui/src/types';
+
+export interface LokiQuery extends DataQuery {
+  expr: string;
+}
+

+ 4 - 4
public/app/plugins/datasource/prometheus/components/PromQueryField.tsx

@@ -11,7 +11,7 @@ import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/
 import BracesPlugin from 'app/features/explore/slate-plugins/braces';
 import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
 import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
-import { DataQuery } from '@grafana/ui/src/types';
+import { PromQuery } from '../types';
 
 const HISTOGRAM_GROUP = '__histograms__';
 const METRIC_MARK = 'metric';
@@ -88,13 +88,13 @@ interface CascaderOption {
 interface PromQueryFieldProps {
   datasource: any;
   error?: string | JSX.Element;
-  initialQuery: DataQuery;
+  initialQuery: PromQuery;
   hint?: any;
   history?: any[];
   metricsByPrefix?: CascaderOption[];
   onClickHintFix?: (action: any) => void;
   onPressEnter?: () => void;
-  onQueryChange?: (value: DataQuery, override?: boolean) => void;
+  onQueryChange?: (value: PromQuery, override?: boolean) => void;
 }
 
 interface PromQueryFieldState {
@@ -166,7 +166,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
     // Send text change to parent
     const { initialQuery, onQueryChange } = this.props;
     if (onQueryChange) {
-      const query: DataQuery = {
+      const query: PromQuery = {
         ...initialQuery,
         expr: value,
       };

+ 49 - 45
public/app/plugins/datasource/prometheus/datasource.ts

@@ -1,57 +1,24 @@
+// Libraries
 import _ from 'lodash';
-
 import $ from 'jquery';
+
+// Services & Utils
 import kbn from 'app/core/utils/kbn';
 import * as dateMath from 'app/core/utils/datemath';
 import PrometheusMetricFindQuery from './metric_find_query';
 import { ResultTransformer } from './result_transformer';
 import PrometheusLanguageProvider from './language_provider';
 import { BackendSrv } from 'app/core/services/backend_srv';
-
 import addLabelToQuery from './add_label_to_query';
 import { getQueryHints } from './query_hints';
 import { expandRecordingRules } from './language_utils';
-import { DataQuery } from '@grafana/ui/src/types';
-import { ExploreUrlState } from 'app/types/explore';
-
-export function alignRange(start, end, step) {
-  const alignedEnd = Math.ceil(end / step) * step;
-  const alignedStart = Math.floor(start / step) * step;
-  return {
-    end: alignedEnd,
-    start: alignedStart,
-  };
-}
 
-export function extractRuleMappingFromGroups(groups: any[]) {
-  return groups.reduce(
-    (mapping, group) =>
-      group.rules.filter(rule => rule.type === 'recording').reduce(
-        (acc, rule) => ({
-          ...acc,
-          [rule.name]: rule.query,
-        }),
-        mapping
-      ),
-    {}
-  );
-}
-
-export function prometheusRegularEscape(value) {
-  if (typeof value === 'string') {
-    return value.replace(/'/g, "\\\\'");
-  }
-  return value;
-}
-
-export function prometheusSpecialRegexEscape(value) {
-  if (typeof value === 'string') {
-    return prometheusRegularEscape(value.replace(/\\/g, '\\\\\\\\').replace(/[$^*{}\[\]+?.()]/g, '\\\\$&'));
-  }
-  return value;
-}
+// Types
+import { PromQuery } from './types';
+import { DataQueryOptions, DataSourceApi } from '@grafana/ui/src/types';
+import { ExploreUrlState } from 'app/types/explore';
 
-export class PrometheusDatasource {
+export class PrometheusDatasource implements DataSourceApi<PromQuery> {
   type: string;
   editorSrc: string;
   name: string;
@@ -149,7 +116,7 @@ export class PrometheusDatasource {
     return this.templateSrv.variableExists(target.expr);
   }
 
-  query(options) {
+  query(options: DataQueryOptions<PromQuery>) {
     const start = this.getPrometheusTime(options.range.from, false);
     const end = this.getPrometheusTime(options.range.to, true);
 
@@ -423,7 +390,7 @@ export class PrometheusDatasource {
     });
   }
 
-  getExploreState(queries: DataQuery[]): Partial<ExploreUrlState> {
+  getExploreState(queries: PromQuery[]): Partial<ExploreUrlState> {
     let state: Partial<ExploreUrlState> = { datasource: this.name };
     if (queries && queries.length > 0) {
       const expandedQueries = queries.map(query => ({
@@ -438,7 +405,7 @@ export class PrometheusDatasource {
     return state;
   }
 
-  getQueryHints(query: DataQuery, result: any[]) {
+  getQueryHints(query: PromQuery, result: any[]) {
     return getQueryHints(query.expr || '', result, this);
   }
 
@@ -457,7 +424,7 @@ export class PrometheusDatasource {
       });
   }
 
-  modifyQuery(query: DataQuery, action: any): DataQuery {
+  modifyQuery(query: PromQuery, action: any): PromQuery {
     let expression = query.expr || '';
     switch (action.type) {
       case 'ADD_FILTER': {
@@ -507,3 +474,40 @@ export class PrometheusDatasource {
     return this.resultTransformer.getOriginalMetricName(labelData);
   }
 }
+
+export function alignRange(start, end, step) {
+  const alignedEnd = Math.ceil(end / step) * step;
+  const alignedStart = Math.floor(start / step) * step;
+  return {
+    end: alignedEnd,
+    start: alignedStart,
+  };
+}
+
+export function extractRuleMappingFromGroups(groups: any[]) {
+  return groups.reduce(
+    (mapping, group) =>
+      group.rules.filter(rule => rule.type === 'recording').reduce(
+        (acc, rule) => ({
+          ...acc,
+          [rule.name]: rule.query,
+        }),
+        mapping
+      ),
+    {}
+  );
+}
+
+export function prometheusRegularEscape(value) {
+  if (typeof value === 'string') {
+    return value.replace(/'/g, "\\\\'");
+  }
+  return value;
+}
+
+export function prometheusSpecialRegexEscape(value) {
+  if (typeof value === 'string') {
+    return prometheusRegularEscape(value.replace(/\\/g, '\\\\\\\\').replace(/[$^*{}\[\]+?.()]/g, '\\\\$&'));
+  }
+  return value;
+}

+ 6 - 0
public/app/plugins/datasource/prometheus/types.ts

@@ -0,0 +1,6 @@
+import { DataQuery } from '@grafana/ui/src/types';
+
+export interface PromQuery extends DataQuery {
+  expr: string;
+}
+

+ 8 - 8
public/app/plugins/datasource/testdata/QueryEditor.tsx

@@ -10,18 +10,17 @@ import { FormLabel, Select, SelectOptionItem } from '@grafana/ui';
 
 // Types
 import { QueryEditorProps } from '@grafana/ui/src/types';
-
-interface Scenario {
-  id: string;
-  name: string;
-}
+import { TestDataDatasource } from './datasource';
+import { TestDataQuery, Scenario } from './types';
 
 interface State {
   scenarioList: Scenario[];
   current: Scenario | null;
 }
 
-export class QueryEditor extends PureComponent<QueryEditorProps> {
+type Props = QueryEditorProps<TestDataDatasource, TestDataQuery>;
+
+export class QueryEditor extends PureComponent<Props> {
   backendSrv: BackendSrv = getBackendSrv();
 
   state: State = {
@@ -30,11 +29,12 @@ export class QueryEditor extends PureComponent<QueryEditorProps> {
   };
 
   async componentDidMount() {
-    const { query } = this.props;
+    const { query, datasource } = this.props;
 
     query.scenarioId = query.scenarioId || 'random_walk';
 
-    const scenarioList = await this.backendSrv.get('/api/tsdb/testdata/scenarios');
+    // const scenarioList = await this.backendSrv.get('/api/tsdb/testdata/scenarios');
+    const scenarioList = await datasource.getScenarios();
     const current = _.find(scenarioList, { id: query.scenarioId });
 
     this.setState({ scenarioList: scenarioList, current: current });

+ 9 - 4
public/app/plugins/datasource/testdata/datasource.ts

@@ -1,15 +1,17 @@
 import _ from 'lodash';
 import TableModel from 'app/core/table_model';
+import { DataSourceApi, DataQueryOptions } from '@grafana/ui';
+import { TestDataQuery, Scenario } from './types';
 
-class TestDataDatasource {
-  id: any;
+export class TestDataDatasource implements DataSourceApi<TestDataQuery> {
+  id: number;
 
   /** @ngInject */
   constructor(instanceSettings, private backendSrv, private $q) {
     this.id = instanceSettings.id;
   }
 
-  query(options) {
+  query(options: DataQueryOptions<TestDataQuery>) {
     const queries = _.filter(options.targets, item => {
       return item.hide !== true;
     }).map(item => {
@@ -91,6 +93,9 @@ class TestDataDatasource {
       message: 'Data source is working',
     });
   }
+
+  getScenarios(): Promise<Scenario[]> {
+    return this.backendSrv.get('/api/tsdb/testdata/scenarios');
+  }
 }
 
-export { TestDataDatasource };

+ 11 - 0
public/app/plugins/datasource/testdata/types.ts

@@ -0,0 +1,11 @@
+import { DataQuery } from '@grafana/ui/src/types';
+
+export interface TestDataQuery extends DataQuery {
+  scenarioId: string;
+}
+
+export interface Scenario {
+  id: string;
+  name: string;
+}
+

+ 2 - 2
public/app/types/explore.ts

@@ -243,9 +243,9 @@ export interface ExploreUrlState {
   range: RawTimeRange;
 }
 
-export interface HistoryItem {
+export interface HistoryItem<TQuery extends DataQuery = DataQuery> {
   ts: number;
-  query: DataQuery;
+  query: TQuery;
 }
 
 export abstract class LanguageProvider {

+ 25 - 0
public/test/helpers/getQueryOptions.ts

@@ -0,0 +1,25 @@
+import { DataQueryOptions, DataQuery } from '@grafana/ui';
+import moment from 'moment';
+
+
+export function getQueryOptions<TQuery extends DataQuery>(options: Partial<DataQueryOptions<TQuery>>): DataQueryOptions<TQuery> {
+  const raw = {from: 'now', to: 'now-1h'};
+  const range = { from: moment(), to: moment(), raw: raw};
+
+  const defaults: DataQueryOptions<TQuery> = {
+    range: range,
+    rangeRaw: raw,
+    targets: [],
+    scopedVars: {},
+    timezone: 'browser',
+    panelId: 1,
+    dashboardId: 1,
+    interval: '60s',
+    intervalMs: 60000,
+    maxDataPoints: 500,
+  };
+
+  Object.assign(defaults, options);
+
+  return defaults;
+}