123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 |
- import { of } from 'rxjs';
- import { TestScheduler } from 'rxjs/testing';
- import {
- dataFrameToJSON,
- DataQueryRequest,
- DataSourceInstanceSettings,
- dateTime,
- MutableDataFrame,
- } from '@grafana/data';
- import { FetchResponse } from '@grafana/runtime';
- import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
- import { TemplateSrv } from 'app/features/templating/template_srv';
- import { initialCustomVariableModelState } from '../../../../features/variables/custom/reducer';
- import { PostgresDatasource } from '../datasource';
- import { PostgresOptions, PostgresQuery } from '../types';
- jest.mock('@grafana/runtime', () => ({
- ...(jest.requireActual('@grafana/runtime') as unknown as object),
- getBackendSrv: () => backendSrv,
- }));
- jest.mock('@grafana/runtime/src/services', () => ({
- ...(jest.requireActual('@grafana/runtime/src/services') as unknown as object),
- getBackendSrv: () => backendSrv,
- getDataSourceSrv: () => {
- return {
- getInstanceSettings: () => ({ id: 8674 }),
- };
- },
- }));
- describe('PostgreSQLDatasource', () => {
- const fetchMock = jest.spyOn(backendSrv, 'fetch');
- const setupTestContext = (data: any) => {
- jest.clearAllMocks();
- fetchMock.mockImplementation(() => of(createFetchResponse(data)));
- const instanceSettings = {
- jsonData: {
- defaultProject: 'testproject',
- },
- } as unknown as DataSourceInstanceSettings<PostgresOptions>;
- const templateSrv: TemplateSrv = new TemplateSrv();
- const variable = { ...initialCustomVariableModelState };
- const ds = new PostgresDatasource(instanceSettings, templateSrv);
- return { ds, templateSrv, variable };
- };
- // https://rxjs-dev.firebaseapp.com/guide/testing/marble-testing
- const runMarbleTest = (args: {
- options: any;
- values: { [marble: string]: FetchResponse };
- marble: string;
- expectedValues: { [marble: string]: any };
- expectedMarble: string;
- }) => {
- const { expectedValues, expectedMarble, options, values, marble } = args;
- const scheduler: TestScheduler = new TestScheduler((actual, expected) => {
- expect(actual).toEqual(expected);
- });
- const { ds } = setupTestContext({});
- scheduler.run(({ cold, expectObservable }) => {
- const source = cold(marble, values);
- jest.clearAllMocks();
- fetchMock.mockImplementation(() => source);
- const result = ds.query(options);
- expectObservable(result).toBe(expectedMarble, expectedValues);
- });
- };
- describe('When performing a time series query', () => {
- it('should transform response correctly', () => {
- const options = {
- range: {
- from: dateTime(1432288354),
- to: dateTime(1432288401),
- },
- targets: [
- {
- format: 'time_series',
- rawQuery: true,
- rawSql: 'select time, metric from grafana_metric',
- refId: 'A',
- datasource: 'gdev-ds',
- },
- ],
- };
- const response = {
- results: {
- A: {
- refId: 'A',
- frames: [
- dataFrameToJSON(
- new MutableDataFrame({
- fields: [
- { name: 'time', values: [1599643351085] },
- { name: 'metric', values: [30.226249741223704], labels: { metric: 'America' } },
- ],
- meta: {
- executedQueryString: 'select time, metric from grafana_metric',
- },
- })
- ),
- ],
- },
- },
- };
- const values = { a: createFetchResponse(response) };
- const marble = '-a|';
- const expectedMarble = '-a|';
- const expectedValues = {
- a: {
- data: [
- {
- fields: [
- {
- config: {},
- entities: {},
- name: 'time',
- type: 'time',
- values: {
- buffer: [1599643351085],
- },
- },
- {
- config: {},
- entities: {},
- labels: {
- metric: 'America',
- },
- name: 'metric',
- type: 'number',
- values: {
- buffer: [30.226249741223704],
- },
- },
- ],
- length: 1,
- meta: {
- executedQueryString: 'select time, metric from grafana_metric',
- },
- name: undefined,
- refId: 'A',
- },
- ],
- state: 'Done',
- },
- };
- runMarbleTest({ options, marble, values, expectedMarble, expectedValues });
- });
- });
- describe('When performing a table query', () => {
- it('should transform response correctly', () => {
- const options = {
- range: {
- from: dateTime(1432288354),
- to: dateTime(1432288401),
- },
- targets: [
- {
- format: 'table',
- rawQuery: true,
- rawSql: 'select time, metric, value from grafana_metric',
- refId: 'A',
- datasource: 'gdev-ds',
- },
- ],
- };
- const response = {
- results: {
- A: {
- refId: 'A',
- frames: [
- dataFrameToJSON(
- new MutableDataFrame({
- fields: [
- { name: 'time', values: [1599643351085] },
- { name: 'metric', values: ['America'] },
- { name: 'value', values: [30.226249741223704] },
- ],
- meta: {
- executedQueryString: 'select time, metric, value from grafana_metric',
- },
- })
- ),
- ],
- },
- },
- };
- const values = { a: createFetchResponse(response) };
- const marble = '-a|';
- const expectedMarble = '-a|';
- const expectedValues = {
- a: {
- data: [
- {
- fields: [
- {
- config: {},
- entities: {},
- name: 'time',
- type: 'time',
- values: {
- buffer: [1599643351085],
- },
- },
- {
- config: {},
- entities: {},
- name: 'metric',
- type: 'string',
- values: {
- buffer: ['America'],
- },
- },
- {
- config: {},
- entities: {},
- name: 'value',
- type: 'number',
- values: {
- buffer: [30.226249741223704],
- },
- },
- ],
- length: 1,
- meta: {
- executedQueryString: 'select time, metric, value from grafana_metric',
- },
- name: undefined,
- refId: 'A',
- },
- ],
- state: 'Done',
- },
- };
- runMarbleTest({ options, marble, values, expectedMarble, expectedValues });
- });
- });
- describe('When performing a query with hidden target', () => {
- it('should return empty result and backendSrv.fetch should not be called', async () => {
- const options = {
- range: {
- from: dateTime(1432288354),
- to: dateTime(1432288401),
- },
- targets: [
- {
- format: 'table',
- rawQuery: true,
- rawSql: 'select time, metric, value from grafana_metric',
- refId: 'A',
- datasource: 'gdev-ds',
- hide: true,
- },
- ],
- } as unknown as DataQueryRequest<PostgresQuery>;
- const { ds } = setupTestContext({});
- await expect(ds.query(options)).toEmitValuesWith((received) => {
- expect(received[0]).toEqual({ data: [] });
- expect(fetchMock).not.toHaveBeenCalled();
- });
- });
- });
- describe('When performing annotationQuery', () => {
- let results: any;
- const annotationName = 'MyAnno';
- const options = {
- annotation: {
- name: annotationName,
- rawQuery: 'select time, title, text, tags from table;',
- },
- range: {
- from: dateTime(1432288354),
- to: dateTime(1432288401),
- },
- };
- const response = {
- results: {
- MyAnno: {
- frames: [
- dataFrameToJSON(
- new MutableDataFrame({
- fields: [
- { name: 'time', values: [1432288355, 1432288390, 1432288400] },
- { name: 'text', values: ['some text', 'some text2', 'some text3'] },
- { name: 'tags', values: ['TagA,TagB', ' TagB , TagC', null] },
- ],
- })
- ),
- ],
- },
- },
- };
- beforeEach(async () => {
- const { ds } = setupTestContext(response);
- results = await ds.annotationQuery(options);
- });
- it('should return annotation list', async () => {
- expect(results.length).toBe(3);
- expect(results[0].text).toBe('some text');
- expect(results[0].tags[0]).toBe('TagA');
- expect(results[0].tags[1]).toBe('TagB');
- expect(results[1].tags[0]).toBe('TagB');
- expect(results[1].tags[1]).toBe('TagC');
- expect(results[2].tags.length).toBe(0);
- });
- });
- describe('When performing metricFindQuery that returns multiple string fields', () => {
- it('should return list of all string field values', async () => {
- const query = 'select * from atable';
- const response = {
- results: {
- tempvar: {
- refId: 'tempvar',
- frames: [
- dataFrameToJSON(
- new MutableDataFrame({
- fields: [
- { name: 'title', values: ['aTitle', 'aTitle2', 'aTitle3'] },
- { name: 'text', values: ['some text', 'some text2', 'some text3'] },
- ],
- meta: {
- executedQueryString: 'select * from atable',
- },
- })
- ),
- ],
- },
- },
- };
- const { ds } = setupTestContext(response);
- const results = await ds.metricFindQuery(query, {});
- expect(results.length).toBe(6);
- expect(results[0].text).toBe('aTitle');
- expect(results[5].text).toBe('some text3');
- });
- });
- describe('When performing metricFindQuery with $__searchFilter and a searchFilter is given', () => {
- it('should return list of all column values', async () => {
- const query = "select title from atable where title LIKE '$__searchFilter'";
- const response = {
- results: {
- tempvar: {
- refId: 'tempvar',
- frames: [
- dataFrameToJSON(
- new MutableDataFrame({
- fields: [
- { name: 'title', values: ['aTitle', 'aTitle2', 'aTitle3'] },
- { name: 'text', values: ['some text', 'some text2', 'some text3'] },
- ],
- meta: {
- executedQueryString: 'select * from atable',
- },
- })
- ),
- ],
- },
- },
- };
- const { ds } = setupTestContext(response);
- const results = await ds.metricFindQuery(query, { searchFilter: 'aTit' });
- expect(fetchMock).toBeCalledTimes(1);
- expect(fetchMock.mock.calls[0][0].data.queries[0].rawSql).toBe(
- "select title from atable where title LIKE 'aTit%'"
- );
- expect(results).toEqual([
- { text: 'aTitle' },
- { text: 'aTitle2' },
- { text: 'aTitle3' },
- { text: 'some text' },
- { text: 'some text2' },
- { text: 'some text3' },
- ]);
- });
- });
- describe('When performing metricFindQuery with $__searchFilter but no searchFilter is given', () => {
- it('should return list of all column values', async () => {
- const query = "select title from atable where title LIKE '$__searchFilter'";
- const response = {
- results: {
- tempvar: {
- refId: 'tempvar',
- frames: [
- dataFrameToJSON(
- new MutableDataFrame({
- fields: [
- { name: 'title', values: ['aTitle', 'aTitle2', 'aTitle3'] },
- { name: 'text', values: ['some text', 'some text2', 'some text3'] },
- ],
- meta: {
- executedQueryString: 'select * from atable',
- },
- })
- ),
- ],
- },
- },
- };
- const { ds } = setupTestContext(response);
- const results = await ds.metricFindQuery(query, {});
- expect(fetchMock).toBeCalledTimes(1);
- expect(fetchMock.mock.calls[0][0].data.queries[0].rawSql).toBe("select title from atable where title LIKE '%'");
- expect(results).toEqual([
- { text: 'aTitle' },
- { text: 'aTitle2' },
- { text: 'aTitle3' },
- { text: 'some text' },
- { text: 'some text2' },
- { text: 'some text3' },
- ]);
- });
- });
- describe('When performing metricFindQuery with key, value columns', () => {
- it('should return list of as text, value', async () => {
- const query = 'select * from atable';
- const response = {
- results: {
- tempvar: {
- refId: 'tempvar',
- frames: [
- dataFrameToJSON(
- new MutableDataFrame({
- fields: [
- { name: '__value', values: ['value1', 'value2', 'value3'] },
- { name: '__text', values: ['aTitle', 'aTitle2', 'aTitle3'] },
- ],
- meta: {
- executedQueryString: 'select * from atable',
- },
- })
- ),
- ],
- },
- },
- };
- const { ds } = setupTestContext(response);
- const results = await ds.metricFindQuery(query, {});
- expect(results).toEqual([
- { text: 'aTitle', value: 'value1' },
- { text: 'aTitle2', value: 'value2' },
- { text: 'aTitle3', value: 'value3' },
- ]);
- });
- });
- describe('When performing metricFindQuery without key, value columns', () => {
- it('should return list of all field values as text', async () => {
- const query = 'select id, values from atable';
- const response = {
- results: {
- tempvar: {
- refId: 'tempvar',
- frames: [
- dataFrameToJSON(
- new MutableDataFrame({
- fields: [
- { name: 'id', values: [1, 2, 3] },
- { name: 'values', values: ['test1', 'test2', 'test3'] },
- ],
- meta: {
- executedQueryString: 'select id, values from atable',
- },
- })
- ),
- ],
- },
- },
- };
- const { ds } = setupTestContext(response);
- const results = await ds.metricFindQuery(query, {});
- expect(results).toEqual([
- { text: 1 },
- { text: 2 },
- { text: 3 },
- { text: 'test1' },
- { text: 'test2' },
- { text: 'test3' },
- ]);
- });
- });
- describe('When performing metricFindQuery with key, value columns and with duplicate keys', () => {
- it('should return list of unique keys', async () => {
- const query = 'select * from atable';
- const response = {
- results: {
- tempvar: {
- refId: 'tempvar',
- frames: [
- dataFrameToJSON(
- new MutableDataFrame({
- fields: [
- { name: '__text', values: ['aTitle', 'aTitle', 'aTitle'] },
- { name: '__value', values: ['same', 'same', 'diff'] },
- ],
- meta: {
- executedQueryString: 'select * from atable',
- },
- })
- ),
- ],
- },
- },
- };
- const { ds } = setupTestContext(response);
- const results = await ds.metricFindQuery(query, {});
- expect(results).toEqual([{ text: 'aTitle', value: 'same' }]);
- });
- });
- describe('When interpolating variables', () => {
- describe('and value is a string', () => {
- it('should return an unquoted value', () => {
- const { ds, variable } = setupTestContext({});
- expect(ds.interpolateVariable('abc', variable)).toEqual('abc');
- });
- });
- describe('and value is a number', () => {
- it('should return an unquoted value', () => {
- const { ds, variable } = setupTestContext({});
- expect(ds.interpolateVariable(1000 as unknown as string, variable)).toEqual(1000);
- });
- });
- describe('and value is an array of strings', () => {
- it('should return comma separated quoted values', () => {
- const { ds, variable } = setupTestContext({});
- expect(ds.interpolateVariable(['a', 'b', 'c'], variable)).toEqual("'a','b','c'");
- });
- });
- describe('and variable allows multi-value and is a string', () => {
- it('should return a quoted value', () => {
- const { ds, variable } = setupTestContext({});
- variable.multi = true;
- expect(ds.interpolateVariable('abc', variable)).toEqual("'abc'");
- });
- });
- describe('and variable contains single quote', () => {
- it('should return a quoted value', () => {
- const { ds, variable } = setupTestContext({});
- variable.multi = true;
- expect(ds.interpolateVariable("a'bc", variable)).toEqual("'a''bc'");
- expect(ds.interpolateVariable("a'b'c", variable)).toEqual("'a''b''c'");
- });
- });
- describe('and variable allows all and is a string', () => {
- it('should return a quoted value', () => {
- const { ds, variable } = setupTestContext({});
- variable.includeAll = true;
- expect(ds.interpolateVariable('abc', variable)).toEqual("'abc'");
- });
- });
- });
- describe('targetContainsTemplate', () => {
- it('given query that contains template variable it should return true', () => {
- const rawSql = `SELECT
- $__timeGroup("createdAt",'$summarize'),
- avg(value) as "value",
- hostname as "metric"
- FROM
- grafana_metric
- WHERE
- $__timeFilter("createdAt") AND
- measurement = 'logins.count' AND
- hostname IN($host)
- GROUP BY time, metric
- ORDER BY time`;
- const query = {
- rawSql,
- rawQuery: true,
- };
- const { templateSrv, ds } = setupTestContext({});
- templateSrv.init([
- { type: 'query', name: 'summarize', current: { value: '1m' } },
- { type: 'query', name: 'host', current: { value: 'a' } },
- ]);
- expect(ds.targetContainsTemplate(query)).toBeTruthy();
- });
- it('given query that only contains global template variable it should return false', () => {
- const rawSql = `SELECT
- $__timeGroup("createdAt",'$__interval'),
- avg(value) as "value",
- hostname as "metric"
- FROM
- grafana_metric
- WHERE
- $__timeFilter("createdAt") AND
- measurement = 'logins.count'
- GROUP BY time, metric
- ORDER BY time`;
- const query = {
- rawSql,
- rawQuery: true,
- };
- const { templateSrv, ds } = setupTestContext({});
- templateSrv.init([
- { type: 'query', name: 'summarize', current: { value: '1m' } },
- { type: 'query', name: 'host', current: { value: 'a' } },
- ]);
- expect(ds.targetContainsTemplate(query)).toBeFalsy();
- });
- });
- });
- const createFetchResponse = <T>(data: T): FetchResponse<T> => ({
- data,
- status: 200,
- url: 'http://localhost:3000/api/query',
- config: { url: 'http://localhost:3000/api/query' },
- type: 'basic',
- statusText: 'Ok',
- redirected: false,
- headers: {} as unknown as Headers,
- ok: true,
- });
|