feat(posthog): keep social_accounts_count and posts_count fresh on account group#43
Merged
Merged
Conversation
…count group Onboarding/lifecycle workflows in PostHog (and downstream tools like SendKit) need to segment users by how many social accounts they've connected and how many posts they've created. The existing SyncUser job only re-emitted these counts on signup and billing changes, so the values went stale the moment a user did anything meaningful. This wires up two new paths that refresh the account group automatically: - SocialAccountObserver (#[ObservedBy] on the model) fires SyncAccountUsage on created/deleted, covering all 14 OAuth callback paths in one hook. - SyncUsageOnPostCreated / SyncUsageOnPostDeleted listeners (auto-discovered) fire SyncAccountUsage on the corresponding events dispatched by CreatePost and DeletePost. SyncAccountUsage is the new dedicated job for group properties only (groupIdentify account + workspace). SyncUser was slimmed to just identify the user and delegate the group sync, removing the duplicated property mapping between the two jobs. All entry points (observer + both listeners) short-circuit when PostHog is disabled, so self-hosted instances without PostHog configured see zero queued jobs and zero overhead. posts_count cache is invalidated before each sync so the job reads fresh counts from the database instead of stale cached values.
Use Account::postsCountCacheKey everywhere, avoid findOrFail when syncing post deletes, dispatch SyncAccountUsage after DeleteWorkspace when enabled, and add coverage for the new paths. Co-authored-by: Cursor <cursoragent@cursor.com>
Refactor the user identification process by removing unnecessary type casting for user ID and account ID. This change enhances code readability and maintains functionality for syncing account usage.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Onboarding/lifecycle workflows in PostHog (and downstream tools like SendKit) need to segment users by how many social accounts they've connected and how many posts they've created. The existing
SyncUserjob only re-emitted these counts on signup and billing changes, so cohorts like "users withsocial_accounts_count >= 1" went stale the moment a user did anything meaningful — and never updated again.What changes
Two new paths that refresh the
accountgroup automatically:SocialAccountObserver—#[ObservedBy]on theSocialAccountmodel. FiresSyncAccountUsageoncreated/deleted. One hook captures all 14 OAuth callback paths automatically (Facebook, X, LinkedIn, TikTok, YouTube, Threads, Instagram, Bluesky, Mastodon, Pinterest, etc).SyncUsageOnPostCreated/SyncUsageOnPostDeleted— auto-discovered listeners on the existingPostCreated/PostDeletedevents dispatched byCreatePostandDeletePostactions.Both paths dispatch
SyncAccountUsage, a new dedicated job that doesgroupIdentify('account', ...)and (optionally)groupIdentify('workspace', ...). The property mapping previously duplicated betweenSyncUserand inline group syncs now lives in one place.SyncUserwas slimmed to justidentify(user)and delegate the group sync toSyncAccountUsage. Zero behavioural change for existing signup/billing flows — same end state in PostHog, just split across two jobs.Open-source safe
All three entry points (observer + both listeners) short-circuit via
PostHogService::isEnabled()before dispatch. Self-hosted instances without PostHog configured see zero queued jobs, zero worker overhead, zero noise.Cache correctness
account:{id}:posts_countis invalidated before every sync so the job reads fresh counts from the DB, thenusage()repopulates the cache for downstream dashboard readers. No double-invalidation — the cache survives across non-syncing reads as designed.Tests
33 tests (10 new files):
SyncAccountUsage: 7 (disabled no-op, missing account, account group, workspace group, skip workspace null, cache invalidation, queue name)SyncUser(refactored): 5 (disabled no-op, missing user, identify, delegate dispatch, queue name)SocialAccountObserver: 4 (create dispatches, delete dispatches, update does not, disabled does not)SyncUsageOnPostCreated: 3 (handle dispatches, auto-discovery wired, disabled does not)SyncUsageOnPostDeleted: 3 (handle dispatches, auto-discovery wired, disabled does not)Full suite: 1558 passing, 0 failing, 2 skipped (pre-existing). Pint clean.
Test plan
php artisan test --compact --parallel— full suite passesvendor/bin/pint --dirty --format agent— cleanphp artisan event:list | grep -i postcreated— confirms auto-discovery wiredaccountgroup →social_accounts_countincrementedposts_countfollowsPOSTHOG_ENABLED=false, create a post and confirm Horizon shows zeroSyncAccountUsagejobs queuedOut of scope
SyncUseris still dispatched (and runs as a no-op) fromCreateUserandTrackBillingwhen PostHog is disabled. Those existing dispatch sites could also be gated for full open-source-friendly zero-queue behaviour — small follow-up if we want it.