@grafana/data#DataSourceApi TypeScript Examples

The following examples show how to use @grafana/data#DataSourceApi. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: datasource.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
/**
 * This should not really be called
 */
export class DashboardDatasource extends DataSourceApi<DashboardQuery> {
  constructor(instanceSettings: DataSourceInstanceSettings) {
    super(instanceSettings);
  }

  getCollapsedText(query: DashboardQuery) {
    return `Dashboard Reference: ${query.panelId}`;
  }

  query(options: DataQueryRequest<DashboardQuery>): Promise<DataQueryResponse> {
    return Promise.reject('This should not be called directly');
  }

  testDatasource() {
    return Promise.resolve({});
  }
}
Example #2
Source File: Graph2.tsx    From loudml-grafana-app with MIT License 5 votes vote down vote up
getGlobalAnnotations(range: any) {
    const promises = [];
    const dsPromises = [];

    for (const annotation of this.dashboard.annotations.list) {
      if (!annotation.enable) {
        continue;
      }

      if (annotation.snapshotData) {
        return this.translateQueryResult(annotation, annotation.snapshotData);
      }

      const datasourcePromise = getDataSourceSrv().get(annotation.datasource);
      dsPromises.push(datasourcePromise);
      promises.push(
        datasourcePromise
          .then((datasource: DataSourceApi) => {
            // issue query against data source
            return datasource.annotationQuery({
              range,
              rangeRaw: range.raw,
              annotation: annotation,
              dashboard: this.dashboard,
            });
          })
          .then(results => {
            // store response in annotation object if this is a snapshot call
            if (this.dashboard.snapshot) {
              annotation.snapshotData = cloneDeep(results);
            }
            // translate result
            return this.translateQueryResult(annotation, results);
          })
      );
    }

    this.datasourcePromises = Promise.all(dsPromises);
    this.globalAnnotationsPromise = Promise.all(promises);
    return this.globalAnnotationsPromise;
  }
Example #3
Source File: runRequest.test.ts    From grafana-chinese with Apache License 2.0 5 votes vote down vote up
ds: DataSourceApi;
Example #4
Source File: datasource.ts    From grafana-kdb-datasource-ws with Apache License 2.0 4 votes vote down vote up
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
  //This is declaring the types of each member
  id: any;
  name: any;
  variables: any;
  responseParser: ResponseParser;
  queryModel: KDBQuery;
  //  interval: string;
  message = {};
  url: string;
  wsUrl: string;
  ws: WebSocket;
  awaitingResponse: boolean;
  c: C = new C();
  maxRowCount: number;
  connectionStateCycles: number;
  timeoutLength: number;

  //WebSocket communication variables
  requestSentList: any[];
  requestSentIDList: any[];
  responseReceivedList: any[];

  constructor(
    instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>,
    private backendSrv,
    private $q,
    private templateSrv
  ) {
    super(instanceSettings);

    this.templateSrv = templateSrv;
    this.name = instanceSettings.name;
    this.id = instanceSettings.id;
    this.responseParser = new ResponseParser(this.$q);
    this.queryModel = new KDBQuery({});
    this.interval = (instanceSettings.jsonData || {}).timeInterval;
    if (!instanceSettings.jsonData.timeoutLength) {
      this.timeoutLength = defaultTimeout;
    } else {
      this.timeoutLength = Number(instanceSettings.jsonData.timeoutLength);
    }
    this.requestSentList = [];
    this.requestSentIDList = [];
    this.responseReceivedList = [];

    this.url = 'http://' + instanceSettings.jsonData.host;
    if (instanceSettings.jsonData.useAuthentication) {
      if (instanceSettings.jsonData.useTLS === true) {
        this.wsUrl =
          'wss://' +
          instanceSettings.jsonData.user +
          ':' +
          instanceSettings.jsonData.password +
          '@' +
          instanceSettings.jsonData.host;
      } else {
        this.wsUrl =
          'ws://' +
          instanceSettings.jsonData.user +
          ':' +
          instanceSettings.jsonData.password +
          '@' +
          instanceSettings.jsonData.host;
      }
    } else {
      this.wsUrl = 'ws://' + instanceSettings.jsonData.host;
    }
  }

  query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
    var prefilterResultCount = options.targets.length;

    if (prefilterResultCount == 0) {
      return new Promise((resolve) => {
        resolve({ data: [] });
      });
    }

    var allRefIDs = [];
    var blankRefIDs = [];
    var validRequestList = [];
    var errorList = [];

    for (var i = 0; i < prefilterResultCount; i++) {
      //Inject variables into target
      this.injectVariables(options.targets[i], options.scopedVars, options.range);

      // for some reason randomWalk is defaulted
      if (options.targets[i].queryType == 'randomWalk') {
        options.targets[i].queryType = 'selectQuery';
      }
      allRefIDs.push(options.targets[i].refId);
      options.targets[i].range = options.range;
      if (
        (!options.targets[i].table && options.targets[i].queryType === 'selectQuery') ||
        (options.targets[i].queryType === 'functionQuery' && options.targets[i].kdbFunction === '') ||
        options.targets[i].hide === true
      ) {
        blankRefIDs.push(options.targets[i].refId);
      } else if (!options.targets[i].queryError) {
        blankRefIDs.push(options.targets[i].refId);
      } else if (options.targets[i].queryError.error.indexOf(true) !== -1) {
        errorList.push({
          refId: options.targets[i].refId,
          errorMessage: options.targets[i].queryError.message[options.targets[i].queryError.error.indexOf(true)],
        });
      } else validRequestList.push(options.targets[i]);
    }

    var nrBlankRequests = blankRefIDs.length;
    var requestList = validRequestList.map((target) => {
      return this.buildKdbRequest(target);
    });

    var nrRequests: number = requestList.length;
    if (!this.ws || this.ws.readyState > 1)
      return this.connectWS().then((connectStatus) => {
        if (connectStatus === true && nrRequests > 0)
          return this.sendQueries(nrRequests, requestList, nrBlankRequests, blankRefIDs, errorList).then(
            (series: any) => {
              return this.buildDataFrames(series);
            }
          );
        else if (connectStatus === true && nrRequests === 0)
          return this.emptyQueries(nrBlankRequests, blankRefIDs, errorList).then(() => {
            return { data: [] };
          });
        else
          return this.connectFail(prefilterResultCount, allRefIDs).then(() => {
            return { data: [] };
          });
      });
    else {
      return this.webSocketWait().then(() => {
        if (nrRequests > 0)
          return this.sendQueries(nrRequests, requestList, nrBlankRequests, blankRefIDs, errorList).then(
            (series: any) => {
              return this.buildDataFrames(series);
            }
          );
        else
          return this.emptyQueries(nrBlankRequests, blankRefIDs, errorList).then(() => {
            return { data: [] };
          });
      });
    }
  }

  // Build the DataFrames to return to Grafana
  buildDataFrames(series) {
    let data: MutableDataFrame[] = [];
    let error = {} as DataQueryError;

    series.data.forEach((target) => {
      if (target.meta.errorMessage) {
        error.message = target.meta.errorMessage;
        throw new Error(target.meta.errorMessage);
      }
      if (target.columns) {
        var fields = [];
        target.columns.forEach((column) => {
          fields.push({ name: column.text });
        });
        const frame = new MutableDataFrame({
          refId: target.refId,
          fields: fields,
        });
        target.rows.forEach((element) => {
          var row = [];
          element.forEach((entry) => {
            row.push(entry);
          });
          frame.appendRow(row);
        });
        data.push(frame);
      } else {
        // time series

        let datapoints = target.datapoints;
        const timeValues = datapoints.map((datapoint) => datapoint[1]);
        const values = datapoints.map((datapoint) => datapoint[0]);

        const fields = [
          { name: 'Time', values: timeValues, type: FieldType.time },
          {
            name: target.target,
            values: values,
          },
        ];

        data.push(
          new MutableDataFrame({
            refId: target.refId,
            fields: fields,
          })
        );
      }
    });
    return { data, state: 'done' };
  }

  //This is the function called by Grafana when it is testing a connection on the configuration page
  testDatasource() {
    return this.connect().then((result) => {
      return result;
    });
  }
  //Replace variables with their values
  private variablesReplace(target: any, search: string, replace: any) {
    //Format Options as array or scalar
    if (Array.isArray(replace)) {
      target.kdbFunction = target.kdbFunction.replace(search, replace.join(','));
    } else {
      target.kdbFunction = target.kdbFunction.replace(search, replace);
    }
    //Replace Table Variables
    target.table = this.fieldInjectVariables(target.table, search, replace);
    //Replace select clause variables
    if (target.select !== [] && target.select.length > 0) {
      for (let i = 0; i < target.select[0].length; i++) {
        for (let y = 0; y < target.select[0][i].params.length; y++) {
          target.select[0][i].params[y] = target.select[0][i].params[y].replace(search, replace);
        }
      }
    }
    //Replace where clause variables
    if (target.where !== []) {
      for (let i = 0; i < target.where.length; i++) {
        for (let y = 0; y < target.where[i].params.length; y++) {
          if (Array.isArray(replace) && replace.length > 1) {
            if (target.where[i].params[y] === search) target.where[i].params[y] = replace;
          } else if ('string' == typeof target.where[i].params[y]) {
            target.where[i].params[y] = target.where[i].params[y].replace(search, replace);
          }
        }
      }
    }
    //Replace time, grouping and funcGroup columns if required
    target.timeColumn = this.fieldInjectVariables(target.timeColumn, search, replace);
    target.groupingField = this.fieldInjectVariables(target.groupingField, search, replace);
    target.funcGroupCol = this.fieldInjectVariables(target.funcGroupCol, search, replace);
    //Check row count is formatted correctly
    if ('string' == typeof target.rowCountLimit) {
      if (target.rowCountLimit === search) {
        if (Number.isInteger(Number(replace)) && Number(replace) > 0) {
          target.rowCountLimit = Number(replace);
        } else {
          target.queryError.error[2] = true;
          target.queryError.message[2] = 'Row count limit not a positive integer';
        }
      }
    }
    //Check conflation params are formatted correctly
    if ('string' == typeof target.conflationDuration) {
      if (target.conflationDuration === search) {
        if (isNaN(Number(replace))) {
          target.queryError.error[1] = true;
          target.queryError.message[1] = 'Conflation duration not a number';
        } else {
          target.conflationDuration = Number(replace);
        }
      }
    }
  }

  //Check if attribute needs replacing, then replace if so
  private fieldInjectVariables(attrib: any, search: string, replace: any) {
    // console.log('s',search)
    // console.log('r',replace)
    if (attrib) {
      attrib = attrib.replace(search, replace);
      // console.log('a1',attrib)
      return attrib;
    } else {
      // console.log('a2',attrib)
      return attrib;
    }
  }

  private injectVariables(target, scoped, range) {
    let instVariables = this.newGetVariables(getTemplateSrv());
    // console.log('TEMPLATESRV:', this.templateSrv);
    // console.log('VARIABLES: ', instVariables);
    // console.log('scp',scoped)
    let scopedVarArray = Object.keys(scoped);
    let scopedValueArray = [];
    //scoped variables inject
    for (let k = 0; k < scopedVarArray.length; k++) {
      scopedValueArray.push(scoped[scopedVarArray[k]].value);
      scopedVarArray[k] = '$' + scopedVarArray[k];
    }
    //local variables inject (user variables)
    for (let i = 0; i < instVariables.length; i++) {
      let varname = '${' + instVariables[i].name + '}';

      // console.log(varname.length)
      // console.log('vname:',varname)
      if (scopedVarArray.indexOf(varname) == -1) {
        scopedVarArray.push(varname);
        if (instVariables[i].current.text === 'All') {
          // console.log('trig1')
          scopedValueArray.push(instVariables[i].allValue);
        } else {
          // console.log('trig2')
          scopedValueArray.push(instVariables[i].current.value);
        }
      }
    }
    // console.log('scopedval',scopedValueArray)
    //$__from & $__to inject
    scopedVarArray.push('${__from}');
    scopedValueArray.push('(`timestamp$' + this.buildKdbTimestamp(range.from._d) + ')');
    scopedVarArray.push('${__to}');
    scopedValueArray.push('(`timestamp$' + this.buildKdbTimestamp(range.to._d) + ')');
    //Replace variables
    // console.log('TARGET: ',target);
    // console.log('SCOPEDVARARRAY:', scopedVarArray);
    // console.log('SCOPEDVALUEARRAY:', scopedValueArray);
    for (let kv = 0; kv < scopedVarArray.length; kv++) {
      this.variablesReplace(target, scopedVarArray[kv], scopedValueArray[kv]);
    }
  }

  //Change templateSrv object to be handled as variables
  private newGetVariables(templatesrv) {
    let instVariables = [];
    let variables = JSON.parse(JSON.stringify(templatesrv.getVariables()));
    for (let i = 0; i < variables.length; i++) {
      //Set the 'all' value if the option is enabled
      if (variables[i].options[0] && variables[i].options[0].text === 'All') {
        let valueArray = [];
        for (let j = 1; j < variables[i].options.length; j++) {
          valueArray.push(variables[i].options[j].value);
        }
        variables[i].allValue = valueArray;
      }
      instVariables.push(variables[i]);
    }
    return instVariables;
  }

  //Websocket per request?
  private buildKdbRequest(target) {
    let queryParam = new QueryParam();
    let kdbRequest = new KdbRequest();
    let queryDictionary = new QueryDictionary();
    let conflationParams = new ConflationParams();

    //Need to take into account quotes in line, replace " with \"
    queryDictionary.type = target.queryType == 'selectQuery' ? '`select' : '`function';
    queryDictionary.value = target.kdbFunction;

    queryParam.query = Object.assign({}, queryDictionary);
    queryParam.table = '`' + target.table;
    queryParam.column = this.buildColumnParams(target);
    queryParam.temporal_field = target.useTemporalField ? this.buildTemporalField(target) : [];
    queryParam.temporal_range = this.buildTemporalRange(target.range);
    queryParam.maxRowCount = target.rowCountLimit;
    // if (target.postbackFunction) queryParam.postbackFunction = target.postbackFunction;

    if (target.queryType == 'selectQuery') queryParam.where = this.buildWhereParams(target.where ? target.where : []);
    //conflation
    if (target.useConflation) {
      this.buildConflation(target);
      conflationParams.val = target.conflationDurationMS.toString();
      conflationParams.agg = target.conflation.aggregate;
      queryParam.conflation = Object.assign({}, conflationParams);
    } else {
      queryParam.conflation = [];
    }

    //add condition, has grouping been selected?
    if (target.useGrouping && target.queryType == 'selectQuery' && target.groupingField) {
      queryParam.grouping = ['`' + target.groupingField];
    } else if (target.useGrouping && target.queryType == 'functionQuery' && target.funcGroupCol) {
      queryParam.grouping = ['`' + target.funcGroupCol];
    } else {
      queryParam.grouping = [];
    }

    kdbRequest.time = this.getTimeStamp(new Date());
    kdbRequest.refId = target.refId;
    kdbRequest.query = ''; //query;
    kdbRequest.queryParam = Object.assign({}, queryParam);
    kdbRequest.format = target.format;
    kdbRequest.queryId = target.queryId;
    kdbRequest.version = target.version;
    // if (target.useAsyncFunction) kdbRequest.useAsyncFunction = target.useAsyncFunction;
    // if (target.useCustomPostback) kdbRequest.useCustomPostback = target.useCustomPostback;
    // if (target.asyncProcTypes) kdbRequest.asyncProcTypes = target.asyncProcTypes;

    for (let i = 0; i < target.queryError.error.length; i++) {
      if (target.queryError.error[i]) {
        throw new Error(target.queryError.message[i]);
      }
    }

    return [target.format == 'time series' ? graphFunction : tabFunction, Object.assign({}, kdbRequest)];
  }

  //This function
  private buildTemporalField(queryDetails) {
    if (queryDetails.queryType == 'selectQuery' && queryDetails.timeColumn) {
      return '`' + queryDetails.timeColumn;
    } else if (queryDetails.queryType == 'functionQuery' && queryDetails.funcTimeCol) {
      return '`' + queryDetails.funcTimeCol;
    } else {
      return [];
    }
  }

  private buildConflation(queryDetails) {
    if (['s', 'm', 'h', 'ms'].indexOf(queryDetails.conflation.unitType) == -1) {
      queryDetails.conflationUnit = conflationUnitDefault;
      queryDetails.queryError.error[1] = true;
      queryDetails.queryError.message[1] =
        'Conflation unit not support. Please post conflation settings on our GitHub page.';
    }
    queryDetails.conflationDurationMS =
      queryDetails.conflation.duration * durationMap[queryDetails.conflation.unitType];
  }

  private buildKdbTimestamp(date: Date) {
    return 1000000 * (date.valueOf() - kdbEpoch);
  }

  //Getting it to work via strings would require supporting timezones fully. Rabbit hole.
  /* private ES2015padStart(obj: string, length: number, fill: string) {
    //Effectively polyfill for String.padStart (fill length will only fill up to 10 missing characters)
    let f = length - obj.length;
    return f > 0 ? fill.repeat(10).substr(0,f) + obj : obj
}

private buildKdbTimestampString(date : Date) {
    let dt = date.getFullYear().toString() + '.' + 
        this.ES2015padStart((date.getMonth() + 1).toString(), 2, "0") + '.' + 
        this.ES2015padStart(date.getDate().toString(), 2, "0");
    let tm = this.ES2015padStart(date.getHours().toString(), 2, "0") + ':' + 
        this.ES2015padStart(date.getMinutes().toString(), 2, "0") + ':' + 
        this.ES2015padStart(date.getSeconds().toString(), 2, "0") + '.' + 
        this.ES2015padStart(date.getMilliseconds().toString(), 3, "0");
    return dt + 'D' + tm;
} */

  private buildTemporalRange(range) {
    let temporalRange: number[] = [];
    if (range) {
      temporalRange.push(this.buildKdbTimestamp(range.from._d));
      temporalRange.push(this.buildKdbTimestamp(range.to._d));
    }
    return temporalRange;
  }

  private buildWhereParams(queryWhereList): Array<string> {
    let whereArray = [];
    let whereClause = [];

    if (queryWhereList.length > 0) {
      queryWhereList.forEach((clause) => {
        let notStatement = false;
        if (clause.params[0] !== 'select field' && clause.params[2] !== 'enter value') {
          whereClause = [];
          if (clause.params[1].substr(0, 3) == 'not') {
            clause.params[1] = clause.params[1].substr(4);
            whereClause.push(clause.params[1]);
            notStatement = true;
          } else whereClause.push(clause.params[1]);
          whereClause.push('`' + clause.params[0]);
          //                    if (clause.datatype == 's') {
          if (['in', 'within'].indexOf(clause.params[1]) != -1) {
            if ('string' == typeof clause.params[2]) {
              whereClause.push(clause.params[2].split(',').map((str) => str.trim()));
            } else {
              whereClause.push(clause.params[2]);
            }
          } else if (clause.params[1] == 'like') {
            whereClause.push('"' + clause.params[2] + '"');
          } else whereClause.push(clause.params[2]);
          //                    }
          //                    else if (clause.datatype == 'c') {
          //                        whereClause.push('\"' + clause.params[2] + '\"');
          //                    }
          //                    else {
          //                        if (clause.params[1] == "within") {
          //                            whereClause.push(clause.params[2].split(",").map(str => str.trim()))
          //                        } else whereClause.push(clause.params[2]);
          //                    }
          if (notStatement === true) {
            // console.log('WHERECLAUSE', whereClause)
            whereClause.push('x');
          } else whereClause.push('o');
          whereArray.push(whereClause);
        }
      });
    }

    return whereArray;
  } //end of building of where clause

  //Builds the list of select functions consisting of the column name and an aggregation function where applicable
  private buildColumnParams(target): Array<string> {
    let columnArray: any[] = [];

    if (target.select) {
      target.select.forEach((select) => {
        if (select[0].params[0] !== 'select column') {
          let selectElement = [];
          if (target.useConflation) {
            if (select.length !== 1) {
              if (select[1].type == 'aggregate') {
                selectElement.push(select[1].params[0]);
              } else {
                selectElement.push(target.conflation.aggregate);
              }
            } else {
              selectElement.push(target.conflation.aggregate);
            }
          } else {
            selectElement.push('::'); //dummy value for kdb function
          }
          selectElement.push('`' + select[0].params[0]);

          //dealing with aliasing
          let alias = '::';
          if (select.length > 1) {
            if (select[1].type == 'alias') {
              alias = select[1].params[0];
            }
          }
          if (select.length == 3) {
            if (select[2].type == 'alias') {
              alias = select[2].params[0];
            }
          }

          selectElement.push(alias);
          columnArray.push(selectElement);
        }
      });
    } else {
      //temporary fix
      columnArray.push(['::', '`value', '::']);
    }
    return columnArray;
  }

  private getTimeStamp(date: Date): string {
    let dateString = date.valueOf().toString();
    return dateString.substring(0, dateString.length - 3);
  }

  showEmpty(Id: string, errormessage?: string) {
    if (typeof errormessage === 'undefined') {
      var returnobj = {
        refId: Id,
        columns: [],
        rows: [],
        meta: { refId: Id, errorReceived: false, errorMessage: '' },
      };
    } else {
      var returnobj = {
        refId: Id,
        columns: [],
        rows: [],
        meta: { refId: Id, errorReceived: true, errorMessage: errormessage },
      };
    }
    return returnobj;
  }

  errorReturn(errorstring: string) {
    return { payload: [], error: errorstring, success: false };
  }

  sendQueries(nrRequests, requestList, nrBlankRequests, blankRefIDs, errorList) {
    var curRequest: number = 0;
    var resultList = [];

    return new Promise((resolve) => {
      this.ProcessData(curRequest, nrRequests, resultList, requestList)
        .then(() => {
          for (var i = 0; i < nrBlankRequests; i++) {
            resultList.push(this.showEmpty(blankRefIDs[i]));
          }
          for (var i = 0; i < errorList.length; i++) {
            resultList.push(this.showEmpty(errorList[i].refId, errorList[i].errorMessage));
          }
          resolve({ data: resultList });
        })
        .catch((e) => {});
    });
  }
  connectFail(prefilterResultCount, allRefIDs) {
    return new Promise((resolve) => {
      let serverUnavailableResponse = [];
      for (var i = 0; i < prefilterResultCount; i++) {
        serverUnavailableResponse.push(this.showEmpty(allRefIDs[i], 'KDB+ server unavailable.'));
      }
      resolve({ data: serverUnavailableResponse });
    });
  }

  emptyQueries(nrBlankRequests, blankRefIDs, errorList) {
    return new Promise((resolve) => {
      let resultList = [];
      for (var i = 0; i < nrBlankRequests; i++) {
        resultList.push(this.showEmpty(blankRefIDs[i]));
      }
      for (var i = 0; i < errorList.length; i++) {
        resultList.push(this.showEmpty(errorList[i].refId, errorList[i].errorMessage));
      }
      resolve({ data: resultList });
    });
  }

  private ProcessData(curRequest, nrRequests, resultList, requestList) {
    return new Promise((resolve) => {
      this.getQueryResult(requestList[curRequest]).then((result) => {
        var indicies = Object.keys(result);
        if (result.hasOwnProperty('meta.errorReceived')) {
          resultList.push(result);
        } else {
          for (let i = 0; i < indicies.length; i++) {
            resultList.push(result[i]);
          }
        }

        if (curRequest == nrRequests - 1) {
          let returnVal = resultList;
          resolve(returnVal);
        } else {
          curRequest++;
          resolve(this.ProcessData(curRequest, nrRequests, resultList, requestList));
        }
      });
    });
  }

  //Response parser called here**********************
  private getQueryResult = (request: any): Promise<Object> => {
    let curRequest = request;
    let timeoutError = 'Query sent at ' + new Date() + ' timed out.';
    let malformedResError = 'Malformed response. Check KDB+ WebSocket handler is correctly configured.';
    let response = new Promise((resolve) => {
      this.executeAsyncQuery(curRequest).then((result) => {
        {
          const processedResult = this.responseParser.processQueryResult(result, curRequest);
          if (Object.keys(result).indexOf('payload') === -1) {
            return resolve([this.showEmpty(curRequest[1].refId, malformedResError)]);
          } else return resolve(processedResult);
        }
      });
    });
    let timeout = new Promise((resolve) => {
      let wait = setTimeout(() => {
        clearTimeout(wait);
        resolve([this.showEmpty(curRequest[1].refId, timeoutError)]);
      }, this.timeoutLength);
    });
    return Promise.race([timeout, response]);
  };

  connectWS() {
    return new Promise((connected) => {
      this.ws = new WebSocket(this.wsUrl);
      this.ws.binaryType = 'arraybuffer';
      this.ws.onmessage = (response) => {
        this.executeAsyncReceive(response);
      };

      this.ws.onopen = () => {
        connected(true);
      };

      this.ws.onclose = () => {
        connected(false);
      };

      this.ws.onerror = () => {};
    });
  }

  webSocketWait() {
    return new Promise((ready) => {
      if (this.ws.readyState === 0) {
        setTimeout(() => ready(this.webSocketWait()), 20);
      } else ready('');
    });
  }

  executeAsyncQuery(request: any) {
    var requestResolve;
    let _c = this.c;
    var requestPromise = new Promise((resolve) => {
      let refIDn = Math.round(10000000 * Math.random());
      var wrappedRequest = { i: request, ID: refIDn };
      this.ws.send(_c.serialize(wrappedRequest));
      this.requestSentIDList.push(refIDn);
      requestResolve = resolve;
    });

    Object.assign(requestPromise, { resolve: requestResolve });
    let countSentList = this.requestSentList.length;
    this.requestSentList.push(requestPromise);
    return this.requestSentList[countSentList];
  }

  executeAsyncReceive(responseObj) {
    let _c = this.c;
    let deserializedResult = _c.deserialize(responseObj.data);
    if (!deserializedResult.ID) {
      // return console.log('received malformed data')
    } else if (this.requestSentIDList.indexOf(deserializedResult.ID) === -1) {
      // return console.log('received unrequested data');
    } else {
      var requestNum = this.requestSentIDList.indexOf(deserializedResult.ID);
      this.requestSentList[requestNum].resolve(deserializedResult.o);
    }
  }

  //Called for query variables
  metricFindQuery(kdbRequest: KdbRequest): Promise<MetricFindValue[]> {
    kdbRequest = this.injectUserVars(kdbRequest);
    return new Promise((resolve, reject) => {
      return this.connectWS().then((connectStatus) => {
        if (connectStatus === true){
            resolve(
              this.executeAsyncQuery(kdbRequest).then((result) => {
                const values = [];
                var properties = [];
                if (Array.isArray(result)) {
                  if (typeof result[0] === 'string') {
                    for (let i = 0; i < result.length; i++) {
                      values.push({ text: result[i] });
                    }
                  } else if (typeof result[0] === 'object') {
                    if (Object.keys(result[0]).length > 1) {
                      //checking that multiple rows for multiple columns don't come back as the preview tab only shows single values (not objects)
                      const errorResponse =
                        'Can only select single values. Attempted to return an object of key-value pairs. Unsafe query';
                      throw new Error(errorResponse);
                    }
                    for (var key in result[0]) {
                      if (result[0].hasOwnProperty(key) && typeof result[0][key] !== 'function') {
                        properties.push(key);
                      }
                    }
                    for (let i = 0; i < result.length; i++) {
                      values.push({ text: result[i][properties[0]] });
                    }
                  }
                } else if (typeof result === 'string') {
                  const errorResponse = `Check Query. Syntax error with: [ ${result} ]`;
                  throw new Error(errorResponse);
                }
                return values;
              })
            );
          }else{
            resolve([]);
          }
      });
    });
  }

  // Possibly not needed? Can reuse injectVariables instead possibly (no scoped/ranged vars though)
  injectUserVars(kdbRequest) {
    let instVariables = this.newGetVariables(getTemplateSrv());
    let localVarArray = [];
    let localValueArray = [];
    //local variables inject (user variables)
    for (let i = 0; i < instVariables.length; i++) {
      localVarArray.push('$' + instVariables[i].name);

      // Make sure length of current is greater than 0, otherwise an option hasn't been selected yet
      if (Object.keys(instVariables[i].current).length > 0 && instVariables[i].current.text[0] === 'All') {
        localValueArray.push(instVariables[i].allValue);
      } else {
        localValueArray.push(instVariables[i].current.value);
      }
    }
    for (let kv = 0; kv < localVarArray.length; kv++) {
      kdbRequest = kdbRequest.replace(localVarArray[kv], localValueArray[kv]);
    }
    return kdbRequest;
  }

  metricFindQueryDefault(/*kdbRequest: KdbRequest*/ kdbRequest: any) {
    // console.log('met',kdbRequest)
    return new Promise((resolve, reject) => {
      resolve(
        this.executeAsyncQuery(kdbRequest).then((result) => {
          return result;
        })
      );
    });
  }

  //Called for dropdowns of type s
  metricFindQuerySym(/*kdbRequest: KdbRequest*/ kdbRequest: any) {
    // console.log('met',kdbRequest)
    return new Promise((resolve, reject) => {
      resolve(
        this.executeAsyncQuery(kdbRequest).then((result) => {
          // console.log('res',result)
          let properties = [];
          for (var key in result[0]) {
            if (result[0].hasOwnProperty(key) && typeof result[0][key] !== 'function') {
              properties.push(key);
            }
          }
          for (let i = 0; i < result.length; i++) {
            result[i][properties[0]] = '`' + result[i][properties[0]];
          }

          return result;
        })
      );
    });
  }

  connect(): Promise<Object> {
    return new Promise<Object>((resolve, reject) => {
      if ('WebSocket' in window) {
        this.$q.when(this.setupWebSocket()).then(
          setTimeout(() => {
            resolve(
              this.checkConnectionState().then((result) => {
                //clearTimeout;
                return result;
              })
            );
          }, 2000)
        );
      } else {
        resolve(this.buildResponse('Error', 'WebSocket not supported!', 'Error'));
      }
    });
  }

  //This checks the kdb+ connection state for the 'test connection' funciton
  checkConnectionState(): Promise<Object> {
    return new Promise((resolve) => {
      return this.connectWS().then((connectStatus) => {
        if (connectStatus === false) {
          resolve(
            this.buildResponse(
              'fail',
              'Data source cannot be connected, if using authentication/TLS check settings as above.',
              'Fail'
            )
          );
        } else {
          let timeout = new Promise((resolve) => {
            let wait = setTimeout(() => {
              clearTimeout(wait);
              resolve(
                this.buildResponse(
                  'fail',
                  "Web socket connections aren't configured correctly for Grafana on your kdb+ instance.  Please speak to your system administrator.",
                  'Fail'
                )
              );
            }, this.timeoutLength);
          });
          let response = new Promise((resolve) => {
            this.executeAsyncQuery('.z.ws').then((res) => {
              if (typeof res !== 'string') {
                resolve(
                  this.buildResponse(
                    'fail',
                    'Malformed response. Check KDB+ WebSocket handler is correctly configured.',
                    'Fail'
                  )
                );
              } else if (res.replace(' ', '').includes('ds:-9!x;')) {
                //if it looks like .z.ws is correctly configured then return success
                resolve(this.buildResponse('success', 'Data source successfully connected!', 'Success'));
              } else {
                //If .z.ws hasn't been configured correctly on the database then return an error message
                resolve(
                  this.buildResponse(
                    'fail',
                    "Web socket connections aren't configured correctly for Grafana on your kdb+ instance.  Please speak to your system administrator.",
                    'Fail'
                  )
                );
              }
            });
          });
          return resolve(Promise.race([timeout, response]));
        }
      });
    });
  }

  setupWebSocket() {
    this.ws = new WebSocket(this.wsUrl);
    this.ws.binaryType = 'arraybuffer';

    this.ws.onopen = () => {};

    this.ws.onmessage = (messageEvent: MessageEvent) => {};

    this.ws.onclose = () => {};

    this.ws.onerror = () => {};
  }

  buildResponse(status: string, message: string, title: string) {
    return Promise.resolve({
      status,
      message,
      title,
    });
  }
}
Example #5
Source File: datasource.ts    From grafana-chinese with Apache License 2.0 4 votes vote down vote up
export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOptions> {
  basicAuth: string;
  url: string;
  name: string;
  graphiteVersion: any;
  supportsTags: boolean;
  isMetricTank: boolean;
  cacheTimeout: any;
  withCredentials: boolean;
  funcDefs: any = null;
  funcDefsPromise: Promise<any> = null;
  _seriesRefLetters: string;

  /** @ngInject */
  constructor(instanceSettings: any, private templateSrv: TemplateSrv) {
    super(instanceSettings);
    this.basicAuth = instanceSettings.basicAuth;
    this.url = instanceSettings.url;
    this.name = instanceSettings.name;
    this.graphiteVersion = instanceSettings.jsonData.graphiteVersion || '0.9';
    this.isMetricTank = instanceSettings.jsonData.graphiteType === GraphiteType.Metrictank;
    this.supportsTags = supportsTags(this.graphiteVersion);
    this.cacheTimeout = instanceSettings.cacheTimeout;
    this.withCredentials = instanceSettings.withCredentials;
    this.funcDefs = null;
    this.funcDefsPromise = null;
    this._seriesRefLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  }

  getQueryOptionsInfo() {
    return {
      maxDataPoints: true,
      cacheTimeout: true,
      links: [
        {
          text: 'Help',
          url: 'http://docs.grafana.org/features/datasources/graphite/#using-graphite-in-grafana',
        },
      ],
    };
  }

  async query(options: DataQueryRequest<GraphiteQuery>): Promise<DataQueryResponse> {
    const graphOptions = {
      from: this.translateTime(options.rangeRaw.from, false, options.timezone),
      until: this.translateTime(options.rangeRaw.to, true, options.timezone),
      targets: options.targets,
      format: (options as any).format,
      cacheTimeout: options.cacheTimeout || this.cacheTimeout,
      maxDataPoints: options.maxDataPoints,
    };

    const params = this.buildGraphiteParams(graphOptions, options.scopedVars);
    if (params.length === 0) {
      return Promise.resolve({ data: [] });
    }

    if (this.isMetricTank) {
      params.push('meta=true');
    }

    const httpOptions: any = {
      method: 'POST',
      url: '/render',
      data: params.join('&'),
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    };

    this.addTracingHeaders(httpOptions, options);

    if (options.panelId) {
      httpOptions.requestId = this.name + '.panelId.' + options.panelId;
    }

    return this.doGraphiteRequest(httpOptions).then(this.convertResponseToDataFrames);
  }

  addTracingHeaders(httpOptions: { headers: any }, options: { dashboardId: any; panelId: any }) {
    const proxyMode = !this.url.match(/^http/);
    if (proxyMode) {
      httpOptions.headers['X-Dashboard-Id'] = options.dashboardId;
      httpOptions.headers['X-Panel-Id'] = options.panelId;
    }
  }

  convertResponseToDataFrames = (result: any): DataQueryResponse => {
    const data: DataFrame[] = [];
    if (!result || !result.data) {
      return { data };
    }
    // Series are either at the root or under a node called 'series'
    const series = result.data.series || result.data;
    if (!_.isArray(series)) {
      throw { message: 'Missing series in result', data: result };
    }

    for (let i = 0; i < series.length; i++) {
      const s = series[i];
      for (let y = 0; y < s.datapoints.length; y++) {
        s.datapoints[y][1] *= 1000;
      }
      const frame = toDataFrame(s);

      // Metrictank metadata
      if (s.meta) {
        frame.meta = {
          custom: {
            request: result.data.meta, // info for the whole request
            info: s.meta, // Array of metadata
          },
        };
      }
      data.push(frame);
    }
    return { data };
  };

  parseTags(tagString: string) {
    let tags: string[] = [];
    tags = tagString.split(',');
    if (tags.length === 1) {
      tags = tagString.split(' ');
      if (tags[0] === '') {
        tags = [];
      }
    }
    return tags;
  }

  interpolateVariablesInQueries(queries: GraphiteQuery[], scopedVars: ScopedVars): GraphiteQuery[] {
    let expandedQueries = queries;
    if (queries && queries.length > 0) {
      expandedQueries = queries.map(query => {
        const expandedQuery = {
          ...query,
          datasource: this.name,
          target: this.templateSrv.replace(query.target, scopedVars),
        };
        return expandedQuery;
      });
    }
    return expandedQueries;
  }

  annotationQuery(options: any) {
    // Graphite metric as annotation
    if (options.annotation.target) {
      const target = this.templateSrv.replace(options.annotation.target, {}, 'glob');
      const graphiteQuery = ({
        rangeRaw: options.rangeRaw,
        targets: [{ target: target }],
        format: 'json',
        maxDataPoints: 100,
      } as unknown) as DataQueryRequest<GraphiteQuery>;

      return this.query(graphiteQuery).then(result => {
        const list = [];

        for (let i = 0; i < result.data.length; i++) {
          const target = result.data[i];

          for (let y = 0; y < target.length; y++) {
            const time = target.fields[1].values.get(y);
            const value = target.fields[0].values.get(y);

            if (!value) {
              continue;
            }

            list.push({
              annotation: options.annotation,
              time,
              title: target.name,
            });
          }
        }

        return list;
      });
    } else {
      // Graphite event as annotation
      const tags = this.templateSrv.replace(options.annotation.tags);
      return this.events({ range: options.rangeRaw, tags: tags }).then((results: any) => {
        const list = [];
        for (let i = 0; i < results.data.length; i++) {
          const e = results.data[i];

          let tags = e.tags;
          if (_.isString(e.tags)) {
            tags = this.parseTags(e.tags);
          }

          list.push({
            annotation: options.annotation,
            time: e.when * 1000,
            title: e.what,
            tags: tags,
            text: e.data,
          });
        }

        return list;
      });
    }
  }

  events(options: { range: any; tags: any; timezone?: any }) {
    try {
      let tags = '';
      if (options.tags) {
        tags = '&tags=' + options.tags;
      }
      return this.doGraphiteRequest({
        method: 'GET',
        url:
          '/events/get_data?from=' +
          this.translateTime(options.range.from, false, options.timezone) +
          '&until=' +
          this.translateTime(options.range.to, true, options.timezone) +
          tags,
      });
    } catch (err) {
      return Promise.reject(err);
    }
  }

  targetContainsTemplate(target: GraphiteQuery) {
    return this.templateSrv.variableExists(target.target);
  }

  translateTime(date: any, roundUp: any, timezone: any) {
    if (_.isString(date)) {
      if (date === 'now') {
        return 'now';
      } else if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) {
        date = date.substring(3);
        date = date.replace('m', 'min');
        date = date.replace('M', 'mon');
        return date;
      }
      date = dateMath.parse(date, roundUp, timezone);
    }

    // graphite' s from filter is exclusive
    // here we step back one minute in order
    // to guarantee that we get all the data that
    // exists for the specified range
    if (roundUp) {
      if (date.get('s')) {
        date.add(1, 's');
      }
    } else if (roundUp === false) {
      if (date.get('s')) {
        date.subtract(1, 's');
      }
    }

    return date.unix();
  }

  metricFindQuery(query: string, optionalOptions: any) {
    const options: any = optionalOptions || {};
    let interpolatedQuery = this.templateSrv.replace(
      query,
      getSearchFilterScopedVar({ query, wildcardChar: '', options: optionalOptions })
    );

    // special handling for tag_values(<tag>[,<expression>]*), this is used for template variables
    let matches = interpolatedQuery.match(/^tag_values\(([^,]+)((, *[^,]+)*)\)$/);
    if (matches) {
      const expressions = [];
      const exprRegex = /, *([^,]+)/g;
      let match = exprRegex.exec(matches[2]);
      while (match !== null) {
        expressions.push(match[1]);
        match = exprRegex.exec(matches[2]);
      }
      options.limit = 10000;
      return this.getTagValuesAutoComplete(expressions, matches[1], undefined, options);
    }

    // special handling for tags(<expression>[,<expression>]*), this is used for template variables
    matches = interpolatedQuery.match(/^tags\(([^,]*)((, *[^,]+)*)\)$/);
    if (matches) {
      const expressions = [];
      if (matches[1]) {
        expressions.push(matches[1]);
        const exprRegex = /, *([^,]+)/g;
        let match = exprRegex.exec(matches[2]);
        while (match !== null) {
          expressions.push(match[1]);
          match = exprRegex.exec(matches[2]);
        }
      }
      options.limit = 10000;
      return this.getTagsAutoComplete(expressions, undefined, options);
    }

    interpolatedQuery = this.templateSrv.replace(
      query,
      getSearchFilterScopedVar({ query, wildcardChar: '*', options: optionalOptions })
    );

    const httpOptions: any = {
      method: 'POST',
      url: '/metrics/find',
      params: {},
      data: `query=${interpolatedQuery}`,
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      // for cancellations
      requestId: options.requestId,
    };

    if (options.range) {
      httpOptions.params.from = this.translateTime(options.range.from, false, options.timezone);
      httpOptions.params.until = this.translateTime(options.range.to, true, options.timezone);
    }

    return this.doGraphiteRequest(httpOptions).then((results: any) => {
      return _.map(results.data, metric => {
        return {
          text: metric.text,
          expandable: metric.expandable ? true : false,
        };
      });
    });
  }

  getTags(optionalOptions: any) {
    const options = optionalOptions || {};

    const httpOptions: any = {
      method: 'GET',
      url: '/tags',
      // for cancellations
      requestId: options.requestId,
    };

    if (options.range) {
      httpOptions.params.from = this.translateTime(options.range.from, false, options.timezone);
      httpOptions.params.until = this.translateTime(options.range.to, true, options.timezone);
    }

    return this.doGraphiteRequest(httpOptions).then((results: any) => {
      return _.map(results.data, tag => {
        return {
          text: tag.tag,
          id: tag.id,
        };
      });
    });
  }

  getTagValues(options: any = {}) {
    const httpOptions: any = {
      method: 'GET',
      url: '/tags/' + this.templateSrv.replace(options.key),
      // for cancellations
      requestId: options.requestId,
    };

    if (options.range) {
      httpOptions.params.from = this.translateTime(options.range.from, false, options.timezone);
      httpOptions.params.until = this.translateTime(options.range.to, true, options.timezone);
    }

    return this.doGraphiteRequest(httpOptions).then((results: any) => {
      if (results.data && results.data.values) {
        return _.map(results.data.values, value => {
          return {
            text: value.value,
            id: value.id,
          };
        });
      } else {
        return [];
      }
    });
  }

  getTagsAutoComplete(expressions: any[], tagPrefix: any, optionalOptions: any) {
    const options = optionalOptions || {};

    const httpOptions: any = {
      method: 'GET',
      url: '/tags/autoComplete/tags',
      params: {
        expr: _.map(expressions, expression => this.templateSrv.replace((expression || '').trim())),
      },
      // for cancellations
      requestId: options.requestId,
    };

    if (tagPrefix) {
      httpOptions.params.tagPrefix = tagPrefix;
    }
    if (options.limit) {
      httpOptions.params.limit = options.limit;
    }
    if (options.range) {
      httpOptions.params.from = this.translateTime(options.range.from, false, options.timezone);
      httpOptions.params.until = this.translateTime(options.range.to, true, options.timezone);
    }

    return this.doGraphiteRequest(httpOptions).then((results: any) => {
      if (results.data) {
        return _.map(results.data, tag => {
          return { text: tag };
        });
      } else {
        return [];
      }
    });
  }

  getTagValuesAutoComplete(expressions: any[], tag: any, valuePrefix: any, optionalOptions: any) {
    const options = optionalOptions || {};

    const httpOptions: any = {
      method: 'GET',
      url: '/tags/autoComplete/values',
      params: {
        expr: _.map(expressions, expression => this.templateSrv.replace((expression || '').trim())),
        tag: this.templateSrv.replace((tag || '').trim()),
      },
      // for cancellations
      requestId: options.requestId,
    };

    if (valuePrefix) {
      httpOptions.params.valuePrefix = valuePrefix;
    }
    if (options.limit) {
      httpOptions.params.limit = options.limit;
    }
    if (options.range) {
      httpOptions.params.from = this.translateTime(options.range.from, false, options.timezone);
      httpOptions.params.until = this.translateTime(options.range.to, true, options.timezone);
    }

    return this.doGraphiteRequest(httpOptions).then((results: any) => {
      if (results.data) {
        return _.map(results.data, value => {
          return { text: value };
        });
      } else {
        return [];
      }
    });
  }

  getVersion(optionalOptions: any) {
    const options = optionalOptions || {};

    const httpOptions = {
      method: 'GET',
      url: '/version',
      requestId: options.requestId,
    };

    return this.doGraphiteRequest(httpOptions)
      .then((results: any) => {
        if (results.data) {
          const semver = new SemVersion(results.data);
          return semver.isValid() ? results.data : '';
        }
        return '';
      })
      .catch(() => {
        return '';
      });
  }

  createFuncInstance(funcDef: any, options?: any) {
    return gfunc.createFuncInstance(funcDef, options, this.funcDefs);
  }

  getFuncDef(name: string) {
    return gfunc.getFuncDef(name, this.funcDefs);
  }

  waitForFuncDefsLoaded() {
    return this.getFuncDefs();
  }

  getFuncDefs() {
    if (this.funcDefsPromise !== null) {
      return this.funcDefsPromise;
    }

    if (!supportsFunctionIndex(this.graphiteVersion)) {
      this.funcDefs = gfunc.getFuncDefs(this.graphiteVersion);
      this.funcDefsPromise = Promise.resolve(this.funcDefs);
      return this.funcDefsPromise;
    }

    const httpOptions = {
      method: 'GET',
      url: '/functions',
    };

    this.funcDefsPromise = this.doGraphiteRequest(httpOptions)
      .then((results: any) => {
        if (results.status !== 200 || typeof results.data !== 'object') {
          this.funcDefs = gfunc.getFuncDefs(this.graphiteVersion);
        } else {
          this.funcDefs = gfunc.parseFuncDefs(results.data);
        }
        return this.funcDefs;
      })
      .catch((err: any) => {
        console.log('Fetching graphite functions error', err);
        this.funcDefs = gfunc.getFuncDefs(this.graphiteVersion);
        return this.funcDefs;
      });

    return this.funcDefsPromise;
  }

  testDatasource() {
    const query = ({
      panelId: 3,
      rangeRaw: { from: 'now-1h', to: 'now' },
      targets: [{ target: 'constantLine(100)' }],
      maxDataPoints: 300,
    } as unknown) as DataQueryRequest<GraphiteQuery>;
    return this.query(query).then(() => {
      return { status: 'success', message: 'Data source is working' };
    });
  }

  doGraphiteRequest(options: {
    method?: string;
    url: any;
    requestId?: any;
    withCredentials?: any;
    headers?: any;
    inspect?: any;
  }) {
    if (this.basicAuth || this.withCredentials) {
      options.withCredentials = true;
    }
    if (this.basicAuth) {
      options.headers = options.headers || {};
      options.headers.Authorization = this.basicAuth;
    }

    options.url = this.url + options.url;
    options.inspect = { type: 'graphite' };

    return getBackendSrv().datasourceRequest(options);
  }

  buildGraphiteParams(options: any, scopedVars: ScopedVars): string[] {
    const graphiteOptions = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
    const cleanOptions = [],
      targets: any = {};
    let target, targetValue, i;
    const regex = /\#([A-Z])/g;
    const intervalFormatFixRegex = /'(\d+)m'/gi;
    let hasTargets = false;

    options['format'] = 'json';

    function fixIntervalFormat(match: any) {
      return match.replace('m', 'min').replace('M', 'mon');
    }

    for (i = 0; i < options.targets.length; i++) {
      target = options.targets[i];
      if (!target.target) {
        continue;
      }

      if (!target.refId) {
        target.refId = this._seriesRefLetters[i];
      }

      targetValue = this.templateSrv.replace(target.target, scopedVars);
      targetValue = targetValue.replace(intervalFormatFixRegex, fixIntervalFormat);
      targets[target.refId] = targetValue;
    }

    function nestedSeriesRegexReplacer(match: any, g1: string | number) {
      return targets[g1] || match;
    }

    for (i = 0; i < options.targets.length; i++) {
      target = options.targets[i];
      if (!target.target) {
        continue;
      }

      targetValue = targets[target.refId];
      targetValue = targetValue.replace(regex, nestedSeriesRegexReplacer);
      targets[target.refId] = targetValue;

      if (!target.hide) {
        hasTargets = true;
        cleanOptions.push('target=' + encodeURIComponent(targetValue));
      }
    }

    _.each(options, (value, key) => {
      if (_.indexOf(graphiteOptions, key) === -1) {
        return;
      }
      if (value) {
        cleanOptions.push(key + '=' + encodeURIComponent(value));
      }
    });

    if (!hasTargets) {
      return [];
    }

    return cleanOptions;
  }
}