Centralize heartbeat ingestion logic#1270
Conversation
Greptile SummaryThis PR extracts the heartbeat ingestion logic that was previously split across
Confidence Score: 4/5The refactoring is structurally sound with no new breaking changes on the core heartbeat path, but three subtle behavioral shifts around rollup scheduling, last-language tracking on persist failure, and batch-size measurement are worth resolving before merge. The rollup-scheduling gap (skipped when every heartbeat in an import is a duplicate) is the most user-visible change in behavior. The last-language leak on persist failure and the raw-vs-deduplicated BATCH_SIZE shift are edge cases unlikely to surface in normal usage but diverge from the previous implementation's intent. app/services/heartbeat_import_runner.rb (rollup scheduling removal), app/services/heartbeat_ingest.rb (last_language update ordering), app/services/heartbeat_import_service.rb (BATCH_SIZE comparison change) Important Files Changed
Sequence DiagramsequenceDiagram
participant Controller as HackatimeController
participant ImportService as HeartbeatImportService
participant ImportRunner as HeartbeatImportRunner
participant Ingest as HeartbeatIngest
participant DB as Heartbeat (DB)
participant Job as DashboardRollupRefreshJob
Note over Controller: Direct (live) path
Controller->>Ingest: call(mode: :direct, heartbeats:, request_context:)
loop each heartbeat
Ingest->>Ingest: normalize_direct_heartbeat
Ingest->>DB: find_by(fields_hash)
alt new heartbeat
Ingest->>DB: insert(returning: ...)
Ingest->>Job: schedule_rollup_refresh
end
Ingest->>Ingest: queue_project_mapping
end
Ingest-->>Controller: Result(items)
Note over ImportRunner: Import path
ImportRunner->>ImportService: import_from_file(file, user)
loop each BATCH_SIZE raw heartbeats
ImportService->>Ingest: call(mode: :import, schedule_rollup_refresh: false)
Ingest->>DB: upsert_all(seen_hashes.values)
Ingest-->>ImportService: Result(persisted_count, errors)
end
ImportService->>Job: schedule_rollup_refresh (if imported_count > 0)
ImportService-->>ImportRunner: result hash
ImportRunner->>ImportRunner: complete_run!
|
There was a problem hiding this comment.
Pull request overview
This PR centralizes heartbeat normalization, deduplication, and rollup-refresh behavior into a new HeartbeatIngest service so both direct API ingestion and import ingestion share one code path.
Changes:
- Adds
HeartbeatIngestto handle direct and import heartbeat processing. - Refactors the Hackatime API controller and import service to use the new shared service.
- Updates import completion flow and adds service-level tests for ingestion behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
app/services/heartbeat_ingest.rb |
New shared service for direct/import heartbeat normalization, persistence, deduplication, and follow-up jobs. |
app/services/heartbeat_import_service.rb |
Replaces inline import normalization/upsert logic with batched calls into HeartbeatIngest. |
app/services/heartbeat_import_runner.rb |
Removes runner-level rollup scheduling now that import scheduling is delegated elsewhere. |
app/controllers/api/hackatime/v1/hackatime_controller.rb |
Replaces inline direct heartbeat ingestion logic with HeartbeatIngest. |
test/services/heartbeat_ingest_test.rb |
Adds service tests for direct ingestion, duplicate handling, language carry-forward, and import deduplication. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Heartbeat code was messy and duplicated. No more!