Skip to content
This repository was archived by the owner on Dec 11, 2022. It is now read-only.

Commit 5c2c799

Browse files
committed
Support time shift per query
Fixes #58
1 parent 1a48fb1 commit 5c2c799

File tree

8 files changed

+18113
-57
lines changed

8 files changed

+18113
-57
lines changed

dist/module.js

Lines changed: 17882 additions & 16 deletions
Large diffs are not rendered by default.

dist/module.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/partials/query.editor.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<div ng-if="!ctrl.target.rawQuery">
1414
<div class="gf-form-inline">
1515
<div class="gf-form">
16-
<label class="gf-form-label query-keyword width-6">FROM</label>
16+
<label class="gf-form-label query-keyword width-8">FROM</label>
1717
<metric-segment segment="ctrl.projectSegment"
1818
get-options="ctrl.getProjectSegments()"
1919
on-change="ctrl.projectChanged()"></metric-segment>
@@ -27,7 +27,7 @@
2727
<metric-segment segment="ctrl.timeColumnSegment" get-options="ctrl.getTimeColumnSegments()"
2828
on-change="ctrl.timeColumnChanged()"></metric-segment>
2929

30-
<label class="gf-form-label query-keyword width-9">
30+
<label class="gf-form-label query-keyword width-10">
3131
Metric column
3232
<info-popover mode="right-normal">Column to be used as metric name for the value column.
3333
</info-popover>
@@ -44,7 +44,7 @@
4444

4545
<div class="gf-form-inline" ng-repeat="selectParts in ctrl.selectParts">
4646
<div class="gf-form">
47-
<label class="gf-form-label query-keyword width-6">
47+
<label class="gf-form-label query-keyword width-8">
4848
<span ng-show="$index === 0">SELECT</span>&nbsp;
4949
</label>
5050
</div>
@@ -71,7 +71,7 @@
7171

7272
<div class="gf-form-inline">
7373
<div class="gf-form">
74-
<label class="gf-form-label query-keyword width-6">WHERE</label>
74+
<label class="gf-form-label query-keyword width-8">WHERE</label>
7575
</div>
7676

7777
<div class="gf-form" ng-repeat="part in ctrl.whereParts">
@@ -93,7 +93,7 @@
9393

9494
<div class="gf-form-inline">
9595
<div class="gf-form">
96-
<label class="gf-form-label query-keyword width-6">
96+
<label class="gf-form-label query-keyword width-8">
9797
<span>GROUP BY</span>
9898
</label>
9999

@@ -112,7 +112,6 @@
112112
<div class="gf-form-label gf-form-label--grow"></div>
113113
</div>
114114
</div>
115-
116115
</div>
117116

118117
<div class="gf-form-inline">
@@ -171,6 +170,7 @@
171170
Macros:
172171
- $__timeFilter(column) -&gt; column BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:01:17Z'
173172
- $__timeGroup(column,'5m') -&gt; (extract(epoch from column)/300)::bigint*300 AS "time"
173+
- $__timeShifting(1m) -&gt; compare data from the current range to the previous range
174174

175175

176176
Example of group by and order by with $__timeGroup:

src/bigquery_query.ts

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,30 @@ export default class BigQueryQuery {
3434
}
3535
return res;
3636
}
37-
37+
public static getUnixSecondsFromString(str) {
38+
switch (str) {
39+
case "1s":
40+
return "1";
41+
case "1min":
42+
return "60";
43+
case "1h":
44+
return "3600";
45+
case "1d":
46+
return "86400";
47+
case "1w":
48+
return "604800";
49+
case "1m":
50+
return "2629743"
51+
case "1y":
52+
return "31536000";
53+
}
54+
return "0";
55+
}
3856
public static _getIntervalStr(interval: string, timeColumn: string) {
3957
let IntervalStr =
4058
"TIMESTAMP_SECONDS(DIV(UNIX_SECONDS(" + timeColumn + "), ";
41-
if (interval === "1s") {
42-
IntervalStr += "1) * 1)";
43-
} else if (interval === "1min") {
44-
IntervalStr += "60) * 60)";
45-
} else if (interval === "1h") {
46-
IntervalStr += "3600) * 3600)";
47-
} else if (interval === "1d") {
48-
IntervalStr += "86400) * 86400)";
49-
} else if (interval === "1w") {
50-
IntervalStr += "604800) * 604800)";
51-
} else if (interval === "1m") {
59+
const unixSeconds = BigQueryQuery.getUnixSecondsFromString(interval);
60+
if (interval === "1m") {
5261
IntervalStr =
5362
"TIMESTAMP(" +
5463
" (" +
@@ -62,11 +71,23 @@ export default class BigQueryQuery {
6271
")" +
6372
")" +
6473
")";
65-
} else if (interval === "1y") {
66-
IntervalStr += "31536000) * 31536000)";
74+
} else {
75+
IntervalStr += unixSeconds + ") * " + unixSeconds + ")";
6776
}
6877
return IntervalStr;
6978
}
79+
80+
public static getTimeShift(q) {
81+
let res: string;
82+
res = q.match(/(.*\$__timeShifting\().*?(?=\))/g);
83+
if (res) {
84+
res = res[0].substr(1 + res[0].lastIndexOf("("));
85+
}
86+
return res;
87+
}
88+
public static replaceTimeShift(q) {
89+
return q.replace(/(\$__timeShifting\().*?(?=\))./g, "");
90+
}
7091
public target: any;
7192
public templateSrv: any;
7293
public scopedVars: any;
@@ -94,7 +115,6 @@ export default class BigQueryQuery {
94115
target.select = target.select || [
95116
[{ type: "column", params: ["-- value --"] }]
96117
];
97-
98118
// handle pre query gui panels gracefully
99119
if (!("rawQuery" in this.target)) {
100120
target.rawQuery = "rawSql" in target;
@@ -128,12 +148,10 @@ export default class BigQueryQuery {
128148

129149
public render(interpolate?) {
130150
const target = this.target;
131-
132151
// new query with no table set yet
133152
if (!this.target.rawQuery && !("table" in this.target)) {
134153
return "";
135154
}
136-
137155
if (!target.rawQuery) {
138156
target.rawSql = this.buildQuery();
139157
}
@@ -172,7 +190,6 @@ export default class BigQueryQuery {
172190
query += " AS time";
173191
}
174192
}
175-
176193
return query;
177194
}
178195

@@ -228,6 +245,10 @@ export default class BigQueryQuery {
228245
column,
229246
(g: any) => g.type === "window" || g.type === "moving_window"
230247
);
248+
const timeshift = _.find(
249+
column,
250+
(g: any) => g.type === "timeshift"
251+
);
231252
query = this._buildAggregate(aggregate, query);
232253
if (windows) {
233254
this.isWindow = true;
@@ -319,9 +340,11 @@ export default class BigQueryQuery {
319340
if (alias) {
320341
query += " AS " + alias.params[0];
321342
}
343+
if (timeshift) {
344+
query += " $__timeShifting(" + timeshift.params[0] + ")";
345+
}
322346
return query;
323347
}
324-
325348
public buildWhereClause() {
326349
let query = "";
327350
const conditions = _.map(this.target.where, (tag, index) => {
@@ -433,10 +456,10 @@ export default class BigQueryQuery {
433456
public expend_macros(options) {
434457
if (this.target.rawSql) {
435458
let q = this.target.rawSql;
459+
q = BigQueryQuery.replaceTimeShift(q);
436460
q = this.replaceTimeFilters(q, options);
437461
q = this.replacetimeGroupAlias(q, true);
438462
q = this.replacetimeGroupAlias(q, false);
439-
console.log(q);
440463
return q;
441464
}
442465
}

src/datasource.ts

Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import _ from "lodash";
2+
import moment from "moment";
23
import BigQueryQuery from "./bigquery_query";
34
import ResponseParser, { IResultFormat } from "./response_parser";
4-
import {countBy} from "lodash-es";
5+
import {countBy, size} from "lodash-es";
6+
import {sheets} from "googleapis/build/src/apis/sheets";
57

8+
const Shifted = "_shifted";
69
function sleep(ms) {
710
return new Promise(resolve => {
811
setTimeout(resolve, ms);
@@ -40,7 +43,95 @@ export class BigQueryDatasource {
4043
}
4144
throw BigQueryDatasource.formatBigqueryError(msg);
4245
}
46+
private static _createTimeShiftQuery(query) {
47+
const res = BigQueryQuery.getTimeShift(query.rawSql);
48+
if (!res) {
49+
return res;
50+
}
51+
const copy = query.constructor();
52+
for (const attr in query) {
53+
if (query.hasOwnProperty(attr)) {
54+
copy[attr] = query[attr];
55+
}
56+
}
57+
copy.rawSql = BigQueryQuery.replaceTimeShift(copy.rawSql);
58+
copy.format += "#" + res;
59+
copy.refId += Shifted + "_" + res;
60+
return copy;
61+
}
62+
63+
private static _getShiftPeriod(strInterval) {
64+
const shift = strInterval.match(/\d+/)[0];
65+
strInterval = strInterval.substr(shift.length, strInterval.length);
66+
if (strInterval === "m") {
67+
strInterval = "M";
68+
}
69+
70+
if (strInterval === "min") {
71+
strInterval = "m";
72+
}
73+
return [strInterval, shift];
74+
}
75+
private static _setupTimeShiftQuery(query, options) {
76+
const index = query.format.indexOf("#");
77+
const copy = options.constructor();
78+
for (const attr in options) {
79+
if (options.hasOwnProperty(attr)) {
80+
copy[attr] = options[attr];
81+
}
82+
}
83+
if (index === -1) {
84+
return copy;
85+
}
86+
let strInterval = query.format.substr(index + 1, query.format.len);
87+
const res = BigQueryDatasource._getShiftPeriod(strInterval);
88+
strInterval = res[0];
89+
if (
90+
!["s", "min", "h", "d", "w", "m", "w", "y", "M"].includes(strInterval)
91+
) {
92+
return copy;
93+
}
94+
query.format = query.format.substr(0, index);
95+
strInterval = res[0];
96+
const shift = res[1];
97+
if (strInterval === "m") {
98+
strInterval = "M";
99+
}
100+
101+
if (strInterval === "min") {
102+
strInterval = "m";
103+
}
104+
copy.range.from = options.range.from.subtract(
105+
parseInt(shift, 10),
106+
strInterval
107+
);
108+
copy.range.to = options.range.to.subtract(parseInt(shift, 10), strInterval);
109+
return copy;
110+
}
43111

112+
private static _updatePartition(q, options) {
113+
if (q.indexOf("AND _PARTITIONTIME >= ") < 1) {
114+
return q;
115+
}
116+
if (q.indexOf("AND _PARTITIONTIME <") < 1) {
117+
return q;
118+
}
119+
const from = q.substr(q.indexOf("AND _PARTITIONTIME >= ") + 22, 21);
120+
121+
const newFrom =
122+
"'" +
123+
BigQueryQuery.formatDateToString(options.range.from._d, "-", true) +
124+
"'";
125+
q = q.replace(from, newFrom);
126+
const to = q.substr(q.indexOf("AND _PARTITIONTIME < ") + 21, 21);
127+
const newTo =
128+
"'" +
129+
BigQueryQuery.formatDateToString(options.range.to._d, "-", true) +
130+
"'";
131+
132+
q = q.replace(to, newTo) + "\n ";
133+
return q;
134+
}
44135
public authenticationType: string;
45136
public projectName: string;
46137
private readonly id: any;
@@ -99,10 +190,25 @@ export class BigQueryDatasource {
99190
if (queries.length === 0) {
100191
return this.$q.when({ data: [] });
101192
}
193+
_.map(queries, query => {
194+
const newQuery = BigQueryDatasource._createTimeShiftQuery(query);
195+
if (newQuery) {
196+
queries.push(newQuery);
197+
}
198+
});
102199
const allQueryPromise = _.map(queries, query => {
103200
const tmpQ = this.queryModel.target.rawSql;
104201
this.queryModel.target.rawSql = query.rawSql;
105-
const q = this.queryModel.expend_macros(options);
202+
const modOptions = BigQueryDatasource._setupTimeShiftQuery(
203+
query,
204+
options
205+
);
206+
let q = this.queryModel.expend_macros(modOptions);
207+
q = BigQueryDatasource._updatePartition(q, modOptions);
208+
if (query.refId.search(Shifted) > -1) {
209+
q = this._updateAlias(q, modOptions, query.refId);
210+
}
211+
console.log(q);
106212
this.queryModel.target.rawSql = tmpQ;
107213
return this.doQuery(q, options.panelId + query.refId).then(response => {
108214
return ResponseParser.parseDataQuery(response, query.format);
@@ -120,6 +226,20 @@ export class BigQueryDatasource {
120226
}
121227
}
122228
}
229+
for (const d of data) {
230+
if (d.target.search(Shifted) > -1) {
231+
const res = BigQueryDatasource._getShiftPeriod(
232+
d.target.substring(d.target.lastIndexOf("_") + 1, d.target.length)
233+
);
234+
const shiftPeriod = res[0];
235+
const shiftVal = res[1];
236+
for(let i = 0; i < d.datapoints.length; i++){
237+
d.datapoints[i][1] = moment(d.datapoints[i][1])
238+
.subtract(shiftVal, shiftPeriod)
239+
.valueOf();
240+
}
241+
}
242+
}
123243
return { data };
124244
}
125245
);
@@ -179,8 +299,7 @@ export class BigQueryDatasource {
179299
return ResponseParser.parseTableFields(data, filter);
180300
}
181301

182-
183-
public async getDefaultProject(){
302+
public async getDefaultProject() {
184303
try {
185304
if (this.authenticationType === "gce" || !this.projectName) {
186305
let data;
@@ -405,4 +524,25 @@ export class BigQueryDatasource {
405524
}
406525
return data;
407526
}
527+
private _updateAlias(q, options, shiftstr) {
528+
const index = shiftstr.search(Shifted);
529+
const shifted = shiftstr.substr(index, shiftstr.length);
530+
for (const al of options.targets[0].select[0]) {
531+
if (al.type === "alias") {
532+
q = q.replace("AS " + al.params[0], "AS " + al.params[0] + shifted);
533+
return q;
534+
}
535+
}
536+
const aliasshiftted = [options.targets[0].select[0][0].params[0] + shifted];
537+
const oldSelect = this.queryModel.buildValueColumn(
538+
options.targets[0].select[0]
539+
);
540+
const newSelect = this.queryModel.buildValueColumn([
541+
options.targets[0].select[0][0],
542+
options.targets[0].select[0][1],
543+
{ type: "alias", params: [aliasshiftted] }
544+
]);
545+
q = q.replace(oldSelect, newSelect);
546+
return q;
547+
}
408548
}

0 commit comments

Comments
 (0)