Files
twenty/packages/twenty-server
Paul Rastoin 7939cd8684 feat(server): app lifecycle metrics (install/uninstall/upgrade + marketplace publish) (#22656)
## What

Adds product metrics for the app/marketplace lifecycle so they can be
graphed in Grafana. No app-lifecycle metrics existed before; the flows
only logged.

### New counters (`MetricsKeys`)
- `app-install/succeeded` · `app-install/failed`
- `app-upgrade/succeeded` · `app-upgrade/failed`
- `app-uninstall/succeeded` · `app-uninstall/failed`
- `app-registration/created` (new app published) ·
`app-registration/version-published` (new version available)

All carry `universalIdentifier`, `appName`, `sourceType` attributes
(plus `version`, and `errorCode` on failures).

### New gauge
- `twenty_app_installed_workspaces_total` — observable gauge emitting
the top 100 external apps by installed-workspace count (excludes
built-in LOCAL apps). Powers a "most installed apps" leaderboard;
combine with the 24h install/uninstall event counters for recent
activity.

## Where metrics are emitted
- **Install / upgrade**
(`ApplicationInstallService.doInstallApplication`): success + failure
branches, distinguished by the existing `isVersionUpgrade` flag.
- **Uninstall** (`ApplicationInstallResolver.uninstallApplication`): at
the resolver, deliberately *not* in the sync service, so
rollback-triggered internal uninstalls (fired from the install catch
block) don't pollute uninstall counts.
- **Publish / new version**: `upsertFromCatalog` (npm marketplace sync),
`checkForUpdates` (npm version poll), and the tarball CLI publish path.

### Exactly-once version-published
Both the catalog-sync and version-check crons converge
`latestAvailableVersion`. Each emission point is **change-guarded**
(`stored !== incoming`), so whichever cron observes the change first
emits, and the other becomes a no-op. No double counting, no
race-dependent misses.

## Pipeline
Metrics flow through the existing OTel -> ClickHouse path and can be
graphed from the `twenty-product-metrics` dashboard (dashboard changes
live in infra-twenty, not this PR).

## Test plan
- [x] `nx typecheck twenty-server`
- [x] oxlint + oxfmt on changed files
- [x] `oauth-discovery.controller.spec` (the one existing spec touching
these services) passes
- [ ] Reviewer: sanity-check metric names/attributes and cardinality
choices (no `workspaceId` attribute, LOCAL apps excluded from the gauge)


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22656?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: martmull <martmull@hotmail.fr>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-07-16 15:53:59 +02:00
..