feat(web): record service ping history and add usage report download - #1348

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history
Jun 18, 2026
Merged

feat(web): record service ping history and add usage report download#1348
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Overview

Offline / air-gapped deployments can't report usage to Lighthouse automatically. This change records each service ping locally so those deployments can download their usage history and send it to us out-of-band.

Changes

  • DB: New ServicePingEvent table (id, payload JSON, createdAt) + migration.
  • Recording: syncWithLighthouse now persists each ping (via recordServicePingInDB) before sending it to Lighthouse, so the history is captured even when the outbound ping fails (as it always will on air-gapped instances). The activation code is stripped before persisting, and recording is best-effort (wrapped in try/catch + logging) so a DB error never blocks the real ping.
  • Export UI: A "Download usage report" button on the offline license settings card (Settings → License), backed by an OWNER-gated server action that returns the recorded events. Downloads as <date>-usage-history.json.
  • Copy: The offline license card explains that the instance doesn't report usage automatically and that usage must be manually sent to ar@sourcebot.dev.
image

Notes

  • The export button only renders for offline licenses, but recording runs on all deployments (one tiny row/day, negligible).
  • No retention/pruning yet.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Offline license settings page now includes a "Download usage report" button to export usage data as JSON files.
    • Service ping history is now recorded locally for offline deployments to support usage tracking.
  • Refactor

    • Improved license validation helper functions for better code organization.

Record each service ping in a new ServicePingEvent table (activation code
stripped) so offline deployments, which can't report usage to Lighthouse
automatically, can download their usage history and email it to us.
Adds a "Download usage report" button to the offline license settings card
that exports the recorded pings as a JSON file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a687f59b-8ce6-4388-9572-17e7790ee58f

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5271e and e8bbbaa.

📒 Files selected for processing (7)
  • packages/db/prisma/migrations/20260618210032_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/shared/src/entitlements.ts
  • packages/shared/src/index.server.ts
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/features/billing/servicePing.ts

Walkthrough

Adds a ServicePingEvent database table to persist sanitized service ping payloads per organization. syncWithLighthouse now records each ping to the DB before sending it to Lighthouse. A new server action getServicePingHistory retrieves the history, and a DownloadServicePingHistoryButton in the offline license card lets owners export it as JSON.

Changes

Service Ping Event Recording and Download

Layer / File(s)Summary
ServicePingEvent DB schema and migration
packages/db/prisma/migrations/.../migration.sql, packages/db/prisma/schema.prisma
Migration creates the ServicePingEvent table with id, payload JSONB, createdAt, and a cascading FK to Org. Prisma schema adds the model and back-reference field on Org.
Best-effort ping recording in syncWithLighthouse
packages/shared/src/entitlements.ts, packages/shared/src/index.server.ts, packages/web/src/features/billing/servicePing.ts
isValidOfflineLicenseActive and isValidOnlineLicenseActive are extracted as exported helpers. recordServicePingInDB strips activationCode and writes the sanitized payload to servicePingEvent, swallowing errors. syncWithLighthouse awaits this call before the offline-license early return.
Server action, download button, and offline card integration
packages/web/src/app/(app)/settings/license/actions.ts, packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx, packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
getServicePingHistory server action queries servicePingEvent ordered by createdAt and maps rows to ServicePingHistoryEntry. DownloadServicePingHistoryButton calls the action, handles error/empty toasts, and triggers a JSON blob download. OfflineLicenseCard gains a new bordered footer section rendering the button alongside a manual-reporting notice.
Changelog
CHANGELOG.md
Adds an unreleased entry describing service ping history recording and the new download button.

Sequence Diagram(s)

sequenceDiagram
rect rgba(173, 216, 230, 0.5)
note over syncWithLighthouse,servicePingEvent: Every ping cycle
syncWithLighthouse->>recordServicePingInDB: await recordServicePingInDB(orgId, payload)
recordServicePingInDB->>recordServicePingInDB: strip activationCode
recordServicePingInDB->>servicePingEvent: prisma.servicePingEvent.create()
servicePingEvent-->>recordServicePingInDB: saved (errors caught+logged)
recordServicePingInDB-->>syncWithLighthouse: returns
syncWithLighthouse->>LighthouseClient: ping(payload) or skip if offline
end
rect rgba(144, 238, 144, 0.5)
note over Owner,servicePingEvent: On-demand export
Owner->>DownloadServicePingHistoryButton: click "Download usage report"
DownloadServicePingHistoryButton->>getServicePingHistory: getServicePingHistory()
getServicePingHistory->>servicePingEvent: findMany(orgId, orderBy: createdAt asc)
servicePingEvent-->>getServicePingHistory: rows
getServicePingHistory-->>DownloadServicePingHistoryButton: ServicePingHistoryEntry[]
DownloadServicePingHistoryButton->>Owner: JSON blob download
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1296: Modifies syncWithLighthouse in the same servicePing.ts file to extend the ping payload, directly adjacent to where this PR inserts the recordServicePingInDB call.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main changes: recording service ping history and adding a usage report download button for offline deployments.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/service-ping-history

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

brendan-kellamand others added 2 commits June 18, 2026 11:51
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/web/src/app/(app)/settings/license/actions.ts (1)

19-21: ⚖️ Poor tradeoff

Consider pagination or limits for large datasets.

This query fetches all service ping events without pagination. For deployments running for years with daily pings, this could return thousands of records. While offline deployments may genuinely need all history, consider whether:

  1. A take/skip pagination strategy would be useful
  2. A date range filter would help users export specific periods
  3. A reasonable upper limit (e.g., last 1000 events) should be enforced
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/`(app)/settings/license/actions.ts around lines 19 - 21,
The query in the servicePingEvent.findMany() call fetches all records without
any limit or pagination, which could cause performance issues with large
datasets over time. Add a take parameter to limit the number of records returned
(e.g., last 1000 events) as a reasonable upper bound, and optionally implement
pagination with take and skip parameters or add a where clause with a date range
filter to allow users to query specific time periods instead of always fetching
the complete history.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql`:
- Around line 1-8: In the migration file creating the ServicePingEvent table,
add a database index on the createdAt column to optimize the ordered queries
performed by the getServicePingHistory server action. The createdAt column is
used for sorting results in ascending order but currently lacks an index, which
will cause full table scans as the table grows. Add a CREATE INDEX statement
after the table creation to create an index on the createdAt column of the
ServicePingEvent table to ensure queries remain performant as data accumulates.
In `@packages/db/prisma/schema.prisma`:
- Around line 361-365: The ServicePingEvent model lacks an orgId field which
breaks multi-tenant isolation and allows any OWNER to access all organizations'
service ping events. Add an orgId field to the ServicePingEvent model in
schema.prisma as a String required field with a foreign key relation to the
Organization model. Then update the getServicePingHistory function in actions.ts
to filter ServicePingEvent records by the requesting organization's orgId
instead of fetching all records. Finally, update the recordServicePingInDB
function in servicePing.ts to capture and include the orgId when creating new
ServicePingEvent records.
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The servicePingEvent.findMany() query is missing an orgId
filter, which creates a security vulnerability allowing cross-organization data
access. Add a where clause to the findMany call that filters servicePingEvent
records by orgId using role.orgId to ensure only the current organization's
service ping events are retrieved. This prevents any OWNER from accessing
service ping history from other organizations.
---
Nitpick comments:
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The query in the servicePingEvent.findMany() call fetches
all records without any limit or pagination, which could cause performance
issues with large datasets over time. Add a take parameter to limit the number
of records returned (e.g., last 1000 events) as a reasonable upper bound, and
optionally implement pagination with take and skip parameters or add a where
clause with a date range filter to allow users to query specific time periods
instead of always fetching the complete history.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 29aa261b-7964-47ab-b600-895f44389483

📥 Commits

Reviewing files that changed from the base of the PR and between 9320065 and 5c5271e.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
  • packages/web/src/features/billing/servicePing.ts

Comment threadpackages/db/prisma/schema.prisma
Comment threadpackages/web/src/app/(app)/settings/license/actions.ts
Comment threadpackages/web/src/features/billing/servicePing.ts
msukkari
msukkari previously approved these changes Jun 18, 2026
@brendan-kellam
brendan-kellam merged commit f0bb90b into mainJun 18, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/service-ping-history branch June 18, 2026 21:28
@github-actionsgithub-actionsBot mentioned this pull request Jun 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@brendan-kellam@msukkari
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(web): record service ping history and add usage report download - #1348

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history
Jun 18, 2026
Merged

feat(web): record service ping history and add usage report download#1348
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Overview

Offline / air-gapped deployments can't report usage to Lighthouse automatically. This change records each service ping locally so those deployments can download their usage history and send it to us out-of-band.

Changes

  • DB: New ServicePingEvent table (id, payload JSON, createdAt) + migration.
  • Recording: syncWithLighthouse now persists each ping (via recordServicePingInDB) before sending it to Lighthouse, so the history is captured even when the outbound ping fails (as it always will on air-gapped instances). The activation code is stripped before persisting, and recording is best-effort (wrapped in try/catch + logging) so a DB error never blocks the real ping.
  • Export UI: A "Download usage report" button on the offline license settings card (Settings → License), backed by an OWNER-gated server action that returns the recorded events. Downloads as <date>-usage-history.json.
  • Copy: The offline license card explains that the instance doesn't report usage automatically and that usage must be manually sent to ar@sourcebot.dev.
image

Notes

  • The export button only renders for offline licenses, but recording runs on all deployments (one tiny row/day, negligible).
  • No retention/pruning yet.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Offline license settings page now includes a "Download usage report" button to export usage data as JSON files.
    • Service ping history is now recorded locally for offline deployments to support usage tracking.
  • Refactor

    • Improved license validation helper functions for better code organization.

Record each service ping in a new ServicePingEvent table (activation code
stripped) so offline deployments, which can't report usage to Lighthouse
automatically, can download their usage history and email it to us.
Adds a "Download usage report" button to the offline license settings card
that exports the recorded pings as a JSON file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a687f59b-8ce6-4388-9572-17e7790ee58f

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5271e and e8bbbaa.

📒 Files selected for processing (7)
  • packages/db/prisma/migrations/20260618210032_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/shared/src/entitlements.ts
  • packages/shared/src/index.server.ts
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/features/billing/servicePing.ts

Walkthrough

Adds a ServicePingEvent database table to persist sanitized service ping payloads per organization. syncWithLighthouse now records each ping to the DB before sending it to Lighthouse. A new server action getServicePingHistory retrieves the history, and a DownloadServicePingHistoryButton in the offline license card lets owners export it as JSON.

Changes

Service Ping Event Recording and Download

Layer / File(s)Summary
ServicePingEvent DB schema and migration
packages/db/prisma/migrations/.../migration.sql, packages/db/prisma/schema.prisma
Migration creates the ServicePingEvent table with id, payload JSONB, createdAt, and a cascading FK to Org. Prisma schema adds the model and back-reference field on Org.
Best-effort ping recording in syncWithLighthouse
packages/shared/src/entitlements.ts, packages/shared/src/index.server.ts, packages/web/src/features/billing/servicePing.ts
isValidOfflineLicenseActive and isValidOnlineLicenseActive are extracted as exported helpers. recordServicePingInDB strips activationCode and writes the sanitized payload to servicePingEvent, swallowing errors. syncWithLighthouse awaits this call before the offline-license early return.
Server action, download button, and offline card integration
packages/web/src/app/(app)/settings/license/actions.ts, packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx, packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
getServicePingHistory server action queries servicePingEvent ordered by createdAt and maps rows to ServicePingHistoryEntry. DownloadServicePingHistoryButton calls the action, handles error/empty toasts, and triggers a JSON blob download. OfflineLicenseCard gains a new bordered footer section rendering the button alongside a manual-reporting notice.
Changelog
CHANGELOG.md
Adds an unreleased entry describing service ping history recording and the new download button.

Sequence Diagram(s)

sequenceDiagram
rect rgba(173, 216, 230, 0.5)
note over syncWithLighthouse,servicePingEvent: Every ping cycle
syncWithLighthouse->>recordServicePingInDB: await recordServicePingInDB(orgId, payload)
recordServicePingInDB->>recordServicePingInDB: strip activationCode
recordServicePingInDB->>servicePingEvent: prisma.servicePingEvent.create()
servicePingEvent-->>recordServicePingInDB: saved (errors caught+logged)
recordServicePingInDB-->>syncWithLighthouse: returns
syncWithLighthouse->>LighthouseClient: ping(payload) or skip if offline
end
rect rgba(144, 238, 144, 0.5)
note over Owner,servicePingEvent: On-demand export
Owner->>DownloadServicePingHistoryButton: click "Download usage report"
DownloadServicePingHistoryButton->>getServicePingHistory: getServicePingHistory()
getServicePingHistory->>servicePingEvent: findMany(orgId, orderBy: createdAt asc)
servicePingEvent-->>getServicePingHistory: rows
getServicePingHistory-->>DownloadServicePingHistoryButton: ServicePingHistoryEntry[]
DownloadServicePingHistoryButton->>Owner: JSON blob download
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1296: Modifies syncWithLighthouse in the same servicePing.ts file to extend the ping payload, directly adjacent to where this PR inserts the recordServicePingInDB call.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main changes: recording service ping history and adding a usage report download button for offline deployments.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/service-ping-history

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

brendan-kellamand others added 2 commits June 18, 2026 11:51
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/web/src/app/(app)/settings/license/actions.ts (1)

19-21: ⚖️ Poor tradeoff

Consider pagination or limits for large datasets.

This query fetches all service ping events without pagination. For deployments running for years with daily pings, this could return thousands of records. While offline deployments may genuinely need all history, consider whether:

  1. A take/skip pagination strategy would be useful
  2. A date range filter would help users export specific periods
  3. A reasonable upper limit (e.g., last 1000 events) should be enforced
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/`(app)/settings/license/actions.ts around lines 19 - 21,
The query in the servicePingEvent.findMany() call fetches all records without
any limit or pagination, which could cause performance issues with large
datasets over time. Add a take parameter to limit the number of records returned
(e.g., last 1000 events) as a reasonable upper bound, and optionally implement
pagination with take and skip parameters or add a where clause with a date range
filter to allow users to query specific time periods instead of always fetching
the complete history.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql`:
- Around line 1-8: In the migration file creating the ServicePingEvent table,
add a database index on the createdAt column to optimize the ordered queries
performed by the getServicePingHistory server action. The createdAt column is
used for sorting results in ascending order but currently lacks an index, which
will cause full table scans as the table grows. Add a CREATE INDEX statement
after the table creation to create an index on the createdAt column of the
ServicePingEvent table to ensure queries remain performant as data accumulates.
In `@packages/db/prisma/schema.prisma`:
- Around line 361-365: The ServicePingEvent model lacks an orgId field which
breaks multi-tenant isolation and allows any OWNER to access all organizations'
service ping events. Add an orgId field to the ServicePingEvent model in
schema.prisma as a String required field with a foreign key relation to the
Organization model. Then update the getServicePingHistory function in actions.ts
to filter ServicePingEvent records by the requesting organization's orgId
instead of fetching all records. Finally, update the recordServicePingInDB
function in servicePing.ts to capture and include the orgId when creating new
ServicePingEvent records.
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The servicePingEvent.findMany() query is missing an orgId
filter, which creates a security vulnerability allowing cross-organization data
access. Add a where clause to the findMany call that filters servicePingEvent
records by orgId using role.orgId to ensure only the current organization's
service ping events are retrieved. This prevents any OWNER from accessing
service ping history from other organizations.
---
Nitpick comments:
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The query in the servicePingEvent.findMany() call fetches
all records without any limit or pagination, which could cause performance
issues with large datasets over time. Add a take parameter to limit the number
of records returned (e.g., last 1000 events) as a reasonable upper bound, and
optionally implement pagination with take and skip parameters or add a where
clause with a date range filter to allow users to query specific time periods
instead of always fetching the complete history.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 29aa261b-7964-47ab-b600-895f44389483

📥 Commits

Reviewing files that changed from the base of the PR and between 9320065 and 5c5271e.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
  • packages/web/src/features/billing/servicePing.ts

Comment threadpackages/db/prisma/schema.prisma
Comment threadpackages/web/src/app/(app)/settings/license/actions.ts
Comment threadpackages/web/src/features/billing/servicePing.ts
msukkari
msukkari previously approved these changes Jun 18, 2026
@brendan-kellam
brendan-kellam merged commit f0bb90b into mainJun 18, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/service-ping-history branch June 18, 2026 21:28
@github-actionsgithub-actionsBot mentioned this pull request Jun 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@brendan-kellam@msukkari
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): record service ping history and add usage report download - #1348

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history
Jun 18, 2026
Merged

feat(web): record service ping history and add usage report download#1348
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Overview

Offline / air-gapped deployments can't report usage to Lighthouse automatically. This change records each service ping locally so those deployments can download their usage history and send it to us out-of-band.

Changes

  • DB: New ServicePingEvent table (id, payload JSON, createdAt) + migration.
  • Recording: syncWithLighthouse now persists each ping (via recordServicePingInDB) before sending it to Lighthouse, so the history is captured even when the outbound ping fails (as it always will on air-gapped instances). The activation code is stripped before persisting, and recording is best-effort (wrapped in try/catch + logging) so a DB error never blocks the real ping.
  • Export UI: A "Download usage report" button on the offline license settings card (Settings → License), backed by an OWNER-gated server action that returns the recorded events. Downloads as <date>-usage-history.json.
  • Copy: The offline license card explains that the instance doesn't report usage automatically and that usage must be manually sent to ar@sourcebot.dev.
image

Notes

  • The export button only renders for offline licenses, but recording runs on all deployments (one tiny row/day, negligible).
  • No retention/pruning yet.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Offline license settings page now includes a "Download usage report" button to export usage data as JSON files.
    • Service ping history is now recorded locally for offline deployments to support usage tracking.
  • Refactor

    • Improved license validation helper functions for better code organization.

Record each service ping in a new ServicePingEvent table (activation code
stripped) so offline deployments, which can't report usage to Lighthouse
automatically, can download their usage history and email it to us.
Adds a "Download usage report" button to the offline license settings card
that exports the recorded pings as a JSON file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a687f59b-8ce6-4388-9572-17e7790ee58f

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5271e and e8bbbaa.

📒 Files selected for processing (7)
  • packages/db/prisma/migrations/20260618210032_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/shared/src/entitlements.ts
  • packages/shared/src/index.server.ts
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/features/billing/servicePing.ts

Walkthrough

Adds a ServicePingEvent database table to persist sanitized service ping payloads per organization. syncWithLighthouse now records each ping to the DB before sending it to Lighthouse. A new server action getServicePingHistory retrieves the history, and a DownloadServicePingHistoryButton in the offline license card lets owners export it as JSON.

Changes

Service Ping Event Recording and Download

Layer / File(s)Summary
ServicePingEvent DB schema and migration
packages/db/prisma/migrations/.../migration.sql, packages/db/prisma/schema.prisma
Migration creates the ServicePingEvent table with id, payload JSONB, createdAt, and a cascading FK to Org. Prisma schema adds the model and back-reference field on Org.
Best-effort ping recording in syncWithLighthouse
packages/shared/src/entitlements.ts, packages/shared/src/index.server.ts, packages/web/src/features/billing/servicePing.ts
isValidOfflineLicenseActive and isValidOnlineLicenseActive are extracted as exported helpers. recordServicePingInDB strips activationCode and writes the sanitized payload to servicePingEvent, swallowing errors. syncWithLighthouse awaits this call before the offline-license early return.
Server action, download button, and offline card integration
packages/web/src/app/(app)/settings/license/actions.ts, packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx, packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
getServicePingHistory server action queries servicePingEvent ordered by createdAt and maps rows to ServicePingHistoryEntry. DownloadServicePingHistoryButton calls the action, handles error/empty toasts, and triggers a JSON blob download. OfflineLicenseCard gains a new bordered footer section rendering the button alongside a manual-reporting notice.
Changelog
CHANGELOG.md
Adds an unreleased entry describing service ping history recording and the new download button.

Sequence Diagram(s)

sequenceDiagram
rect rgba(173, 216, 230, 0.5)
note over syncWithLighthouse,servicePingEvent: Every ping cycle
syncWithLighthouse->>recordServicePingInDB: await recordServicePingInDB(orgId, payload)
recordServicePingInDB->>recordServicePingInDB: strip activationCode
recordServicePingInDB->>servicePingEvent: prisma.servicePingEvent.create()
servicePingEvent-->>recordServicePingInDB: saved (errors caught+logged)
recordServicePingInDB-->>syncWithLighthouse: returns
syncWithLighthouse->>LighthouseClient: ping(payload) or skip if offline
end
rect rgba(144, 238, 144, 0.5)
note over Owner,servicePingEvent: On-demand export
Owner->>DownloadServicePingHistoryButton: click "Download usage report"
DownloadServicePingHistoryButton->>getServicePingHistory: getServicePingHistory()
getServicePingHistory->>servicePingEvent: findMany(orgId, orderBy: createdAt asc)
servicePingEvent-->>getServicePingHistory: rows
getServicePingHistory-->>DownloadServicePingHistoryButton: ServicePingHistoryEntry[]
DownloadServicePingHistoryButton->>Owner: JSON blob download
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1296: Modifies syncWithLighthouse in the same servicePing.ts file to extend the ping payload, directly adjacent to where this PR inserts the recordServicePingInDB call.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main changes: recording service ping history and adding a usage report download button for offline deployments.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/service-ping-history

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

brendan-kellamand others added 2 commits June 18, 2026 11:51
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/web/src/app/(app)/settings/license/actions.ts (1)

19-21: ⚖️ Poor tradeoff

Consider pagination or limits for large datasets.

This query fetches all service ping events without pagination. For deployments running for years with daily pings, this could return thousands of records. While offline deployments may genuinely need all history, consider whether:

  1. A take/skip pagination strategy would be useful
  2. A date range filter would help users export specific periods
  3. A reasonable upper limit (e.g., last 1000 events) should be enforced
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/`(app)/settings/license/actions.ts around lines 19 - 21,
The query in the servicePingEvent.findMany() call fetches all records without
any limit or pagination, which could cause performance issues with large
datasets over time. Add a take parameter to limit the number of records returned
(e.g., last 1000 events) as a reasonable upper bound, and optionally implement
pagination with take and skip parameters or add a where clause with a date range
filter to allow users to query specific time periods instead of always fetching
the complete history.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql`:
- Around line 1-8: In the migration file creating the ServicePingEvent table,
add a database index on the createdAt column to optimize the ordered queries
performed by the getServicePingHistory server action. The createdAt column is
used for sorting results in ascending order but currently lacks an index, which
will cause full table scans as the table grows. Add a CREATE INDEX statement
after the table creation to create an index on the createdAt column of the
ServicePingEvent table to ensure queries remain performant as data accumulates.
In `@packages/db/prisma/schema.prisma`:
- Around line 361-365: The ServicePingEvent model lacks an orgId field which
breaks multi-tenant isolation and allows any OWNER to access all organizations'
service ping events. Add an orgId field to the ServicePingEvent model in
schema.prisma as a String required field with a foreign key relation to the
Organization model. Then update the getServicePingHistory function in actions.ts
to filter ServicePingEvent records by the requesting organization's orgId
instead of fetching all records. Finally, update the recordServicePingInDB
function in servicePing.ts to capture and include the orgId when creating new
ServicePingEvent records.
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The servicePingEvent.findMany() query is missing an orgId
filter, which creates a security vulnerability allowing cross-organization data
access. Add a where clause to the findMany call that filters servicePingEvent
records by orgId using role.orgId to ensure only the current organization's
service ping events are retrieved. This prevents any OWNER from accessing
service ping history from other organizations.
---
Nitpick comments:
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The query in the servicePingEvent.findMany() call fetches
all records without any limit or pagination, which could cause performance
issues with large datasets over time. Add a take parameter to limit the number
of records returned (e.g., last 1000 events) as a reasonable upper bound, and
optionally implement pagination with take and skip parameters or add a where
clause with a date range filter to allow users to query specific time periods
instead of always fetching the complete history.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 29aa261b-7964-47ab-b600-895f44389483

📥 Commits

Reviewing files that changed from the base of the PR and between 9320065 and 5c5271e.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
  • packages/web/src/features/billing/servicePing.ts

Comment threadpackages/db/prisma/schema.prisma
Comment threadpackages/web/src/app/(app)/settings/license/actions.ts
Comment threadpackages/web/src/features/billing/servicePing.ts
msukkari
msukkari previously approved these changes Jun 18, 2026
@brendan-kellam
brendan-kellam merged commit f0bb90b into mainJun 18, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/service-ping-history branch June 18, 2026 21:28
@github-actionsgithub-actionsBot mentioned this pull request Jun 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@brendan-kellam@msukkari
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): record service ping history and add usage report download - #1348

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history
Jun 18, 2026
Merged

feat(web): record service ping history and add usage report download#1348
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Overview

Offline / air-gapped deployments can't report usage to Lighthouse automatically. This change records each service ping locally so those deployments can download their usage history and send it to us out-of-band.

Changes

  • DB: New ServicePingEvent table (id, payload JSON, createdAt) + migration.
  • Recording: syncWithLighthouse now persists each ping (via recordServicePingInDB) before sending it to Lighthouse, so the history is captured even when the outbound ping fails (as it always will on air-gapped instances). The activation code is stripped before persisting, and recording is best-effort (wrapped in try/catch + logging) so a DB error never blocks the real ping.
  • Export UI: A "Download usage report" button on the offline license settings card (Settings → License), backed by an OWNER-gated server action that returns the recorded events. Downloads as <date>-usage-history.json.
  • Copy: The offline license card explains that the instance doesn't report usage automatically and that usage must be manually sent to ar@sourcebot.dev.
image

Notes

  • The export button only renders for offline licenses, but recording runs on all deployments (one tiny row/day, negligible).
  • No retention/pruning yet.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Offline license settings page now includes a "Download usage report" button to export usage data as JSON files.
    • Service ping history is now recorded locally for offline deployments to support usage tracking.
  • Refactor

    • Improved license validation helper functions for better code organization.

Record each service ping in a new ServicePingEvent table (activation code
stripped) so offline deployments, which can't report usage to Lighthouse
automatically, can download their usage history and email it to us.
Adds a "Download usage report" button to the offline license settings card
that exports the recorded pings as a JSON file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a687f59b-8ce6-4388-9572-17e7790ee58f

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5271e and e8bbbaa.

📒 Files selected for processing (7)
  • packages/db/prisma/migrations/20260618210032_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/shared/src/entitlements.ts
  • packages/shared/src/index.server.ts
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/features/billing/servicePing.ts

Walkthrough

Adds a ServicePingEvent database table to persist sanitized service ping payloads per organization. syncWithLighthouse now records each ping to the DB before sending it to Lighthouse. A new server action getServicePingHistory retrieves the history, and a DownloadServicePingHistoryButton in the offline license card lets owners export it as JSON.

Changes

Service Ping Event Recording and Download

Layer / File(s)Summary
ServicePingEvent DB schema and migration
packages/db/prisma/migrations/.../migration.sql, packages/db/prisma/schema.prisma
Migration creates the ServicePingEvent table with id, payload JSONB, createdAt, and a cascading FK to Org. Prisma schema adds the model and back-reference field on Org.
Best-effort ping recording in syncWithLighthouse
packages/shared/src/entitlements.ts, packages/shared/src/index.server.ts, packages/web/src/features/billing/servicePing.ts
isValidOfflineLicenseActive and isValidOnlineLicenseActive are extracted as exported helpers. recordServicePingInDB strips activationCode and writes the sanitized payload to servicePingEvent, swallowing errors. syncWithLighthouse awaits this call before the offline-license early return.
Server action, download button, and offline card integration
packages/web/src/app/(app)/settings/license/actions.ts, packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx, packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
getServicePingHistory server action queries servicePingEvent ordered by createdAt and maps rows to ServicePingHistoryEntry. DownloadServicePingHistoryButton calls the action, handles error/empty toasts, and triggers a JSON blob download. OfflineLicenseCard gains a new bordered footer section rendering the button alongside a manual-reporting notice.
Changelog
CHANGELOG.md
Adds an unreleased entry describing service ping history recording and the new download button.

Sequence Diagram(s)

sequenceDiagram
rect rgba(173, 216, 230, 0.5)
note over syncWithLighthouse,servicePingEvent: Every ping cycle
syncWithLighthouse->>recordServicePingInDB: await recordServicePingInDB(orgId, payload)
recordServicePingInDB->>recordServicePingInDB: strip activationCode
recordServicePingInDB->>servicePingEvent: prisma.servicePingEvent.create()
servicePingEvent-->>recordServicePingInDB: saved (errors caught+logged)
recordServicePingInDB-->>syncWithLighthouse: returns
syncWithLighthouse->>LighthouseClient: ping(payload) or skip if offline
end
rect rgba(144, 238, 144, 0.5)
note over Owner,servicePingEvent: On-demand export
Owner->>DownloadServicePingHistoryButton: click "Download usage report"
DownloadServicePingHistoryButton->>getServicePingHistory: getServicePingHistory()
getServicePingHistory->>servicePingEvent: findMany(orgId, orderBy: createdAt asc)
servicePingEvent-->>getServicePingHistory: rows
getServicePingHistory-->>DownloadServicePingHistoryButton: ServicePingHistoryEntry[]
DownloadServicePingHistoryButton->>Owner: JSON blob download
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1296: Modifies syncWithLighthouse in the same servicePing.ts file to extend the ping payload, directly adjacent to where this PR inserts the recordServicePingInDB call.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main changes: recording service ping history and adding a usage report download button for offline deployments.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/service-ping-history

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

brendan-kellamand others added 2 commits June 18, 2026 11:51
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/web/src/app/(app)/settings/license/actions.ts (1)

19-21: ⚖️ Poor tradeoff

Consider pagination or limits for large datasets.

This query fetches all service ping events without pagination. For deployments running for years with daily pings, this could return thousands of records. While offline deployments may genuinely need all history, consider whether:

  1. A take/skip pagination strategy would be useful
  2. A date range filter would help users export specific periods
  3. A reasonable upper limit (e.g., last 1000 events) should be enforced
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/`(app)/settings/license/actions.ts around lines 19 - 21,
The query in the servicePingEvent.findMany() call fetches all records without
any limit or pagination, which could cause performance issues with large
datasets over time. Add a take parameter to limit the number of records returned
(e.g., last 1000 events) as a reasonable upper bound, and optionally implement
pagination with take and skip parameters or add a where clause with a date range
filter to allow users to query specific time periods instead of always fetching
the complete history.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql`:
- Around line 1-8: In the migration file creating the ServicePingEvent table,
add a database index on the createdAt column to optimize the ordered queries
performed by the getServicePingHistory server action. The createdAt column is
used for sorting results in ascending order but currently lacks an index, which
will cause full table scans as the table grows. Add a CREATE INDEX statement
after the table creation to create an index on the createdAt column of the
ServicePingEvent table to ensure queries remain performant as data accumulates.
In `@packages/db/prisma/schema.prisma`:
- Around line 361-365: The ServicePingEvent model lacks an orgId field which
breaks multi-tenant isolation and allows any OWNER to access all organizations'
service ping events. Add an orgId field to the ServicePingEvent model in
schema.prisma as a String required field with a foreign key relation to the
Organization model. Then update the getServicePingHistory function in actions.ts
to filter ServicePingEvent records by the requesting organization's orgId
instead of fetching all records. Finally, update the recordServicePingInDB
function in servicePing.ts to capture and include the orgId when creating new
ServicePingEvent records.
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The servicePingEvent.findMany() query is missing an orgId
filter, which creates a security vulnerability allowing cross-organization data
access. Add a where clause to the findMany call that filters servicePingEvent
records by orgId using role.orgId to ensure only the current organization's
service ping events are retrieved. This prevents any OWNER from accessing
service ping history from other organizations.
---
Nitpick comments:
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The query in the servicePingEvent.findMany() call fetches
all records without any limit or pagination, which could cause performance
issues with large datasets over time. Add a take parameter to limit the number
of records returned (e.g., last 1000 events) as a reasonable upper bound, and
optionally implement pagination with take and skip parameters or add a where
clause with a date range filter to allow users to query specific time periods
instead of always fetching the complete history.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 29aa261b-7964-47ab-b600-895f44389483

📥 Commits

Reviewing files that changed from the base of the PR and between 9320065 and 5c5271e.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
  • packages/web/src/features/billing/servicePing.ts

Comment threadpackages/db/prisma/schema.prisma
Comment threadpackages/web/src/app/(app)/settings/license/actions.ts
Comment threadpackages/web/src/features/billing/servicePing.ts
msukkari
msukkari previously approved these changes Jun 18, 2026
@brendan-kellam
brendan-kellam merged commit f0bb90b into mainJun 18, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/service-ping-history branch June 18, 2026 21:28
@github-actionsgithub-actionsBot mentioned this pull request Jun 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@brendan-kellam@msukkari
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(web): record service ping history and add usage report download - #1348

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history
Jun 18, 2026
Merged

feat(web): record service ping history and add usage report download#1348
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Overview

Offline / air-gapped deployments can't report usage to Lighthouse automatically. This change records each service ping locally so those deployments can download their usage history and send it to us out-of-band.

Changes

  • DB: New ServicePingEvent table (id, payload JSON, createdAt) + migration.
  • Recording: syncWithLighthouse now persists each ping (via recordServicePingInDB) before sending it to Lighthouse, so the history is captured even when the outbound ping fails (as it always will on air-gapped instances). The activation code is stripped before persisting, and recording is best-effort (wrapped in try/catch + logging) so a DB error never blocks the real ping.
  • Export UI: A "Download usage report" button on the offline license settings card (Settings → License), backed by an OWNER-gated server action that returns the recorded events. Downloads as <date>-usage-history.json.
  • Copy: The offline license card explains that the instance doesn't report usage automatically and that usage must be manually sent to ar@sourcebot.dev.
image

Notes

  • The export button only renders for offline licenses, but recording runs on all deployments (one tiny row/day, negligible).
  • No retention/pruning yet.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Offline license settings page now includes a "Download usage report" button to export usage data as JSON files.
    • Service ping history is now recorded locally for offline deployments to support usage tracking.
  • Refactor

    • Improved license validation helper functions for better code organization.

Record each service ping in a new ServicePingEvent table (activation code
stripped) so offline deployments, which can't report usage to Lighthouse
automatically, can download their usage history and email it to us.
Adds a "Download usage report" button to the offline license settings card
that exports the recorded pings as a JSON file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a687f59b-8ce6-4388-9572-17e7790ee58f

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5271e and e8bbbaa.

📒 Files selected for processing (7)
  • packages/db/prisma/migrations/20260618210032_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/shared/src/entitlements.ts
  • packages/shared/src/index.server.ts
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/features/billing/servicePing.ts

Walkthrough

Adds a ServicePingEvent database table to persist sanitized service ping payloads per organization. syncWithLighthouse now records each ping to the DB before sending it to Lighthouse. A new server action getServicePingHistory retrieves the history, and a DownloadServicePingHistoryButton in the offline license card lets owners export it as JSON.

Changes

Service Ping Event Recording and Download

Layer / File(s)Summary
ServicePingEvent DB schema and migration
packages/db/prisma/migrations/.../migration.sql, packages/db/prisma/schema.prisma
Migration creates the ServicePingEvent table with id, payload JSONB, createdAt, and a cascading FK to Org. Prisma schema adds the model and back-reference field on Org.
Best-effort ping recording in syncWithLighthouse
packages/shared/src/entitlements.ts, packages/shared/src/index.server.ts, packages/web/src/features/billing/servicePing.ts
isValidOfflineLicenseActive and isValidOnlineLicenseActive are extracted as exported helpers. recordServicePingInDB strips activationCode and writes the sanitized payload to servicePingEvent, swallowing errors. syncWithLighthouse awaits this call before the offline-license early return.
Server action, download button, and offline card integration
packages/web/src/app/(app)/settings/license/actions.ts, packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx, packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
getServicePingHistory server action queries servicePingEvent ordered by createdAt and maps rows to ServicePingHistoryEntry. DownloadServicePingHistoryButton calls the action, handles error/empty toasts, and triggers a JSON blob download. OfflineLicenseCard gains a new bordered footer section rendering the button alongside a manual-reporting notice.
Changelog
CHANGELOG.md
Adds an unreleased entry describing service ping history recording and the new download button.

Sequence Diagram(s)

sequenceDiagram
rect rgba(173, 216, 230, 0.5)
note over syncWithLighthouse,servicePingEvent: Every ping cycle
syncWithLighthouse->>recordServicePingInDB: await recordServicePingInDB(orgId, payload)
recordServicePingInDB->>recordServicePingInDB: strip activationCode
recordServicePingInDB->>servicePingEvent: prisma.servicePingEvent.create()
servicePingEvent-->>recordServicePingInDB: saved (errors caught+logged)
recordServicePingInDB-->>syncWithLighthouse: returns
syncWithLighthouse->>LighthouseClient: ping(payload) or skip if offline
end
rect rgba(144, 238, 144, 0.5)
note over Owner,servicePingEvent: On-demand export
Owner->>DownloadServicePingHistoryButton: click "Download usage report"
DownloadServicePingHistoryButton->>getServicePingHistory: getServicePingHistory()
getServicePingHistory->>servicePingEvent: findMany(orgId, orderBy: createdAt asc)
servicePingEvent-->>getServicePingHistory: rows
getServicePingHistory-->>DownloadServicePingHistoryButton: ServicePingHistoryEntry[]
DownloadServicePingHistoryButton->>Owner: JSON blob download
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1296: Modifies syncWithLighthouse in the same servicePing.ts file to extend the ping payload, directly adjacent to where this PR inserts the recordServicePingInDB call.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main changes: recording service ping history and adding a usage report download button for offline deployments.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/service-ping-history

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

brendan-kellamand others added 2 commits June 18, 2026 11:51
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/web/src/app/(app)/settings/license/actions.ts (1)

19-21: ⚖️ Poor tradeoff

Consider pagination or limits for large datasets.

This query fetches all service ping events without pagination. For deployments running for years with daily pings, this could return thousands of records. While offline deployments may genuinely need all history, consider whether:

  1. A take/skip pagination strategy would be useful
  2. A date range filter would help users export specific periods
  3. A reasonable upper limit (e.g., last 1000 events) should be enforced
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/`(app)/settings/license/actions.ts around lines 19 - 21,
The query in the servicePingEvent.findMany() call fetches all records without
any limit or pagination, which could cause performance issues with large
datasets over time. Add a take parameter to limit the number of records returned
(e.g., last 1000 events) as a reasonable upper bound, and optionally implement
pagination with take and skip parameters or add a where clause with a date range
filter to allow users to query specific time periods instead of always fetching
the complete history.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql`:
- Around line 1-8: In the migration file creating the ServicePingEvent table,
add a database index on the createdAt column to optimize the ordered queries
performed by the getServicePingHistory server action. The createdAt column is
used for sorting results in ascending order but currently lacks an index, which
will cause full table scans as the table grows. Add a CREATE INDEX statement
after the table creation to create an index on the createdAt column of the
ServicePingEvent table to ensure queries remain performant as data accumulates.
In `@packages/db/prisma/schema.prisma`:
- Around line 361-365: The ServicePingEvent model lacks an orgId field which
breaks multi-tenant isolation and allows any OWNER to access all organizations'
service ping events. Add an orgId field to the ServicePingEvent model in
schema.prisma as a String required field with a foreign key relation to the
Organization model. Then update the getServicePingHistory function in actions.ts
to filter ServicePingEvent records by the requesting organization's orgId
instead of fetching all records. Finally, update the recordServicePingInDB
function in servicePing.ts to capture and include the orgId when creating new
ServicePingEvent records.
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The servicePingEvent.findMany() query is missing an orgId
filter, which creates a security vulnerability allowing cross-organization data
access. Add a where clause to the findMany call that filters servicePingEvent
records by orgId using role.orgId to ensure only the current organization's
service ping events are retrieved. This prevents any OWNER from accessing
service ping history from other organizations.
---
Nitpick comments:
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The query in the servicePingEvent.findMany() call fetches
all records without any limit or pagination, which could cause performance
issues with large datasets over time. Add a take parameter to limit the number
of records returned (e.g., last 1000 events) as a reasonable upper bound, and
optionally implement pagination with take and skip parameters or add a where
clause with a date range filter to allow users to query specific time periods
instead of always fetching the complete history.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 29aa261b-7964-47ab-b600-895f44389483

📥 Commits

Reviewing files that changed from the base of the PR and between 9320065 and 5c5271e.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
  • packages/web/src/features/billing/servicePing.ts

Comment threadpackages/db/prisma/schema.prisma
Comment threadpackages/web/src/app/(app)/settings/license/actions.ts
Comment threadpackages/web/src/features/billing/servicePing.ts
msukkari
msukkari previously approved these changes Jun 18, 2026
@brendan-kellam
brendan-kellam merged commit f0bb90b into mainJun 18, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/service-ping-history branch June 18, 2026 21:28
@github-actionsgithub-actionsBot mentioned this pull request Jun 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@brendan-kellam@msukkari
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): record service ping history and add usage report download - #1348

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history
Jun 18, 2026
Merged

feat(web): record service ping history and add usage report download#1348
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Overview

Offline / air-gapped deployments can't report usage to Lighthouse automatically. This change records each service ping locally so those deployments can download their usage history and send it to us out-of-band.

Changes

  • DB: New ServicePingEvent table (id, payload JSON, createdAt) + migration.
  • Recording: syncWithLighthouse now persists each ping (via recordServicePingInDB) before sending it to Lighthouse, so the history is captured even when the outbound ping fails (as it always will on air-gapped instances). The activation code is stripped before persisting, and recording is best-effort (wrapped in try/catch + logging) so a DB error never blocks the real ping.
  • Export UI: A "Download usage report" button on the offline license settings card (Settings → License), backed by an OWNER-gated server action that returns the recorded events. Downloads as <date>-usage-history.json.
  • Copy: The offline license card explains that the instance doesn't report usage automatically and that usage must be manually sent to ar@sourcebot.dev.
image

Notes

  • The export button only renders for offline licenses, but recording runs on all deployments (one tiny row/day, negligible).
  • No retention/pruning yet.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Offline license settings page now includes a "Download usage report" button to export usage data as JSON files.
    • Service ping history is now recorded locally for offline deployments to support usage tracking.
  • Refactor

    • Improved license validation helper functions for better code organization.

Record each service ping in a new ServicePingEvent table (activation code
stripped) so offline deployments, which can't report usage to Lighthouse
automatically, can download their usage history and email it to us.
Adds a "Download usage report" button to the offline license settings card
that exports the recorded pings as a JSON file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a687f59b-8ce6-4388-9572-17e7790ee58f

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5271e and e8bbbaa.

📒 Files selected for processing (7)
  • packages/db/prisma/migrations/20260618210032_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/shared/src/entitlements.ts
  • packages/shared/src/index.server.ts
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/features/billing/servicePing.ts

Walkthrough

Adds a ServicePingEvent database table to persist sanitized service ping payloads per organization. syncWithLighthouse now records each ping to the DB before sending it to Lighthouse. A new server action getServicePingHistory retrieves the history, and a DownloadServicePingHistoryButton in the offline license card lets owners export it as JSON.

Changes

Service Ping Event Recording and Download

Layer / File(s)Summary
ServicePingEvent DB schema and migration
packages/db/prisma/migrations/.../migration.sql, packages/db/prisma/schema.prisma
Migration creates the ServicePingEvent table with id, payload JSONB, createdAt, and a cascading FK to Org. Prisma schema adds the model and back-reference field on Org.
Best-effort ping recording in syncWithLighthouse
packages/shared/src/entitlements.ts, packages/shared/src/index.server.ts, packages/web/src/features/billing/servicePing.ts
isValidOfflineLicenseActive and isValidOnlineLicenseActive are extracted as exported helpers. recordServicePingInDB strips activationCode and writes the sanitized payload to servicePingEvent, swallowing errors. syncWithLighthouse awaits this call before the offline-license early return.
Server action, download button, and offline card integration
packages/web/src/app/(app)/settings/license/actions.ts, packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx, packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
getServicePingHistory server action queries servicePingEvent ordered by createdAt and maps rows to ServicePingHistoryEntry. DownloadServicePingHistoryButton calls the action, handles error/empty toasts, and triggers a JSON blob download. OfflineLicenseCard gains a new bordered footer section rendering the button alongside a manual-reporting notice.
Changelog
CHANGELOG.md
Adds an unreleased entry describing service ping history recording and the new download button.

Sequence Diagram(s)

sequenceDiagram
rect rgba(173, 216, 230, 0.5)
note over syncWithLighthouse,servicePingEvent: Every ping cycle
syncWithLighthouse->>recordServicePingInDB: await recordServicePingInDB(orgId, payload)
recordServicePingInDB->>recordServicePingInDB: strip activationCode
recordServicePingInDB->>servicePingEvent: prisma.servicePingEvent.create()
servicePingEvent-->>recordServicePingInDB: saved (errors caught+logged)
recordServicePingInDB-->>syncWithLighthouse: returns
syncWithLighthouse->>LighthouseClient: ping(payload) or skip if offline
end
rect rgba(144, 238, 144, 0.5)
note over Owner,servicePingEvent: On-demand export
Owner->>DownloadServicePingHistoryButton: click "Download usage report"
DownloadServicePingHistoryButton->>getServicePingHistory: getServicePingHistory()
getServicePingHistory->>servicePingEvent: findMany(orgId, orderBy: createdAt asc)
servicePingEvent-->>getServicePingHistory: rows
getServicePingHistory-->>DownloadServicePingHistoryButton: ServicePingHistoryEntry[]
DownloadServicePingHistoryButton->>Owner: JSON blob download
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1296: Modifies syncWithLighthouse in the same servicePing.ts file to extend the ping payload, directly adjacent to where this PR inserts the recordServicePingInDB call.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main changes: recording service ping history and adding a usage report download button for offline deployments.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/service-ping-history

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

brendan-kellamand others added 2 commits June 18, 2026 11:51
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/web/src/app/(app)/settings/license/actions.ts (1)

19-21: ⚖️ Poor tradeoff

Consider pagination or limits for large datasets.

This query fetches all service ping events without pagination. For deployments running for years with daily pings, this could return thousands of records. While offline deployments may genuinely need all history, consider whether:

  1. A take/skip pagination strategy would be useful
  2. A date range filter would help users export specific periods
  3. A reasonable upper limit (e.g., last 1000 events) should be enforced
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/`(app)/settings/license/actions.ts around lines 19 - 21,
The query in the servicePingEvent.findMany() call fetches all records without
any limit or pagination, which could cause performance issues with large
datasets over time. Add a take parameter to limit the number of records returned
(e.g., last 1000 events) as a reasonable upper bound, and optionally implement
pagination with take and skip parameters or add a where clause with a date range
filter to allow users to query specific time periods instead of always fetching
the complete history.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql`:
- Around line 1-8: In the migration file creating the ServicePingEvent table,
add a database index on the createdAt column to optimize the ordered queries
performed by the getServicePingHistory server action. The createdAt column is
used for sorting results in ascending order but currently lacks an index, which
will cause full table scans as the table grows. Add a CREATE INDEX statement
after the table creation to create an index on the createdAt column of the
ServicePingEvent table to ensure queries remain performant as data accumulates.
In `@packages/db/prisma/schema.prisma`:
- Around line 361-365: The ServicePingEvent model lacks an orgId field which
breaks multi-tenant isolation and allows any OWNER to access all organizations'
service ping events. Add an orgId field to the ServicePingEvent model in
schema.prisma as a String required field with a foreign key relation to the
Organization model. Then update the getServicePingHistory function in actions.ts
to filter ServicePingEvent records by the requesting organization's orgId
instead of fetching all records. Finally, update the recordServicePingInDB
function in servicePing.ts to capture and include the orgId when creating new
ServicePingEvent records.
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The servicePingEvent.findMany() query is missing an orgId
filter, which creates a security vulnerability allowing cross-organization data
access. Add a where clause to the findMany call that filters servicePingEvent
records by orgId using role.orgId to ensure only the current organization's
service ping events are retrieved. This prevents any OWNER from accessing
service ping history from other organizations.
---
Nitpick comments:
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The query in the servicePingEvent.findMany() call fetches
all records without any limit or pagination, which could cause performance
issues with large datasets over time. Add a take parameter to limit the number
of records returned (e.g., last 1000 events) as a reasonable upper bound, and
optionally implement pagination with take and skip parameters or add a where
clause with a date range filter to allow users to query specific time periods
instead of always fetching the complete history.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 29aa261b-7964-47ab-b600-895f44389483

📥 Commits

Reviewing files that changed from the base of the PR and between 9320065 and 5c5271e.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
  • packages/web/src/features/billing/servicePing.ts

Comment threadpackages/db/prisma/schema.prisma
Comment threadpackages/web/src/app/(app)/settings/license/actions.ts
Comment threadpackages/web/src/features/billing/servicePing.ts
msukkari
msukkari previously approved these changes Jun 18, 2026
@brendan-kellam
brendan-kellam merged commit f0bb90b into mainJun 18, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/service-ping-history branch June 18, 2026 21:28
@github-actionsgithub-actionsBot mentioned this pull request Jun 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@brendan-kellam@msukkari
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): record service ping history and add usage report download - #1348

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history
Jun 18, 2026
Merged

feat(web): record service ping history and add usage report download#1348
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Overview

Offline / air-gapped deployments can't report usage to Lighthouse automatically. This change records each service ping locally so those deployments can download their usage history and send it to us out-of-band.

Changes

  • DB: New ServicePingEvent table (id, payload JSON, createdAt) + migration.
  • Recording: syncWithLighthouse now persists each ping (via recordServicePingInDB) before sending it to Lighthouse, so the history is captured even when the outbound ping fails (as it always will on air-gapped instances). The activation code is stripped before persisting, and recording is best-effort (wrapped in try/catch + logging) so a DB error never blocks the real ping.
  • Export UI: A "Download usage report" button on the offline license settings card (Settings → License), backed by an OWNER-gated server action that returns the recorded events. Downloads as <date>-usage-history.json.
  • Copy: The offline license card explains that the instance doesn't report usage automatically and that usage must be manually sent to ar@sourcebot.dev.
image

Notes

  • The export button only renders for offline licenses, but recording runs on all deployments (one tiny row/day, negligible).
  • No retention/pruning yet.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Offline license settings page now includes a "Download usage report" button to export usage data as JSON files.
    • Service ping history is now recorded locally for offline deployments to support usage tracking.
  • Refactor

    • Improved license validation helper functions for better code organization.

Record each service ping in a new ServicePingEvent table (activation code
stripped) so offline deployments, which can't report usage to Lighthouse
automatically, can download their usage history and email it to us.
Adds a "Download usage report" button to the offline license settings card
that exports the recorded pings as a JSON file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a687f59b-8ce6-4388-9572-17e7790ee58f

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5271e and e8bbbaa.

📒 Files selected for processing (7)
  • packages/db/prisma/migrations/20260618210032_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/shared/src/entitlements.ts
  • packages/shared/src/index.server.ts
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/features/billing/servicePing.ts

Walkthrough

Adds a ServicePingEvent database table to persist sanitized service ping payloads per organization. syncWithLighthouse now records each ping to the DB before sending it to Lighthouse. A new server action getServicePingHistory retrieves the history, and a DownloadServicePingHistoryButton in the offline license card lets owners export it as JSON.

Changes

Service Ping Event Recording and Download

Layer / File(s)Summary
ServicePingEvent DB schema and migration
packages/db/prisma/migrations/.../migration.sql, packages/db/prisma/schema.prisma
Migration creates the ServicePingEvent table with id, payload JSONB, createdAt, and a cascading FK to Org. Prisma schema adds the model and back-reference field on Org.
Best-effort ping recording in syncWithLighthouse
packages/shared/src/entitlements.ts, packages/shared/src/index.server.ts, packages/web/src/features/billing/servicePing.ts
isValidOfflineLicenseActive and isValidOnlineLicenseActive are extracted as exported helpers. recordServicePingInDB strips activationCode and writes the sanitized payload to servicePingEvent, swallowing errors. syncWithLighthouse awaits this call before the offline-license early return.
Server action, download button, and offline card integration
packages/web/src/app/(app)/settings/license/actions.ts, packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx, packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
getServicePingHistory server action queries servicePingEvent ordered by createdAt and maps rows to ServicePingHistoryEntry. DownloadServicePingHistoryButton calls the action, handles error/empty toasts, and triggers a JSON blob download. OfflineLicenseCard gains a new bordered footer section rendering the button alongside a manual-reporting notice.
Changelog
CHANGELOG.md
Adds an unreleased entry describing service ping history recording and the new download button.

Sequence Diagram(s)

sequenceDiagram
rect rgba(173, 216, 230, 0.5)
note over syncWithLighthouse,servicePingEvent: Every ping cycle
syncWithLighthouse->>recordServicePingInDB: await recordServicePingInDB(orgId, payload)
recordServicePingInDB->>recordServicePingInDB: strip activationCode
recordServicePingInDB->>servicePingEvent: prisma.servicePingEvent.create()
servicePingEvent-->>recordServicePingInDB: saved (errors caught+logged)
recordServicePingInDB-->>syncWithLighthouse: returns
syncWithLighthouse->>LighthouseClient: ping(payload) or skip if offline
end
rect rgba(144, 238, 144, 0.5)
note over Owner,servicePingEvent: On-demand export
Owner->>DownloadServicePingHistoryButton: click "Download usage report"
DownloadServicePingHistoryButton->>getServicePingHistory: getServicePingHistory()
getServicePingHistory->>servicePingEvent: findMany(orgId, orderBy: createdAt asc)
servicePingEvent-->>getServicePingHistory: rows
getServicePingHistory-->>DownloadServicePingHistoryButton: ServicePingHistoryEntry[]
DownloadServicePingHistoryButton->>Owner: JSON blob download
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1296: Modifies syncWithLighthouse in the same servicePing.ts file to extend the ping payload, directly adjacent to where this PR inserts the recordServicePingInDB call.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main changes: recording service ping history and adding a usage report download button for offline deployments.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/service-ping-history

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

brendan-kellamand others added 2 commits June 18, 2026 11:51
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/web/src/app/(app)/settings/license/actions.ts (1)

19-21: ⚖️ Poor tradeoff

Consider pagination or limits for large datasets.

This query fetches all service ping events without pagination. For deployments running for years with daily pings, this could return thousands of records. While offline deployments may genuinely need all history, consider whether:

  1. A take/skip pagination strategy would be useful
  2. A date range filter would help users export specific periods
  3. A reasonable upper limit (e.g., last 1000 events) should be enforced
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/`(app)/settings/license/actions.ts around lines 19 - 21,
The query in the servicePingEvent.findMany() call fetches all records without
any limit or pagination, which could cause performance issues with large
datasets over time. Add a take parameter to limit the number of records returned
(e.g., last 1000 events) as a reasonable upper bound, and optionally implement
pagination with take and skip parameters or add a where clause with a date range
filter to allow users to query specific time periods instead of always fetching
the complete history.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql`:
- Around line 1-8: In the migration file creating the ServicePingEvent table,
add a database index on the createdAt column to optimize the ordered queries
performed by the getServicePingHistory server action. The createdAt column is
used for sorting results in ascending order but currently lacks an index, which
will cause full table scans as the table grows. Add a CREATE INDEX statement
after the table creation to create an index on the createdAt column of the
ServicePingEvent table to ensure queries remain performant as data accumulates.
In `@packages/db/prisma/schema.prisma`:
- Around line 361-365: The ServicePingEvent model lacks an orgId field which
breaks multi-tenant isolation and allows any OWNER to access all organizations'
service ping events. Add an orgId field to the ServicePingEvent model in
schema.prisma as a String required field with a foreign key relation to the
Organization model. Then update the getServicePingHistory function in actions.ts
to filter ServicePingEvent records by the requesting organization's orgId
instead of fetching all records. Finally, update the recordServicePingInDB
function in servicePing.ts to capture and include the orgId when creating new
ServicePingEvent records.
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The servicePingEvent.findMany() query is missing an orgId
filter, which creates a security vulnerability allowing cross-organization data
access. Add a where clause to the findMany call that filters servicePingEvent
records by orgId using role.orgId to ensure only the current organization's
service ping events are retrieved. This prevents any OWNER from accessing
service ping history from other organizations.
---
Nitpick comments:
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The query in the servicePingEvent.findMany() call fetches
all records without any limit or pagination, which could cause performance
issues with large datasets over time. Add a take parameter to limit the number
of records returned (e.g., last 1000 events) as a reasonable upper bound, and
optionally implement pagination with take and skip parameters or add a where
clause with a date range filter to allow users to query specific time periods
instead of always fetching the complete history.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 29aa261b-7964-47ab-b600-895f44389483

📥 Commits

Reviewing files that changed from the base of the PR and between 9320065 and 5c5271e.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
  • packages/web/src/features/billing/servicePing.ts

Comment threadpackages/db/prisma/schema.prisma
Comment threadpackages/web/src/app/(app)/settings/license/actions.ts
Comment threadpackages/web/src/features/billing/servicePing.ts
msukkari
msukkari previously approved these changes Jun 18, 2026
@brendan-kellam
brendan-kellam merged commit f0bb90b into mainJun 18, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/service-ping-history branch June 18, 2026 21:28
@github-actionsgithub-actionsBot mentioned this pull request Jun 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@brendan-kellam@msukkari
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(web): record service ping history and add usage report download - #1348

Merged
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history
Jun 18, 2026
Merged

feat(web): record service ping history and add usage report download#1348
brendan-kellam merged 5 commits into
mainfrom
brendan/service-ping-history

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Overview

Offline / air-gapped deployments can't report usage to Lighthouse automatically. This change records each service ping locally so those deployments can download their usage history and send it to us out-of-band.

Changes

  • DB: New ServicePingEvent table (id, payload JSON, createdAt) + migration.
  • Recording: syncWithLighthouse now persists each ping (via recordServicePingInDB) before sending it to Lighthouse, so the history is captured even when the outbound ping fails (as it always will on air-gapped instances). The activation code is stripped before persisting, and recording is best-effort (wrapped in try/catch + logging) so a DB error never blocks the real ping.
  • Export UI: A "Download usage report" button on the offline license settings card (Settings → License), backed by an OWNER-gated server action that returns the recorded events. Downloads as <date>-usage-history.json.
  • Copy: The offline license card explains that the instance doesn't report usage automatically and that usage must be manually sent to ar@sourcebot.dev.
image

Notes

  • The export button only renders for offline licenses, but recording runs on all deployments (one tiny row/day, negligible).
  • No retention/pruning yet.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Offline license settings page now includes a "Download usage report" button to export usage data as JSON files.
    • Service ping history is now recorded locally for offline deployments to support usage tracking.
  • Refactor

    • Improved license validation helper functions for better code organization.

Record each service ping in a new ServicePingEvent table (activation code
stripped) so offline deployments, which can't report usage to Lighthouse
automatically, can download their usage history and email it to us.
Adds a "Download usage report" button to the offline license settings card
that exports the recorded pings as a JSON file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a687f59b-8ce6-4388-9572-17e7790ee58f

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5271e and e8bbbaa.

📒 Files selected for processing (7)
  • packages/db/prisma/migrations/20260618210032_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/shared/src/entitlements.ts
  • packages/shared/src/index.server.ts
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/features/billing/servicePing.ts

Walkthrough

Adds a ServicePingEvent database table to persist sanitized service ping payloads per organization. syncWithLighthouse now records each ping to the DB before sending it to Lighthouse. A new server action getServicePingHistory retrieves the history, and a DownloadServicePingHistoryButton in the offline license card lets owners export it as JSON.

Changes

Service Ping Event Recording and Download

Layer / File(s)Summary
ServicePingEvent DB schema and migration
packages/db/prisma/migrations/.../migration.sql, packages/db/prisma/schema.prisma
Migration creates the ServicePingEvent table with id, payload JSONB, createdAt, and a cascading FK to Org. Prisma schema adds the model and back-reference field on Org.
Best-effort ping recording in syncWithLighthouse
packages/shared/src/entitlements.ts, packages/shared/src/index.server.ts, packages/web/src/features/billing/servicePing.ts
isValidOfflineLicenseActive and isValidOnlineLicenseActive are extracted as exported helpers. recordServicePingInDB strips activationCode and writes the sanitized payload to servicePingEvent, swallowing errors. syncWithLighthouse awaits this call before the offline-license early return.
Server action, download button, and offline card integration
packages/web/src/app/(app)/settings/license/actions.ts, packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx, packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
getServicePingHistory server action queries servicePingEvent ordered by createdAt and maps rows to ServicePingHistoryEntry. DownloadServicePingHistoryButton calls the action, handles error/empty toasts, and triggers a JSON blob download. OfflineLicenseCard gains a new bordered footer section rendering the button alongside a manual-reporting notice.
Changelog
CHANGELOG.md
Adds an unreleased entry describing service ping history recording and the new download button.

Sequence Diagram(s)

sequenceDiagram
rect rgba(173, 216, 230, 0.5)
note over syncWithLighthouse,servicePingEvent: Every ping cycle
syncWithLighthouse->>recordServicePingInDB: await recordServicePingInDB(orgId, payload)
recordServicePingInDB->>recordServicePingInDB: strip activationCode
recordServicePingInDB->>servicePingEvent: prisma.servicePingEvent.create()
servicePingEvent-->>recordServicePingInDB: saved (errors caught+logged)
recordServicePingInDB-->>syncWithLighthouse: returns
syncWithLighthouse->>LighthouseClient: ping(payload) or skip if offline
end
rect rgba(144, 238, 144, 0.5)
note over Owner,servicePingEvent: On-demand export
Owner->>DownloadServicePingHistoryButton: click "Download usage report"
DownloadServicePingHistoryButton->>getServicePingHistory: getServicePingHistory()
getServicePingHistory->>servicePingEvent: findMany(orgId, orderBy: createdAt asc)
servicePingEvent-->>getServicePingHistory: rows
getServicePingHistory-->>DownloadServicePingHistoryButton: ServicePingHistoryEntry[]
DownloadServicePingHistoryButton->>Owner: JSON blob download
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1296: Modifies syncWithLighthouse in the same servicePing.ts file to extend the ping payload, directly adjacent to where this PR inserts the recordServicePingInDB call.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main changes: recording service ping history and adding a usage report download button for offline deployments.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/service-ping-history

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

brendan-kellamand others added 2 commits June 18, 2026 11:51
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/web/src/app/(app)/settings/license/actions.ts (1)

19-21: ⚖️ Poor tradeoff

Consider pagination or limits for large datasets.

This query fetches all service ping events without pagination. For deployments running for years with daily pings, this could return thousands of records. While offline deployments may genuinely need all history, consider whether:

  1. A take/skip pagination strategy would be useful
  2. A date range filter would help users export specific periods
  3. A reasonable upper limit (e.g., last 1000 events) should be enforced
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/`(app)/settings/license/actions.ts around lines 19 - 21,
The query in the servicePingEvent.findMany() call fetches all records without
any limit or pagination, which could cause performance issues with large
datasets over time. Add a take parameter to limit the number of records returned
(e.g., last 1000 events) as a reasonable upper bound, and optionally implement
pagination with take and skip parameters or add a where clause with a date range
filter to allow users to query specific time periods instead of always fetching
the complete history.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql`:
- Around line 1-8: In the migration file creating the ServicePingEvent table,
add a database index on the createdAt column to optimize the ordered queries
performed by the getServicePingHistory server action. The createdAt column is
used for sorting results in ascending order but currently lacks an index, which
will cause full table scans as the table grows. Add a CREATE INDEX statement
after the table creation to create an index on the createdAt column of the
ServicePingEvent table to ensure queries remain performant as data accumulates.
In `@packages/db/prisma/schema.prisma`:
- Around line 361-365: The ServicePingEvent model lacks an orgId field which
breaks multi-tenant isolation and allows any OWNER to access all organizations'
service ping events. Add an orgId field to the ServicePingEvent model in
schema.prisma as a String required field with a foreign key relation to the
Organization model. Then update the getServicePingHistory function in actions.ts
to filter ServicePingEvent records by the requesting organization's orgId
instead of fetching all records. Finally, update the recordServicePingInDB
function in servicePing.ts to capture and include the orgId when creating new
ServicePingEvent records.
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The servicePingEvent.findMany() query is missing an orgId
filter, which creates a security vulnerability allowing cross-organization data
access. Add a where clause to the findMany call that filters servicePingEvent
records by orgId using role.orgId to ensure only the current organization's
service ping events are retrieved. This prevents any OWNER from accessing
service ping history from other organizations.
---
Nitpick comments:
In `@packages/web/src/app/`(app)/settings/license/actions.ts:
- Around line 19-21: The query in the servicePingEvent.findMany() call fetches
all records without any limit or pagination, which could cause performance
issues with large datasets over time. Add a take parameter to limit the number
of records returned (e.g., last 1000 events) as a reasonable upper bound, and
optionally implement pagination with take and skip parameters or add a where
clause with a date range filter to allow users to query specific time periods
instead of always fetching the complete history.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 29aa261b-7964-47ab-b600-895f44389483

📥 Commits

Reviewing files that changed from the base of the PR and between 9320065 and 5c5271e.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260618182127_add_service_ping_event_table/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/(app)/settings/license/actions.ts
  • packages/web/src/app/(app)/settings/license/downloadServicePingHistoryButton.tsx
  • packages/web/src/app/(app)/settings/license/offlineLicenseCard.tsx
  • packages/web/src/features/billing/servicePing.ts

Comment threadpackages/db/prisma/schema.prisma
Comment threadpackages/web/src/app/(app)/settings/license/actions.ts
Comment threadpackages/web/src/features/billing/servicePing.ts
msukkari
msukkari previously approved these changes Jun 18, 2026
@brendan-kellam
brendan-kellam merged commit f0bb90b into mainJun 18, 2026
8 of 9 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/service-ping-history branch June 18, 2026 21:28
@github-actionsgithub-actionsBot mentioned this pull request Jun 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@brendan-kellam@msukkari