Skip to content
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
ede4aa2
fix: route gtm through telemetry entrypoint
benceruleanlu Jan 28, 2026
c247d7b
fix: add gtm type import for knip
benceruleanlu Jan 28, 2026
563cdbf
fix: restore gtm purchase tracking
benceruleanlu Jan 28, 2026
2947a99
Merge remote-tracking branch 'origin/main' into fix/gtm-telemetry-ent…
benceruleanlu Jan 28, 2026
2db667b
fix: stabilize useSubscription tests
benceruleanlu Jan 28, 2026
074ec62
FirebaseUID gating pending purchases
benceruleanlu Jan 28, 2026
bca2c62
Add testing for telemetry in local dist assets
benceruleanlu Jan 28, 2026
a3ccd92
fix: simplify telemetry scan
benceruleanlu Jan 29, 2026
c0ee820
feat: add TelemetryRegistry for multi-provider dispatch
Jan 29, 2026
acbaf04
fix: harden telemetry dispatch
benceruleanlu Jan 29, 2026
c375b53
fix: tidy telemetry types
benceruleanlu Jan 29, 2026
f2cf5ab
fix: guard subscription purchase tracking
benceruleanlu Jan 30, 2026
a43f596
[automated] Apply ESLint and Oxfmt fixes
actions-user Jan 30, 2026
d864e7e
fix: limit gtm purchase events
benceruleanlu Jan 30, 2026
969134e
Merge branch 'fix/gtm-telemetry-entrypoint' of https://github.com/Com…
benceruleanlu Jan 30, 2026
66cee7d
fix: remove gtm id from remote config
benceruleanlu Jan 30, 2026
c439ab1
fix: isolate cloud telemetry init
benceruleanlu Jan 30, 2026
a4fce64
fix: define gtm id for cloud builds
benceruleanlu Jan 30, 2026
dd2e18d
fix: guard subscription purchase telemetry
benceruleanlu Jan 30, 2026
921c168
test: make firebase auth mock userId dynamic
benceruleanlu Jan 30, 2026
0d2dc2f
Use allowlist
benceruleanlu Jan 30, 2026
8c75f14
pin SHAs
benceruleanlu Jan 30, 2026
894fe54
chore: update ci dist telemetry action pins
benceruleanlu Jan 30, 2026
20a9514
test: fix subscription mocks
benceruleanlu Jan 30, 2026
6927c15
Fix circular import by extracting ComfyWorkflow
benceruleanlu Jan 30, 2026
22c76c7
[automated] Apply ESLint and Oxfmt fixes
actions-user Jan 30, 2026
3899450
Lazy import useDialogService
benceruleanlu Jan 30, 2026
100d0eb
Align with proper item name and item name
benceruleanlu Jan 30, 2026
50a2f16
Prefer Cloud Remote Config
benceruleanlu Jan 30, 2026
3ec99e8
fix: exclude ts files from dist scan
benceruleanlu Feb 2, 2026
ea55302
fix: rename page view tracking helper
benceruleanlu Feb 3, 2026
68944ca
fix: move auth telemetry metadata builder
benceruleanlu Feb 3, 2026
233a0c7
Rename test:dist:no-telemetry
benceruleanlu Feb 3, 2026
294675b
Update src/platform/telemetry/providers/cloud/GtmTelemetryProvider.ts
benceruleanlu Feb 3, 2026
3cd9de4
fix: use window.dataLayer directly
benceruleanlu Feb 3, 2026
d898568
Merge branch 'fix/gtm-telemetry-entrypoint' of https://github.com/Com…
benceruleanlu Feb 3, 2026
2b2043d
fix: remove duplicate dataLayer declaration
benceruleanlu Feb 3, 2026
320cafd
fix: scope dist telemetry scan permissions
benceruleanlu Feb 3, 2026
c29c6fc
test: stub crypto in auth metadata tests
benceruleanlu Feb 3, 2026
c5d170a
Merge branch 'main' into fix/gtm-telemetry-entrypoint
simula-r Feb 3, 2026
dfbbac8
fix: track begin checkout in gtm
benceruleanlu Feb 4, 2026
6fa89b8
chore: move dist telemetry scan into workflow
benceruleanlu Feb 4, 2026
ea49807
feat: add checkout type to begin checkout
benceruleanlu Feb 4, 2026
3b1e425
Merge remote-tracking branch 'origin/main' into fix/gtm-telemetry-ent…
benceruleanlu Feb 4, 2026
6580b76
Fix unit test
benceruleanlu Feb 4, 2026
65b3089
fix: add checkout attribution to gtm
benceruleanlu Feb 5, 2026
56df3eb
fix unit test by hoisting
benceruleanlu Feb 5, 2026
4d9b015
Switch to __DISTRIBUTION__ in main.ts
benceruleanlu Feb 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/workflows/ci-dist-telemetry-scan.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: 'CI: Dist Telemetry Scan'

on:
pull_request:
branches-ignore: [wip/*, draft/*, temp/*]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
scan:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

- name: Install pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
with:
version: 10

- name: Use Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 'lts/*'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build project
run: pnpm build

- name: Scan dist for telemetry references
run: |
set -euo pipefail
if rg --no-ignore -n \
-g '*.html' \
-g '*.js' \
-e 'Google Tag Manager' \
-e '(?i)\bgtm\.js\b' \
-e '(?i)googletagmanager\.com/gtm\.js\\?id=' \
-e '(?i)googletagmanager\.com/ns\.html\\?id=' \
dist; then
echo 'Telemetry references found in dist assets.'
exit 1
fi
echo 'No telemetry references found in dist assets.'
2 changes: 2 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ declare const __USE_PROD_CONFIG__: boolean

interface Window {
__CONFIG__: {
gtm_container_id?: string
mixpanel_token?: string
require_whitelist?: boolean
subscription_required?: boolean
Expand All @@ -30,6 +31,7 @@ interface Window {
badge?: string
}
}
dataLayer?: Array<Record<string, unknown>>
Copy link
Contributor

Choose a reason for hiding this comment

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

In src/platform/telemetry/providers/cloud/GtmTelemetryProvider.ts, this is declared as dataLayer?: Record<string, unknown>[] which is distinct from Array<Record<string, unknown>>. Is the goal to assert non-nullability if the gtm module is loaded?

Copy link
Member Author

Choose a reason for hiding this comment

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

We’re not trying to assert non‑nullability, I removed the duplicate declare block in GtmTelemetryProvider.ts and kept the single optional type in global.d.ts. The provider initializes window.dataLayer when a GTM ID exists and still guards with ?.push for safety.

}

interface Navigator {
Expand Down
3 changes: 3 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ if (isCloud) {
const { refreshRemoteConfig } =
await import('@/platform/remoteConfig/refreshRemoteConfig')
await refreshRemoteConfig({ useAuth: false })

const { initTelemetry } = await import('@/platform/telemetry/initTelemetry')
await initTelemetry()
}

const ComfyUIPreset = definePreset(Aura, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ vi.mock('@/composables/useErrorHandling', () => ({

const subscriptionMocks = vi.hoisted(() => ({
isActiveSubscription: { value: false },
isInitialized: { value: true }
isInitialized: { value: true },
subscriptionStatus: { value: null }
}))

vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
Expand Down Expand Up @@ -125,7 +126,8 @@ describe('CloudSubscriptionRedirectView', () => {
expect(mockPerformSubscriptionCheckout).toHaveBeenCalledWith(
'creator',
'monthly',
false
false,
undefined
)

// Shows loading affordances
Expand Down Expand Up @@ -156,7 +158,8 @@ describe('CloudSubscriptionRedirectView', () => {
expect(mockPerformSubscriptionCheckout).toHaveBeenCalledWith(
'creator',
'monthly',
false
false,
undefined
)
})
})
21 changes: 19 additions & 2 deletions src/platform/cloud/subscription/components/PricingTable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const mockSubscriptionTier = ref<
const mockIsYearlySubscription = ref(false)
const mockAccessBillingPortal = vi.fn()
const mockReportError = vi.fn()
const mockTrackBeginCheckout = vi.fn()
const mockGetFirebaseAuthHeader = vi.fn(() =>
Promise.resolve({ Authorization: 'Bearer test-token' })
)
Expand All @@ -22,7 +23,8 @@ vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
useSubscription: () => ({
isActiveSubscription: computed(() => mockIsActiveSubscription.value),
subscriptionTier: computed(() => mockSubscriptionTier.value),
isYearlySubscription: computed(() => mockIsYearlySubscription.value)
isYearlySubscription: computed(() => mockIsYearlySubscription.value),
subscriptionStatus: ref(null)
})
}))

Expand Down Expand Up @@ -53,11 +55,18 @@ vi.mock('@/composables/useErrorHandling', () => ({

vi.mock('@/stores/firebaseAuthStore', () => ({
useFirebaseAuthStore: () => ({
getFirebaseAuthHeader: mockGetFirebaseAuthHeader
getFirebaseAuthHeader: mockGetFirebaseAuthHeader,
userId: 'user-123'
}),
FirebaseAuthStoreError: class extends Error {}
}))

vi.mock('@/platform/telemetry', () => ({
useTelemetry: () => ({
trackBeginCheckout: mockTrackBeginCheckout
})
}))

vi.mock('@/platform/distribution/types', () => ({
isCloud: true
}))
Expand Down Expand Up @@ -126,6 +135,7 @@ describe('PricingTable', () => {
mockIsActiveSubscription.value = false
mockSubscriptionTier.value = null
mockIsYearlySubscription.value = false
mockTrackBeginCheckout.mockReset()
vi.mocked(global.fetch).mockResolvedValue({
ok: true,
json: async () => ({ checkout_url: 'https://checkout.stripe.com/test' })
Expand All @@ -148,6 +158,13 @@ describe('PricingTable', () => {
await creatorButton?.trigger('click')
await flushPromises()

expect(mockTrackBeginCheckout).toHaveBeenCalledWith({
user_id: 'user-123',
tier: 'creator',
cycle: 'yearly',
checkout_type: 'change',
previous_tier: 'standard'
})
expect(mockAccessBillingPortal).toHaveBeenCalledWith('creator-yearly')
})

Expand Down
21 changes: 20 additions & 1 deletion src/platform/cloud/subscription/components/PricingTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ import { performSubscriptionCheckout } from '@/platform/cloud/subscription/utils
import { isPlanDowngrade } from '@/platform/cloud/subscription/utils/subscriptionTierRank'
import type { BillingCycle } from '@/platform/cloud/subscription/utils/subscriptionTierRank'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import type { components } from '@/types/comfyRegistryTypes'

type SubscriptionTier = components['schemas']['SubscriptionTier']
Expand Down Expand Up @@ -330,6 +332,8 @@ const tiers: PricingTierConfig[] = [
const { n } = useI18n()
const { isActiveSubscription, subscriptionTier, isYearlySubscription } =
useSubscription()
const telemetry = useTelemetry()
const { userId } = useFirebaseAuthStore()
const { accessBillingPortal, reportError } = useFirebaseAuthActions()
const { wrapWithErrorHandlingAsync } = useErrorHandling()

Expand Down Expand Up @@ -410,6 +414,17 @@ const handleSubscribe = wrapWithErrorHandlingAsync(

try {
if (isActiveSubscription.value) {
if (userId) {
telemetry?.trackBeginCheckout({
user_id: userId,
tier: tierKey,
cycle: currentBillingCycle.value,
checkout_type: 'change',
...(currentTierKey.value
? { previous_tier: currentTierKey.value }
: {})
})
}
// Pass the target tier to create a deep link to subscription update confirmation
const checkoutTier = getCheckoutTier(tierKey, currentBillingCycle.value)
const targetPlan = {
Expand All @@ -430,7 +445,11 @@ const handleSubscribe = wrapWithErrorHandlingAsync(
await accessBillingPortal(checkoutTier)
}
} else {
await performSubscriptionCheckout(tierKey, currentBillingCycle.value)
await performSubscriptionCheckout(
tierKey,
currentBillingCycle.value,
true
)
}
} finally {
isLoading.value = false
Expand Down
Loading