feature(dashboard,api): add timezone support

* feat(dashboard): add support for today, yesterday etc (timezones)

* fix(db): escape js dates

* fix(dashboard): ensure we support default timezone

* final fixes

* remove complete series and add sql with fill instead
This commit is contained in:
Carl-Gerhard Lindesvärd
2025-05-23 11:26:44 +02:00
committed by GitHub
parent 46bfeee131
commit 680727355b
48 changed files with 1817 additions and 758 deletions

View File

@@ -73,7 +73,11 @@ export class Query<T = any> {
};
private _transform?: Record<string, (item: T) => any>;
private _union?: Query;
constructor(private client: ClickHouseClient) {}
private _dateRegex = /\d{4}-\d{2}-\d{2}([\s\:\d\.]+)?/g;
constructor(
private client: ClickHouseClient,
private timezone: string,
) {}
// Select methods
select<U>(
@@ -121,9 +125,14 @@ export class Query<T = any> {
if (Array.isArray(value)) {
return `(${value.map((v) => this.escapeValue(v)).join(', ')})`;
}
if (value instanceof Date) {
return escape(clix.datetime(value));
if (
(typeof value === 'string' && this._dateRegex.test(value)) ||
value instanceof Date
) {
return this.escapeDate(value);
}
return escape(value);
}
@@ -249,10 +258,10 @@ export class Query<T = any> {
private escapeDate(value: string | Date): string {
if (value instanceof Date) {
return clix.datetime(value);
return escape(clix.datetime(value));
}
return value.replaceAll(/\d{4}-\d{2}-\d{2}([\s\:\d\.]+)?/g, (match) => {
return value.replaceAll(this._dateRegex, (match) => {
return escape(match);
});
}
@@ -348,7 +357,10 @@ export class Query<T = any> {
// SELECT
if (this._select.length > 0) {
parts.push('SELECT', this._select.map(this.escapeDate).join(', '));
parts.push(
'SELECT',
this._select.map((col) => this.escapeDate(col)).join(', '),
);
} else {
parts.push('SELECT *');
}
@@ -483,26 +495,16 @@ export class Query<T = any> {
// Execution methods
async execute(): Promise<T[]> {
const query = this.buildQuery();
console.log('TEST QUERY ----->');
console.log(query);
console.log('<----------');
const perf = performance.now();
try {
const result = await this.client.query({
query,
});
const json = await result.json<T>();
const perf2 = performance.now();
console.log(`PERF: ${perf2 - perf}ms`);
return this.transformJson(json).data;
} catch (error) {
console.log('ERROR ----->');
console.log(error);
console.log('<----------');
console.log(query);
console.log('<----------');
throw error;
}
console.log('query', query);
const result = await this.client.query({
query,
clickhouse_settings: {
session_timezone: this.timezone,
},
});
const json = await result.json<T>();
return this.transformJson(json).data;
}
// Debug methods
@@ -535,7 +537,7 @@ export class Query<T = any> {
}
clone(): Query<T> {
return new Query(this.client).merge(this);
return new Query(this.client, this.timezone).merge(this);
}
// Add merge method
@@ -629,12 +631,8 @@ export class WhereGroupBuilder {
}
// Helper function to create a new query
export function createQuery(client: ClickHouseClient): Query {
return new Query(client);
}
export function clix(client: ClickHouseClient): Query {
return new Query(client);
export function clix(client: ClickHouseClient, timezone?: string): Query {
return new Query(client, timezone ?? 'UTC');
}
clix.exp = (expr: string | Query<any>) =>
@@ -654,7 +652,7 @@ clix.dynamicDatetime = (date: string | Date, interval: IInterval) => {
return clix.datetime(date);
};
clix.toStartOf = (node: string, interval: IInterval) => {
clix.toStartOf = (node: string, interval: IInterval, timezone?: string) => {
switch (interval) {
case 'minute': {
return `toStartOfMinute(${node})`;
@@ -666,10 +664,12 @@ clix.toStartOf = (node: string, interval: IInterval) => {
return `toStartOfDay(${node})`;
}
case 'week': {
return `toStartOfWeek(${node})`;
// Does not respect timezone settings (session_timezone) so we need to pass it manually
return `toStartOfWeek(${node}${timezone ? `, 1, '${timezone}'` : ''})`;
}
case 'month': {
return `toStartOfMonth(${node})`;
// Does not respect timezone settings (session_timezone) so we need to pass it manually
return `toStartOfMonth(${node}${timezone ? `, '${timezone}'` : ''})`;
}
}
};