├── dist
├── partials
│ ├── query.options.html
│ ├── config.html
│ └── query.editor.html
├── img
│ ├── crate_logo.png
│ ├── crate-datasource-error.png
│ ├── crate-datasource-graph.png
│ ├── crate-datasource-add-src.png
│ └── crate-datasource-nonvalidation.png
├── response_handler.d.ts
├── sdk
│ ├── sdk.d.ts
│ ├── sdk.ts
│ ├── query_ctrl.d.ts
│ ├── sdk.js.map
│ ├── query_ctrl.ts
│ ├── query_ctrl.js.map
│ ├── query_ctrl.js
│ └── sdk.js
├── config_ctrl.d.ts
├── module.js.map
├── query_def.d.ts
├── module.d.ts
├── module.ts
├── config_ctrl.ts
├── plugin.json
├── config_ctrl.js.map
├── query_def.ts
├── query_def.js.map
├── module.js
├── config_ctrl.js
├── query_def.js
├── datasource.d.ts
├── query_ctrl.d.ts
├── query_builder.d.ts
├── response_handler.ts
├── response_handler.js.map
├── README.md
├── response_handler.js
├── query_ctrl.ts
├── datasource.ts
├── datasource.js.map
└── query_builder.ts
├── src
├── partials
│ ├── query.options.html
│ ├── config.html
│ └── query.editor.html
├── img
│ ├── crate_logo.png
│ ├── crate-datasource-add-src.png
│ ├── crate-datasource-error.png
│ ├── crate-datasource-graph.png
│ └── crate-datasource-nonvalidation.png
├── sdk
│ ├── sdk.ts
│ └── query_ctrl.ts
├── module.ts
├── config_ctrl.ts
├── plugin.json
├── query_def.ts
├── spec
│ ├── test-main.js
│ ├── datasource_specs.js
│ └── response_handler_specs.js
├── response_handler.ts
├── query_ctrl.ts
├── datasource.ts
└── query_builder.ts
├── .gitattributes
├── crate-logo.png
├── .jscsrc
├── tsconfig.json
├── .gitignore
├── .jshintrc
├── tsd.json
├── headers
├── common.d.ts
├── zone
│ └── zone.d.ts
└── mocha
│ └── mocha.d.ts
├── CHANGELOG.md
├── package.json
├── tslint.json
├── Gruntfile.js
├── README.md
└── LICENSE
/dist/partials/query.options.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/partials/query.options.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Don't diff files in dist/
2 | *.map binary
3 | dist/** binary
4 |
--------------------------------------------------------------------------------
/crate-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raintank/crate-datasource/HEAD/crate-logo.png
--------------------------------------------------------------------------------
/dist/img/crate_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raintank/crate-datasource/HEAD/dist/img/crate_logo.png
--------------------------------------------------------------------------------
/src/img/crate_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raintank/crate-datasource/HEAD/src/img/crate_logo.png
--------------------------------------------------------------------------------
/dist/img/crate-datasource-error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raintank/crate-datasource/HEAD/dist/img/crate-datasource-error.png
--------------------------------------------------------------------------------
/dist/img/crate-datasource-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raintank/crate-datasource/HEAD/dist/img/crate-datasource-graph.png
--------------------------------------------------------------------------------
/src/img/crate-datasource-add-src.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raintank/crate-datasource/HEAD/src/img/crate-datasource-add-src.png
--------------------------------------------------------------------------------
/src/img/crate-datasource-error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raintank/crate-datasource/HEAD/src/img/crate-datasource-error.png
--------------------------------------------------------------------------------
/src/img/crate-datasource-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raintank/crate-datasource/HEAD/src/img/crate-datasource-graph.png
--------------------------------------------------------------------------------
/dist/img/crate-datasource-add-src.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raintank/crate-datasource/HEAD/dist/img/crate-datasource-add-src.png
--------------------------------------------------------------------------------
/dist/img/crate-datasource-nonvalidation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raintank/crate-datasource/HEAD/dist/img/crate-datasource-nonvalidation.png
--------------------------------------------------------------------------------
/src/img/crate-datasource-nonvalidation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raintank/crate-datasource/HEAD/src/img/crate-datasource-nonvalidation.png
--------------------------------------------------------------------------------
/dist/response_handler.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | export default function handleResponse(target: any, result: any): any;
3 |
--------------------------------------------------------------------------------
/dist/sdk/sdk.d.ts:
--------------------------------------------------------------------------------
1 | import { QueryCtrl } from './query_ctrl';
2 | export declare function loadPluginCss(options: any): void;
3 | export { QueryCtrl };
4 |
--------------------------------------------------------------------------------
/dist/config_ctrl.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | export declare class CrateConfigCtrl {
3 | static templateUrl: string;
4 | current: any;
5 | timeIntervals: any[];
6 | constructor($scope: any);
7 | }
8 |
--------------------------------------------------------------------------------
/dist/module.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"module.js","sourceRoot":"","sources":["module.ts"],"names":["CrateQueryOptionsCtrl","CrateQueryOptionsCtrl.constructor"],"mappings":";;;;;;;;;;;;;;;YAIA;gBAAAA;gBAEAC,CAACA;gBADQD,iCAAWA,GAAGA,6BAA6BA,CAACA;gBACrDA,4BAACA;YAADA,CAACA,AAFD,IAEC;YAGoB,qDAAU;YACD,6DAAS;YAClB,sDAAU;YACJ,oDAAgB"}
--------------------------------------------------------------------------------
/src/sdk/sdk.ts:
--------------------------------------------------------------------------------
1 | import {QueryCtrl} from './query_ctrl';
2 |
3 | import config from 'app/core/config';
4 |
5 | export function loadPluginCss(options) {
6 | if (config.bootData.user.lightTheme) {
7 | System.import(options.light + '!css');
8 | } else {
9 | System.import(options.dark + '!css');
10 | }
11 | }
12 |
13 | export {
14 | QueryCtrl
15 | }
16 |
--------------------------------------------------------------------------------
/dist/sdk/sdk.ts:
--------------------------------------------------------------------------------
1 | import {QueryCtrl} from './query_ctrl';
2 |
3 | import config from 'app/core/config';
4 |
5 | export function loadPluginCss(options) {
6 | if (config.bootData.user.lightTheme) {
7 | System.import(options.light + '!css');
8 | } else {
9 | System.import(options.dark + '!css');
10 | }
11 | }
12 |
13 | export {
14 | QueryCtrl
15 | }
16 |
--------------------------------------------------------------------------------
/dist/query_def.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | export default class QueryDef {
3 | static getMetricAggTypes(): ({
4 | text: string;
5 | value: string;
6 | allValue: boolean;
7 | } | {
8 | text: string;
9 | value: string;
10 | allValue: boolean;
11 | anyDataType: boolean;
12 | })[];
13 | }
14 |
--------------------------------------------------------------------------------
/dist/sdk/query_ctrl.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | export declare class QueryCtrl {
3 | $scope: any;
4 | private $injector;
5 | target: any;
6 | datasource: any;
7 | panelCtrl: any;
8 | panel: any;
9 | hasRawMode: boolean;
10 | error: string;
11 | constructor($scope: any, $injector: any);
12 | refresh(): void;
13 | }
14 |
--------------------------------------------------------------------------------
/dist/module.d.ts:
--------------------------------------------------------------------------------
1 | import { CrateDatasource } from './datasource';
2 | import { CrateDatasourceQueryCtrl } from './query_ctrl';
3 | import { CrateConfigCtrl } from './config_ctrl';
4 | declare class CrateQueryOptionsCtrl {
5 | static templateUrl: string;
6 | }
7 | export { CrateDatasource as Datasource, CrateDatasourceQueryCtrl as QueryCtrl, CrateConfigCtrl as ConfigCtrl, CrateQueryOptionsCtrl as QueryOptionsCtrl };
8 |
--------------------------------------------------------------------------------
/dist/sdk/sdk.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"sdk.js","sourceRoot":"","sources":["sdk.ts"],"names":["loadPluginCss"],"mappings":";;IAIA,uBAA8B,OAAO;QACnCA,EAAEA,CAACA,CAACA,mBAAMA,CAACA,QAAQA,CAACA,IAAIA,CAACA,UAAUA,CAACA,CAACA,CAACA;YACpCA,MAAMA,CAACA,MAAMA,CAACA,OAAOA,CAACA,KAAKA,GAAGA,MAAMA,CAACA,CAACA;QACxCA,CAACA;QAACA,IAAIA,CAACA,CAACA;YACNA,MAAMA,CAACA,MAAMA,CAACA,OAAOA,CAACA,IAAIA,GAAGA,MAAMA,CAACA,CAACA;QACvCA,CAACA;IACHA,CAACA;IAND,yCAMC,CAAA;;;;;;;;;;YAGC,8CAAS"}
--------------------------------------------------------------------------------
/src/module.ts:
--------------------------------------------------------------------------------
1 | import {CrateDatasource} from './datasource';
2 | import {CrateDatasourceQueryCtrl} from './query_ctrl';
3 | import {CrateConfigCtrl} from './config_ctrl';
4 |
5 | class CrateQueryOptionsCtrl {
6 | static templateUrl = 'partials/query.options.html';
7 | }
8 |
9 | export {
10 | CrateDatasource as Datasource,
11 | CrateDatasourceQueryCtrl as QueryCtrl,
12 | CrateConfigCtrl as ConfigCtrl,
13 | CrateQueryOptionsCtrl as QueryOptionsCtrl
14 | };
15 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "disallowImplicitTypeConversion": ["string"],
3 | "disallowKeywords": ["with"],
4 | "disallowMultipleLineBreaks": true,
5 | "disallowMixedSpacesAndTabs": true,
6 | "disallowTrailingWhitespace": true,
7 | "requireSpacesInFunctionExpression": {
8 | "beforeOpeningCurlyBrace": true
9 | },
10 | "disallowSpacesInsideArrayBrackets": true,
11 | "disallowSpacesInsideParentheses": true,
12 | "validateIndentation": 2
13 | }
--------------------------------------------------------------------------------
/dist/module.ts:
--------------------------------------------------------------------------------
1 | import {CrateDatasource} from './datasource';
2 | import {CrateDatasourceQueryCtrl} from './query_ctrl';
3 | import {CrateConfigCtrl} from './config_ctrl';
4 |
5 | class CrateQueryOptionsCtrl {
6 | static templateUrl = 'partials/query.options.html';
7 | }
8 |
9 | export {
10 | CrateDatasource as Datasource,
11 | CrateDatasourceQueryCtrl as QueryCtrl,
12 | CrateConfigCtrl as ConfigCtrl,
13 | CrateQueryOptionsCtrl as QueryOptionsCtrl
14 | };
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": true,
5 | "outDir": "dist",
6 | "noImplicitAny": false,
7 | "target": "es3",
8 | "rootDir": "src/",
9 | "sourceRoot": "src/",
10 | "module": "system",
11 | "noEmitOnError": true,
12 | "emitDecoratorMetadata": true,
13 | "experimentalDecorators": true
14 | },
15 | "files": [
16 | "src/**/*.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/dist/sdk/query_ctrl.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import angular from 'angular';
4 | import _ from 'lodash';
5 |
6 | export class QueryCtrl {
7 | target: any;
8 | datasource: any;
9 | panelCtrl: any;
10 | panel: any;
11 | hasRawMode: boolean;
12 | error: string;
13 |
14 | constructor(public $scope, private $injector) {
15 | this.panel = this.panelCtrl.panel;
16 | }
17 |
18 | refresh() {
19 | this.panelCtrl.refresh();
20 | }
21 |
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/src/sdk/query_ctrl.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import angular from 'angular';
4 | import _ from 'lodash';
5 |
6 | export class QueryCtrl {
7 | target: any;
8 | datasource: any;
9 | panelCtrl: any;
10 | panel: any;
11 | hasRawMode: boolean;
12 | error: string;
13 |
14 | constructor(public $scope, private $injector) {
15 | this.panel = this.panelCtrl.panel;
16 | }
17 |
18 | refresh() {
19 | this.panelCtrl.refresh();
20 | }
21 |
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/dist/sdk/query_ctrl.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"query_ctrl.js","sourceRoot":"","sources":["query_ctrl.ts"],"names":["QueryCtrl","QueryCtrl.constructor","QueryCtrl.refresh"],"mappings":"AAAA,iDAAiD;;;;;;YAKjD;gBAQEA,mBAAmBA,MAAMA,EAAUA,SAASA;oBAAzBC,WAAMA,GAANA,MAAMA,CAAAA;oBAAUA,cAASA,GAATA,SAASA,CAAAA;oBAC1CA,IAAIA,CAACA,KAAKA,GAAGA,IAAIA,CAACA,SAASA,CAACA,KAAKA,CAACA;gBACpCA,CAACA;gBAEDD,2BAAOA,GAAPA;oBACEE,IAAIA,CAACA,SAASA,CAACA,OAAOA,EAAEA,CAACA;gBAC3BA,CAACA;gBAEHF,gBAACA;YAADA,CAACA,AAhBD,IAgBC;YAhBD,iCAgBC,CAAA"}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | coverage/
4 | .aws-config.json
5 | awsconfig
6 | /emails/dist
7 | /public_gen
8 | /tmp
9 | vendor/phantomjs/phantomjs
10 |
11 | # Builded dist
12 | # /dist
13 |
14 | # Test artifacts
15 | /dist/test
16 | /dist/spec
17 |
18 | docs/AWS_S3_BUCKET
19 | docs/GIT_BRANCH
20 | docs/VERSION
21 | docs/GITCOMMIT
22 | docs/changed-files
23 | docs/changed-files
24 |
25 | # locally required config files
26 | public/css/*.min.css
27 |
28 | # Editor junk
29 | *.sublime-workspace
30 | *.sublime-project
31 | *.swp
32 | .idea/
33 | *.iml
34 |
35 | /data/*
36 | /bin/*
37 |
38 | conf/custom.ini
39 | fig.yml
40 | profile.cov
41 | grafana
42 | .notouch
43 |
--------------------------------------------------------------------------------
/dist/sdk/query_ctrl.js:
--------------------------------------------------------------------------------
1 | ///
2 | System.register([], function(exports_1) {
3 | var QueryCtrl;
4 | return {
5 | setters:[],
6 | execute: function() {
7 | QueryCtrl = (function () {
8 | function QueryCtrl($scope, $injector) {
9 | this.$scope = $scope;
10 | this.$injector = $injector;
11 | this.panel = this.panelCtrl.panel;
12 | }
13 | QueryCtrl.prototype.refresh = function () {
14 | this.panelCtrl.refresh();
15 | };
16 | return QueryCtrl;
17 | })();
18 | exports_1("QueryCtrl", QueryCtrl);
19 | }
20 | }
21 | });
22 | //# sourceMappingURL=query_ctrl.js.map
--------------------------------------------------------------------------------
/dist/sdk/sdk.js:
--------------------------------------------------------------------------------
1 | System.register(['./query_ctrl', 'app/core/config'], function(exports_1) {
2 | var query_ctrl_1, config_1;
3 | function loadPluginCss(options) {
4 | if (config_1["default"].bootData.user.lightTheme) {
5 | System.import(options.light + '!css');
6 | }
7 | else {
8 | System.import(options.dark + '!css');
9 | }
10 | }
11 | exports_1("loadPluginCss", loadPluginCss);
12 | return {
13 | setters:[
14 | function (query_ctrl_1_1) {
15 | query_ctrl_1 = query_ctrl_1_1;
16 | },
17 | function (config_1_1) {
18 | config_1 = config_1_1;
19 | }],
20 | execute: function() {
21 | exports_1("QueryCtrl", query_ctrl_1.QueryCtrl);
22 | }
23 | }
24 | });
25 | //# sourceMappingURL=sdk.js.map
--------------------------------------------------------------------------------
/src/config_ctrl.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import angular from 'angular';
4 | import _ from 'lodash';
5 |
6 | export class CrateConfigCtrl {
7 | static templateUrl = 'partials/config.html';
8 | current: any;
9 |
10 | timeIntervals: any[] = [
11 | {name: 'Auto', value: 'auto'},
12 | {name: 'Auto (Grafana)', value: 'auto_gf'},
13 | {name: 'Second', value: 'second'},
14 | {name: 'Minute', value: 'minute'},
15 | {name: 'Hour', value: 'hour'},
16 | {name: 'Day', value: 'day'},
17 | {name: 'Week', value: 'week'},
18 | {name: 'Month', value: 'month'},
19 | {name: 'Quarter', value: 'quarter'},
20 | {name: 'Year', value: 'year'}
21 | ];
22 |
23 | constructor($scope) {
24 | this.current.jsonData.timeInterval = this.current.jsonData.timeInterval || this.timeIntervals[1].value;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/dist/config_ctrl.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import angular from 'angular';
4 | import _ from 'lodash';
5 |
6 | export class CrateConfigCtrl {
7 | static templateUrl = 'partials/config.html';
8 | current: any;
9 |
10 | timeIntervals: any[] = [
11 | {name: 'Auto', value: 'auto'},
12 | {name: 'Auto (Grafana)', value: 'auto_gf'},
13 | {name: 'Second', value: 'second'},
14 | {name: 'Minute', value: 'minute'},
15 | {name: 'Hour', value: 'hour'},
16 | {name: 'Day', value: 'day'},
17 | {name: 'Week', value: 'week'},
18 | {name: 'Month', value: 'month'},
19 | {name: 'Quarter', value: 'quarter'},
20 | {name: 'Year', value: 'year'}
21 | ];
22 |
23 | constructor($scope) {
24 | this.current.jsonData.timeInterval = this.current.jsonData.timeInterval || this.timeIntervals[1].value;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/dist/plugin.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Crate",
3 | "id": "crate-datasource",
4 | "type": "datasource",
5 |
6 | "partials": {
7 | "config": "public/app/plugins/datasource/crate/partials/config.html"
8 | },
9 |
10 | "metrics": true,
11 | "annotations": false,
12 |
13 | "info": {
14 | "description": "Crate SQL Database datasource",
15 | "author": {
16 | "name": "Crate.IO",
17 | "url": "https://crate.io"
18 | },
19 | "logos": {
20 | "small": "img/crate_logo.png",
21 | "large": "img/crate_logo.png"
22 | },
23 | "links": [
24 | {"name": "GitHub", "url": "https://github.com/raintank/crate-datasource"},
25 | {"name": "MIT License", "url": "https://github.com/raintank/crate-datasource/blob/master/LICENSE"}
26 | ],
27 | "version": "0.5.1",
28 | "updated": "2017-04-05"
29 | },
30 |
31 | "dependencies": {
32 | "grafanaVersion": "3.x.x",
33 | "plugins": []
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/plugin.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Crate",
3 | "id": "crate-datasource",
4 | "type": "datasource",
5 |
6 | "partials": {
7 | "config": "public/app/plugins/datasource/crate/partials/config.html"
8 | },
9 |
10 | "metrics": true,
11 | "annotations": false,
12 |
13 | "info": {
14 | "description": "Crate SQL Database datasource",
15 | "author": {
16 | "name": "Crate.IO",
17 | "url": "https://crate.io"
18 | },
19 | "logos": {
20 | "small": "img/crate_logo.png",
21 | "large": "img/crate_logo.png"
22 | },
23 | "links": [
24 | {"name": "GitHub", "url": "https://github.com/raintank/crate-datasource"},
25 | {"name": "MIT License", "url": "https://github.com/raintank/crate-datasource/blob/master/LICENSE"}
26 | ],
27 | "version": "0.5.1",
28 | "updated": "2017-04-05"
29 | },
30 |
31 | "dependencies": {
32 | "grafanaVersion": "3.x.x",
33 | "plugins": []
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "browser": true,
3 |
4 | "bitwise":false,
5 | "curly": true,
6 | "eqnull": true,
7 | "strict": true,
8 | "module": true,
9 | "devel": true,
10 | "eqeqeq": true,
11 | "forin": false,
12 | "immed": true,
13 | "supernew": true,
14 | "expr": true,
15 | "indent": 2,
16 | "latedef": false,
17 | "newcap": true,
18 | "noarg": true,
19 | "noempty": true,
20 | "undef": true,
21 | "boss": true,
22 | "trailing": true,
23 | "laxbreak": true,
24 | "laxcomma": true,
25 | "sub": true,
26 | "unused": true,
27 | "maxdepth": 6,
28 | "maxlen": 140,
29 | "esnext": true,
30 |
31 | "globals": {
32 | "System": true,
33 | "Promise": true,
34 | "define": true,
35 | "require": true,
36 | "Chromath": false,
37 | "setImmediate": true,
38 | "expect": true,
39 | "it": true,
40 | "describe": true,
41 | "sinon": true,
42 | "module": true,
43 | "beforeEach": true,
44 | "inject": true
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tsd.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "v4",
3 | "repo": "angular/DefinitelyTyped",
4 | "ref": "master",
5 | "path": "headers",
6 | "bundle": "headers/common.d.ts",
7 | "installed": {
8 | "es6-promise/es6-promise.d.ts": {
9 | "commit": "31e7317c9a0793857109236ef7c7f223305a8aa9"
10 | },
11 | "mocha/mocha.d.ts": {
12 | "commit": "055b3172e8eb374a75826710c4d08677872620d3"
13 | },
14 | "zone/zone.d.ts": {
15 | "commit": "055b3172e8eb374a75826710c4d08677872620d3"
16 | },
17 | "rx/rx.d.ts": {
18 | "commit": "31e7317c9a0793857109236ef7c7f223305a8aa9"
19 | },
20 | "rx/rx-lite.d.ts": {
21 | "commit": "31e7317c9a0793857109236ef7c7f223305a8aa9"
22 | },
23 | "angular2/angular2.d.ts": {
24 | "commit": "31e7317c9a0793857109236ef7c7f223305a8aa9"
25 | },
26 | "es6-shim/es6-shim.d.ts": {
27 | "commit": "31e7317c9a0793857109236ef7c7f223305a8aa9"
28 | },
29 | "jquery/jquery.d.ts": {
30 | "commit": "31e7317c9a0793857109236ef7c7f223305a8aa9"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/dist/config_ctrl.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"config_ctrl.js","sourceRoot":"","sources":["config_ctrl.ts"],"names":["CrateConfigCtrl","CrateConfigCtrl.constructor"],"mappings":"AAAA,8CAA8C;;;;;;YAK9C;gBAiBEA,yBAAYA,MAAMA;oBAblBC,kBAAaA,GAAUA;wBACrBA,EAACA,IAAIA,EAAEA,MAAMA,EAAKA,KAAKA,EAAEA,MAAMA,EAACA;wBAChCA,EAACA,IAAIA,EAAEA,gBAAgBA,EAAKA,KAAKA,EAAEA,SAASA,EAACA;wBAC7CA,EAACA,IAAIA,EAAEA,QAAQA,EAAGA,KAAKA,EAAEA,QAAQA,EAACA;wBAClCA,EAACA,IAAIA,EAAEA,QAAQA,EAAGA,KAAKA,EAAEA,QAAQA,EAACA;wBAClCA,EAACA,IAAIA,EAAEA,MAAMA,EAAKA,KAAKA,EAAEA,MAAMA,EAACA;wBAChCA,EAACA,IAAIA,EAAEA,KAAKA,EAAMA,KAAKA,EAAEA,KAAKA,EAACA;wBAC/BA,EAACA,IAAIA,EAAEA,MAAMA,EAAKA,KAAKA,EAAEA,MAAMA,EAACA;wBAChCA,EAACA,IAAIA,EAAEA,OAAOA,EAAIA,KAAKA,EAAEA,OAAOA,EAACA;wBACjCA,EAACA,IAAIA,EAAEA,SAASA,EAAEA,KAAKA,EAAEA,SAASA,EAACA;wBACnCA,EAACA,IAAIA,EAAEA,MAAMA,EAAKA,KAAKA,EAAEA,MAAMA,EAACA;qBACjCA,CAACA;oBAGAA,IAAIA,CAACA,OAAOA,CAACA,QAAQA,CAACA,YAAYA,GAAGA,IAAIA,CAACA,OAAOA,CAACA,QAAQA,CAACA,YAAYA,IAAIA,IAAIA,CAACA,aAAaA,CAACA,CAACA,CAACA,CAACA,KAAKA,CAACA;gBACzGA,CAACA;gBAlBMD,2BAAWA,GAAGA,sBAAsBA,CAACA;gBAmB9CA,sBAACA;YAADA,CAACA,AApBD,IAoBC;YApBD,6CAoBC,CAAA"}
--------------------------------------------------------------------------------
/dist/query_def.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import _ from 'lodash';
4 |
5 | let _metricAggTypes = [
6 | {text: "Raw", value: "raw", allValue: false},
7 | {text: "Count", value: 'count', allValue: true, anyDataType: true},
8 | {text: "Distinct Count", value: 'count_distinct', allValue: false, anyDataType: true},
9 | {text: "Avg / Mean", value: 'avg', allValue: false},
10 | {text: "Min", value: 'min', allValue: false},
11 | {text: "Max", value: 'max', allValue: false},
12 | {text: "Sum", value: 'sum', allValue: false},
13 | {text: "Geometric Mean", value: 'geometric_mean', allValue: false},
14 | {text: "Variance", value: 'variance', allValue: false},
15 | {text: "Std Deviation", value: 'stddev', allValue: false},
16 | {text: "Arbitrary", value: "arbitrary", allValue: false}
17 | ];
18 |
19 | export default class QueryDef {
20 |
21 | static getMetricAggTypes() {
22 | return _metricAggTypes;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/query_def.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import _ from 'lodash';
4 |
5 | let _metricAggTypes = [
6 | {text: "Raw", value: "raw", allValue: false},
7 | {text: "Count", value: 'count', allValue: true, anyDataType: true},
8 | {text: "Distinct Count", value: 'count_distinct', allValue: false, anyDataType: true},
9 | {text: "Avg / Mean", value: 'avg', allValue: false},
10 | {text: "Min", value: 'min', allValue: false},
11 | {text: "Max", value: 'max', allValue: false},
12 | {text: "Sum", value: 'sum', allValue: false},
13 | {text: "Geometric Mean", value: 'geometric_mean', allValue: false},
14 | {text: "Variance", value: 'variance', allValue: false},
15 | {text: "Std Deviation", value: 'stddev', allValue: false},
16 | {text: "Arbitrary", value: "arbitrary", allValue: false}
17 | ];
18 |
19 | export default class QueryDef {
20 |
21 | static getMetricAggTypes() {
22 | return _metricAggTypes;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/dist/query_def.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"query_def.js","sourceRoot":"","sources":["query_def.ts"],"names":["QueryDef","QueryDef.constructor","QueryDef.getMetricAggTypes"],"mappings":"AAAA,8CAA8C;;QAI1C,eAAe;;;;YAAf,eAAe,GAAG;gBACpB,EAAC,IAAI,EAAE,KAAK,EAAa,KAAK,EAAE,KAAK,EAAa,QAAQ,EAAE,KAAK,EAAC;gBAClE,EAAC,IAAI,EAAE,OAAO,EAAW,KAAK,EAAE,OAAO,EAAW,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAC;gBACpF,EAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAC;gBACrF,EAAC,IAAI,EAAE,YAAY,EAAM,KAAK,EAAE,KAAK,EAAa,QAAQ,EAAE,KAAK,EAAC;gBAClE,EAAC,IAAI,EAAE,KAAK,EAAa,KAAK,EAAE,KAAK,EAAa,QAAQ,EAAE,KAAK,EAAC;gBAClE,EAAC,IAAI,EAAE,KAAK,EAAa,KAAK,EAAE,KAAK,EAAa,QAAQ,EAAE,KAAK,EAAC;gBAClE,EAAC,IAAI,EAAE,KAAK,EAAa,KAAK,EAAE,KAAK,EAAa,QAAQ,EAAE,KAAK,EAAC;gBAClE,EAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,EAAC;gBAClE,EAAC,IAAI,EAAE,UAAU,EAAQ,KAAK,EAAE,UAAU,EAAQ,QAAQ,EAAE,KAAK,EAAC;gBAClE,EAAC,IAAI,EAAE,eAAe,EAAG,KAAK,EAAE,QAAQ,EAAU,QAAQ,EAAE,KAAK,EAAC;gBAClE,EAAC,IAAI,EAAE,WAAW,EAAO,KAAK,EAAE,WAAW,EAAO,QAAQ,EAAE,KAAK,EAAC;aACnE,CAAC;YAEF;gBAAAA;gBAKAC,CAACA;gBAHQD,0BAAiBA,GAAxBA;oBACEE,MAAMA,CAACA,eAAeA,CAACA;gBACzBA,CAACA;gBACHF,eAACA;YAADA,CAACA,AALD,IAKC;YALD,8BAKC,CAAA"}
--------------------------------------------------------------------------------
/dist/module.js:
--------------------------------------------------------------------------------
1 | System.register(['./datasource', './query_ctrl', './config_ctrl'], function(exports_1) {
2 | var datasource_1, query_ctrl_1, config_ctrl_1;
3 | var CrateQueryOptionsCtrl;
4 | return {
5 | setters:[
6 | function (datasource_1_1) {
7 | datasource_1 = datasource_1_1;
8 | },
9 | function (query_ctrl_1_1) {
10 | query_ctrl_1 = query_ctrl_1_1;
11 | },
12 | function (config_ctrl_1_1) {
13 | config_ctrl_1 = config_ctrl_1_1;
14 | }],
15 | execute: function() {
16 | CrateQueryOptionsCtrl = (function () {
17 | function CrateQueryOptionsCtrl() {
18 | }
19 | CrateQueryOptionsCtrl.templateUrl = 'partials/query.options.html';
20 | return CrateQueryOptionsCtrl;
21 | })();
22 | exports_1("Datasource", datasource_1.CrateDatasource);
23 | exports_1("QueryCtrl", query_ctrl_1.CrateDatasourceQueryCtrl);
24 | exports_1("ConfigCtrl", config_ctrl_1.CrateConfigCtrl);
25 | exports_1("QueryOptionsCtrl", CrateQueryOptionsCtrl);
26 | }
27 | }
28 | });
29 | //# sourceMappingURL=module.js.map
--------------------------------------------------------------------------------
/headers/common.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare var System: any;
4 |
5 | // dummy modules
6 | declare module 'app/core/config' {
7 | var config: any;
8 | export default config;
9 | }
10 |
11 | declare module 'lodash' {
12 | var lodash: any;
13 | export default lodash;
14 | }
15 |
16 | declare module 'moment' {
17 | var moment: any;
18 | export default moment;
19 | }
20 |
21 | declare module 'angular' {
22 | var angular: any;
23 | export default angular;
24 | }
25 |
26 | declare module 'jquery' {
27 | var jquery: any;
28 | export default jquery;
29 | }
30 |
31 | declare module 'app/core/utils/kbn' {
32 | var kbn: any;
33 | export default kbn;
34 | }
35 |
36 | // Hack for datemath module
37 | declare module 'app/core/utils/datemath' {
38 | export var parse: any;
39 | export var isValid: any;
40 | export var parseDateMath: any;
41 | }
42 |
43 | declare module 'app/core/store' {
44 | var store: any;
45 | export default store;
46 | }
47 |
48 | declare module 'tether' {
49 | var config: any;
50 | export default config;
51 | }
52 |
53 | declare module 'tether-drop' {
54 | var config: any;
55 | export default config;
56 | }
57 |
58 | declare module 'eventemitter3' {
59 | var config: any;
60 | export default config;
61 | }
62 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 |
4 | ## [Unreleased]
5 |
6 |
7 | ## [0.5.1] - 2017-04-05
8 | ### Changed
9 | - Use date_trunk() when interval set to second, minute, etc
10 | - Override limit only for Raw agg queries.
11 |
12 |
13 | ## [0.5.0] - 2017-03-22
14 | ### Added
15 | - Checks schema and table (prevent queries to different source).
16 |
17 |
18 | ## [0.4.0] - 2017-03-19
19 | ### Added
20 | - 'Auto' (uses date_trunk()) and 'Auto (Grafana)' (uses floor()) time intervals.
21 |
22 | ### Fixed
23 | - 10K issue
24 |
25 | ### Changed
26 | - Use explicit aggregation by time interval based on floor() instead date_trunk()
27 |
28 | ## [0.3.0] - 2017-03-02
29 | ### Added
30 | - Table mode support
31 | - Ad-hoc filters support
32 | - $timeFilter variable support
33 | - Quote column names with capital letters [#28](https://github.com/raintank/crate-datasource/issues/28)
34 | - Support GROUP BY in raw queries, issue [#30](https://github.com/raintank/crate-datasource/issues/30)
35 |
36 | ### Fixed
37 | - Schema queries (changed in Crate 1.0)
38 |
39 |
40 | ## [0.2.0] - 2016-11-29
41 | ### Added
42 | - Special "Raw" aggregation type [#9](https://github.com/raintank/crate-datasource/issues/9)
43 | - Alias for each field in SELECT
44 |
45 |
46 | ## [0.1.0] - 2016-07-10
47 | - Initial release
48 | - Implementation by [raintank](http://raintank.io)
49 | - Documentation contributions from [Crate.io](https://crate.io)
50 |
--------------------------------------------------------------------------------
/dist/config_ctrl.js:
--------------------------------------------------------------------------------
1 | ///
2 | System.register([], function(exports_1) {
3 | var CrateConfigCtrl;
4 | return {
5 | setters:[],
6 | execute: function() {
7 | CrateConfigCtrl = (function () {
8 | function CrateConfigCtrl($scope) {
9 | this.timeIntervals = [
10 | { name: 'Auto', value: 'auto' },
11 | { name: 'Auto (Grafana)', value: 'auto_gf' },
12 | { name: 'Second', value: 'second' },
13 | { name: 'Minute', value: 'minute' },
14 | { name: 'Hour', value: 'hour' },
15 | { name: 'Day', value: 'day' },
16 | { name: 'Week', value: 'week' },
17 | { name: 'Month', value: 'month' },
18 | { name: 'Quarter', value: 'quarter' },
19 | { name: 'Year', value: 'year' }
20 | ];
21 | this.current.jsonData.timeInterval = this.current.jsonData.timeInterval || this.timeIntervals[1].value;
22 | }
23 | CrateConfigCtrl.templateUrl = 'partials/config.html';
24 | return CrateConfigCtrl;
25 | })();
26 | exports_1("CrateConfigCtrl", CrateConfigCtrl);
27 | }
28 | }
29 | });
30 | //# sourceMappingURL=config_ctrl.js.map
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "crate-datasource",
3 | "private": true,
4 | "version": "0.0.1",
5 | "description": "",
6 | "main": "index.js",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/raintank/crate-datasource.git"
13 | },
14 | "author": "",
15 | "license": "ISC",
16 | "bugs": {
17 | "url": "https://github.com/raintank/crate-datasource/issues"
18 | },
19 | "devDependencies": {
20 | "babel": "~6.5.1",
21 | "chai": "~3.5.0",
22 | "grunt": "~0.4.5",
23 | "grunt-babel": "~6.0.0",
24 | "grunt-contrib-clean": "~0.6.0",
25 | "grunt-contrib-copy": "~0.8.2",
26 | "grunt-contrib-uglify": "~0.11.0",
27 | "grunt-contrib-watch": "^0.6.1",
28 | "grunt-execute": "~0.2.2",
29 | "grunt-mocha-test": "~0.12.7",
30 | "grunt-systemjs-builder": "^0.2.5",
31 | "grunt-tslint": "^3.0.2",
32 | "grunt-typescript": "^0.8.0",
33 | "jsdom": "~3.1.2",
34 | "load-grunt-tasks": "~3.2.0",
35 | "prunk": "~1.2.1",
36 | "q": "~1.4.1"
37 | },
38 | "dependencies": {
39 | "babel-plugin-transform-es2015-for-of": "^6.6.0",
40 | "babel-plugin-transform-es2015-modules-systemjs": "^6.5.0",
41 | "babel-preset-es2015": "^6.5.0",
42 | "lodash": "~4.0.0",
43 | "mocha": "^2.4.5",
44 | "tslint": "^3.4.0",
45 | "typescript": "^1.7.5"
46 | },
47 | "homepage": "https://github.com/raintank/crate-datasource#readme"
48 | }
49 |
--------------------------------------------------------------------------------
/dist/query_def.js:
--------------------------------------------------------------------------------
1 | ///
2 | System.register([], function(exports_1) {
3 | var _metricAggTypes, QueryDef;
4 | return {
5 | setters:[],
6 | execute: function() {
7 | _metricAggTypes = [
8 | { text: "Raw", value: "raw", allValue: false },
9 | { text: "Count", value: 'count', allValue: true, anyDataType: true },
10 | { text: "Distinct Count", value: 'count_distinct', allValue: false, anyDataType: true },
11 | { text: "Avg / Mean", value: 'avg', allValue: false },
12 | { text: "Min", value: 'min', allValue: false },
13 | { text: "Max", value: 'max', allValue: false },
14 | { text: "Sum", value: 'sum', allValue: false },
15 | { text: "Geometric Mean", value: 'geometric_mean', allValue: false },
16 | { text: "Variance", value: 'variance', allValue: false },
17 | { text: "Std Deviation", value: 'stddev', allValue: false },
18 | { text: "Arbitrary", value: "arbitrary", allValue: false }
19 | ];
20 | QueryDef = (function () {
21 | function QueryDef() {
22 | }
23 | QueryDef.getMetricAggTypes = function () {
24 | return _metricAggTypes;
25 | };
26 | return QueryDef;
27 | })();
28 | exports_1("default", QueryDef);
29 | }
30 | }
31 | });
32 | //# sourceMappingURL=query_def.js.map
--------------------------------------------------------------------------------
/src/spec/test-main.js:
--------------------------------------------------------------------------------
1 | import prunk from 'prunk';
2 | import {jsdom} from 'jsdom';
3 | import chai from 'chai';
4 | import _ from 'lodash';
5 |
6 | // Mock angular module
7 | var angularMocks = {
8 | module: function() {
9 | return {
10 | directive: function() { }
11 | };
12 | }
13 | };
14 |
15 | var datemathMock = {
16 | parse: function() { }
17 | };
18 |
19 | var configMock = {
20 | bootData: {
21 | user: {
22 | lightTheme: false
23 | }
24 | }
25 | };
26 |
27 | // Mock lodash for using with CommonJS in tests.
28 | // Because typescript compiler generates code like this:
29 | // lodash["default"].map() - it uses default export.
30 | var lodashMock = {
31 | default: _
32 | };
33 |
34 | // Mock Grafana modules that are not available outside of the core project
35 | // Required for loading module.js
36 | prunk.mock('./css/query-editor.css!', 'no css, dude.');
37 | prunk.mock('app/plugins/sdk', {
38 | QueryCtrl: null
39 | });
40 | prunk.mock('app/core/utils/datemath', datemathMock);
41 | prunk.mock('app/core/config', configMock);
42 | prunk.mock('angular', angularMocks);
43 | prunk.mock('jquery', 'module not found');
44 | prunk.mock('lodash', lodashMock);
45 |
46 | // Setup jsdom
47 | // Required for loading angularjs
48 | global.document = jsdom('
');
49 | global.window = global.document.parentWindow;
50 | global.navigator = window.navigator = {};
51 | global.Node = window.Node;
52 |
53 | // Setup Chai
54 | chai.should();
55 | global.assert = chai.assert;
56 | global.expect = chai.expect;
57 |
--------------------------------------------------------------------------------
/dist/datasource.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { CrateQueryBuilder } from './query_builder';
3 | export declare class CrateDatasource {
4 | private $q;
5 | private backendSrv;
6 | private templateSrv;
7 | private timeSrv;
8 | type: string;
9 | url: string;
10 | name: string;
11 | basicAuth: string;
12 | withCredentials: boolean;
13 | schema: string;
14 | table: string;
15 | defaultTimeColumn: string;
16 | defaultGroupInterval: string;
17 | checkQuerySource: boolean;
18 | queryBuilder: CrateQueryBuilder;
19 | CRATE_ROWS_LIMIT: number;
20 | constructor(instanceSettings: any, $q: any, backendSrv: any, templateSrv: any, timeSrv: any);
21 | query(options: any): any;
22 | /**
23 | * Required.
24 | * Checks datasource and returns Crate cluster name and version or
25 | * error details.
26 | */
27 | testDatasource(): any;
28 | metricFindQuery(query: string): any;
29 | getTimeFilter(timeFrom: any, timeTo: any): string;
30 | getTagKeys(options: any): any;
31 | getTagValues(options: any): any;
32 | setScopedVars(scopedVars: any): any;
33 | /**
34 | * Sends SQL query to Crate and returns result.
35 | * @param {string} query SQL query string
36 | * @param {any[]} args Optional query arguments
37 | * @return
38 | */
39 | _sql_query(query: string, args?: any[]): any;
40 | checkSQLSource(query: any): void;
41 | _request(method: string, url: string, data?: any): any;
42 | _get(url?: string): any;
43 | _post(url: string, data?: any): any;
44 | }
45 | export declare function convertToCrateInterval(grafanaInterval: any): any;
46 |
--------------------------------------------------------------------------------
/dist/partials/config.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | Crate details
7 |
8 |
51 |
--------------------------------------------------------------------------------
/src/partials/config.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | Crate details
7 |
8 |
51 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "class-name": true,
4 | "comment-format": [false, "check-space"],
5 | "curly": true,
6 | "eofline": true,
7 | "forin": false,
8 | "indent": [true, "spaces"],
9 | "label-position": true,
10 | "label-undefined": true,
11 | "max-line-length": [true, 140],
12 | "member-access": false,
13 | "no-arg": true,
14 | "no-bitwise": true,
15 | "no-console": [true,
16 | "debug",
17 | "info",
18 | "time",
19 | "timeEnd",
20 | "trace"
21 | ],
22 | "no-construct": true,
23 | "no-debugger": true,
24 | "no-duplicate-key": true,
25 | "no-duplicate-variable": true,
26 | "no-empty": false,
27 | "no-eval": true,
28 | "no-inferrable-types": true,
29 | "no-shadowed-variable": false,
30 | "no-string-literal": false,
31 | "no-switch-case-fall-through": false,
32 | "no-trailing-whitespace": true,
33 | "no-unused-expression": false,
34 | "no-unused-variable": false,
35 | "no-unreachable": true,
36 | "no-use-before-declare": true,
37 | "no-var-keyword": false,
38 | "object-literal-sort-keys": false,
39 | "one-line": [true,
40 | "check-open-brace",
41 | "check-catch",
42 | "check-else"
43 | ],
44 | "radix": false,
45 | "semicolon": true,
46 | "triple-equals": [true, "allow-null-check"],
47 | "typedef-whitespace": [true, {
48 | "call-signature": "nospace",
49 | "index-signature": "nospace",
50 | "parameter": "nospace",
51 | "property-declaration": "nospace",
52 | "variable-declaration": "nospace"
53 | }],
54 | "variable-name": [true, "ban-keywords"],
55 | "whitespace": [true,
56 | "check-branch",
57 | "check-decl",
58 | "check-type"
59 | ]
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/dist/query_ctrl.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { QueryCtrl } from './sdk/sdk';
3 | import { CrateQueryBuilder } from './query_builder';
4 | export declare class CrateDatasourceQueryCtrl extends QueryCtrl {
5 | private $q;
6 | private uiSegmentSrv;
7 | private templateSrv;
8 | static templateUrl: string;
9 | crateQueryBuilder: CrateQueryBuilder;
10 | groupBySegments: any;
11 | whereSegments: any;
12 | removeWhereSegment: any;
13 | operators: any;
14 | timeIntervals: any[];
15 | resultFormats: any[];
16 | constructor($scope: any, $injector: any, $q: any, uiSegmentSrv: any, templateSrv: any);
17 | crateQuery(query: any, args?: any[]): any;
18 | getCollapsedText(): string;
19 | onChangeInternal(): void;
20 | groupBySegmentChanged(segment: any, index: any): void;
21 | onGroupByAliasChange(index: any): void;
22 | onAggTypeChange(): void;
23 | addMetricAgg(): void;
24 | removeMetricAgg(index: any): void;
25 | toggleShowMetric(agg: any): void;
26 | toggleEditorMode(): void;
27 | getColumns(allValue?: boolean, onlyNumeric?: boolean): any;
28 | getGroupByColumns(): any;
29 | getValues(column: any, limit?: number): any;
30 | getColumnsOrValues(segment: any, index: any): any;
31 | getMetricAggTypes(): ({
32 | text: string;
33 | value: string;
34 | allValue: boolean;
35 | } | {
36 | text: string;
37 | value: string;
38 | allValue: boolean;
39 | anyDataType: boolean;
40 | })[];
41 | getMetricAggDef(aggType: any): any;
42 | whereSegmentUpdated(segment: any, index: any): void;
43 | buildWhereSegments(whereClauses: any): void;
44 | buildWhereClauses(): void;
45 | fixSegments(segments: any): void;
46 | transformToSegments(results: any, addTemplateVars?: boolean): any;
47 | updateGroupByAliases(): void;
48 | }
49 |
--------------------------------------------------------------------------------
/dist/query_builder.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | export declare class CrateQueryBuilder {
3 | private templateSrv;
4 | schema: string;
5 | table: string;
6 | defaultTimeColumn: string;
7 | defaultGroupInterval: string;
8 | constructor(schema: string, table: string, defaultTimeColumn: string, defaultGroupInterval: string, templateSrv: any);
9 | /**
10 | * Builds Crate SQL query from given target object.
11 | * @param {any} target Target object.
12 | * @param {number} groupInterval Interval for grouping values.
13 | * @param {string} defaultAgg Default aggregation for values.
14 | * @return {string} SQL query.
15 | */
16 | build(target: any, groupInterval?: number, adhocFilters?: any[], limit?: number, defaultAgg?: string): string;
17 | buildAggQuery(target: any, groupInterval?: number, adhocFilters?: any[], limit?: number): string;
18 | buildRawAggQuery(target: any, groupInterval?: number, adhocFilters?: any[], limit?: number): string;
19 | renderAdhocFilters(filters: any): any;
20 | /**
21 | * Builds SQL query for getting available columns from table.
22 | * @return {string} SQL query.
23 | */
24 | getColumnsQuery(): string;
25 | getNumericColumnsQuery(): string;
26 | /**
27 | * Builds SQL query for getting unique values for given column.
28 | * @param {string} column Column name
29 | * @param {number} limit Optional. Limit number returned values.
30 | */
31 | getValuesQuery(column: string, limit?: number, timeRange?: any): string;
32 | private renderMetricAggs(metricAggs, withAlias?);
33 | private renderWhereClauses(whereClauses);
34 | private containsVariable(str);
35 | }
36 | export declare function getSchemas(): string;
37 | export declare function getTables(schema: any): string;
38 | export declare function getEnabledAggs(metricAggs: any): any;
39 | export declare function getRawAggs(metricAggs: any): any;
40 | export declare function getNotRawAggs(metricAggs: any): any;
41 |
--------------------------------------------------------------------------------
/headers/zone/zone.d.ts:
--------------------------------------------------------------------------------
1 | declare module Zone {
2 | export class Stacktrace {
3 | constructor(e: Error);
4 | get(): string;
5 | }
6 | }
7 |
8 |
9 | declare class Zone {
10 | constructor(parentZone: Zone, data: any);
11 | fork(locals: any): Zone;
12 | bind(fn, skipEnqueue): void;
13 | bindOnce(fn): any;
14 | run(fn, applyTo?, applyWith?): void;
15 | beforeTask(): void;
16 | onZoneCreated(): void;
17 | afterTask(): void;
18 | enqueueTask(): void;
19 | dequeueTask(): void;
20 |
21 | static patchSetClearFn(obj, fnNames): string;
22 | static patchPrototype(obj, fnNames): any;
23 | static bindArguments(args: any[]): any;
24 | static bindArgumentsOnce(args: any[]): any;
25 | static patchableFn(obj, fnNames): any
26 | static patchProperty(obj, prop): void;
27 | static patchProperties(obj, properties): void;
28 | static patchEventTargetMethods(obj): void;
29 | static patch(): void;
30 | static canPatchViaPropertyDescriptor(): boolean;
31 | static patchViaPropertyDescriptor(): void;
32 | static patchViaCapturingAllTheEvents(): void;
33 | static patchWebSocket(): void;
34 | static patchClass(className: string): void;
35 | static patchMutationObserverClass(className: string): void;
36 | static patchDefineProperty(): void;
37 | static patchRegisterElement(): void;
38 | static eventNames: string;
39 | static onEventNames: string;
40 | static init(): void;
41 | static exceptZone: {
42 | boringZone: Zone;
43 | interestingZone: Zone,
44 | beforeTask: () => void;
45 | afterTask: () => void;
46 | fork: (ops: any) => Zone;
47 | };
48 | static longStackTraceZone: {
49 | getLongStacktrace(exception: any): string;
50 | stackFramesFilter(line: string): boolean;
51 | onError(exception): void;
52 | fork(locals): Zone;
53 | };
54 | static getStacktrace(): Zone.Stacktrace;
55 | static countingZone: {
56 | '+enqueueTask': () => void;
57 | '-dequeueTask': () => void;
58 | '+afterTask': () => void;
59 | counter: () => void;
60 | data: {
61 | count: number;
62 | flushed: boolean;
63 | };
64 | onFlush: () => void;
65 | };
66 | }
67 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 |
3 | require('load-grunt-tasks')(grunt);
4 |
5 | grunt.loadNpmTasks('grunt-execute');
6 | grunt.loadNpmTasks('grunt-contrib-clean');
7 |
8 | grunt.initConfig({
9 |
10 | clean: ["dist"],
11 |
12 | copy: {
13 | src_to_dist: {
14 | cwd: 'src',
15 | expand: true,
16 | src: ['**/*', '!**/*.js', '!**/*.scss'],
17 | dest: 'dist'
18 | },
19 | pluginDef: {
20 | expand: true,
21 | src: ['README.md'],
22 | dest: 'dist'
23 | }
24 | },
25 |
26 | watch: {
27 | rebuild_all: {
28 | files: ['src/**/*'],
29 | tasks: ['watch-ts'],
30 | options: {spawn: false}
31 | }
32 | },
33 |
34 | typescript: {
35 | build: {
36 | src: ['dist/**/*.ts', "!src/spec/**/*", "!**/*.d.ts"],
37 | dest: 'dist/',
38 | options: {
39 | module: 'system', //or commonjs
40 | target: 'es3', //or es5
41 | rootDir: 'dist/',
42 | keepDirectoryHierarchy: false,
43 | declaration: true,
44 | emitDecoratorMetadata: true,
45 | experimentalDecorators: true,
46 | sourceMap: true,
47 | noImplicitAny: false,
48 | }
49 | },
50 | distTests: {
51 | src: ['src/**/*.ts', "!src/spec/**/*", "!**/*.d.ts"],
52 | dest: 'dist/test/',
53 | options: {
54 | module: 'commonjs', //or commonjs
55 | target: 'es5', //or es5
56 | rootDir: 'src/',
57 | sourceRoot: 'src/',
58 | declaration: true,
59 | emitDecoratorMetadata: true,
60 | experimentalDecorators: true,
61 | sourceMap: true,
62 | noImplicitAny: false,
63 | }
64 | },
65 | // distTestsSpecs: {
66 | // src: ['src/spec/**/*.ts'],
67 | // dest: 'dist/test/',
68 | // options: {
69 | // module: 'commonjs', //or commonjs
70 | // target: 'es5', //or es5
71 | // declaration: true,
72 | // emitDecoratorMetadata: true,
73 | // experimentalDecorators: true,
74 | // sourceMap: true,
75 | // noImplicitAny: false,
76 | // }
77 | // }
78 | },
79 |
80 | babel: {
81 | options: {
82 | sourceMap: true,
83 | presets: ['es2015']
84 | },
85 | distTestsSpecsNoSystemJs: {
86 | files: [{
87 | expand: true,
88 | cwd: 'src/spec',
89 | src: ['**/*.js'],
90 | dest: 'dist/test/spec',
91 | ext:'.js'
92 | }]
93 | }
94 | },
95 |
96 | mochaTest: {
97 | test: {
98 | options: {
99 | reporter: 'spec'
100 | },
101 | src: ['dist/test/spec/test-main.js', 'dist/test/spec/*_specs.js']
102 | }
103 | }
104 | });
105 |
106 | grunt.registerTask('default', [
107 | 'clean',
108 | 'copy',
109 | 'typescript:build',
110 | 'typescript:distTests',
111 | 'babel',
112 | 'mochaTest'
113 | ]);
114 |
115 | grunt.registerTask('watch-ts', [
116 | 'clean',
117 | 'copy:src_to_dist',
118 | 'copy:pluginDef',
119 | 'typescript:build'
120 | ]);
121 | };
122 |
--------------------------------------------------------------------------------
/dist/response_handler.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import _ from 'lodash';
4 |
5 | export default function handleResponse(target, result) {
6 | if (target.resultFormat === 'table') {
7 | return handleTableResponse(target, result);
8 | }
9 |
10 | if (target.rawQuery) {
11 | return handleRawResponse(target, result);
12 | } else {
13 | return handleBuildedResponse(target, result);
14 | }
15 | }
16 |
17 | function handleTableResponse(target, result) {
18 | return {
19 | columns: _.map(result.cols, col => {
20 | return {text: col};
21 | }),
22 | rows: result.rows,
23 | type: 'table'
24 | };
25 | }
26 |
27 | function handleRawResponse(target, result) {
28 | let columns = result.cols;
29 | let timeColumnIndex = 0;
30 | let valueColumnIndex = 1;
31 |
32 | if (columns.length > 2) {
33 | let groupedResponse = _.groupBy(result.rows, row => {
34 | // Assume row structure is
35 | // [ts, value, ...group by columns]
36 | return row.slice(2).join(' ');
37 | });
38 |
39 | return _.map(groupedResponse, (rows, key) => {
40 | return {
41 | target: key + ': ' + columns[valueColumnIndex],
42 | datapoints: convertToGrafanaPoints(rows, timeColumnIndex, valueColumnIndex)
43 | };
44 | });
45 | } else {
46 | return [{
47 | target: columns[valueColumnIndex],
48 | datapoints: convertToGrafanaPoints(result.rows, timeColumnIndex, valueColumnIndex)
49 | }];
50 | }
51 | }
52 |
53 | function handleBuildedResponse(target, result) {
54 | let columns = result.cols;
55 | let timeColumnIndex = 0;
56 | let groupByColumnIndexes: number[], selectColumnIndexes: number[];
57 |
58 | if (target.groupByColumns.length) {
59 | groupByColumnIndexes = _.map(target.groupByColumns, groupByCol => {
60 | return _.indexOf(columns, groupByCol);
61 | });
62 | }
63 |
64 | let enabledAggs = _.filter(target.metricAggs, (agg) => {
65 | return !agg.hide;
66 | });
67 |
68 | if (enabledAggs.length) {
69 | selectColumnIndexes = _.map(enabledAggs, (metricAgg, index) => {
70 | return index + 1;
71 | });
72 | } else {
73 | return [];
74 | }
75 |
76 | if (groupByColumnIndexes && groupByColumnIndexes.length && !_.some(groupByColumnIndexes, -1)) {
77 | let groupedResponse = _.groupBy(result.rows, row => {
78 | // Construct groupBy key from Group By columns, for example:
79 | // [metric, host] => 'metric host'
80 | return _.map(groupByColumnIndexes, columnIndex => {
81 | return row[columnIndex];
82 | }).join(' ');
83 | });
84 |
85 | return _.flatten(_.map(groupedResponse, (rows, key) => {
86 | return _.map(selectColumnIndexes, (valueIndex) => {
87 | let datapoints = convertToGrafanaPoints(rows, timeColumnIndex, valueIndex);
88 |
89 | // Build alias for Group By column values
90 | let group_by_alias: string;
91 | if (rows.length) {
92 | group_by_alias = _.map(groupByColumnIndexes, (columnIndex, i) => {
93 | let first_row = rows[0];
94 | if (target.groupByAliases && target.groupByAliases[i]) {
95 | let pattern = new RegExp(target.groupByAliases[i]);
96 | let match = pattern.exec(first_row[columnIndex]);
97 | if (match && match.length > 1) {
98 | return match[1];
99 | } else if (match){
100 | return match[0];
101 | } else {
102 | return first_row[columnIndex];
103 | }
104 | } else {
105 | return first_row[columnIndex];
106 | }
107 | }).join(' ');
108 | } else {
109 | group_by_alias = key;
110 | }
111 |
112 | return {
113 | target: group_by_alias + ': ' + columns[valueIndex],
114 | datapoints: datapoints
115 | };
116 | });
117 | }));
118 | } else {
119 | return _.map(selectColumnIndexes, (valueIndex) => {
120 | let datapoints = convertToGrafanaPoints(result.rows, timeColumnIndex, valueIndex);
121 |
122 | return {
123 | target: columns[valueIndex],
124 | datapoints: datapoints
125 | };
126 | });
127 | }
128 | }
129 |
130 | function convertToGrafanaPoints(rows, timeColumnIndex, valueColumnIndex) {
131 | return _.map(rows, row => {
132 | let ts = Number(row[timeColumnIndex]);
133 | let val = row[valueColumnIndex];
134 | val = val !== null ? Number(val) : null;
135 |
136 | return [val, ts];
137 | });
138 | }
139 |
140 | function makeColName(aggType, column) {
141 | if (aggType === 'count_distinct') {
142 | return 'count(DISTINCT ' + column + ')';
143 | } else {
144 | return aggType + '(' + column + ')';
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/response_handler.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import _ from 'lodash';
4 |
5 | export default function handleResponse(target, result) {
6 | if (target.resultFormat === 'table') {
7 | return handleTableResponse(target, result);
8 | }
9 |
10 | if (target.rawQuery) {
11 | return handleRawResponse(target, result);
12 | } else {
13 | return handleBuildedResponse(target, result);
14 | }
15 | }
16 |
17 | function handleTableResponse(target, result) {
18 | return {
19 | columns: _.map(result.cols, col => {
20 | return {text: col};
21 | }),
22 | rows: result.rows,
23 | type: 'table'
24 | };
25 | }
26 |
27 | function handleRawResponse(target, result) {
28 | let columns = result.cols;
29 | let timeColumnIndex = 0;
30 | let valueColumnIndex = 1;
31 |
32 | if (columns.length > 2) {
33 | let groupedResponse = _.groupBy(result.rows, row => {
34 | // Assume row structure is
35 | // [ts, value, ...group by columns]
36 | return row.slice(2).join(' ');
37 | });
38 |
39 | return _.map(groupedResponse, (rows, key) => {
40 | return {
41 | target: key + ': ' + columns[valueColumnIndex],
42 | datapoints: convertToGrafanaPoints(rows, timeColumnIndex, valueColumnIndex)
43 | };
44 | });
45 | } else {
46 | return [{
47 | target: columns[valueColumnIndex],
48 | datapoints: convertToGrafanaPoints(result.rows, timeColumnIndex, valueColumnIndex)
49 | }];
50 | }
51 | }
52 |
53 | function handleBuildedResponse(target, result) {
54 | let columns = result.cols;
55 | let timeColumnIndex = 0;
56 | let groupByColumnIndexes: number[], selectColumnIndexes: number[];
57 |
58 | if (target.groupByColumns.length) {
59 | groupByColumnIndexes = _.map(target.groupByColumns, groupByCol => {
60 | return _.indexOf(columns, groupByCol);
61 | });
62 | }
63 |
64 | let enabledAggs = _.filter(target.metricAggs, (agg) => {
65 | return !agg.hide;
66 | });
67 |
68 | if (enabledAggs.length) {
69 | selectColumnIndexes = _.map(enabledAggs, (metricAgg, index) => {
70 | return index + 1;
71 | });
72 | } else {
73 | return [];
74 | }
75 |
76 | if (groupByColumnIndexes && groupByColumnIndexes.length && !_.some(groupByColumnIndexes, -1)) {
77 | let groupedResponse = _.groupBy(result.rows, row => {
78 | // Construct groupBy key from Group By columns, for example:
79 | // [metric, host] => 'metric host'
80 | return _.map(groupByColumnIndexes, columnIndex => {
81 | return row[columnIndex];
82 | }).join(' ');
83 | });
84 |
85 | return _.flatten(_.map(groupedResponse, (rows, key) => {
86 | return _.map(selectColumnIndexes, (valueIndex) => {
87 | let datapoints = convertToGrafanaPoints(rows, timeColumnIndex, valueIndex);
88 |
89 | // Build alias for Group By column values
90 | let group_by_alias: string;
91 | if (rows.length) {
92 | group_by_alias = _.map(groupByColumnIndexes, (columnIndex, i) => {
93 | let first_row = rows[0];
94 | if (target.groupByAliases && target.groupByAliases[i]) {
95 | let pattern = new RegExp(target.groupByAliases[i]);
96 | let match = pattern.exec(first_row[columnIndex]);
97 | if (match && match.length > 1) {
98 | return match[1];
99 | } else if (match){
100 | return match[0];
101 | } else {
102 | return first_row[columnIndex];
103 | }
104 | } else {
105 | return first_row[columnIndex];
106 | }
107 | }).join(' ');
108 | } else {
109 | group_by_alias = key;
110 | }
111 |
112 | return {
113 | target: group_by_alias + ': ' + columns[valueIndex],
114 | datapoints: datapoints
115 | };
116 | });
117 | }));
118 | } else {
119 | return _.map(selectColumnIndexes, (valueIndex) => {
120 | let datapoints = convertToGrafanaPoints(result.rows, timeColumnIndex, valueIndex);
121 |
122 | return {
123 | target: columns[valueIndex],
124 | datapoints: datapoints
125 | };
126 | });
127 | }
128 | }
129 |
130 | function convertToGrafanaPoints(rows, timeColumnIndex, valueColumnIndex) {
131 | return _.map(rows, row => {
132 | let ts = Number(row[timeColumnIndex]);
133 | let val = row[valueColumnIndex];
134 | val = val !== null ? Number(val) : null;
135 |
136 | return [val, ts];
137 | });
138 | }
139 |
140 | function makeColName(aggType, column) {
141 | if (aggType === 'count_distinct') {
142 | return 'count(DISTINCT ' + column + ')';
143 | } else {
144 | return aggType + '(' + column + ')';
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/headers/mocha/mocha.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for mocha 2.0.1
2 | // Project: http://mochajs.org/
3 | // Definitions by: Kazi Manzur Rashid , otiai10 , jt000
4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped
5 |
6 | interface Mocha {
7 | // Setup mocha with the given setting options.
8 | setup(options: MochaSetupOptions): Mocha;
9 |
10 | //Run tests and invoke `fn()` when complete.
11 | run(callback?: () => void): void;
12 |
13 | // Set reporter as function
14 | reporter(reporter: () => void): Mocha;
15 |
16 | // Set reporter, defaults to "dot"
17 | reporter(reporter: string): Mocha;
18 |
19 | // Enable growl support.
20 | growl(): Mocha
21 | }
22 |
23 | interface MochaSetupOptions {
24 | //milliseconds to wait before considering a test slow
25 | slow?: number;
26 |
27 | // timeout in milliseconds
28 | timeout?: number;
29 |
30 | // ui name "bdd", "tdd", "exports" etc
31 | ui?: string;
32 |
33 | //array of accepted globals
34 | globals?: any[];
35 |
36 | // reporter instance (function or string), defaults to `mocha.reporters.Dot`
37 | reporter?: any;
38 |
39 | // bail on the first test failure
40 | bail?: boolean;
41 |
42 | // ignore global leaks
43 | ignoreLeaks?: boolean;
44 |
45 | // grep string or regexp to filter tests with
46 | grep?: any;
47 | }
48 |
49 | interface MochaDone {
50 | (error?: Error): void;
51 | }
52 |
53 | declare var mocha: Mocha;
54 |
55 | declare var describe : {
56 | (description: string, spec: () => void): void;
57 | only(description: string, spec: () => void): void;
58 | skip(description: string, spec: () => void): void;
59 | timeout(ms: number): void;
60 | }
61 |
62 | // alias for `describe`
63 | declare var context : {
64 | (contextTitle: string, spec: () => void): void;
65 | only(contextTitle: string, spec: () => void): void;
66 | skip(contextTitle: string, spec: () => void): void;
67 | timeout(ms: number): void;
68 | }
69 |
70 | declare var it: {
71 | (expectation: string, assertion?: () => void): void;
72 | (expectation: string, assertion?: (done: MochaDone) => void): void;
73 | only(expectation: string, assertion?: () => void): void;
74 | only(expectation: string, assertion?: (done: MochaDone) => void): void;
75 | skip(expectation: string, assertion?: () => void): void;
76 | skip(expectation: string, assertion?: (done: MochaDone) => void): void;
77 | timeout(ms: number): void;
78 | };
79 |
80 | declare function before(action: () => void): void;
81 |
82 | declare function before(action: (done: MochaDone) => void): void;
83 |
84 | declare function setup(action: () => void): void;
85 |
86 | declare function setup(action: (done: MochaDone) => void): void;
87 |
88 | declare function after(action: () => void): void;
89 |
90 | declare function after(action: (done: MochaDone) => void): void;
91 |
92 | declare function teardown(action: () => void): void;
93 |
94 | declare function teardown(action: (done: MochaDone) => void): void;
95 |
96 | declare function beforeEach(action: () => void): void;
97 |
98 | declare function beforeEach(action: (done: MochaDone) => void): void;
99 |
100 | declare function suiteSetup(action: () => void): void;
101 |
102 | declare function suiteSetup(action: (done: MochaDone) => void): void;
103 |
104 | declare function afterEach(action: () => void): void;
105 |
106 | declare function afterEach(action: (done: MochaDone) => void): void;
107 |
108 | declare function suiteTeardown(action: () => void): void;
109 |
110 | declare function suiteTeardown(action: (done: MochaDone) => void): void;
111 |
112 | declare module "mocha" {
113 |
114 | class Mocha {
115 | constructor(options?: {
116 | grep?: RegExp;
117 | ui?: string;
118 | reporter?: string;
119 | timeout?: number;
120 | bail?: boolean;
121 | });
122 |
123 | bail(value?: boolean): Mocha;
124 | addFile(file: string): Mocha;
125 | reporter(value: string): Mocha;
126 | ui(value: string): Mocha;
127 | grep(value: string): Mocha;
128 | grep(value: RegExp): Mocha;
129 | invert(): Mocha;
130 | ignoreLeaks(value: boolean): Mocha;
131 | checkLeaks(): Mocha;
132 | growl(): Mocha;
133 | globals(value: string): Mocha;
134 | globals(values: string[]): Mocha;
135 | useColors(value: boolean): Mocha;
136 | useInlineDiffs(value: boolean): Mocha;
137 | timeout(value: number): Mocha;
138 | slow(value: number): Mocha;
139 | enableTimeouts(value: boolean): Mocha;
140 | asyncOnly(value: boolean): Mocha;
141 | noHighlighting(value: boolean): Mocha;
142 |
143 | run(onComplete?: (failures: number) => void): void;
144 | }
145 |
146 | export = Mocha;
147 | }
148 |
--------------------------------------------------------------------------------
/src/spec/datasource_specs.js:
--------------------------------------------------------------------------------
1 | //import {describe, beforeEach, it, sinon, expect} from '../utils/test_common';
2 | import {Datasource} from '../module';
3 | import {convertToCrateInterval} from '../datasource';
4 | import Q from "q";
5 |
6 | describe('CrateDatasource', function() {
7 | var ctx = {};
8 |
9 | describe('When testing datasource', function() {
10 |
11 | beforeEach(function() {
12 | ctx.$q = Q;
13 | ctx.backendSrv = {};
14 | ctx.templateSrv = {};
15 | ctx.instanceSettings = {
16 | url: "http://crate.io:4200",
17 | jsonData: {
18 | schema: 'stats',
19 | table: 'nodes',
20 | defaultTimeColumn: 'ts',
21 | defaultGroupInterval: 'minute'
22 | }
23 | };
24 | ctx.ds = new Datasource(ctx.instanceSettings, ctx.$q, ctx.backendSrv, ctx.templateSrv);
25 | });
26 |
27 | it('should return Crate cluster name and version if test ok', function(done) {
28 | ctx.backendSrv.datasourceRequest = function(options) {
29 | return ctx.$q.when({
30 | status: 200,
31 | data: {
32 | "ok" : true,
33 | "status" : 200,
34 | "name" : "Negasonic Teenage Warhead",
35 | "cluster_name" : "Crate Test Cluster",
36 | "version" : {
37 | "number" : "0.54.8",
38 | "build_hash" : "c3a7dc4caecfe4fef2992148c8b78347d8c30b2e",
39 | "build_timestamp" : "2016-04-08T12:26:00Z",
40 | "build_snapshot" : false,
41 | "es_version" : "1.7.5",
42 | "lucene_version" : "4.10.4"
43 | }
44 | }
45 | });
46 | };
47 |
48 | ctx.ds.testDatasource().then(function(result) {
49 | expect(result).to.have.property('status');
50 | expect(result).to.have.property('title');
51 | expect(result).to.have.property('message');
52 | expect(result.status).to.equal('success');
53 | expect(result.title).to.equal('Success');
54 | expect(result.message).to.equal('Cluster: Crate Test Cluster, version: 0.54.8');
55 | done();
56 | });
57 | });
58 |
59 | it('should return Crate error if host and port was set properly but url is wrong', function(done) {
60 | ctx.backendSrv.datasourceRequest = function(options) {
61 | var deferred = ctx.$q.defer();
62 | deferred.reject({
63 | status: 400,
64 | statusText: "Bad Request",
65 | data: "No handler found for uri [/wrong] and method [GET]"
66 | });
67 | return deferred.promise;
68 | };
69 |
70 | ctx.ds.testDatasource().then(function(result) {
71 | expect(result).to.have.property('status');
72 | expect(result).to.have.property('title');
73 | expect(result).to.have.property('message');
74 | expect(result.status).to.equal('error');
75 | expect(result.title).to.equal('Error');
76 | expect(result.message).to.equal('Bad Request: No handler found for uri [/wrong] and method [GET]');
77 | done();
78 | });
79 | });
80 |
81 | it('should return http error if url is unreachable', function(done) {
82 | ctx.backendSrv.datasourceRequest = function(options) {
83 | var deferred = ctx.$q.defer();
84 | deferred.reject({
85 | status: 500,
86 | statusText: "Internal Server Error",
87 | data: {
88 | error: "Internal Server Error",
89 | message: "Internal Server Error"
90 | }
91 | });
92 | return deferred.promise;
93 | };
94 |
95 | ctx.ds.testDatasource().then(function(result) {
96 | expect(result).to.have.property('status');
97 | expect(result).to.have.property('title');
98 | expect(result).to.have.property('message');
99 | expect(result.status).to.equal('error');
100 | expect(result.title).to.equal('Error');
101 | expect(result.message).to.equal('Internal Server Error: Internal Server Error');
102 | done();
103 | });
104 | });
105 | });
106 |
107 | describe('When converting to Crate interval', function() {
108 |
109 | beforeEach(function() {
110 | });
111 |
112 | it('should return proper interval for date_trunc() function', function(done) {
113 | var test_map = [
114 | ['10s', 'second'],
115 | ['20s', 'second'],
116 | ['30s', 'second'],
117 | ['40s', 'second'],
118 | ['50s', 'second'],
119 | ['59s', 'second'],
120 |
121 | ['1m', 'minute'],
122 | ['1h', 'hour'],
123 | ['1d', 'day'],
124 | ['1w', 'week'],
125 | ['1M', 'month'],
126 | ['1y', 'year']
127 | ];
128 |
129 | var test_intervals = test_map.map(function(value) {
130 | return value[0];
131 | });
132 | var expected_result = test_map.map(function(value) {
133 | return value[1];
134 | });
135 | var result = test_intervals.map(convertToCrateInterval);
136 |
137 | expect(result).to.deep.equal(expected_result);
138 | done();
139 | });
140 | });
141 |
142 | });
143 |
--------------------------------------------------------------------------------
/dist/response_handler.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"response_handler.js","sourceRoot":"","sources":["response_handler.ts"],"names":["handleResponse","handleTableResponse","handleRawResponse","handleBuildedResponse","convertToGrafanaPoints","makeColName"],"mappings":"AAAA,8CAA8C;;;IAI9C,wBAAuC,MAAM,EAAE,MAAM;QACnDA,EAAEA,CAACA,CAACA,MAAMA,CAACA,YAAYA,KAAKA,OAAOA,CAACA,CAACA,CAACA;YACpCA,MAAMA,CAACA,mBAAmBA,CAACA,MAAMA,EAAEA,MAAMA,CAACA,CAACA;QAC7CA,CAACA;QAEDA,EAAEA,CAACA,CAACA,MAAMA,CAACA,QAAQA,CAACA,CAACA,CAACA;YACpBA,MAAMA,CAACA,iBAAiBA,CAACA,MAAMA,EAAEA,MAAMA,CAACA,CAACA;QAC3CA,CAACA;QAACA,IAAIA,CAACA,CAACA;YACNA,MAAMA,CAACA,qBAAqBA,CAACA,MAAMA,EAAEA,MAAMA,CAACA,CAACA;QAC/CA,CAACA;IACHA,CAACA;IAVD,oCAUC,CAAA;IAED,6BAA6B,MAAM,EAAE,MAAM;QACzCC,MAAMA,CAACA;YACLA,OAAOA,EAAEA,mBAACA,CAACA,GAAGA,CAACA,MAAMA,CAACA,IAAIA,EAAEA,UAAAA,GAAGA;gBAC7BA,MAAMA,CAACA,EAACA,IAAIA,EAAEA,GAAGA,EAACA,CAACA;YACrBA,CAACA,CAACA;YACFA,IAAIA,EAAEA,MAAMA,CAACA,IAAIA;YACjBA,IAAIA,EAAEA,OAAOA;SACdA,CAACA;IACJA,CAACA;IAED,2BAA2B,MAAM,EAAE,MAAM;QACvCC,IAAIA,OAAOA,GAAGA,MAAMA,CAACA,IAAIA,CAACA;QAC1BA,IAAIA,eAAeA,GAAGA,CAACA,CAACA;QACxBA,IAAIA,gBAAgBA,GAAGA,CAACA,CAACA;QAEzBA,EAAEA,CAACA,CAACA,OAAOA,CAACA,MAAMA,GAAGA,CAACA,CAACA,CAACA,CAACA;YACvBA,IAAIA,eAAeA,GAAGA,mBAACA,CAACA,OAAOA,CAACA,MAAMA,CAACA,IAAIA,EAAEA,UAAAA,GAAGA;gBAC9CA,0BAA0BA;gBAC1BA,mCAAmCA;gBACnCA,MAAMA,CAACA,GAAGA,CAACA,KAAKA,CAACA,CAACA,CAACA,CAACA,IAAIA,CAACA,GAAGA,CAACA,CAACA;YAChCA,CAACA,CAACA,CAACA;YAEHA,MAAMA,CAACA,mBAACA,CAACA,GAAGA,CAACA,eAAeA,EAAEA,UAACA,IAAIA,EAAEA,GAAGA;gBACtCA,MAAMA,CAACA;oBACLA,MAAMA,EAAEA,GAAGA,GAAGA,IAAIA,GAAGA,OAAOA,CAACA,gBAAgBA,CAACA;oBAC9CA,UAAUA,EAAEA,sBAAsBA,CAACA,IAAIA,EAAEA,eAAeA,EAAEA,gBAAgBA,CAACA;iBAC5EA,CAACA;YACJA,CAACA,CAACA,CAACA;QACLA,CAACA;QAACA,IAAIA,CAACA,CAACA;YACNA,MAAMA,CAACA,CAACA;oBACNA,MAAMA,EAAEA,OAAOA,CAACA,gBAAgBA,CAACA;oBACjCA,UAAUA,EAAEA,sBAAsBA,CAACA,MAAMA,CAACA,IAAIA,EAAEA,eAAeA,EAAEA,gBAAgBA,CAACA;iBACnFA,CAACA,CAACA;QACLA,CAACA;IACHA,CAACA;IAED,+BAA+B,MAAM,EAAE,MAAM;QAC3CC,IAAIA,OAAOA,GAAGA,MAAMA,CAACA,IAAIA,CAACA;QAC1BA,IAAIA,eAAeA,GAAGA,CAACA,CAACA;QACxBA,IAAIA,oBAA8BA,EAAEA,mBAA6BA,CAACA;QAElEA,EAAEA,CAACA,CAACA,MAAMA,CAACA,cAAcA,CAACA,MAAMA,CAACA,CAACA,CAACA;YACjCA,oBAAoBA,GAAGA,mBAACA,CAACA,GAAGA,CAACA,MAAMA,CAACA,cAAcA,EAAEA,UAAAA,UAAUA;gBAC5DA,MAAMA,CAACA,mBAACA,CAACA,OAAOA,CAACA,OAAOA,EAAEA,UAAUA,CAACA,CAACA;YACxCA,CAACA,CAACA,CAACA;QACLA,CAACA;QAEDA,IAAIA,WAAWA,GAAGA,mBAACA,CAACA,MAAMA,CAACA,MAAMA,CAACA,UAAUA,EAAEA,UAACA,GAAGA;YAChDA,MAAMA,CAACA,CAACA,GAAGA,CAACA,IAAIA,CAACA;QACnBA,CAACA,CAACA,CAACA;QAEHA,EAAEA,CAACA,CAACA,WAAWA,CAACA,MAAMA,CAACA,CAACA,CAACA;YACvBA,mBAAmBA,GAAGA,mBAACA,CAACA,GAAGA,CAACA,WAAWA,EAAEA,UAACA,SAASA,EAAEA,KAAKA;gBACxDA,MAAMA,CAACA,KAAKA,GAAGA,CAACA,CAACA;YACnBA,CAACA,CAACA,CAACA;QACLA,CAACA;QAACA,IAAIA,CAACA,CAACA;YACNA,MAAMA,CAACA,EAAEA,CAACA;QACZA,CAACA;QAEDA,EAAEA,CAACA,CAACA,oBAAoBA,IAAIA,oBAAoBA,CAACA,MAAMA,IAAIA,CAACA,mBAACA,CAACA,IAAIA,CAACA,oBAAoBA,EAAEA,CAACA,CAACA,CAACA,CAACA,CAACA,CAACA;YAC7FA,IAAIA,eAAeA,GAAGA,mBAACA,CAACA,OAAOA,CAACA,MAAMA,CAACA,IAAIA,EAAEA,UAAAA,GAAGA;gBAC9CA,4DAA4DA;gBAC5DA,kCAAkCA;gBAClCA,MAAMA,CAACA,mBAACA,CAACA,GAAGA,CAACA,oBAAoBA,EAAEA,UAAAA,WAAWA;oBAC5CA,MAAMA,CAACA,GAAGA,CAACA,WAAWA,CAACA,CAACA;gBAC1BA,CAACA,CAACA,CAACA,IAAIA,CAACA,GAAGA,CAACA,CAACA;YACfA,CAACA,CAACA,CAACA;YAEHA,MAAMA,CAACA,mBAACA,CAACA,OAAOA,CAACA,mBAACA,CAACA,GAAGA,CAACA,eAAeA,EAAEA,UAACA,IAAIA,EAAEA,GAAGA;gBAChDA,MAAMA,CAACA,mBAACA,CAACA,GAAGA,CAACA,mBAAmBA,EAAEA,UAACA,UAAUA;oBAC3CA,IAAIA,UAAUA,GAAGA,sBAAsBA,CAACA,IAAIA,EAAEA,eAAeA,EAAEA,UAAUA,CAACA,CAACA;oBAE3EA,yCAAyCA;oBACzCA,IAAIA,cAAsBA,CAACA;oBAC3BA,EAAEA,CAACA,CAACA,IAAIA,CAACA,MAAMA,CAACA,CAACA,CAACA;wBAChBA,cAAcA,GAAGA,mBAACA,CAACA,GAAGA,CAACA,oBAAoBA,EAAEA,UAACA,WAAWA,EAAEA,CAACA;4BAC1DA,IAAIA,SAASA,GAAGA,IAAIA,CAACA,CAACA,CAACA,CAACA;4BACxBA,EAAEA,CAACA,CAACA,MAAMA,CAACA,cAAcA,IAAIA,MAAMA,CAACA,cAAcA,CAACA,CAACA,CAACA,CAACA,CAACA,CAACA;gCACtDA,IAAIA,OAAOA,GAAGA,IAAIA,MAAMA,CAACA,MAAMA,CAACA,cAAcA,CAACA,CAACA,CAACA,CAACA,CAACA;gCACnDA,IAAIA,KAAKA,GAAGA,OAAOA,CAACA,IAAIA,CAACA,SAASA,CAACA,WAAWA,CAACA,CAACA,CAACA;gCACjDA,EAAEA,CAACA,CAACA,KAAKA,IAAIA,KAAKA,CAACA,MAAMA,GAAGA,CAACA,CAACA,CAACA,CAACA;oCAC9BA,MAAMA,CAACA,KAAKA,CAACA,CAACA,CAACA,CAACA;gCAClBA,CAACA;gCAACA,IAAIA,CAACA,EAAEA,CAACA,CAACA,KAAKA,CAACA,CAAAA,CAACA;oCAChBA,MAAMA,CAACA,KAAKA,CAACA,CAACA,CAACA,CAACA;gCAClBA,CAACA;gCAACA,IAAIA,CAACA,CAACA;oCACNA,MAAMA,CAACA,SAASA,CAACA,WAAWA,CAACA,CAACA;gCAChCA,CAACA;4BACHA,CAACA;4BAACA,IAAIA,CAACA,CAACA;gCACNA,MAAMA,CAACA,SAASA,CAACA,WAAWA,CAACA,CAACA;4BAChCA,CAACA;wBACHA,CAACA,CAACA,CAACA,IAAIA,CAACA,GAAGA,CAACA,CAACA;oBACfA,CAACA;oBAACA,IAAIA,CAACA,CAACA;wBACNA,cAAcA,GAAGA,GAAGA,CAACA;oBACvBA,CAACA;oBAEDA,MAAMA,CAACA;wBACLA,MAAMA,EAAEA,cAAcA,GAAGA,IAAIA,GAAGA,OAAOA,CAACA,UAAUA,CAACA;wBACnDA,UAAUA,EAAEA,UAAUA;qBACvBA,CAACA;gBACJA,CAACA,CAACA,CAACA;YACLA,CAACA,CAACA,CAACA,CAACA;QACNA,CAACA;QAACA,IAAIA,CAACA,CAACA;YACNA,MAAMA,CAACA,mBAACA,CAACA,GAAGA,CAACA,mBAAmBA,EAAEA,UAACA,UAAUA;gBAC3CA,IAAIA,UAAUA,GAAGA,sBAAsBA,CAACA,MAAMA,CAACA,IAAIA,EAAEA,eAAeA,EAAEA,UAAUA,CAACA,CAACA;gBAElFA,MAAMA,CAACA;oBACLA,MAAMA,EAAEA,OAAOA,CAACA,UAAUA,CAACA;oBAC3BA,UAAUA,EAAEA,UAAUA;iBACvBA,CAACA;YACJA,CAACA,CAACA,CAACA;QACLA,CAACA;IACHA,CAACA;IAED,gCAAgC,IAAI,EAAE,eAAe,EAAE,gBAAgB;QACrEC,MAAMA,CAACA,mBAACA,CAACA,GAAGA,CAACA,IAAIA,EAAEA,UAAAA,GAAGA;YACpBA,IAAIA,EAAEA,GAAGA,MAAMA,CAACA,GAAGA,CAACA,eAAeA,CAACA,CAACA,CAACA;YACtCA,IAAIA,GAAGA,GAAGA,GAAGA,CAACA,gBAAgBA,CAACA,CAACA;YAChCA,GAAGA,GAAGA,GAAGA,KAAKA,IAAIA,GAAGA,MAAMA,CAACA,GAAGA,CAACA,GAAGA,IAAIA,CAACA;YAExCA,MAAMA,CAACA,CAACA,GAAGA,EAAEA,EAAEA,CAACA,CAACA;QACnBA,CAACA,CAACA,CAACA;IACLA,CAACA;IAED,qBAAqB,OAAO,EAAE,MAAM;QAClCC,EAAEA,CAACA,CAACA,OAAOA,KAAKA,gBAAgBA,CAACA,CAACA,CAACA;YACjCA,MAAMA,CAACA,iBAAiBA,GAAGA,MAAMA,GAAGA,GAAGA,CAACA;QAC1CA,CAACA;QAACA,IAAIA,CAACA,CAACA;YACNA,MAAMA,CAACA,OAAOA,GAAGA,GAAGA,GAAGA,MAAMA,GAAGA,GAAGA,CAACA;QACtCA,CAACA;IACHA,CAACA"}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Grafana Data Source Plugin for CrateDB
2 |
3 | [](https://crate.io)
4 |
5 |
6 | ## What Is CrateDB?
7 | CrateDB is a SQL database that makes it simple to store and analyze
8 | massive amounts of machine data in real-time. CrateDB customers have
9 | reported improving predictive analytic query performance of machine
10 | data by 20x more than MySQL, while reducing database hardware costs by
11 | 75%.
12 |
13 | Here’s how CrateDB makes this possible:
14 |
15 | - **Combining SQL & Search** into a single DBMS - allowing you to process any data structure...time series, geospatial, JSON, full-text, etc.
16 | - **Distributed query innovations** - that deliver real-time SQL performance
17 | - **An auto-scaling architecture** - grow CrateDB with less DBA expertise
18 | - **Dynamic schemas, adhoc queries** - quickly adapt to data structure changes
19 |
20 | For these reasons and more, CrateDB is your perfect datasource for Grafana.
21 |
22 | ## The CrateDB Datasource Plugin for Grafana
23 |
24 | ### Features
25 | Enables CrateDB clusters to act as data sources for your Grafana deployment, providing real-time analytical and time-series data with SQL.
26 |
27 | ### Requirements
28 | - **Grafana** > 3.x.x
29 | - **CrateDB** - All stable versions are supported by this plugin
30 |
31 | ### Setup
32 | 
33 |
34 | > The screenshot shows a connection to http://localhost:44200 which is a test database for the purpose of this tutorial. CrateDB's default binding is to http://localhost:4200.
35 |
36 | 1. Click on the Grafana icon on the top left.
37 | 2. After the menu opened, you should see a link `Data Sources` below `Dashboards`.
38 | 3. Click `+ Add data source`.
39 | 4. Select `CrateDB` from the 'Type' dropdown.
40 |
41 | #### Cross-origin Resource Sharing (CORS)
42 |
43 | CrateDB supports [cross-origin resource sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) and if Grafana is running on a different origin (e.g. another domain), it is required to configure CrateDB accordingly. For example by this is the minimum required configuration in your `crate.yml`:
44 | ```
45 | http.cors.enabled: true
46 | http.cors.allow-origin: "http://mydomain.com"
47 | ```
48 | > Replace http://mydomain.com with the domain Grafana is running on, or use a "*" if it's OK to allow any domain to access CrateDB
49 |
50 | For further options look in [CrateDB's documentation](https://crate.io/docs/reference/en/latest/configuration.html#cross-origin-resource-sharing-cors)
51 |
52 | #### The CrateDB Data Source
53 |
54 | Name | Description
55 | ------------ | -------------
56 | Name | The data source name.
57 | Default | Set this data source as default for new panels.
58 |
59 | ##### HTTP Settings
60 |
61 | Name | Description
62 | ------------ | -------------
63 | Url | The URI to any node in your CrateDB cluster.
64 | Access | Via Grafana backend (proxy) or directly from the browser (direct).
65 | Basic Auth | Enable basic authentication (only available via NGINX proxy in CrateDB).
66 | User | Not available in CrateDB.
67 | Password | Not available in CrateDB.
68 |
69 | ##### CrateDB Details
70 |
71 | These are specific settings for the CrateDB data source and it's required to set a fixed `schema`, `table`, and time series column per data source.
72 |
73 | Name | Description
74 | ------------ | -------------
75 | Schema | CrateDB schema to query from (defaults to `doc`).
76 | Table | Table to retrieve the data from. Has to be available in the previously defined schema.
77 | Time Column | Time series column, has to be of type `timestamp` in CrateDB.
78 | Default grouping interval | The grouping resolution (can be changed by query).
79 |
80 | 
81 |
82 | > Grafana will not check (yet) if the `time column`, the `schema`, or the `table` exists. Be sure to double check these values to avoid running into problems later.
83 |
84 | ### Querying CrateDB
85 |
86 | After adding a new dashboard and having the query editor open, define and run the queries you like - it's just like other SQL databases. For example we have added the [NYC yellow cab data set](http://www.nyc.gov/html/tlc/html/about/trip_record_data.shtml) in our cluster to show you something interesting!
87 |
88 | 
89 |
90 | > This graph shows the number of yellow cab pick ups between on a weekend in August 2013.
91 |
92 | ### Debugging Queries
93 |
94 | Grafana runs queries almost immediately after change and it will also auto-complete columns or previous values. However, sometimes queries might still be invalid and Grafana will then show a small exclamation mark in the top corner of the graph. Clicking on it will give you the error message.
95 |
96 | 
97 |
98 | The CrateDB data source for Grafana supports a great range of scalar functions and operators. To read more about them, install or scale a cluster, or even to contribute to Crate, please have a look at the [official Crate documentation](https://crate.io/docs)
99 |
100 | ### License
101 | - This plugins is made available under the terms of the [Apache License, Version 2.0](https://github.com/crate/crate-datasource/blob/master/LICENSE).
102 |
103 | ## Getting Help
104 |
105 | - Read the CrateDB documentation [here](https://crate.io/docs)
106 | - Issues with the Grafana plugin can be reported or discussed [here](https://github.com/raintank/crate-datasource/issues)
107 | - Issues with CrateDB can be reported or discussed [here](https://github.com/crate/crate/issues)
108 | - Join the CrateDB Community Slack channel [here](https://crate.io/docs/support/slackin/)
109 |
--------------------------------------------------------------------------------
/dist/README.md:
--------------------------------------------------------------------------------
1 | # Grafana Data Source Plugin for CrateDB
2 |
3 | [](https://crate.io)
4 |
5 |
6 | ## What Is CrateDB?
7 | CrateDB is a SQL database that makes it simple to store and analyze
8 | massive amounts of machine data in real-time. CrateDB customers have
9 | reported improving predictive analytic query performance of machine
10 | data by 20x more than MySQL, while reducing database hardware costs by
11 | 75%.
12 |
13 | Here’s how CrateDB makes this possible:
14 |
15 | - **Combining SQL & Search** into a single DBMS - allowing you to process any data structure...time series, geospatial, JSON, full-text, etc.
16 | - **Distributed query innovations** - that deliver real-time SQL performance
17 | - **An auto-scaling architecture** - grow CrateDB with less DBA expertise
18 | - **Dynamic schemas, adhoc queries** - quickly adapt to data structure changes
19 |
20 | For these reasons and more, CrateDB is your perfect datasource for Grafana.
21 |
22 | ## The CrateDB Datasource Plugin for Grafana
23 |
24 | ### Features
25 | Enables CrateDB clusters to act as data sources for your Grafana deployment, providing real-time analytical and time-series data with SQL.
26 |
27 | ### Requirements
28 | - **Grafana** > 3.x.x
29 | - **CrateDB** - All stable versions are supported by this plugin
30 |
31 | ### Setup
32 | 
33 |
34 | > The screenshot shows a connection to http://localhost:44200 which is a test database for the purpose of this tutorial. CrateDB's default binding is to http://localhost:4200.
35 |
36 | 1. Click on the Grafana icon on the top left.
37 | 2. After the menu opened, you should see a link `Data Sources` below `Dashboards`.
38 | 3. Click `+ Add data source`.
39 | 4. Select `CrateDB` from the 'Type' dropdown.
40 |
41 | #### Cross-origin Resource Sharing (CORS)
42 |
43 | CrateDB supports [cross-origin resource sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) and if Grafana is running on a different origin (e.g. another domain), it is required to configure CrateDB accordingly. For example by this is the minimum required configuration in your `crate.yml`:
44 | ```
45 | http.cors.enabled: true
46 | http.cors.allow-origin: "http://mydomain.com"
47 | ```
48 | > Replace http://mydomain.com with the domain Grafana is running on, or use a "*" if it's OK to allow any domain to access CrateDB
49 |
50 | For further options look in [CrateDB's documentation](https://crate.io/docs/reference/en/latest/configuration.html#cross-origin-resource-sharing-cors)
51 |
52 | #### The CrateDB Data Source
53 |
54 | Name | Description
55 | ------------ | -------------
56 | Name | The data source name.
57 | Default | Set this data source as default for new panels.
58 |
59 | ##### HTTP Settings
60 |
61 | Name | Description
62 | ------------ | -------------
63 | Url | The URI to any node in your CrateDB cluster.
64 | Access | Via Grafana backend (proxy) or directly from the browser (direct).
65 | Basic Auth | Enable basic authentication (only available via NGINX proxy in CrateDB).
66 | User | Not available in CrateDB.
67 | Password | Not available in CrateDB.
68 |
69 | ##### CrateDB Details
70 |
71 | These are specific settings for the CrateDB data source and it's required to set a fixed `schema`, `table`, and time series column per data source.
72 |
73 | Name | Description
74 | ------------ | -------------
75 | Schema | CrateDB schema to query from (defaults to `doc`).
76 | Table | Table to retrieve the data from. Has to be available in the previously defined schema.
77 | Time Column | Time series column, has to be of type `timestamp` in CrateDB.
78 | Default grouping interval | The grouping resolution (can be changed by query).
79 |
80 | 
81 |
82 | > Grafana will not check (yet) if the `time column`, the `schema`, or the `table` exists. Be sure to double check these values to avoid running into problems later.
83 |
84 | ### Querying CrateDB
85 |
86 | After adding a new dashboard and having the query editor open, define and run the queries you like - it's just like other SQL databases. For example we have added the [NYC yellow cab data set](http://www.nyc.gov/html/tlc/html/about/trip_record_data.shtml) in our cluster to show you something interesting!
87 |
88 | 
89 |
90 | > This graph shows the number of yellow cab pick ups between on a weekend in August 2013.
91 |
92 | ### Debugging Queries
93 |
94 | Grafana runs queries almost immediately after change and it will also auto-complete columns or previous values. However, sometimes queries might still be invalid and Grafana will then show a small exclamation mark in the top corner of the graph. Clicking on it will give you the error message.
95 |
96 | 
97 |
98 | The CrateDB data source for Grafana supports a great range of scalar functions and operators. To read more about them, install or scale a cluster, or even to contribute to Crate, please have a look at the [official Crate documentation](https://crate.io/docs)
99 |
100 | ### License
101 | - This plugins is made available under the terms of the [Apache License, Version 2.0](https://github.com/crate/crate-datasource/blob/master/LICENSE).
102 |
103 | ## Getting Help
104 |
105 | - Read the CrateDB documentation [here](https://crate.io/docs)
106 | - Issues with the Grafana plugin can be reported or discussed [here](https://github.com/raintank/crate-datasource/issues)
107 | - Issues with CrateDB can be reported or discussed [here](https://github.com/crate/crate/issues)
108 | - Join the CrateDB Community Slack channel [here](https://crate.io/docs/support/slackin/)
109 |
--------------------------------------------------------------------------------
/src/partials/query.editor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
39 |
40 |
103 |
104 |
128 |
129 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/dist/partials/query.editor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
39 |
40 |
103 |
104 |
128 |
129 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/dist/response_handler.js:
--------------------------------------------------------------------------------
1 | ///
2 | System.register(['lodash'], function(exports_1) {
3 | var lodash_1;
4 | function handleResponse(target, result) {
5 | if (target.resultFormat === 'table') {
6 | return handleTableResponse(target, result);
7 | }
8 | if (target.rawQuery) {
9 | return handleRawResponse(target, result);
10 | }
11 | else {
12 | return handleBuildedResponse(target, result);
13 | }
14 | }
15 | exports_1("default", handleResponse);
16 | function handleTableResponse(target, result) {
17 | return {
18 | columns: lodash_1["default"].map(result.cols, function (col) {
19 | return { text: col };
20 | }),
21 | rows: result.rows,
22 | type: 'table'
23 | };
24 | }
25 | function handleRawResponse(target, result) {
26 | var columns = result.cols;
27 | var timeColumnIndex = 0;
28 | var valueColumnIndex = 1;
29 | if (columns.length > 2) {
30 | var groupedResponse = lodash_1["default"].groupBy(result.rows, function (row) {
31 | // Assume row structure is
32 | // [ts, value, ...group by columns]
33 | return row.slice(2).join(' ');
34 | });
35 | return lodash_1["default"].map(groupedResponse, function (rows, key) {
36 | return {
37 | target: key + ': ' + columns[valueColumnIndex],
38 | datapoints: convertToGrafanaPoints(rows, timeColumnIndex, valueColumnIndex)
39 | };
40 | });
41 | }
42 | else {
43 | return [{
44 | target: columns[valueColumnIndex],
45 | datapoints: convertToGrafanaPoints(result.rows, timeColumnIndex, valueColumnIndex)
46 | }];
47 | }
48 | }
49 | function handleBuildedResponse(target, result) {
50 | var columns = result.cols;
51 | var timeColumnIndex = 0;
52 | var groupByColumnIndexes, selectColumnIndexes;
53 | if (target.groupByColumns.length) {
54 | groupByColumnIndexes = lodash_1["default"].map(target.groupByColumns, function (groupByCol) {
55 | return lodash_1["default"].indexOf(columns, groupByCol);
56 | });
57 | }
58 | var enabledAggs = lodash_1["default"].filter(target.metricAggs, function (agg) {
59 | return !agg.hide;
60 | });
61 | if (enabledAggs.length) {
62 | selectColumnIndexes = lodash_1["default"].map(enabledAggs, function (metricAgg, index) {
63 | return index + 1;
64 | });
65 | }
66 | else {
67 | return [];
68 | }
69 | if (groupByColumnIndexes && groupByColumnIndexes.length && !lodash_1["default"].some(groupByColumnIndexes, -1)) {
70 | var groupedResponse = lodash_1["default"].groupBy(result.rows, function (row) {
71 | // Construct groupBy key from Group By columns, for example:
72 | // [metric, host] => 'metric host'
73 | return lodash_1["default"].map(groupByColumnIndexes, function (columnIndex) {
74 | return row[columnIndex];
75 | }).join(' ');
76 | });
77 | return lodash_1["default"].flatten(lodash_1["default"].map(groupedResponse, function (rows, key) {
78 | return lodash_1["default"].map(selectColumnIndexes, function (valueIndex) {
79 | var datapoints = convertToGrafanaPoints(rows, timeColumnIndex, valueIndex);
80 | // Build alias for Group By column values
81 | var group_by_alias;
82 | if (rows.length) {
83 | group_by_alias = lodash_1["default"].map(groupByColumnIndexes, function (columnIndex, i) {
84 | var first_row = rows[0];
85 | if (target.groupByAliases && target.groupByAliases[i]) {
86 | var pattern = new RegExp(target.groupByAliases[i]);
87 | var match = pattern.exec(first_row[columnIndex]);
88 | if (match && match.length > 1) {
89 | return match[1];
90 | }
91 | else if (match) {
92 | return match[0];
93 | }
94 | else {
95 | return first_row[columnIndex];
96 | }
97 | }
98 | else {
99 | return first_row[columnIndex];
100 | }
101 | }).join(' ');
102 | }
103 | else {
104 | group_by_alias = key;
105 | }
106 | return {
107 | target: group_by_alias + ': ' + columns[valueIndex],
108 | datapoints: datapoints
109 | };
110 | });
111 | }));
112 | }
113 | else {
114 | return lodash_1["default"].map(selectColumnIndexes, function (valueIndex) {
115 | var datapoints = convertToGrafanaPoints(result.rows, timeColumnIndex, valueIndex);
116 | return {
117 | target: columns[valueIndex],
118 | datapoints: datapoints
119 | };
120 | });
121 | }
122 | }
123 | function convertToGrafanaPoints(rows, timeColumnIndex, valueColumnIndex) {
124 | return lodash_1["default"].map(rows, function (row) {
125 | var ts = Number(row[timeColumnIndex]);
126 | var val = row[valueColumnIndex];
127 | val = val !== null ? Number(val) : null;
128 | return [val, ts];
129 | });
130 | }
131 | function makeColName(aggType, column) {
132 | if (aggType === 'count_distinct') {
133 | return 'count(DISTINCT ' + column + ')';
134 | }
135 | else {
136 | return aggType + '(' + column + ')';
137 | }
138 | }
139 | return {
140 | setters:[
141 | function (lodash_1_1) {
142 | lodash_1 = lodash_1_1;
143 | }],
144 | execute: function() {
145 | }
146 | }
147 | });
148 | //# sourceMappingURL=response_handler.js.map
--------------------------------------------------------------------------------
/src/spec/response_handler_specs.js:
--------------------------------------------------------------------------------
1 | //import {describe, beforeEach, it, sinon, expect} from '../utils/test_common';
2 | import handleResponse from '../response_handler';
3 |
4 | describe('Response Handler', function() {
5 | var ctx = {};
6 |
7 | describe('When handling Crate response', function() {
8 |
9 | beforeEach(function() {
10 | ctx.target = {};
11 | ctx.crateResponse = {};
12 | });
13 |
14 | it('should convert timeseries data to grafana format', function(done) {
15 | ctx.target = {
16 | "groupByColumns": [],
17 | "metricAggs": [
18 | {"column": "value", "type": "avg"}
19 | ],
20 | "whereClauses": []
21 | };
22 |
23 | ctx.crateResponse = {
24 | "cols": ["time","avg(value)"],
25 | "duration": 16,
26 | "rowcount":5,
27 | "rows":[
28 | [1466640780000,1.2562332153320312],
29 | [1466640840000,1.1889413595199585],
30 | [1466640900000,1.3127131064732869],
31 | [1466640960000,1.3972599903742473],
32 | [1466641020000,1.27950386206309]
33 | ]
34 | };
35 |
36 | var result = handleResponse(ctx.target, ctx.crateResponse);
37 | expect(result[0].datapoints).to.deep.equal([
38 | [1.2562332153320312,1466640780000],
39 | [1.1889413595199585,1466640840000],
40 | [1.3127131064732869,1466640900000],
41 | [1.3972599903742473,1466640960000],
42 | [1.27950386206309,1466641020000]
43 | ]);
44 | done();
45 | });
46 |
47 | // TODO: find better default metric name
48 | it('should set metric name if no group by columns selected', function(done) {
49 | ctx.target = {
50 | "groupByColumns": [],
51 | "metricAggs": [
52 | {"column": "value", "type": "avg"}
53 | ],
54 | "whereClauses": []
55 | };
56 |
57 | ctx.crateResponse = {
58 | "cols": ["time","avg(value)"],
59 | "duration": 16,
60 | "rowcount":5,
61 | "rows":[
62 | [1466640780000,1.2562332153320312],
63 | [1466640840000,1.1889413595199585],
64 | [1466640900000,1.3127131064732869],
65 | [1466640960000,1.3972599903742473],
66 | [1466641020000,1.27950386206309]
67 | ]
68 | };
69 |
70 | var result = handleResponse(ctx.target, ctx.crateResponse);
71 | var series = result[0];
72 | expect(series).to.have.property('target');
73 | expect(series).to.have.property('datapoints');
74 | //expect(series.target).to.equal("");
75 | done();
76 | });
77 |
78 | it('should group response by selected columns', function(done) {
79 | ctx.target = {
80 | "groupByColumns": ["host"],
81 | "groupByAliases": [],
82 | "metricAggs": [
83 | {"column": "value", "type": "avg"}
84 | ],
85 | "whereClauses": []
86 | };
87 |
88 | ctx.crateResponse = {
89 | "cols": ["time","avg(value)","host"],
90 | "duration": 16,
91 | "rowcount":10,
92 | "rows":[
93 | [1466640780000,1.2562332153320312,"backend01"],
94 | [1466640840000,1.1889413595199585,"backend01"],
95 | [1466640900000,1.3127131064732869,"backend01"],
96 | [1466640960000,1.3972599903742473,"backend01"],
97 | [1466641020000,1.27950386206309,"backend01"],
98 | [1466641080000,1.3942841291427612,"backend02"],
99 | [1466641140000,1.245995541413625,"backend02"],
100 | [1466641200000,1.3355928659439087,"backend02"],
101 | [1466641260000,1.2358959714571636,"backend02"],
102 | [1466641320000,1.4172419905662537,"backend02"]
103 | ]
104 | };
105 |
106 | var result = handleResponse(ctx.target, ctx.crateResponse);
107 | expect(result.length).to.equal(2);
108 | expect(result[0]).to.have.property('target');
109 | expect(result[0]).to.have.property('datapoints');
110 | expect(result[0].target).to.equal('backend01: avg(value)');
111 | expect(result[1]).to.have.property('target');
112 | expect(result[1]).to.have.property('datapoints');
113 | expect(result[1].target).to.equal('backend02: avg(value)');
114 | done();
115 | });
116 |
117 | it('should transform to set of series for all group by columns', function(done) {
118 | ctx.target = {
119 | "groupByColumns": ["host", "metric"],
120 | "groupByAliases": [],
121 | "metricAggs": [
122 | {"column": "value", "type": "avg"}
123 | ],
124 | "whereClauses": []
125 | };
126 |
127 | ctx.crateResponse = {
128 | "cols": ["time","avg(value)","host", "metric"],
129 | "duration": 16,
130 | "rowcount":10,
131 | "rows":[
132 | [1466640780000,1.2562332153320312,"backend01", "load1"],
133 | [1466640840000,1.1889413595199585,"backend01", "load1"],
134 |
135 | [1466640900000,1.3127131064732869,"backend01", "load5"],
136 | [1466640960000,1.3972599903742473,"backend01", "load5"],
137 | [1466641020000,1.27950386206309, "backend01", "load5"],
138 |
139 | [1466641080000,1.3942841291427612,"backend02", "load1"],
140 | [1466641140000,1.245995541413625, "backend02", "load1"],
141 |
142 | [1466641200000,1.3355928659439087,"backend02", "load5"],
143 | [1466641260000,1.2358959714571636,"backend02", "load5"],
144 | [1466641320000,1.4172419905662537,"backend02", "load5"]
145 | ]
146 | };
147 |
148 | var result = handleResponse(ctx.target, ctx.crateResponse);
149 | expect(result.length).to.equal(4);
150 | expect(result[0].datapoints).to.deep.equal([
151 | [1.2562332153320312,1466640780000],
152 | [1.1889413595199585,1466640840000]
153 | ]);
154 | expect(result[0].target).to.equal('backend01 load1: avg(value)');
155 | done();
156 | });
157 |
158 | it('should handle multiple select values', function(done) {
159 | ctx.target = {
160 | "groupByColumns": [],
161 | "metricAggs": [
162 | {"column": "load1", "type": "avg"},
163 | {"column": "load15", "type": "avg"}
164 | ],
165 | "whereClauses": []
166 | };
167 |
168 | ctx.crateResponse = {
169 | "cols": ["time","avg(load1)","avg(load15)"],
170 | "duration": 16,
171 | "rowcount":5,
172 | "rows":[
173 | [1466640780000,1.2562332153320312,1.12],
174 | [1466640840000,1.1889413595199585,1.01],
175 | [1466640900000,1.3127131064732869,1.21],
176 | [1466640960000,1.3972599903742473,1.28],
177 | [1466641020000,1.27950386206309,1.16]
178 | ]
179 | };
180 |
181 | var result = handleResponse(ctx.target, ctx.crateResponse);
182 | expect(result).to.deep.equal([
183 | {
184 | target: 'avg(load1)',
185 | datapoints: [
186 | [1.2562332153320312,1466640780000],
187 | [1.1889413595199585,1466640840000],
188 | [1.3127131064732869,1466640900000],
189 | [1.3972599903742473,1466640960000],
190 | [1.27950386206309,1466641020000]
191 | ]
192 | },
193 | {
194 | target: 'avg(load15)',
195 | datapoints: [
196 | [1.12,1466640780000],
197 | [1.01,1466640840000],
198 | [1.21,1466640900000],
199 | [1.28,1466640960000],
200 | [1.16,1466641020000]
201 | ]
202 | }
203 | ]);
204 | done();
205 | });
206 |
207 | });
208 |
209 | describe('When handling raw response', function() {
210 |
211 | beforeEach(function() {
212 | ctx.target = {};
213 | ctx.crateResponse = {};
214 | });
215 |
216 | it('should handle GROUP BY columns', function(done) {
217 | ctx.target = {
218 | "rawQuery": true,
219 | "query": "SELECT ts as time, load, hostname " +
220 | "FROM stats.nodes WHERE ts >= ? AND ts <= ? " +
221 | "GROUP BY time, load, hostname " +
222 | "ORDER BY time ASC"
223 | };
224 |
225 | ctx.crateResponse = {
226 | "cols": ["time", "load", "hostname"],
227 | "duration": 16,
228 | "rowcount":5,
229 | "rows":[
230 | [1466640780000,1.2562332153320312, "host01"],
231 | [1466640780000,1.2562332153320312, "host02"],
232 | [1466640840000,1.1889413595199585, "host01"],
233 | [1466640840000,1.1889413595199585, "host02"],
234 | [1466640900000,1.3127131064732869, "host01"],
235 | [1466640900000,1.3127131064732869, "host02"],
236 | [1466640960000,1.3972599903742473, "host01"],
237 | [1466640960000,1.3972599903742473, "host02"],
238 | [1466641020000,1.27950386206309, "host01"],
239 | [1466641020000,1.27950386206309, "host02"]
240 | ]
241 | };
242 |
243 | var result = handleResponse(ctx.target, ctx.crateResponse);
244 | expect(result).to.deep.equal([
245 | {
246 | target: 'host01: load',
247 | datapoints: [
248 | [1.2562332153320312,1466640780000],
249 | [1.1889413595199585,1466640840000],
250 | [1.3127131064732869,1466640900000],
251 | [1.3972599903742473,1466640960000],
252 | [1.27950386206309,1466641020000]
253 | ]
254 | },
255 | {
256 | target: 'host02: load',
257 | datapoints: [
258 | [1.2562332153320312,1466640780000],
259 | [1.1889413595199585,1466640840000],
260 | [1.3127131064732869,1466640900000],
261 | [1.3972599903742473,1466640960000],
262 | [1.27950386206309,1466641020000]
263 | ]
264 | }
265 | ]);
266 | done();
267 | });
268 | });
269 |
270 | });
271 |
--------------------------------------------------------------------------------
/src/query_ctrl.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import angular from 'angular';
4 | import _ from 'lodash';
5 | import {QueryCtrl} from './sdk/sdk';
6 | import {CrateQueryBuilder} from './query_builder';
7 | import queryDef from './query_def';
8 |
9 | export class CrateDatasourceQueryCtrl extends QueryCtrl {
10 | static templateUrl = 'partials/query.editor.html';
11 |
12 | crateQueryBuilder: CrateQueryBuilder;
13 | groupBySegments: any;
14 | whereSegments: any;
15 | removeWhereSegment: any;
16 | operators: any;
17 | timeIntervals: any[];
18 | resultFormats: any[];
19 |
20 | constructor($scope, $injector, private $q, private uiSegmentSrv, private templateSrv) {
21 | super($scope, $injector);
22 |
23 | this.uiSegmentSrv = uiSegmentSrv;
24 | this.templateSrv = templateSrv;
25 |
26 | let ds = this.datasource;
27 | this.crateQueryBuilder = new CrateQueryBuilder(ds.schema,
28 | ds.table,
29 | ds.defaultTimeColumn,
30 | ds.defaultGroupInterval,
31 | templateSrv);
32 |
33 | this.operators = ['<', '>', '<=', '>=', '=', '<>', '!=', 'in', 'like', '~', '!~'];
34 |
35 | this.timeIntervals = [
36 | {name: 'Auto', value: 'auto'},
37 | {name: 'Auto (Grafana)', value: 'auto_gf'},
38 | {name: 'Second', value: 'second'},
39 | {name: 'Minute', value: 'minute'},
40 | {name: 'Hour', value: 'hour'},
41 | {name: 'Day', value: 'day'},
42 | {name: 'Week', value: 'week'},
43 | {name: 'Month', value: 'month'},
44 | {name: 'Quarter', value: 'quarter'},
45 | {name: 'Year', value: 'year'}
46 | ];
47 |
48 | this.resultFormats = [
49 | {text: 'Time series', value: 'time_series'},
50 | {text: 'Table', value: 'table'},
51 | ];
52 |
53 | var target_defaults = {
54 | metricAggs: [
55 | {type: 'avg', column: 'value'}
56 | ],
57 | groupByColumns: [],
58 | groupByAliases: [],
59 | whereClauses: [],
60 | timeInterval: ds.defaultGroupInterval,
61 | resultFormat: 'time_series'
62 | };
63 | _.defaults(this.target, target_defaults);
64 |
65 | this.updateGroupByAliases();
66 |
67 | this.groupBySegments = _.map(this.target.groupByColumns, this.uiSegmentSrv.newSegment);
68 |
69 | // Build WHERE segments
70 | this.whereSegments = [];
71 | this.buildWhereSegments(this.target.whereClauses);
72 |
73 | this.removeWhereSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove --'});
74 | this.fixSegments(this.groupBySegments);
75 | }
76 |
77 | crateQuery(query, args = []) {
78 | return this.datasource._sql_query(query, args).then(response => {
79 | return response.rows;
80 | });
81 | }
82 |
83 | getCollapsedText(): string {
84 | if (this.target.rawQuery) {
85 | return this.target.query;
86 | } else {
87 | return this.crateQueryBuilder.build(this.target);
88 | }
89 | }
90 |
91 | ////////////////////
92 | // Event handlers //
93 | ////////////////////
94 |
95 | onChangeInternal(): void {
96 | this.panelCtrl.refresh(); // Asks the panel to refresh data.
97 | }
98 |
99 | groupBySegmentChanged(segment, index): void {
100 | if (segment.type === 'plus-button') {
101 | segment.type = undefined;
102 | }
103 | this.target.groupByColumns = _.map(_.filter(this.groupBySegments, segment => {
104 | return (segment.type !== 'plus-button' &&
105 | segment.value !== this.removeWhereSegment.value);
106 | }), 'value');
107 | this.groupBySegments = _.map(this.target.groupByColumns, this.uiSegmentSrv.newSegment);
108 | this.groupBySegments.push(this.uiSegmentSrv.newPlusButton());
109 |
110 | if (segment.value === this.removeWhereSegment.value) {
111 | this.target.groupByAliases.splice(index, 1);
112 | }
113 | this.updateGroupByAliases();
114 |
115 | this.onChangeInternal();
116 | }
117 |
118 | onGroupByAliasChange(index) {
119 | this.updateGroupByAliases();
120 | this.onChangeInternal();
121 | }
122 |
123 | onAggTypeChange(): void {
124 | this.onChangeInternal();
125 | }
126 |
127 | addMetricAgg(): void {
128 | this.target.metricAggs.push({ type: 'avg', column: 'value' });
129 | this.onChangeInternal();
130 | }
131 |
132 | removeMetricAgg(index): void {
133 | this.target.metricAggs.splice(index, 1);
134 | this.onChangeInternal();
135 | }
136 |
137 | toggleShowMetric(agg): void {
138 | agg.hide = !agg.hide;
139 | this.onChangeInternal();
140 | }
141 |
142 | toggleEditorMode(): void {
143 | this.target.rawQuery = !this.target.rawQuery;
144 | }
145 |
146 | ///////////////////////
147 | // Query suggestions //
148 | ///////////////////////
149 |
150 | getColumns(allValue?: boolean, onlyNumeric?: boolean) {
151 | let query;
152 | if (onlyNumeric) {
153 | query = this.crateQueryBuilder.getNumericColumnsQuery();
154 | } else {
155 | query = this.crateQueryBuilder.getColumnsQuery();
156 | }
157 | let self = this;
158 | return this.crateQuery(query).then(rows => {
159 | if (allValue) {
160 | rows.splice(0, 0, '*');
161 | }
162 | return self.transformToSegments(_.flatten(rows), true);
163 | });
164 | }
165 |
166 | getGroupByColumns() {
167 | return this.getColumns().then(columns => {
168 | columns.splice(0, 0, angular.copy(this.removeWhereSegment));
169 | return columns;
170 | });
171 | }
172 |
173 | getValues(column, limit = 10) {
174 | let self = this;
175 | let time_range = this.panelCtrl.range;
176 | return this.crateQuery(this.crateQueryBuilder.getValuesQuery(column, limit, time_range))
177 | .then(rows => {
178 | return self.transformToSegments(_.flatten(rows), true);
179 | });
180 | }
181 |
182 | getColumnsOrValues(segment, index) {
183 | var self = this;
184 | if (segment.type === 'condition') {
185 | return this.$q.when([this.uiSegmentSrv.newSegment('AND'), this.uiSegmentSrv.newSegment('OR')]);
186 | }
187 | if (segment.type === 'operator') {
188 | return this.$q.when(this.uiSegmentSrv.newOperators(this.operators));
189 | }
190 |
191 | if (segment.type === 'key' || segment.type === 'plus-button') {
192 | return this.getColumns().then(columns => {
193 | columns.splice(0, 0, angular.copy(this.removeWhereSegment));
194 | return columns;
195 | });
196 | } else if (segment.type === 'value') {
197 | return this.getValues(this.whereSegments[index - 2].value);
198 | }
199 | }
200 |
201 | getMetricAggTypes() {
202 | return queryDef.getMetricAggTypes();
203 | }
204 |
205 | getMetricAggDef(aggType) {
206 | return _.find(this.getMetricAggTypes(), { value: aggType });
207 | }
208 |
209 | whereSegmentUpdated(segment, index) {
210 | this.whereSegments[index] = segment;
211 |
212 | if (segment.value === this.removeWhereSegment.value) {
213 | this.whereSegments.splice(index, 3);
214 | if (this.whereSegments.length === 0) {
215 | this.whereSegments.push(this.uiSegmentSrv.newPlusButton());
216 | } else if (this.whereSegments.length > 2) {
217 | this.whereSegments.splice(Math.max(index - 1, 0), 1);
218 | if (this.whereSegments[this.whereSegments.length - 1].type !== 'plus-button') {
219 | this.whereSegments.push(this.uiSegmentSrv.newPlusButton());
220 | }
221 | }
222 | } else {
223 | if (segment.type === 'plus-button') {
224 | if (index > 2) {
225 | this.whereSegments.splice(index, 0, this.uiSegmentSrv.newCondition('AND'));
226 | }
227 | this.whereSegments.push(this.uiSegmentSrv.newOperator('='));
228 | this.whereSegments.push(this.uiSegmentSrv.newFake('select tag value', 'value', 'query-segment-value'));
229 | segment.type = 'key';
230 | segment.cssClass = 'query-segment-key';
231 | }
232 | if ((index + 1) === this.whereSegments.length) {
233 | this.whereSegments.push(this.uiSegmentSrv.newPlusButton());
234 | }
235 | }
236 |
237 | this.buildWhereClauses();
238 |
239 | // Refresh only if all fields setted
240 | if (_.every(this.whereSegments, segment => {
241 | return ((segment.value || segment.type === 'plus-button') &&
242 | !(segment.fake && segment.type !== 'plus-button'));
243 | })) {
244 | this.panelCtrl.refresh();
245 | }
246 | }
247 |
248 | ///////////////////////
249 |
250 | buildWhereSegments(whereClauses: any): void {
251 | var self = this;
252 | _.forEach(whereClauses, whereClause => {
253 | if (whereClause.condition) {
254 | self.whereSegments.push(self.uiSegmentSrv.newCondition(whereClause.condition));
255 | }
256 | self.whereSegments.push(self.uiSegmentSrv.newKey(whereClause.column));
257 | self.whereSegments.push(self.uiSegmentSrv.newOperator(whereClause.operator));
258 | self.whereSegments.push(self.uiSegmentSrv.newKeyValue(whereClause.value));
259 | });
260 | this.fixSegments(this.whereSegments);
261 | }
262 |
263 | buildWhereClauses() {
264 | var i = 0;
265 | var whereIndex = 0;
266 | var segments = this.whereSegments;
267 | var whereClauses = [];
268 | while (segments.length > i && segments[i].type !== 'plus-button') {
269 | if (whereClauses.length < whereIndex + 1) {
270 | whereClauses.push({condition: '', column: '', operator: '', value: ''});
271 | }
272 | if (segments[i].type === 'condition') {
273 | whereClauses[whereIndex].condition = segments[i].value;
274 | } else if (segments[i].type === 'key') {
275 | whereClauses[whereIndex].column = segments[i].value;
276 | } else if (segments[i].type === 'operator') {
277 | whereClauses[whereIndex].operator = segments[i].value;
278 | } else if (segments[i].type === 'value') {
279 | whereClauses[whereIndex].value = segments[i].value;
280 | whereIndex++;
281 | }
282 | i++;
283 | }
284 | this.target.whereClauses = whereClauses;
285 | }
286 |
287 | fixSegments(segments) {
288 | var count = segments.length;
289 | var lastSegment = segments[Math.max(count-1, 0)];
290 |
291 | if (!lastSegment || lastSegment.type !== 'plus-button') {
292 | segments.push(this.uiSegmentSrv.newPlusButton());
293 | }
294 | }
295 |
296 | transformToSegments(results, addTemplateVars?: boolean) {
297 | var segments = _.map(_.flatten(results), value => {
298 | return this.uiSegmentSrv.newSegment({
299 | value: value.toString(),
300 | expandable: false
301 | });
302 | });
303 |
304 | if (addTemplateVars) {
305 | for (let variable of this.templateSrv.variables) {
306 | segments.unshift(this.uiSegmentSrv.newSegment({ type: 'template', value: '$' + variable.name, expandable: true }));
307 | }
308 | }
309 | return segments;
310 | }
311 |
312 | updateGroupByAliases() {
313 | let groupByAliases = new Array(this.target.groupByColumns.length);
314 | this.target.groupByColumns.forEach((column, index) => {
315 | if (this.target.groupByAliases[index]) {
316 | groupByAliases[index] = this.target.groupByAliases[index];
317 | } else {
318 | groupByAliases[index] = "";
319 | }
320 | });
321 | this.target.groupByAliases = groupByAliases;
322 | }
323 |
324 | }
325 |
--------------------------------------------------------------------------------
/dist/query_ctrl.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import angular from 'angular';
4 | import _ from 'lodash';
5 | import {QueryCtrl} from './sdk/sdk';
6 | import {CrateQueryBuilder} from './query_builder';
7 | import queryDef from './query_def';
8 |
9 | export class CrateDatasourceQueryCtrl extends QueryCtrl {
10 | static templateUrl = 'partials/query.editor.html';
11 |
12 | crateQueryBuilder: CrateQueryBuilder;
13 | groupBySegments: any;
14 | whereSegments: any;
15 | removeWhereSegment: any;
16 | operators: any;
17 | timeIntervals: any[];
18 | resultFormats: any[];
19 |
20 | constructor($scope, $injector, private $q, private uiSegmentSrv, private templateSrv) {
21 | super($scope, $injector);
22 |
23 | this.uiSegmentSrv = uiSegmentSrv;
24 | this.templateSrv = templateSrv;
25 |
26 | let ds = this.datasource;
27 | this.crateQueryBuilder = new CrateQueryBuilder(ds.schema,
28 | ds.table,
29 | ds.defaultTimeColumn,
30 | ds.defaultGroupInterval,
31 | templateSrv);
32 |
33 | this.operators = ['<', '>', '<=', '>=', '=', '<>', '!=', 'in', 'like', '~', '!~'];
34 |
35 | this.timeIntervals = [
36 | {name: 'Auto', value: 'auto'},
37 | {name: 'Auto (Grafana)', value: 'auto_gf'},
38 | {name: 'Second', value: 'second'},
39 | {name: 'Minute', value: 'minute'},
40 | {name: 'Hour', value: 'hour'},
41 | {name: 'Day', value: 'day'},
42 | {name: 'Week', value: 'week'},
43 | {name: 'Month', value: 'month'},
44 | {name: 'Quarter', value: 'quarter'},
45 | {name: 'Year', value: 'year'}
46 | ];
47 |
48 | this.resultFormats = [
49 | {text: 'Time series', value: 'time_series'},
50 | {text: 'Table', value: 'table'},
51 | ];
52 |
53 | var target_defaults = {
54 | metricAggs: [
55 | {type: 'avg', column: 'value'}
56 | ],
57 | groupByColumns: [],
58 | groupByAliases: [],
59 | whereClauses: [],
60 | timeInterval: ds.defaultGroupInterval,
61 | resultFormat: 'time_series'
62 | };
63 | _.defaults(this.target, target_defaults);
64 |
65 | this.updateGroupByAliases();
66 |
67 | this.groupBySegments = _.map(this.target.groupByColumns, this.uiSegmentSrv.newSegment);
68 |
69 | // Build WHERE segments
70 | this.whereSegments = [];
71 | this.buildWhereSegments(this.target.whereClauses);
72 |
73 | this.removeWhereSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove --'});
74 | this.fixSegments(this.groupBySegments);
75 | }
76 |
77 | crateQuery(query, args = []) {
78 | return this.datasource._sql_query(query, args).then(response => {
79 | return response.rows;
80 | });
81 | }
82 |
83 | getCollapsedText(): string {
84 | if (this.target.rawQuery) {
85 | return this.target.query;
86 | } else {
87 | return this.crateQueryBuilder.build(this.target);
88 | }
89 | }
90 |
91 | ////////////////////
92 | // Event handlers //
93 | ////////////////////
94 |
95 | onChangeInternal(): void {
96 | this.panelCtrl.refresh(); // Asks the panel to refresh data.
97 | }
98 |
99 | groupBySegmentChanged(segment, index): void {
100 | if (segment.type === 'plus-button') {
101 | segment.type = undefined;
102 | }
103 | this.target.groupByColumns = _.map(_.filter(this.groupBySegments, segment => {
104 | return (segment.type !== 'plus-button' &&
105 | segment.value !== this.removeWhereSegment.value);
106 | }), 'value');
107 | this.groupBySegments = _.map(this.target.groupByColumns, this.uiSegmentSrv.newSegment);
108 | this.groupBySegments.push(this.uiSegmentSrv.newPlusButton());
109 |
110 | if (segment.value === this.removeWhereSegment.value) {
111 | this.target.groupByAliases.splice(index, 1);
112 | }
113 | this.updateGroupByAliases();
114 |
115 | this.onChangeInternal();
116 | }
117 |
118 | onGroupByAliasChange(index) {
119 | this.updateGroupByAliases();
120 | this.onChangeInternal();
121 | }
122 |
123 | onAggTypeChange(): void {
124 | this.onChangeInternal();
125 | }
126 |
127 | addMetricAgg(): void {
128 | this.target.metricAggs.push({ type: 'avg', column: 'value' });
129 | this.onChangeInternal();
130 | }
131 |
132 | removeMetricAgg(index): void {
133 | this.target.metricAggs.splice(index, 1);
134 | this.onChangeInternal();
135 | }
136 |
137 | toggleShowMetric(agg): void {
138 | agg.hide = !agg.hide;
139 | this.onChangeInternal();
140 | }
141 |
142 | toggleEditorMode(): void {
143 | this.target.rawQuery = !this.target.rawQuery;
144 | }
145 |
146 | ///////////////////////
147 | // Query suggestions //
148 | ///////////////////////
149 |
150 | getColumns(allValue?: boolean, onlyNumeric?: boolean) {
151 | let query;
152 | if (onlyNumeric) {
153 | query = this.crateQueryBuilder.getNumericColumnsQuery();
154 | } else {
155 | query = this.crateQueryBuilder.getColumnsQuery();
156 | }
157 | let self = this;
158 | return this.crateQuery(query).then(rows => {
159 | if (allValue) {
160 | rows.splice(0, 0, '*');
161 | }
162 | return self.transformToSegments(_.flatten(rows), true);
163 | });
164 | }
165 |
166 | getGroupByColumns() {
167 | return this.getColumns().then(columns => {
168 | columns.splice(0, 0, angular.copy(this.removeWhereSegment));
169 | return columns;
170 | });
171 | }
172 |
173 | getValues(column, limit = 10) {
174 | let self = this;
175 | let time_range = this.panelCtrl.range;
176 | return this.crateQuery(this.crateQueryBuilder.getValuesQuery(column, limit, time_range))
177 | .then(rows => {
178 | return self.transformToSegments(_.flatten(rows), true);
179 | });
180 | }
181 |
182 | getColumnsOrValues(segment, index) {
183 | var self = this;
184 | if (segment.type === 'condition') {
185 | return this.$q.when([this.uiSegmentSrv.newSegment('AND'), this.uiSegmentSrv.newSegment('OR')]);
186 | }
187 | if (segment.type === 'operator') {
188 | return this.$q.when(this.uiSegmentSrv.newOperators(this.operators));
189 | }
190 |
191 | if (segment.type === 'key' || segment.type === 'plus-button') {
192 | return this.getColumns().then(columns => {
193 | columns.splice(0, 0, angular.copy(this.removeWhereSegment));
194 | return columns;
195 | });
196 | } else if (segment.type === 'value') {
197 | return this.getValues(this.whereSegments[index - 2].value);
198 | }
199 | }
200 |
201 | getMetricAggTypes() {
202 | return queryDef.getMetricAggTypes();
203 | }
204 |
205 | getMetricAggDef(aggType) {
206 | return _.find(this.getMetricAggTypes(), { value: aggType });
207 | }
208 |
209 | whereSegmentUpdated(segment, index) {
210 | this.whereSegments[index] = segment;
211 |
212 | if (segment.value === this.removeWhereSegment.value) {
213 | this.whereSegments.splice(index, 3);
214 | if (this.whereSegments.length === 0) {
215 | this.whereSegments.push(this.uiSegmentSrv.newPlusButton());
216 | } else if (this.whereSegments.length > 2) {
217 | this.whereSegments.splice(Math.max(index - 1, 0), 1);
218 | if (this.whereSegments[this.whereSegments.length - 1].type !== 'plus-button') {
219 | this.whereSegments.push(this.uiSegmentSrv.newPlusButton());
220 | }
221 | }
222 | } else {
223 | if (segment.type === 'plus-button') {
224 | if (index > 2) {
225 | this.whereSegments.splice(index, 0, this.uiSegmentSrv.newCondition('AND'));
226 | }
227 | this.whereSegments.push(this.uiSegmentSrv.newOperator('='));
228 | this.whereSegments.push(this.uiSegmentSrv.newFake('select tag value', 'value', 'query-segment-value'));
229 | segment.type = 'key';
230 | segment.cssClass = 'query-segment-key';
231 | }
232 | if ((index + 1) === this.whereSegments.length) {
233 | this.whereSegments.push(this.uiSegmentSrv.newPlusButton());
234 | }
235 | }
236 |
237 | this.buildWhereClauses();
238 |
239 | // Refresh only if all fields setted
240 | if (_.every(this.whereSegments, segment => {
241 | return ((segment.value || segment.type === 'plus-button') &&
242 | !(segment.fake && segment.type !== 'plus-button'));
243 | })) {
244 | this.panelCtrl.refresh();
245 | }
246 | }
247 |
248 | ///////////////////////
249 |
250 | buildWhereSegments(whereClauses: any): void {
251 | var self = this;
252 | _.forEach(whereClauses, whereClause => {
253 | if (whereClause.condition) {
254 | self.whereSegments.push(self.uiSegmentSrv.newCondition(whereClause.condition));
255 | }
256 | self.whereSegments.push(self.uiSegmentSrv.newKey(whereClause.column));
257 | self.whereSegments.push(self.uiSegmentSrv.newOperator(whereClause.operator));
258 | self.whereSegments.push(self.uiSegmentSrv.newKeyValue(whereClause.value));
259 | });
260 | this.fixSegments(this.whereSegments);
261 | }
262 |
263 | buildWhereClauses() {
264 | var i = 0;
265 | var whereIndex = 0;
266 | var segments = this.whereSegments;
267 | var whereClauses = [];
268 | while (segments.length > i && segments[i].type !== 'plus-button') {
269 | if (whereClauses.length < whereIndex + 1) {
270 | whereClauses.push({condition: '', column: '', operator: '', value: ''});
271 | }
272 | if (segments[i].type === 'condition') {
273 | whereClauses[whereIndex].condition = segments[i].value;
274 | } else if (segments[i].type === 'key') {
275 | whereClauses[whereIndex].column = segments[i].value;
276 | } else if (segments[i].type === 'operator') {
277 | whereClauses[whereIndex].operator = segments[i].value;
278 | } else if (segments[i].type === 'value') {
279 | whereClauses[whereIndex].value = segments[i].value;
280 | whereIndex++;
281 | }
282 | i++;
283 | }
284 | this.target.whereClauses = whereClauses;
285 | }
286 |
287 | fixSegments(segments) {
288 | var count = segments.length;
289 | var lastSegment = segments[Math.max(count-1, 0)];
290 |
291 | if (!lastSegment || lastSegment.type !== 'plus-button') {
292 | segments.push(this.uiSegmentSrv.newPlusButton());
293 | }
294 | }
295 |
296 | transformToSegments(results, addTemplateVars?: boolean) {
297 | var segments = _.map(_.flatten(results), value => {
298 | return this.uiSegmentSrv.newSegment({
299 | value: value.toString(),
300 | expandable: false
301 | });
302 | });
303 |
304 | if (addTemplateVars) {
305 | for (let variable of this.templateSrv.variables) {
306 | segments.unshift(this.uiSegmentSrv.newSegment({ type: 'template', value: '$' + variable.name, expandable: true }));
307 | }
308 | }
309 | return segments;
310 | }
311 |
312 | updateGroupByAliases() {
313 | let groupByAliases = new Array(this.target.groupByColumns.length);
314 | this.target.groupByColumns.forEach((column, index) => {
315 | if (this.target.groupByAliases[index]) {
316 | groupByAliases[index] = this.target.groupByAliases[index];
317 | } else {
318 | groupByAliases[index] = "";
319 | }
320 | });
321 | this.target.groupByAliases = groupByAliases;
322 | }
323 |
324 | }
325 |
--------------------------------------------------------------------------------
/dist/datasource.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import _ from 'lodash';
4 | import * as dateMath from 'app/core/utils/datemath';
5 | import moment from 'moment';
6 | import {CrateQueryBuilder, getEnabledAggs, getRawAggs, getNotRawAggs} from './query_builder';
7 | import handleResponse from './response_handler';
8 |
9 | export class CrateDatasource {
10 | type: string;
11 | url: string;
12 | name: string;
13 | basicAuth: string;
14 | withCredentials: boolean;
15 | schema: string;
16 | table: string;
17 | defaultTimeColumn: string;
18 | defaultGroupInterval: string;
19 | checkQuerySource: boolean;
20 | queryBuilder: CrateQueryBuilder;
21 | CRATE_ROWS_LIMIT: number;
22 |
23 |
24 | constructor(instanceSettings,
25 | private $q,
26 | private backendSrv,
27 | private templateSrv,
28 | private timeSrv) {
29 |
30 | this.type = instanceSettings.type;
31 | this.url = instanceSettings.url;
32 | this.name = instanceSettings.name;
33 | this.basicAuth = instanceSettings.basicAuth;
34 | this.withCredentials = instanceSettings.withCredentials;
35 | this.schema = instanceSettings.jsonData.schema;
36 | this.table = instanceSettings.jsonData.table;
37 | this.defaultTimeColumn = instanceSettings.jsonData.timeColumn;
38 | this.defaultGroupInterval = instanceSettings.jsonData.timeInterval;
39 | this.checkQuerySource = instanceSettings.jsonData.checkQuerySource;
40 |
41 | this.$q = $q;
42 | this.backendSrv = backendSrv;
43 | this.templateSrv = templateSrv;
44 | this.timeSrv = timeSrv;
45 |
46 | this.queryBuilder = new CrateQueryBuilder(this.schema,
47 | this.table,
48 | this.defaultTimeColumn,
49 | this.defaultGroupInterval,
50 | this.templateSrv);
51 |
52 | this.CRATE_ROWS_LIMIT = 10000;
53 | }
54 |
55 | // Called once per panel (graph)
56 | query(options) {
57 | let timeFrom = Math.ceil(dateMath.parse(options.range.from));
58 | let timeTo = Math.ceil(dateMath.parse(options.range.to));
59 | let timeFilter = this.getTimeFilter(timeFrom, timeTo);
60 | let scopedVars = this.setScopedVars(options.scopedVars);
61 |
62 | let queries = _.map(options.targets, target => {
63 | if (target.hide || (target.rawQuery && !target.query)) { return []; }
64 |
65 | let query: string;
66 | let rawAggQuery: string;
67 | let queryTarget, rawAggTarget;
68 | let getQuery: any;
69 | let getRawAggQuery: any;
70 | let getRawAggInterval: any;
71 | let adhocFilters = this.templateSrv.getAdhocFilters(this.name);
72 |
73 | if (target.rawQuery) {
74 | query = target.query;
75 | } else {
76 | let minInterval = Math.ceil((timeTo - timeFrom) / this.CRATE_ROWS_LIMIT);
77 | let maxLimit = timeTo - timeFrom;
78 | let interval;
79 |
80 | if (target.timeInterval === 'auto') {
81 | interval = getMinCrateInterval(options.intervalMs);
82 | } else if (target.timeInterval === 'auto_gf') {
83 | // Use intervalMs for panel, provided by Grafana
84 | interval = options.intervalMs;
85 | } else {
86 | interval = target.timeInterval;
87 | }
88 |
89 | // Split target into two queries (with aggs and raw data)
90 | query = this.queryBuilder.buildAggQuery(target, interval, adhocFilters);
91 | queryTarget = _.cloneDeep(target);
92 | queryTarget.metricAggs = getNotRawAggs(queryTarget.metricAggs);
93 |
94 | rawAggQuery = this.queryBuilder.buildRawAggQuery(target, 0, adhocFilters, maxLimit);
95 | rawAggQuery = this.templateSrv.replace(rawAggQuery, scopedVars, formatCrateValue);
96 | rawAggTarget = _.cloneDeep(target);
97 | rawAggTarget.metricAggs = getRawAggs(rawAggTarget.metricAggs);
98 | }
99 |
100 | query = this.templateSrv.replace(query, scopedVars, formatCrateValue);
101 |
102 | let queries = [
103 | {query: query, target: queryTarget},
104 | {query: rawAggQuery, target: rawAggTarget}
105 | ];
106 | queries = _.filter(queries, q => {
107 | return q.query;
108 | });
109 |
110 | return _.map(queries, q => {
111 | return this._sql_query(q.query, [timeFrom, timeTo])
112 | .then(result => {
113 | if (q.target) {
114 | return handleResponse(q.target, result);
115 | } else {
116 | return handleResponse(target, result);
117 | }
118 | });
119 | })
120 | });
121 | return this.$q.all(_.flattenDepth(queries, 2)).then(result => {
122 | return {
123 | data: _.flatten(result)
124 | };
125 | });
126 | }
127 |
128 | /**
129 | * Required.
130 | * Checks datasource and returns Crate cluster name and version or
131 | * error details.
132 | */
133 | testDatasource() {
134 | return this._get()
135 | .then(response => {
136 | if (response.$$status === 200) {
137 | return {
138 | status: "success",
139 | message: "Cluster: " + response.cluster_name +
140 | ", version: " + response.version.number,
141 | title: "Success"
142 | };
143 | }
144 | })
145 | .catch(error => {
146 | let message = error.statusText ? error.statusText + ': ' : '';
147 | if (error.data && error.data.error) {
148 | message += error.data.error;
149 | } else if (error.data) {
150 | message += error.data;
151 | } else {
152 | message = "Can't connect to Crate instance";
153 | }
154 | return {
155 | status: "error",
156 | message: message,
157 | title: "Error"
158 | };
159 | });
160 | }
161 |
162 | metricFindQuery(query: string) {
163 | if (!query) {
164 | return this.$q.when([]);
165 | }
166 |
167 | query = this.templateSrv.replace(query, null, formatCrateValue);
168 | return this._sql_query(query).then(result => {
169 | return _.map(_.flatten(result.rows), row => {
170 | return {
171 | text: row.toString(),
172 | value: row
173 | };
174 | });
175 | });
176 | }
177 |
178 | getTimeFilter(timeFrom, timeTo) {
179 | return this.defaultTimeColumn + " >= '" + timeFrom + "' and " + this.defaultTimeColumn + " <= '" + timeTo + "'";
180 | }
181 |
182 | getTagKeys(options) {
183 | let query = this.queryBuilder.getColumnsQuery();
184 | return this.metricFindQuery(query);
185 | }
186 |
187 | getTagValues(options) {
188 | let range = this.timeSrv.timeRange();
189 | let query = this.queryBuilder.getValuesQuery(options.key, this.CRATE_ROWS_LIMIT, range);
190 | return this.metricFindQuery(query);
191 | }
192 |
193 | setScopedVars(scopedVars) {
194 | scopedVars.crate_schema = {text: this.schema, value: `"${this.schema}"`};
195 | scopedVars.crate_table = {text: this.table, value: `"${this.table}"`};
196 |
197 | let crate_source = `"${this.schema}"."${this.table}"`;
198 | scopedVars.crate_source = {text: crate_source, value: crate_source};
199 |
200 | return scopedVars;
201 | }
202 |
203 | /**
204 | * Sends SQL query to Crate and returns result.
205 | * @param {string} query SQL query string
206 | * @param {any[]} args Optional query arguments
207 | * @return
208 | */
209 | _sql_query(query: string, args: any[] = []) {
210 | let data = {
211 | "stmt": query,
212 | "args": args
213 | };
214 |
215 | if (this.checkQuerySource) {
216 | // Checks schema and table and throw error if it different from configured in data source
217 | this.checkSQLSource(query);
218 | }
219 |
220 | return this._post('_sql', data);
221 | }
222 |
223 | checkSQLSource(query) {
224 | let source_pattern = /.*[Ff][Rr][Oo][Mm]\s"?([^\.\s\"]*)"?\.?"?([^\.\s\"]*)"?/;
225 | let match = query.match(source_pattern);
226 | let schema = match[1];
227 | let table = match[2];
228 | if (schema !== this.schema || table !== this.table) {
229 | throw { message: `Schema and table should be ${this.schema}.${this.table}` };
230 | }
231 | }
232 |
233 | _request(method: string, url: string, data?: any) {
234 | let options = {
235 | url: this.url + "/" + url,
236 | method: method,
237 | data: data,
238 | headers: {
239 | "Content-Type": "application/json"
240 | }
241 | };
242 |
243 | if (this.basicAuth || this.withCredentials) {
244 | options["withCredentials"] = true;
245 | }
246 | if (this.basicAuth) {
247 | options.headers["Authorization"] = this.basicAuth;
248 | }
249 |
250 | return this.backendSrv.datasourceRequest(options)
251 | .then(response => {
252 | response.data.$$status = response.status;
253 | response.data.$$config = response.config;
254 | return response.data;
255 | });
256 | }
257 |
258 | _get(url = "") {
259 | return this._request('GET', url);
260 | }
261 |
262 | _post(url: string, data?: any) {
263 | return this._request('POST', url, data);
264 | }
265 | }
266 |
267 | // Special value formatter for Crate.
268 | function formatCrateValue(value) {
269 | if (typeof value === 'string') {
270 | return wrapWithQuotes(value);
271 | } else {
272 | return value.map(v => wrapWithQuotes(v)).join(', ');
273 | }
274 | }
275 |
276 | function wrapWithQuotes(value) {
277 | if (!isNaN(value) ||
278 | value.indexOf("'") != -1 ||
279 | value.indexOf('"') != -1) {
280 | return value;
281 | } else {
282 | return "'" + value + "'";
283 | }
284 | }
285 |
286 | export function convertToCrateInterval(grafanaInterval) {
287 | let crateIntervals = [
288 | {shorthand: 's', value: 'second'},
289 | {shorthand: 'm', value: 'minute'},
290 | {shorthand: 'h', value: 'hour'},
291 | {shorthand: 'd', value: 'day'},
292 | {shorthand: 'w', value: 'week'},
293 | {shorthand: 'M', value: 'month'},
294 | {shorthand: 'y', value: 'year'}
295 | ];
296 | let intervalRegex = /([\d]*)([smhdwMy])/;
297 | let parsedInterval = intervalRegex.exec(grafanaInterval);
298 | let value = Number(parsedInterval[1]);
299 | let unit = parsedInterval[2];
300 | let crateInterval = _.find(crateIntervals, {'shorthand': unit});
301 | return crateInterval ? crateInterval.value : undefined;
302 | }
303 |
304 | function crateToMsInterval(crateInterval: string) {
305 | let intervals_s = {
306 | 'year': 60 * 60 * 24 * 30 * 12,
307 | 'quarter': 60 * 60 * 24 * 30 * 3,
308 | 'month': 60 * 60 * 24 * 30,
309 | 'week': 60 * 60 * 24 * 7,
310 | 'day': 60 * 60 * 24,
311 | 'hour': 60 * 60,
312 | 'minute': 60,
313 | 'second': 1
314 | };
315 |
316 | if (intervals_s[crateInterval]) {
317 | return intervals_s[crateInterval] * 1000; // Return ms
318 | } else {
319 | return undefined;
320 | }
321 | }
322 |
323 | function getMinCrateInterval(ms) {
324 | let seconds = ms / 1000;
325 | if (seconds > 60 * 60 * 24 * 30 * 3)
326 | return 'year';
327 | else if (seconds > 60 * 60 * 24 * 30) // TODO: check defenition of month interval
328 | return 'quarter';
329 | else if (seconds > 60 * 60 * 24 * 7)
330 | return 'month';
331 | else if (seconds > 60 * 60 * 24)
332 | return 'week';
333 | else if (seconds > 60 * 60)
334 | return 'day';
335 | else if (seconds > 60)
336 | return 'hour';
337 | else if (seconds > 1)
338 | return 'second';
339 | else
340 | return 'second';
341 | }
342 |
--------------------------------------------------------------------------------
/src/datasource.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import _ from 'lodash';
4 | import * as dateMath from 'app/core/utils/datemath';
5 | import moment from 'moment';
6 | import {CrateQueryBuilder, getEnabledAggs, getRawAggs, getNotRawAggs} from './query_builder';
7 | import handleResponse from './response_handler';
8 |
9 | export class CrateDatasource {
10 | type: string;
11 | url: string;
12 | name: string;
13 | basicAuth: string;
14 | withCredentials: boolean;
15 | schema: string;
16 | table: string;
17 | defaultTimeColumn: string;
18 | defaultGroupInterval: string;
19 | checkQuerySource: boolean;
20 | queryBuilder: CrateQueryBuilder;
21 | CRATE_ROWS_LIMIT: number;
22 |
23 |
24 | constructor(instanceSettings,
25 | private $q,
26 | private backendSrv,
27 | private templateSrv,
28 | private timeSrv) {
29 |
30 | this.type = instanceSettings.type;
31 | this.url = instanceSettings.url;
32 | this.name = instanceSettings.name;
33 | this.basicAuth = instanceSettings.basicAuth;
34 | this.withCredentials = instanceSettings.withCredentials;
35 | this.schema = instanceSettings.jsonData.schema;
36 | this.table = instanceSettings.jsonData.table;
37 | this.defaultTimeColumn = instanceSettings.jsonData.timeColumn;
38 | this.defaultGroupInterval = instanceSettings.jsonData.timeInterval;
39 | this.checkQuerySource = instanceSettings.jsonData.checkQuerySource;
40 |
41 | this.$q = $q;
42 | this.backendSrv = backendSrv;
43 | this.templateSrv = templateSrv;
44 | this.timeSrv = timeSrv;
45 |
46 | this.queryBuilder = new CrateQueryBuilder(this.schema,
47 | this.table,
48 | this.defaultTimeColumn,
49 | this.defaultGroupInterval,
50 | this.templateSrv);
51 |
52 | this.CRATE_ROWS_LIMIT = 10000;
53 | }
54 |
55 | // Called once per panel (graph)
56 | query(options) {
57 | let timeFrom = Math.ceil(dateMath.parse(options.range.from));
58 | let timeTo = Math.ceil(dateMath.parse(options.range.to));
59 | let timeFilter = this.getTimeFilter(timeFrom, timeTo);
60 | let scopedVars = this.setScopedVars(options.scopedVars);
61 |
62 | let queries = _.map(options.targets, target => {
63 | if (target.hide || (target.rawQuery && !target.query)) { return []; }
64 |
65 | let query: string;
66 | let rawAggQuery: string;
67 | let queryTarget, rawAggTarget;
68 | let getQuery: any;
69 | let getRawAggQuery: any;
70 | let getRawAggInterval: any;
71 | let adhocFilters = this.templateSrv.getAdhocFilters(this.name);
72 |
73 | if (target.rawQuery) {
74 | query = target.query;
75 | } else {
76 | let minInterval = Math.ceil((timeTo - timeFrom) / this.CRATE_ROWS_LIMIT);
77 | let maxLimit = timeTo - timeFrom;
78 | let interval;
79 |
80 | if (target.timeInterval === 'auto') {
81 | interval = getMinCrateInterval(options.intervalMs);
82 | } else if (target.timeInterval === 'auto_gf') {
83 | // Use intervalMs for panel, provided by Grafana
84 | interval = options.intervalMs;
85 | } else {
86 | interval = target.timeInterval;
87 | }
88 |
89 | // Split target into two queries (with aggs and raw data)
90 | query = this.queryBuilder.buildAggQuery(target, interval, adhocFilters);
91 | queryTarget = _.cloneDeep(target);
92 | queryTarget.metricAggs = getNotRawAggs(queryTarget.metricAggs);
93 |
94 | rawAggQuery = this.queryBuilder.buildRawAggQuery(target, 0, adhocFilters, maxLimit);
95 | rawAggQuery = this.templateSrv.replace(rawAggQuery, scopedVars, formatCrateValue);
96 | rawAggTarget = _.cloneDeep(target);
97 | rawAggTarget.metricAggs = getRawAggs(rawAggTarget.metricAggs);
98 | }
99 |
100 | query = this.templateSrv.replace(query, scopedVars, formatCrateValue);
101 |
102 | let queries = [
103 | {query: query, target: queryTarget},
104 | {query: rawAggQuery, target: rawAggTarget}
105 | ];
106 | queries = _.filter(queries, q => {
107 | return q.query;
108 | });
109 |
110 | return _.map(queries, q => {
111 | return this._sql_query(q.query, [timeFrom, timeTo])
112 | .then(result => {
113 | if (q.target) {
114 | return handleResponse(q.target, result);
115 | } else {
116 | return handleResponse(target, result);
117 | }
118 | });
119 | })
120 | });
121 | return this.$q.all(_.flattenDepth(queries, 2)).then(result => {
122 | return {
123 | data: _.flatten(result)
124 | };
125 | });
126 | }
127 |
128 | /**
129 | * Required.
130 | * Checks datasource and returns Crate cluster name and version or
131 | * error details.
132 | */
133 | testDatasource() {
134 | return this._get()
135 | .then(response => {
136 | if (response.$$status === 200) {
137 | return {
138 | status: "success",
139 | message: "Cluster: " + response.cluster_name +
140 | ", version: " + response.version.number,
141 | title: "Success"
142 | };
143 | }
144 | })
145 | .catch(error => {
146 | let message = error.statusText ? error.statusText + ': ' : '';
147 | if (error.data && error.data.error) {
148 | message += error.data.error;
149 | } else if (error.data) {
150 | message += error.data;
151 | } else {
152 | message = "Can't connect to Crate instance";
153 | }
154 | return {
155 | status: "error",
156 | message: message,
157 | title: "Error"
158 | };
159 | });
160 | }
161 |
162 | metricFindQuery(query: string) {
163 | if (!query) {
164 | return this.$q.when([]);
165 | }
166 |
167 | query = this.templateSrv.replace(query, null, formatCrateValue);
168 | return this._sql_query(query).then(result => {
169 | return _.map(_.flatten(result.rows), row => {
170 | return {
171 | text: row.toString(),
172 | value: row
173 | };
174 | });
175 | });
176 | }
177 |
178 | getTimeFilter(timeFrom, timeTo) {
179 | return this.defaultTimeColumn + " >= '" + timeFrom + "' and " + this.defaultTimeColumn + " <= '" + timeTo + "'";
180 | }
181 |
182 | getTagKeys(options) {
183 | let query = this.queryBuilder.getColumnsQuery();
184 | return this.metricFindQuery(query);
185 | }
186 |
187 | getTagValues(options) {
188 | let range = this.timeSrv.timeRange();
189 | let query = this.queryBuilder.getValuesQuery(options.key, this.CRATE_ROWS_LIMIT, range);
190 | return this.metricFindQuery(query);
191 | }
192 |
193 | setScopedVars(scopedVars) {
194 | scopedVars.crate_schema = {text: this.schema, value: `"${this.schema}"`};
195 | scopedVars.crate_table = {text: this.table, value: `"${this.table}"`};
196 |
197 | let crate_source = `"${this.schema}"."${this.table}"`;
198 | scopedVars.crate_source = {text: crate_source, value: crate_source};
199 |
200 | return scopedVars;
201 | }
202 |
203 | /**
204 | * Sends SQL query to Crate and returns result.
205 | * @param {string} query SQL query string
206 | * @param {any[]} args Optional query arguments
207 | * @return
208 | */
209 | _sql_query(query: string, args: any[] = []) {
210 | let data = {
211 | "stmt": query,
212 | "args": args
213 | };
214 |
215 | if (this.checkQuerySource) {
216 | // Checks schema and table and throw error if it different from configured in data source
217 | this.checkSQLSource(query);
218 | }
219 |
220 | return this._post('_sql', data);
221 | }
222 |
223 | checkSQLSource(query) {
224 | let source_pattern = /.*[Ff][Rr][Oo][Mm]\s"?([^\.\s\"]*)"?\.?"?([^\.\s\"]*)"?/;
225 | let match = query.match(source_pattern);
226 | let schema = match[1];
227 | let table = match[2];
228 | if (schema !== this.schema || table !== this.table) {
229 | throw { message: `Schema and table should be ${this.schema}.${this.table}` };
230 | }
231 | }
232 |
233 | _request(method: string, url: string, data?: any) {
234 | let options = {
235 | url: this.url + "/" + url,
236 | method: method,
237 | data: data,
238 | headers: {
239 | "Content-Type": "application/json"
240 | }
241 | };
242 |
243 | if (this.basicAuth || this.withCredentials) {
244 | options["withCredentials"] = true;
245 | }
246 | if (this.basicAuth) {
247 | options.headers["Authorization"] = this.basicAuth;
248 | }
249 |
250 | return this.backendSrv.datasourceRequest(options)
251 | .then(response => {
252 | response.data.$$status = response.status;
253 | response.data.$$config = response.config;
254 | return response.data;
255 | });
256 | }
257 |
258 | _get(url = "") {
259 | return this._request('GET', url);
260 | }
261 |
262 | _post(url: string, data?: any) {
263 | return this._request('POST', url, data);
264 | }
265 | }
266 |
267 | // Special value formatter for Crate.
268 | function formatCrateValue(value) {
269 | if (typeof value === 'string') {
270 | return wrapWithQuotes(value);
271 | } else {
272 | return value.map(v => wrapWithQuotes(v)).join(', ');
273 | }
274 | }
275 |
276 | function wrapWithQuotes(value) {
277 | if (!isNaN(value) ||
278 | value.indexOf("'") != -1 ||
279 | value.indexOf('"') != -1) {
280 | return value;
281 | } else {
282 | return "'" + value + "'";
283 | }
284 | }
285 |
286 | export function convertToCrateInterval(grafanaInterval) {
287 | let crateIntervals = [
288 | {shorthand: 's', value: 'second'},
289 | {shorthand: 'm', value: 'minute'},
290 | {shorthand: 'h', value: 'hour'},
291 | {shorthand: 'd', value: 'day'},
292 | {shorthand: 'w', value: 'week'},
293 | {shorthand: 'M', value: 'month'},
294 | {shorthand: 'y', value: 'year'}
295 | ];
296 | let intervalRegex = /([\d]*)([smhdwMy])/;
297 | let parsedInterval = intervalRegex.exec(grafanaInterval);
298 | let value = Number(parsedInterval[1]);
299 | let unit = parsedInterval[2];
300 | let crateInterval = _.find(crateIntervals, {'shorthand': unit});
301 | return crateInterval ? crateInterval.value : undefined;
302 | }
303 |
304 | function crateToMsInterval(crateInterval: string) {
305 | let intervals_s = {
306 | 'year': 60 * 60 * 24 * 30 * 12,
307 | 'quarter': 60 * 60 * 24 * 30 * 3,
308 | 'month': 60 * 60 * 24 * 30,
309 | 'week': 60 * 60 * 24 * 7,
310 | 'day': 60 * 60 * 24,
311 | 'hour': 60 * 60,
312 | 'minute': 60,
313 | 'second': 1
314 | };
315 |
316 | if (intervals_s[crateInterval]) {
317 | return intervals_s[crateInterval] * 1000; // Return ms
318 | } else {
319 | return undefined;
320 | }
321 | }
322 |
323 | function getMinCrateInterval(ms) {
324 | let seconds = ms / 1000;
325 | if (seconds > 60 * 60 * 24 * 30 * 3)
326 | return 'year';
327 | else if (seconds > 60 * 60 * 24 * 30) // TODO: check defenition of month interval
328 | return 'quarter';
329 | else if (seconds > 60 * 60 * 24 * 7)
330 | return 'month';
331 | else if (seconds > 60 * 60 * 24)
332 | return 'week';
333 | else if (seconds > 60 * 60)
334 | return 'day';
335 | else if (seconds > 60)
336 | return 'hour';
337 | else if (seconds > 1)
338 | return 'second';
339 | else
340 | return 'second';
341 | }
342 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/dist/datasource.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"datasource.js","sourceRoot":"","sources":["datasource.ts"],"names":["formatCrateValue","wrapWithQuotes","convertToCrateInterval","crateToMsInterval","getMinCrateInterval","CrateDatasource","CrateDatasource.constructor","CrateDatasource.query","CrateDatasource.testDatasource","CrateDatasource.metricFindQuery","CrateDatasource.getTimeFilter","CrateDatasource.getTagKeys","CrateDatasource.getTagValues","CrateDatasource.setScopedVars","CrateDatasource._sql_query","CrateDatasource.checkSQLSource","CrateDatasource._request","CrateDatasource._get","CrateDatasource._post"],"mappings":"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AMA,CAACA,CAACA,CAACA,KAAKA,CAACA;wBACjBA,CAACA,CAACA,CAACA;wBAEHA,MAAMA,CAACA,mBAACA,CAACA,GAAGA,CAACA,OAAOA,EAAEA,UAAAA,CAACA;4BACrBA,MAAMA,CAACA,KAAIA,CAACA,UAAUA,CAACA,CAACA,CAACA,KAAKA,EAAEA,CAACA,QAAQA,EAAEA,MAAMA,CAACA,CAACA;iCAChDA,IAAIA,CAACA,UAAAA,MAAMA;gCACVA,EAAEA,CAACA,CAACA,CAACA,CAACA,MAAMA,CAACA,CAACA,CAACA;oCACbA,MAAMA,CAACA,6BAAcA,CAACA,CAACA,CAACA,MAAMA,EAAEA,MAAMA,CAACA,CAACA;gCAC1CA,CAACA;gCAACA,IAAIA,CAACA,CAACA;oCACNA,MAAMA,CAACA,6BAAcA,CAACA,MAAMA,EAAEA,MAAMA,CAACA,CAACA;gCACxCA,CAACA;4BACHA,CAACA,CAACA,CAACA;wBACPA,CAACA,CAACA,CAAAA;oBACJA,CAACA,CAACA,CAACA;oBACHA,MAAMA,CAACA,IAAIA,CAACA,EAAEA,CAACA,GAAGA,CAACA,mBAACA,CAACA,YAAYA,CAACA,OAAOA,EAAEA,CAACA,CAACA,CAACA,CAACA,IAAIA,CAACA,UAAAA,MAAMA;wBACxDA,MAAMA,CAACA;4BACLA,IAAIA,EAAEA,mBAACA,CAACA,OAAOA,CAACA,MAAMA,CAACA;yBACxBA,CAACA;oBACJA,CAACA,CAACA,CAACA;gBACLA,CAACA;gBAEDF;;;;mBAIGA;gBACHA,wCAAcA,GAAdA;oBACEG,MAAMA,CAACA,IAAIA,CAACA,IAAIA,EAAEA;yBACjBA,IAAIA,CAACA,UAAAA,QAAQA;wBACZA,EAAEA,CAACA,CAACA,QAAQA,CAACA,QAAQA,KAAKA,GAAGA,CAACA,CAACA,CAACA;4BAC9BA,MAAMA,CAACA;gCACLA,MAAMA,EAAEA,SAASA;gCACjBA,OAAOA,EAAEA,WAAWA,GAAGA,QAAQA,CAACA,YAAYA;oCAC1CA,aAAaA,GAAGA,QAAQA,CAACA,OAAOA,CAACA,MAAMA;gCACzCA,KAAKA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"}
--------------------------------------------------------------------------------
/dist/query_builder.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import _ from 'lodash';
4 |
5 | // Maximum LIMIT value
6 | let MAX_LIMIT = 100000;
7 | let DEFAULT_LIMIT = 10000;
8 |
9 | export class CrateQueryBuilder {
10 | schema: string;
11 | table: string;
12 | defaultTimeColumn: string;
13 | defaultGroupInterval: string;
14 |
15 | constructor(schema: string,
16 | table: string,
17 | defaultTimeColumn: string,
18 | defaultGroupInterval: string,
19 | private templateSrv) {
20 | this.schema = schema;
21 | this.table = table;
22 | this.defaultTimeColumn = defaultTimeColumn;
23 | this.defaultGroupInterval = defaultGroupInterval;
24 | this.templateSrv = templateSrv;
25 | }
26 |
27 | /**
28 | * Builds Crate SQL query from given target object.
29 | * @param {any} target Target object.
30 | * @param {number} groupInterval Interval for grouping values.
31 | * @param {string} defaultAgg Default aggregation for values.
32 | * @return {string} SQL query.
33 | */
34 | build(target: any, groupInterval = 0, adhocFilters = [], limit = DEFAULT_LIMIT, defaultAgg='avg') {
35 | let query: string;
36 | let timeExp: string;
37 |
38 | let timeColumn = quoteColumn(this.defaultTimeColumn);
39 | let aggs = getEnabledAggs(target.metricAggs);
40 | let rawAggs = getRawAggs(aggs);
41 |
42 | if (!aggs.length) { return null; }
43 |
44 | if (groupInterval) {
45 | // Manually aggregate by time interval, ie "SELECT floor(ts/10)*10 as time ..."
46 | timeExp = `floor(${timeColumn}/${groupInterval})*${groupInterval}`;
47 | aggs = aggregateMetrics(aggs, 'avg');
48 | } else {
49 | timeExp = timeColumn;
50 | }
51 |
52 | // SELECT
53 | let renderedAggs = this.renderMetricAggs(aggs);
54 | query = "SELECT " + timeExp + " as time, " + renderedAggs;
55 |
56 | // Add GROUP BY columns to SELECT statement.
57 | if (target.groupByColumns && target.groupByColumns.length) {
58 | query += ", " + target.groupByColumns.join(', ');
59 | }
60 | query += ` FROM "${this.schema}"."${this.table}"` +
61 | ` WHERE ${timeColumn} >= ? AND ${timeColumn} <= ?`;
62 |
63 | // WHERE
64 | if (target.whereClauses && target.whereClauses.length) {
65 | query += " AND " + this.renderWhereClauses(target.whereClauses);
66 | }
67 |
68 | // Add ad-hoc filters
69 | if (adhocFilters.length > 0) {
70 | query += " AND " + this.renderAdhocFilters(adhocFilters);
71 | }
72 |
73 | // GROUP BY
74 | query += " GROUP BY time";
75 | if (!groupInterval && rawAggs.length) {
76 | query += ", " + this.renderMetricAggs(rawAggs, false);
77 | }
78 |
79 | if (target.groupByColumns && target.groupByColumns.length) {
80 | query += ", " + target.groupByColumns.join(', ');
81 | }
82 |
83 | // If GROUP BY specified, sort also by selected columns
84 | query += " ORDER BY time";
85 | if (target.groupByColumns && target.groupByColumns.length) {
86 | query += ", " + target.groupByColumns.join(', ');
87 | }
88 | query += " ASC";
89 |
90 | return query;
91 | }
92 |
93 | buildAggQuery(target: any, groupInterval = 0, adhocFilters = [], limit?: number) {
94 | let query: string;
95 | let timeExp: string;
96 |
97 | let timeColumn = quoteColumn(this.defaultTimeColumn);
98 | let aggs = getEnabledAggs(target.metricAggs);
99 | aggs = getNotRawAggs(aggs);
100 |
101 | if (!aggs.length) { return null; }
102 |
103 | if (!groupInterval) {
104 | groupInterval = 1;
105 | }
106 |
107 | if (typeof groupInterval === 'number') {
108 | // Manually aggregate by time interval, ie "SELECT floor(ts/10)*10 as time ..."
109 | timeExp = `floor(${timeColumn}/${groupInterval})*${groupInterval}`;
110 | } else {
111 | // Use built-in date_trunc() function
112 | timeExp = `date_trunc('${groupInterval}', ${timeColumn})`;
113 | }
114 |
115 | // SELECT
116 | let renderedAggs = this.renderMetricAggs(aggs);
117 | query = `SELECT ${timeExp} as time, ${renderedAggs}`;
118 |
119 | // Add GROUP BY columns to SELECT statement.
120 | if (target.groupByColumns && target.groupByColumns.length) {
121 | query += ", " + target.groupByColumns.join(', ');
122 | }
123 |
124 | // FROM
125 | query += ` FROM "${this.schema}"."${this.table}"`;
126 |
127 | // WHERE
128 | query += ` WHERE ${timeColumn} >= ? AND ${timeColumn} <= ?`;
129 | if (target.whereClauses && target.whereClauses.length) {
130 | query += " AND " + this.renderWhereClauses(target.whereClauses);
131 | }
132 |
133 | // Add ad-hoc filters
134 | if (adhocFilters.length > 0) {
135 | query += " AND " + this.renderAdhocFilters(adhocFilters);
136 | }
137 |
138 | // GROUP BY
139 | query += " GROUP BY time";
140 |
141 | if (target.groupByColumns && target.groupByColumns.length) {
142 | query += ", " + target.groupByColumns.join(', ');
143 | }
144 |
145 | // If GROUP BY specified, sort also by selected columns
146 | query += " ORDER BY time";
147 | if (target.groupByColumns && target.groupByColumns.length) {
148 | query += ", " + target.groupByColumns.join(', ');
149 | }
150 | query += " ASC";
151 |
152 | if (limit && limit > DEFAULT_LIMIT) {
153 | limit = Math.min(limit, MAX_LIMIT);
154 | query += ` LIMIT ${limit}`;
155 | }
156 |
157 | return query;
158 | }
159 |
160 | buildRawAggQuery(target: any, groupInterval = 0, adhocFilters = [], limit?: number) {
161 | let query: string;
162 | let timeExp: string;
163 |
164 | let timeColumn = quoteColumn(this.defaultTimeColumn);
165 | let aggs = getEnabledAggs(target.metricAggs);
166 | let rawAggs = getRawAggs(aggs);
167 |
168 | if (!rawAggs.length) { return null; }
169 |
170 | // SELECT
171 | let renderedAggs = this.renderMetricAggs(rawAggs);
172 | query = "SELECT " + timeColumn + " as time, " + renderedAggs;
173 |
174 | // Add GROUP BY columns to SELECT statement.
175 | if (target.groupByColumns && target.groupByColumns.length) {
176 | query += ", " + target.groupByColumns.join(', ');
177 | }
178 | query += ` FROM "${this.schema}"."${this.table}"` +
179 | ` WHERE ${timeColumn} >= ? AND ${timeColumn} <= ?`;
180 |
181 | // WHERE
182 | if (target.whereClauses && target.whereClauses.length) {
183 | query += " AND " + this.renderWhereClauses(target.whereClauses);
184 | }
185 |
186 | // Add ad-hoc filters
187 | if (adhocFilters.length > 0) {
188 | query += " AND " + this.renderAdhocFilters(adhocFilters);
189 | }
190 |
191 | // GROUP BY
192 | query += " GROUP BY time";
193 | query += ", " + this.renderMetricAggs(rawAggs, false);
194 |
195 | if (target.groupByColumns && target.groupByColumns.length) {
196 | query += ", " + target.groupByColumns.join(', ');
197 | }
198 |
199 | // If GROUP BY specified, sort also by selected columns
200 | query += " ORDER BY time";
201 | if (target.groupByColumns && target.groupByColumns.length) {
202 | query += ", " + target.groupByColumns.join(', ');
203 | }
204 | query += " ASC";
205 |
206 | if (limit) {
207 | limit = Math.min(limit, MAX_LIMIT);
208 | query += ` LIMIT ${limit}`;
209 | }
210 |
211 | return query;
212 | }
213 |
214 | renderAdhocFilters(filters) {
215 | let conditions = _.map(filters, (tag, index) => {
216 | let filter_str = "";
217 | let condition = tag.condition || 'AND';
218 | let key = quoteColumn(tag.key);
219 | let operator = tag.operator;
220 | let value = quoteValue(tag.value);
221 |
222 | if (index > 0) {
223 | filter_str = `${condition} `;
224 | }
225 |
226 | if (operator === '=~') {
227 | operator = '~';
228 | }
229 |
230 | filter_str += `${key} ${operator} ${value}`
231 | return filter_str;
232 | });
233 | return conditions.join(' ');
234 | }
235 |
236 | /**
237 | * Builds SQL query for getting available columns from table.
238 | * @return {string} SQL query.
239 | */
240 | getColumnsQuery() {
241 | let query = "SELECT column_name " +
242 | "FROM information_schema.columns " +
243 | "WHERE table_schema = '" + this.schema + "' " +
244 | "AND table_name = '" + this.table + "' " +
245 | "ORDER BY 1";
246 | return query;
247 | }
248 |
249 | getNumericColumnsQuery() {
250 | return "SELECT column_name " +
251 | "FROM information_schema.columns " +
252 | "WHERE table_schema = '" + this.schema + "' " +
253 | "AND table_name = '" + this.table + "' " +
254 | "AND data_type in ('integer', 'long', 'short', 'double', 'float', 'byte') " +
255 | "ORDER BY 1";
256 | }
257 |
258 | /**
259 | * Builds SQL query for getting unique values for given column.
260 | * @param {string} column Column name
261 | * @param {number} limit Optional. Limit number returned values.
262 | */
263 | getValuesQuery(column: string, limit?: number, timeRange?) {
264 | let timeColumn = quoteColumn(this.defaultTimeColumn);
265 | let query = `SELECT DISTINCT ${column} ` +
266 | `FROM "${this.schema}"."${this.table}"`;
267 |
268 | if (timeRange) {
269 | let timeFrom = timeRange.from.valueOf();
270 | let timeTo = timeRange.to.valueOf();
271 | query += ` WHERE ${timeColumn} >= ${timeFrom} AND ${timeColumn} <= ${timeTo}`;
272 | }
273 |
274 | if (limit) {
275 | query += ` LIMIT ${limit}`;
276 | }
277 | return query;
278 | }
279 |
280 | private renderMetricAggs(metricAggs: any, withAlias=true): string {
281 | let enabledAggs = _.filter(metricAggs, (agg) => {
282 | return !agg.hide;
283 | });
284 |
285 | let renderedAggs = _.map(enabledAggs, (agg) => {
286 | let alias = '';
287 | if (agg.alias && withAlias) {
288 | alias = ' AS \"' + agg.alias + '\"';
289 | }
290 |
291 | let column = quoteColumn(agg.column);
292 | if (agg.type === 'count_distinct') {
293 | return "count(distinct " + column + ")" + alias;
294 | } else if (agg.type === 'raw') {
295 | return column + alias;
296 | } else {
297 | return agg.type + "(" + column + ")" + alias;
298 | }
299 | });
300 |
301 | if (renderedAggs.length) {
302 | return renderedAggs.join(', ');
303 | } else {
304 | return "";
305 | }
306 | }
307 |
308 | private renderWhereClauses(whereClauses): string {
309 | let renderedClauses = _.map(whereClauses, (clauseObj, index) => {
310 | let rendered = "";
311 | if (index !== 0) {
312 | rendered += clauseObj.condition + " ";
313 | }
314 |
315 | // Quote arguments as required by the operator and value type
316 | let rendered_value: string;
317 | let value = clauseObj.value;
318 | if (clauseObj.operator.toLowerCase() === 'in') {
319 | // Handle IN operator. Split comma-separated values.
320 | // "42, 10, a" => 42, 10, 'a'
321 | rendered_value = '(' + _.map(value.split(','), v => {
322 | v = v.trim();
323 | if (!isNaN(v) || this.containsVariable(v)) {
324 | return v;
325 | } else {
326 | return "'" + v + "'";
327 | }
328 | }).join(', ') + ')';
329 | } else {
330 | rendered_value = _.map(value.split(','), v => {
331 | v = v.trim();
332 | if (!isNaN(v) || this.containsVariable(v)) {
333 | return v;
334 | } else {
335 | return "'" + v + "'";
336 | }
337 | }).join(', ');
338 | }
339 | rendered += clauseObj.column + ' ' + clauseObj.operator + ' ' + rendered_value;
340 | return rendered;
341 | });
342 | return renderedClauses.join(' ');
343 | }
344 |
345 | // Check for template variables
346 | private containsVariable(str: string): boolean {
347 | let variables = _.map(this.templateSrv.variables, 'name');
348 | return _.some(variables, variable => {
349 | let pattern = new RegExp('\\$' + variable);
350 | return pattern.test(str);
351 | });
352 | }
353 | }
354 |
355 | export function getSchemas() {
356 | var query = "SELECT schema_name " +
357 | "FROM information_schema.schemata " +
358 | "WHERE schema_name NOT IN ('information_schema', 'blob', 'sys', 'pg_catalog') " +
359 | "ORDER BY 1";
360 | return query;
361 | }
362 |
363 | export function getTables(schema) {
364 | var query = "SELECT table_name " +
365 | "FROM information_schema.tables " +
366 | "WHERE table_schema='" + schema + "' " +
367 | "ORDER BY 1";
368 | return query;
369 | }
370 |
371 | function quoteColumn(column: string): string {
372 | if (isWithUpperCase(column)) {
373 | return '\"' + column + '\"';
374 | } else {
375 | return column;
376 | }
377 | }
378 |
379 | function quoteValue(value: string): string {
380 | value = value.trim();
381 | let match = value.match(/^'?([^']*)'?$/);
382 | if (match[1]) {
383 | value = match[1];
384 | } else {
385 | return value;
386 | }
387 |
388 | if (!isNaN(Number(value))) {
389 | return value;
390 | } else {
391 | return "'" + value + "'";
392 | }
393 | }
394 |
395 | function isWithUpperCase(str: string): boolean {
396 | return str.toLowerCase() !== str;
397 | }
398 |
399 | function aggregateMetrics(metricAggs: any, aggType: string) {
400 | let aggs = _.cloneDeep(metricAggs);
401 | return _.map(aggs, agg => {
402 | if (agg.type === 'raw') {
403 | agg.type = aggType;
404 | return agg;
405 | } else {
406 | return agg;
407 | }
408 | });
409 | }
410 |
411 | export function getEnabledAggs(metricAggs) {
412 | return _.filter(metricAggs, (agg) => {
413 | return !agg.hide;
414 | });
415 | }
416 |
417 | export function getRawAggs(metricAggs) {
418 | return _.filter(metricAggs, {type: 'raw'});
419 | }
420 |
421 | export function getNotRawAggs(metricAggs) {
422 | return _.filter(metricAggs, agg => {
423 | return agg.type !== 'raw';
424 | });
425 | }
426 |
--------------------------------------------------------------------------------
/src/query_builder.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import _ from 'lodash';
4 |
5 | // Maximum LIMIT value
6 | let MAX_LIMIT = 100000;
7 | let DEFAULT_LIMIT = 10000;
8 |
9 | export class CrateQueryBuilder {
10 | schema: string;
11 | table: string;
12 | defaultTimeColumn: string;
13 | defaultGroupInterval: string;
14 |
15 | constructor(schema: string,
16 | table: string,
17 | defaultTimeColumn: string,
18 | defaultGroupInterval: string,
19 | private templateSrv) {
20 | this.schema = schema;
21 | this.table = table;
22 | this.defaultTimeColumn = defaultTimeColumn;
23 | this.defaultGroupInterval = defaultGroupInterval;
24 | this.templateSrv = templateSrv;
25 | }
26 |
27 | /**
28 | * Builds Crate SQL query from given target object.
29 | * @param {any} target Target object.
30 | * @param {number} groupInterval Interval for grouping values.
31 | * @param {string} defaultAgg Default aggregation for values.
32 | * @return {string} SQL query.
33 | */
34 | build(target: any, groupInterval = 0, adhocFilters = [], limit = DEFAULT_LIMIT, defaultAgg='avg') {
35 | let query: string;
36 | let timeExp: string;
37 |
38 | let timeColumn = quoteColumn(this.defaultTimeColumn);
39 | let aggs = getEnabledAggs(target.metricAggs);
40 | let rawAggs = getRawAggs(aggs);
41 |
42 | if (!aggs.length) { return null; }
43 |
44 | if (groupInterval) {
45 | // Manually aggregate by time interval, ie "SELECT floor(ts/10)*10 as time ..."
46 | timeExp = `floor(${timeColumn}/${groupInterval})*${groupInterval}`;
47 | aggs = aggregateMetrics(aggs, 'avg');
48 | } else {
49 | timeExp = timeColumn;
50 | }
51 |
52 | // SELECT
53 | let renderedAggs = this.renderMetricAggs(aggs);
54 | query = "SELECT " + timeExp + " as time, " + renderedAggs;
55 |
56 | // Add GROUP BY columns to SELECT statement.
57 | if (target.groupByColumns && target.groupByColumns.length) {
58 | query += ", " + target.groupByColumns.join(', ');
59 | }
60 | query += ` FROM "${this.schema}"."${this.table}"` +
61 | ` WHERE ${timeColumn} >= ? AND ${timeColumn} <= ?`;
62 |
63 | // WHERE
64 | if (target.whereClauses && target.whereClauses.length) {
65 | query += " AND " + this.renderWhereClauses(target.whereClauses);
66 | }
67 |
68 | // Add ad-hoc filters
69 | if (adhocFilters.length > 0) {
70 | query += " AND " + this.renderAdhocFilters(adhocFilters);
71 | }
72 |
73 | // GROUP BY
74 | query += " GROUP BY time";
75 | if (!groupInterval && rawAggs.length) {
76 | query += ", " + this.renderMetricAggs(rawAggs, false);
77 | }
78 |
79 | if (target.groupByColumns && target.groupByColumns.length) {
80 | query += ", " + target.groupByColumns.join(', ');
81 | }
82 |
83 | // If GROUP BY specified, sort also by selected columns
84 | query += " ORDER BY time";
85 | if (target.groupByColumns && target.groupByColumns.length) {
86 | query += ", " + target.groupByColumns.join(', ');
87 | }
88 | query += " ASC";
89 |
90 | return query;
91 | }
92 |
93 | buildAggQuery(target: any, groupInterval = 0, adhocFilters = [], limit?: number) {
94 | let query: string;
95 | let timeExp: string;
96 |
97 | let timeColumn = quoteColumn(this.defaultTimeColumn);
98 | let aggs = getEnabledAggs(target.metricAggs);
99 | aggs = getNotRawAggs(aggs);
100 |
101 | if (!aggs.length) { return null; }
102 |
103 | if (!groupInterval) {
104 | groupInterval = 1;
105 | }
106 |
107 | if (typeof groupInterval === 'number') {
108 | // Manually aggregate by time interval, ie "SELECT floor(ts/10)*10 as time ..."
109 | timeExp = `floor(${timeColumn}/${groupInterval})*${groupInterval}`;
110 | } else {
111 | // Use built-in date_trunc() function
112 | timeExp = `date_trunc('${groupInterval}', ${timeColumn})`;
113 | }
114 |
115 | // SELECT
116 | let renderedAggs = this.renderMetricAggs(aggs);
117 | query = `SELECT ${timeExp} as time, ${renderedAggs}`;
118 |
119 | // Add GROUP BY columns to SELECT statement.
120 | if (target.groupByColumns && target.groupByColumns.length) {
121 | query += ", " + target.groupByColumns.join(', ');
122 | }
123 |
124 | // FROM
125 | query += ` FROM "${this.schema}"."${this.table}"`;
126 |
127 | // WHERE
128 | query += ` WHERE ${timeColumn} >= ? AND ${timeColumn} <= ?`;
129 | if (target.whereClauses && target.whereClauses.length) {
130 | query += " AND " + this.renderWhereClauses(target.whereClauses);
131 | }
132 |
133 | // Add ad-hoc filters
134 | if (adhocFilters.length > 0) {
135 | query += " AND " + this.renderAdhocFilters(adhocFilters);
136 | }
137 |
138 | // GROUP BY
139 | query += " GROUP BY time";
140 |
141 | if (target.groupByColumns && target.groupByColumns.length) {
142 | query += ", " + target.groupByColumns.join(', ');
143 | }
144 |
145 | // If GROUP BY specified, sort also by selected columns
146 | query += " ORDER BY time";
147 | if (target.groupByColumns && target.groupByColumns.length) {
148 | query += ", " + target.groupByColumns.join(', ');
149 | }
150 | query += " ASC";
151 |
152 | if (limit && limit > DEFAULT_LIMIT) {
153 | limit = Math.min(limit, MAX_LIMIT);
154 | query += ` LIMIT ${limit}`;
155 | }
156 |
157 | return query;
158 | }
159 |
160 | buildRawAggQuery(target: any, groupInterval = 0, adhocFilters = [], limit?: number) {
161 | let query: string;
162 | let timeExp: string;
163 |
164 | let timeColumn = quoteColumn(this.defaultTimeColumn);
165 | let aggs = getEnabledAggs(target.metricAggs);
166 | let rawAggs = getRawAggs(aggs);
167 |
168 | if (!rawAggs.length) { return null; }
169 |
170 | // SELECT
171 | let renderedAggs = this.renderMetricAggs(rawAggs);
172 | query = "SELECT " + timeColumn + " as time, " + renderedAggs;
173 |
174 | // Add GROUP BY columns to SELECT statement.
175 | if (target.groupByColumns && target.groupByColumns.length) {
176 | query += ", " + target.groupByColumns.join(', ');
177 | }
178 | query += ` FROM "${this.schema}"."${this.table}"` +
179 | ` WHERE ${timeColumn} >= ? AND ${timeColumn} <= ?`;
180 |
181 | // WHERE
182 | if (target.whereClauses && target.whereClauses.length) {
183 | query += " AND " + this.renderWhereClauses(target.whereClauses);
184 | }
185 |
186 | // Add ad-hoc filters
187 | if (adhocFilters.length > 0) {
188 | query += " AND " + this.renderAdhocFilters(adhocFilters);
189 | }
190 |
191 | // GROUP BY
192 | query += " GROUP BY time";
193 | query += ", " + this.renderMetricAggs(rawAggs, false);
194 |
195 | if (target.groupByColumns && target.groupByColumns.length) {
196 | query += ", " + target.groupByColumns.join(', ');
197 | }
198 |
199 | // If GROUP BY specified, sort also by selected columns
200 | query += " ORDER BY time";
201 | if (target.groupByColumns && target.groupByColumns.length) {
202 | query += ", " + target.groupByColumns.join(', ');
203 | }
204 | query += " ASC";
205 |
206 | if (limit) {
207 | limit = Math.min(limit, MAX_LIMIT);
208 | query += ` LIMIT ${limit}`;
209 | }
210 |
211 | return query;
212 | }
213 |
214 | renderAdhocFilters(filters) {
215 | let conditions = _.map(filters, (tag, index) => {
216 | let filter_str = "";
217 | let condition = tag.condition || 'AND';
218 | let key = quoteColumn(tag.key);
219 | let operator = tag.operator;
220 | let value = quoteValue(tag.value);
221 |
222 | if (index > 0) {
223 | filter_str = `${condition} `;
224 | }
225 |
226 | if (operator === '=~') {
227 | operator = '~';
228 | }
229 |
230 | filter_str += `${key} ${operator} ${value}`
231 | return filter_str;
232 | });
233 | return conditions.join(' ');
234 | }
235 |
236 | /**
237 | * Builds SQL query for getting available columns from table.
238 | * @return {string} SQL query.
239 | */
240 | getColumnsQuery() {
241 | let query = "SELECT column_name " +
242 | "FROM information_schema.columns " +
243 | "WHERE table_schema = '" + this.schema + "' " +
244 | "AND table_name = '" + this.table + "' " +
245 | "ORDER BY 1";
246 | return query;
247 | }
248 |
249 | getNumericColumnsQuery() {
250 | return "SELECT column_name " +
251 | "FROM information_schema.columns " +
252 | "WHERE table_schema = '" + this.schema + "' " +
253 | "AND table_name = '" + this.table + "' " +
254 | "AND data_type in ('integer', 'long', 'short', 'double', 'float', 'byte') " +
255 | "ORDER BY 1";
256 | }
257 |
258 | /**
259 | * Builds SQL query for getting unique values for given column.
260 | * @param {string} column Column name
261 | * @param {number} limit Optional. Limit number returned values.
262 | */
263 | getValuesQuery(column: string, limit?: number, timeRange?) {
264 | let timeColumn = quoteColumn(this.defaultTimeColumn);
265 | let query = `SELECT DISTINCT ${column} ` +
266 | `FROM "${this.schema}"."${this.table}"`;
267 |
268 | if (timeRange) {
269 | let timeFrom = timeRange.from.valueOf();
270 | let timeTo = timeRange.to.valueOf();
271 | query += ` WHERE ${timeColumn} >= ${timeFrom} AND ${timeColumn} <= ${timeTo}`;
272 | }
273 |
274 | if (limit) {
275 | query += ` LIMIT ${limit}`;
276 | }
277 | return query;
278 | }
279 |
280 | private renderMetricAggs(metricAggs: any, withAlias=true): string {
281 | let enabledAggs = _.filter(metricAggs, (agg) => {
282 | return !agg.hide;
283 | });
284 |
285 | let renderedAggs = _.map(enabledAggs, (agg) => {
286 | let alias = '';
287 | if (agg.alias && withAlias) {
288 | alias = ' AS \"' + agg.alias + '\"';
289 | }
290 |
291 | let column = quoteColumn(agg.column);
292 | if (agg.type === 'count_distinct') {
293 | return "count(distinct " + column + ")" + alias;
294 | } else if (agg.type === 'raw') {
295 | return column + alias;
296 | } else {
297 | return agg.type + "(" + column + ")" + alias;
298 | }
299 | });
300 |
301 | if (renderedAggs.length) {
302 | return renderedAggs.join(', ');
303 | } else {
304 | return "";
305 | }
306 | }
307 |
308 | private renderWhereClauses(whereClauses): string {
309 | let renderedClauses = _.map(whereClauses, (clauseObj, index) => {
310 | let rendered = "";
311 | if (index !== 0) {
312 | rendered += clauseObj.condition + " ";
313 | }
314 |
315 | // Quote arguments as required by the operator and value type
316 | let rendered_value: string;
317 | let value = clauseObj.value;
318 | if (clauseObj.operator.toLowerCase() === 'in') {
319 | // Handle IN operator. Split comma-separated values.
320 | // "42, 10, a" => 42, 10, 'a'
321 | rendered_value = '(' + _.map(value.split(','), v => {
322 | v = v.trim();
323 | if (!isNaN(v) || this.containsVariable(v)) {
324 | return v;
325 | } else {
326 | return "'" + v + "'";
327 | }
328 | }).join(', ') + ')';
329 | } else {
330 | rendered_value = _.map(value.split(','), v => {
331 | v = v.trim();
332 | if (!isNaN(v) || this.containsVariable(v)) {
333 | return v;
334 | } else {
335 | return "'" + v + "'";
336 | }
337 | }).join(', ');
338 | }
339 | rendered += clauseObj.column + ' ' + clauseObj.operator + ' ' + rendered_value;
340 | return rendered;
341 | });
342 | return renderedClauses.join(' ');
343 | }
344 |
345 | // Check for template variables
346 | private containsVariable(str: string): boolean {
347 | let variables = _.map(this.templateSrv.variables, 'name');
348 | return _.some(variables, variable => {
349 | let pattern = new RegExp('\\$' + variable);
350 | return pattern.test(str);
351 | });
352 | }
353 | }
354 |
355 | export function getSchemas() {
356 | var query = "SELECT schema_name " +
357 | "FROM information_schema.schemata " +
358 | "WHERE schema_name NOT IN ('information_schema', 'blob', 'sys', 'pg_catalog') " +
359 | "ORDER BY 1";
360 | return query;
361 | }
362 |
363 | export function getTables(schema) {
364 | var query = "SELECT table_name " +
365 | "FROM information_schema.tables " +
366 | "WHERE table_schema='" + schema + "' " +
367 | "ORDER BY 1";
368 | return query;
369 | }
370 |
371 | function quoteColumn(column: string): string {
372 | if (isWithUpperCase(column)) {
373 | return '\"' + column + '\"';
374 | } else {
375 | return column;
376 | }
377 | }
378 |
379 | function quoteValue(value: string): string {
380 | value = value.trim();
381 | let match = value.match(/^'?([^']*)'?$/);
382 | if (match[1]) {
383 | value = match[1];
384 | } else {
385 | return value;
386 | }
387 |
388 | if (!isNaN(Number(value))) {
389 | return value;
390 | } else {
391 | return "'" + value + "'";
392 | }
393 | }
394 |
395 | function isWithUpperCase(str: string): boolean {
396 | return str.toLowerCase() !== str;
397 | }
398 |
399 | function aggregateMetrics(metricAggs: any, aggType: string) {
400 | let aggs = _.cloneDeep(metricAggs);
401 | return _.map(aggs, agg => {
402 | if (agg.type === 'raw') {
403 | agg.type = aggType;
404 | return agg;
405 | } else {
406 | return agg;
407 | }
408 | });
409 | }
410 |
411 | export function getEnabledAggs(metricAggs) {
412 | return _.filter(metricAggs, (agg) => {
413 | return !agg.hide;
414 | });
415 | }
416 |
417 | export function getRawAggs(metricAggs) {
418 | return _.filter(metricAggs, {type: 'raw'});
419 | }
420 |
421 | export function getNotRawAggs(metricAggs) {
422 | return _.filter(metricAggs, agg => {
423 | return agg.type !== 'raw';
424 | });
425 | }
426 |
--------------------------------------------------------------------------------