Skip to content

Commit 2e1af43

Browse files
authored
Merge pull request #582 from Swetrix/fix/project-reset-chart-calculation
fix: Project reset chart data calculation
2 parents c295e8e + a913b37 commit 2e1af43

2 files changed

Lines changed: 56 additions & 48 deletions

File tree

backend/apps/cloud/src/analytics/analytics.service.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,42 +1888,46 @@ export class AnalyticsService {
18881888
1,
18891889
Math.ceil(spanSeconds / DELETION_TIMELINE_BUCKETS),
18901890
)
1891+
const bucketMs = bucketSeconds * 1000
1892+
1893+
// Anchor buckets to the epoch-aligned start of the window, then bucket on
1894+
// absolute unix time. Matching by integer index (rather than a formatted
1895+
// timestamp string) keeps this correct no matter how `created`'s timezone
1896+
// is rendered in the query result.
1897+
const originMs = Math.floor(fromMs / bucketMs) * bucketMs
1898+
const originSeconds = Math.floor(originMs / 1000)
1899+
1900+
const x: string[] = []
1901+
for (
1902+
let ms = originMs;
1903+
ms <= toMs && x.length < DELETION_TIMELINE_MAX_POINTS;
1904+
ms += bucketMs
1905+
) {
1906+
x.push(dayjs.utc(ms).format('YYYY-MM-DD HH:mm:ss'))
1907+
}
1908+
1909+
const counts = new Array<number>(x.length).fill(0)
18911910

18921911
const { data: rows } = await clickhouse
18931912
.query({
18941913
query: `
18951914
SELECT
1896-
toStartOfInterval(created, INTERVAL {bucketSeconds:UInt32} SECOND) AS t,
1915+
intDiv(toInt64(toUnixTimestamp(created)) - {originSeconds:Int64}, {bucketSeconds:UInt32}) AS bucketIndex,
18971916
count() AS c
18981917
FROM events
18991918
WHERE ${whereClause}
1900-
GROUP BY t
1901-
ORDER BY t
1919+
GROUP BY bucketIndex
1920+
ORDER BY bucketIndex
19021921
`,
1903-
query_params: { ...baseParams, bucketSeconds },
1922+
query_params: { ...baseParams, originSeconds, bucketSeconds },
19041923
})
1905-
.then((resultSet) => resultSet.json<{ t: string; c: string }>())
1924+
.then((resultSet) => resultSet.json<{ bucketIndex: string; c: string }>())
19061925

1907-
const bucketed = new Map<string, number>()
19081926
for (const row of rows) {
1909-
bucketed.set(row.t, Number(row.c) || 0)
1910-
}
1911-
1912-
// toStartOfInterval aligns buckets to multiples of the interval since the
1913-
// unix epoch, so align our JS walk the same way and fill empty buckets.
1914-
const bucketMs = bucketSeconds * 1000
1915-
const startMs = Math.floor(fromMs / bucketMs) * bucketMs
1916-
const x: string[] = []
1917-
const counts: number[] = []
1918-
1919-
for (
1920-
let ms = startMs;
1921-
ms <= toMs && x.length < DELETION_TIMELINE_MAX_POINTS;
1922-
ms += bucketMs
1923-
) {
1924-
const key = dayjs.utc(ms).format('YYYY-MM-DD HH:mm:ss')
1925-
x.push(key)
1926-
counts.push(bucketed.get(key) ?? 0)
1927+
const index = Number(row.bucketIndex)
1928+
if (index >= 0 && index < counts.length) {
1929+
counts[index] = Number(row.c) || 0
1930+
}
19271931
}
19281932

19291933
return { x, counts }

backend/apps/community/src/analytics/analytics.service.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,42 +1548,46 @@ export class AnalyticsService {
15481548
1,
15491549
Math.ceil(spanSeconds / DELETION_TIMELINE_BUCKETS),
15501550
)
1551+
const bucketMs = bucketSeconds * 1000
1552+
1553+
// Anchor buckets to the epoch-aligned start of the window, then bucket on
1554+
// absolute unix time. Matching by integer index (rather than a formatted
1555+
// timestamp string) keeps this correct no matter how `created`'s timezone
1556+
// is rendered in the query result.
1557+
const originMs = Math.floor(fromMs / bucketMs) * bucketMs
1558+
const originSeconds = Math.floor(originMs / 1000)
1559+
1560+
const x: string[] = []
1561+
for (
1562+
let ms = originMs;
1563+
ms <= toMs && x.length < DELETION_TIMELINE_MAX_POINTS;
1564+
ms += bucketMs
1565+
) {
1566+
x.push(dayjs.utc(ms).format('YYYY-MM-DD HH:mm:ss'))
1567+
}
1568+
1569+
const counts = new Array<number>(x.length).fill(0)
15511570

15521571
const { data: rows } = await clickhouse
15531572
.query({
15541573
query: `
15551574
SELECT
1556-
toStartOfInterval(created, INTERVAL {bucketSeconds:UInt32} SECOND) AS t,
1575+
intDiv(toInt64(toUnixTimestamp(created)) - {originSeconds:Int64}, {bucketSeconds:UInt32}) AS bucketIndex,
15571576
count() AS c
15581577
FROM events
15591578
WHERE ${whereClause}
1560-
GROUP BY t
1561-
ORDER BY t
1579+
GROUP BY bucketIndex
1580+
ORDER BY bucketIndex
15621581
`,
1563-
query_params: { ...baseParams, bucketSeconds },
1582+
query_params: { ...baseParams, originSeconds, bucketSeconds },
15641583
})
1565-
.then((resultSet) => resultSet.json<{ t: string; c: string }>())
1584+
.then((resultSet) => resultSet.json<{ bucketIndex: string; c: string }>())
15661585

1567-
const bucketed = new Map<string, number>()
15681586
for (const row of rows) {
1569-
bucketed.set(row.t, Number(row.c) || 0)
1570-
}
1571-
1572-
// toStartOfInterval aligns buckets to multiples of the interval since the
1573-
// unix epoch, so align our JS walk the same way and fill empty buckets.
1574-
const bucketMs = bucketSeconds * 1000
1575-
const startMs = Math.floor(fromMs / bucketMs) * bucketMs
1576-
const x: string[] = []
1577-
const counts: number[] = []
1578-
1579-
for (
1580-
let ms = startMs;
1581-
ms <= toMs && x.length < DELETION_TIMELINE_MAX_POINTS;
1582-
ms += bucketMs
1583-
) {
1584-
const key = dayjs.utc(ms).format('YYYY-MM-DD HH:mm:ss')
1585-
x.push(key)
1586-
counts.push(bucketed.get(key) ?? 0)
1587+
const index = Number(row.bucketIndex)
1588+
if (index >= 0 && index < counts.length) {
1589+
counts[index] = Number(row.c) || 0
1590+
}
15871591
}
15881592

15891593
return { x, counts }

0 commit comments

Comments
 (0)