Skip to content

Commit

Permalink
Merge pull request #2319 from umami-software/feat/um-290-update-click…
Browse files Browse the repository at this point in the history
…house-client

Feat/um 290 update clickhouse client
  • Loading branch information
mikecao authored Oct 3, 2023
2 parents 476dd52 + 8db43cd commit c96f317
Show file tree
Hide file tree
Showing 20 changed files with 200 additions and 376 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
".next/cache"
],
"dependencies": {
"@clickhouse/client": "^0.2.2",
"@fontsource/inter": "^4.5.15",
"@prisma/client": "5.3.1",
"@react-spring/web": "^9.7.3",
Expand All @@ -71,7 +72,6 @@
"chart.js": "^4.2.1",
"chartjs-adapter-date-fns": "^3.0.0",
"classnames": "^2.3.1",
"clickhouse": "^2.5.0",
"colord": "^2.9.2",
"cors": "^2.8.5",
"cross-spawn": "^7.0.3",
Expand Down
30 changes: 18 additions & 12 deletions src/lib/clickhouse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ClickHouse } from 'clickhouse';
import { ClickHouseClient, createClient } from '@clickhouse/client';
import dateFormat from 'dateformat';
import debug from 'debug';
import { CLICKHOUSE } from 'lib/db';
Expand All @@ -17,26 +17,24 @@ export const CLICKHOUSE_DATE_FORMATS = {

const log = debug('umami:clickhouse');

let clickhouse: ClickHouse;
let clickhouse: ClickHouseClient;
const enabled = Boolean(process.env.CLICKHOUSE_URL);

function getClient() {
const {
hostname,
port,
pathname,
protocol,
username = 'default',
password,
} = new URL(process.env.CLICKHOUSE_URL);

const client = new ClickHouse({
url: hostname,
port: Number(port),
format: 'json',
config: {
database: pathname.replace('/', ''),
},
basicAuth: password ? { username, password } : null,
const client = createClient({
host: `${protocol}//${hostname}:${port}`,
database: pathname.replace('/', ''),
username: username,
password,
});

if (process.env.NODE_ENV !== 'production') {
Expand Down Expand Up @@ -118,15 +116,23 @@ async function parseFilters(websiteId: string, filters: QueryFilters = {}, optio
};
}

async function rawQuery<T>(query: string, params: object = {}): Promise<T> {
async function rawQuery(query: string, params: Record<string, unknown> = {}): Promise<unknown> {
if (process.env.LOG_QUERY) {
log('QUERY:\n', query);
log('PARAMETERS:\n', params);
}

await connect();

return clickhouse.query(query, { params }).toPromise() as Promise<T>;
const resultSet = await clickhouse.query({
query: query,
query_params: params,
format: 'JSONEachRow',
});

const data = await resultSet.json();

return data;
}

async function findUnique(data) {
Expand Down
4 changes: 4 additions & 0 deletions src/lib/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export function getDatabaseType(url = process.env.DATABASE_URL) {
return POSTGRESQL;
}

if (process.env.CLICKHOUSE_URL) {
return CLICKHOUSE;
}

return type;
}

Expand Down
31 changes: 26 additions & 5 deletions src/queries/analytics/eventData/getEventDataEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
);
}

async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters,
): Promise<{ eventName: string; fieldName: string; dataType: number; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { event } = filters;
const { params } = await parseFilters(websiteId, filters);
Expand All @@ -75,14 +78,23 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
count(*) as total
from event_data
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime} and {endDate:DateTime}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and event_name = {event:String}
group by event_key, data_type, string_value, event_name
order by 1 asc, 2 asc, 3 asc, 4 desc
limit 100
`,
params,
);
).then(a => {
return Object.values(a).map(a => {
return {
eventName: a.eventName,
fieldName: a.fieldName,
dataType: Number(a.dataType),
total: Number(a.total),
};
});
});
}

return rawQuery(
Expand All @@ -94,11 +106,20 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
count(*) as total
from event_data
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime} and {endDate:DateTime}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
group by event_key, data_type, event_name
order by 1 asc, 2 asc
limit 100
`,
params,
);
).then(a => {
return Object.values(a).map(a => {
return {
eventName: a.eventName,
fieldName: a.fieldName,
dataType: Number(a.dataType),
total: Number(a.total),
};
});
});
}
18 changes: 15 additions & 3 deletions src/queries/analytics/eventData/getEventDataFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ async function relationalQuery(websiteId: string, filters: QueryFilters & { fiel
);
}

async function clickhouseQuery(websiteId: string, filters: QueryFilters & { field?: string }) {
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters & { field?: string },
): Promise<{ fieldName: string; dataType: number; fieldValue: string; total: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters, {
columns: { field: 'event_key' },
Expand All @@ -52,12 +55,21 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters & { fiel
count(*) as total
from event_data
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime} and {endDate:DateTime}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
group by event_key, data_type, string_value
order by 3 desc, 2 desc, 1 asc
limit 100
`,
params,
);
).then(a => {
return Object.values(a).map(a => {
return {
fieldName: a.fieldName,
dataType: Number(a.dataType),
fieldValue: a.fieldValue,
total: Number(a.total),
};
});
});
}
17 changes: 14 additions & 3 deletions src/queries/analytics/eventData/getEventDataStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
);
}

async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters,
): Promise<{ events: number; fields: number; records: number }> {
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters);

Expand All @@ -59,11 +62,19 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
count(*) as "total"
from event_data
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime} and {endDate:DateTime}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
${filterQuery}
group by event_id, event_key
) as t
`,
params,
);
).then(a => {
return Object.values(a).map(a => {
return {
events: Number(a.events),
fields: Number(a.fields),
records: Number(a.records),
};
});
});
}
13 changes: 10 additions & 3 deletions src/queries/analytics/events/getEventMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
);
}

async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters,
): Promise<{ x: string; t: string; y: number }[]> {
const { timezone = 'UTC', unit = 'day' } = filters;
const { rawQuery, getDateQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, {
Expand All @@ -56,12 +59,16 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
count(*) y
from website_event
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime} and {endDate:DateTime}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and event_type = {eventType:UInt32}
${filterQuery}
group by x, t
order by t
`,
params,
);
).then(a => {
return Object.values(a).map(a => {
return { x: a.x, t: a.t, y: Number(a.y) };
});
});
}
2 changes: 1 addition & 1 deletion src/queries/analytics/events/getEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function clickhouseQuery(websiteId: string, startDate: Date, eventType: number)
event_name as eventName
from website_event
where website_id = {websiteId:UUID}
and created_at >= {startDate:DateTime}
and created_at >= {startDate:DateTime64}
and event_type = {eventType:UInt32}
`,
{
Expand Down
14 changes: 10 additions & 4 deletions src/queries/analytics/getActiveVisitors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,23 @@ async function relationalQuery(websiteId: string) {
);
}

async function clickhouseQuery(websiteId: string) {
async function clickhouseQuery(websiteId: string): Promise<{ x: number }> {
const { rawQuery } = clickhouse;

return rawQuery(
const result = rawQuery(
`
select
count(distinct session_id) x
from website_event
where website_id = {websiteId:UUID}
and created_at >= {startAt:DateTime}
and created_at >= {startAt:DateTime64}
`,
{ websiteId, startAt: subMinutes(new Date(), 5) },
);
).then(a => {
return Object.values(a).map(a => {
return { x: Number(a.x) };
});
});

return result[0] ?? null;
}
2 changes: 1 addition & 1 deletion src/queries/analytics/getWebsiteDateRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ async function clickhouseQuery(websiteId: string) {
max(created_at) as maxdate
from website_event
where website_id = {websiteId:UUID}
and created_at >= {startDate:DateTime}
and created_at >= {startDate:DateTime64}
`,
params,
);
Expand Down
18 changes: 15 additions & 3 deletions src/queries/analytics/getWebsiteStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
);
}

async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters,
): Promise<{ pageviews: number; uniques: number; bounces: number; totaltime: number }[]> {
const { rawQuery, getDateQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, {
...filters,
Expand All @@ -69,12 +72,21 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
max(created_at) max_time
from website_event
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime} and {endDate:DateTime}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and event_type = {eventType:UInt32}
${filterQuery}
group by session_id, time_series
) as t;
`,
params,
);
).then(a => {
return Object.values(a).map(a => {
return {
pageviews: Number(a.pageviews),
uniques: Number(a.uniques),
bounces: Number(a.bounces),
totaltime: Number(a.totaltime),
};
});
});
}
14 changes: 11 additions & 3 deletions src/queries/analytics/pageviews/getPageviewMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ async function relationalQuery(websiteId: string, column: string, filters: Query
);
}

async function clickhouseQuery(websiteId: string, column: string, filters: QueryFilters) {
async function clickhouseQuery(
websiteId: string,
column: string,
filters: QueryFilters,
): Promise<{ x: string; y: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, {
...filters,
Expand All @@ -65,7 +69,7 @@ async function clickhouseQuery(websiteId: string, column: string, filters: Query
select ${column} x, count(*) y
from website_event
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime} and {endDate:DateTime}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and event_type = {eventType:UInt32}
${excludeDomain}
${filterQuery}
Expand All @@ -74,5 +78,9 @@ async function clickhouseQuery(websiteId: string, column: string, filters: Query
limit 100
`,
params,
);
).then(a => {
return Object.values(a).map(a => {
return { x: a.x, y: Number(a.y) };
});
});
}
Loading

0 comments on commit c96f317

Please sign in to comment.