Skip to content

Commit b2f6d0b

Browse files
committed
fix: LAG() returns jankie time
1 parent a4e8d3b commit b2f6d0b

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

app/controllers/api/v1/stats_controller.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,14 @@ def user_stats
7171
query = query.where(project: filter_by_project)
7272
end
7373

74-
total_seconds = query.duration_seconds || 0
74+
# do the boundary thingie if requested
75+
use_boundary_aware = params[:boundary_aware] == "true"
76+
total_seconds = if use_boundary_aware
77+
Heartbeat.duration_seconds_boundary_aware(query, start_date.to_f, end_date.to_f) || 0
78+
else
79+
query.duration_seconds || 0
80+
end
81+
7582
return render json: { total_seconds: total_seconds }
7683
end
7784

app/models/concerns/heartbeatable.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,5 +235,60 @@ def duration_seconds(scope = all)
235235
connection.select_value("SELECT COALESCE(SUM(diff), 0)::integer FROM (#{capped_diffs.to_sql}) AS diffs").to_i
236236
end
237237
end
238+
239+
def duration_seconds_boundary_aware(scope, start_time, end_time)
240+
scope = scope.with_valid_timestamps
241+
242+
model_class = scope.model
243+
base_scope = model_class.all.with_valid_timestamps
244+
245+
if scope.where_values_hash["user_id"]
246+
base_scope = base_scope.where(user_id: scope.where_values_hash["user_id"])
247+
end
248+
249+
if scope.where_values_hash["category"]
250+
base_scope = base_scope.where(category: scope.where_values_hash["category"])
251+
end
252+
253+
if scope.where_values_hash["project"]
254+
base_scope = base_scope.where(project: scope.where_values_hash["project"])
255+
end
256+
257+
if scope.where_values_hash["deleted_at"]
258+
base_scope = base_scope.where(deleted_at: scope.where_values_hash["deleted_at"])
259+
end
260+
261+
# get the heartbeat before the start_time
262+
boundary_heartbeat = base_scope
263+
.where("time < ?", start_time)
264+
.order(time: :desc)
265+
.limit(1)
266+
.first
267+
268+
# if it's not NULL, we'll use it
269+
if boundary_heartbeat
270+
combined_scope = base_scope
271+
.where("time >= ? OR time = ?", start_time, boundary_heartbeat.time)
272+
.where("time <= ?", end_time)
273+
else
274+
combined_scope = base_scope
275+
.where(time: start_time..end_time)
276+
end
277+
278+
# we calc w/ the boundary heartbeat, but we only sum within the orignal constraint
279+
capped_diffs = combined_scope
280+
.select("time, CASE
281+
WHEN LAG(time) OVER (ORDER BY time) IS NULL THEN 0
282+
ELSE LEAST(EXTRACT(EPOCH FROM (to_timestamp(time) - to_timestamp(LAG(time) OVER (ORDER BY time)))), #{heartbeat_timeout_duration.to_i})
283+
END as diff")
284+
.where.not(time: nil)
285+
.order(time: :asc)
286+
287+
connection.select_value(
288+
"SELECT COALESCE(SUM(diff), 0)::integer
289+
FROM (#{capped_diffs.to_sql}) AS diffs
290+
WHERE time >= #{start_time}"
291+
).to_i
292+
end
238293
end
239294
end

0 commit comments

Comments
 (0)