[backport rh-test] add telemetry provider for cloud distribution (#6155)

## Summary
This PR manually backports the telemetry provider implementation to the
rh-test branch after the automated backport failed due to merge
conflicts.


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6155-Manual-backport-add-telemetry-provider-for-cloud-distribution-2926d73d3650812e94e2fe0bd5e9bc59)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Christian Byrne
2025-10-19 20:04:57 -07:00
committed by GitHub
parent b210e63f3c
commit b708ebf540
20 changed files with 751 additions and 96 deletions

View File

@@ -67,7 +67,6 @@ export default defineConfig([
...commonParserOptions,
projectService: {
allowDefaultProject: [
'vite.config.mts',
'vite.electron.config.mts',
'vite.types.config.mts',
'playwright.config.ts',

1
global.d.ts vendored
View File

@@ -4,6 +4,7 @@ declare const __SENTRY_DSN__: string
declare const __ALGOLIA_APP_ID__: string
declare const __ALGOLIA_API_KEY__: string
declare const __USE_PROD_CONFIG__: boolean
declare const __MIXPANEL_TOKEN__: string
type BuildFeatureFlags = {
REQUIRE_SUBSCRIPTION: boolean

View File

@@ -85,6 +85,7 @@
"jsdom": "catalog:",
"knip": "catalog:",
"lint-staged": "catalog:",
"mixpanel-browser": "catalog:",
"nx": "catalog:",
"postcss-html": "catalog:",
"prettier": "catalog:",

256
pnpm-lock.yaml generated
View File

@@ -183,9 +183,18 @@ catalogs:
lint-staged:
specifier: ^15.2.7
version: 15.2.7
markdown-table:
specifier: ^3.0.4
version: 3.0.4
mixpanel-browser:
specifier: ^2.71.0
version: 2.71.0
nx:
specifier: 21.4.1
version: 21.4.1
picocolors:
specifier: ^1.1.1
version: 1.1.1
pinia:
specifier: ^2.1.7
version: 2.2.2
@@ -193,14 +202,20 @@ catalogs:
specifier: ^1.8.0
version: 1.8.0
prettier:
specifier: ^3.3.2
version: 3.3.2
specifier: ^3.6.2
version: 3.6.2
pretty-bytes:
specifier: ^7.1.0
version: 7.1.0
primeicons:
specifier: ^7.0.0
version: 7.0.0
primevue:
specifier: ^4.2.5
version: 4.2.5
rollup-plugin-visualizer:
specifier: ^6.0.4
version: 6.0.4
storybook:
specifier: ^9.1.6
version: 9.1.6
@@ -251,7 +266,7 @@ catalogs:
version: 3.5.13
vue-component-type-helpers:
specifier: ^3.0.7
version: 3.1.0
version: 3.1.1
vue-eslint-parser:
specifier: ^10.2.0
version: 10.2.0
@@ -473,7 +488,7 @@ importers:
version: 21.4.1(@babel/traverse@7.28.3)(@playwright/test@1.52.0)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2)
'@nx/storybook':
specifier: 'catalog:'
version: 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)
version: 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)
'@nx/vite':
specifier: 'catalog:'
version: 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)(typescript@5.9.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vitest@3.2.4)
@@ -485,19 +500,19 @@ importers:
version: 1.52.0
'@storybook/addon-docs':
specifier: 'catalog:'
version: 9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
version: 9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
'@storybook/vue3':
specifier: 'catalog:'
version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))
version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))
'@storybook/vue3-vite':
specifier: 'catalog:'
version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))
version: 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))
'@tailwindcss/vite':
specifier: 'catalog:'
version: 4.1.12(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
'@trivago/prettier-plugin-sort-imports':
specifier: 'catalog:'
version: 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.3.2)
version: 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.6.2)
'@types/eslint-plugin-tailwindcss':
specifier: 'catalog:'
version: 3.17.0
@@ -545,10 +560,10 @@ importers:
version: 4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.4.2))
eslint-plugin-prettier:
specifier: 'catalog:'
version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2)
version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.6.2)
eslint-plugin-storybook:
specifier: 'catalog:'
version: 9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)
version: 9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)
eslint-plugin-tailwindcss:
specifier: 'catalog:'
version: 4.0.0-beta.0(tailwindcss@4.1.12)
@@ -582,18 +597,33 @@ importers:
lint-staged:
specifier: 'catalog:'
version: 15.2.7
markdown-table:
specifier: 'catalog:'
version: 3.0.4
mixpanel-browser:
specifier: 'catalog:'
version: 2.71.0
nx:
specifier: 'catalog:'
version: 21.4.1
picocolors:
specifier: 'catalog:'
version: 1.1.1
postcss-html:
specifier: 'catalog:'
version: 1.8.0
prettier:
specifier: 'catalog:'
version: 3.3.2
version: 3.6.2
pretty-bytes:
specifier: 'catalog:'
version: 7.1.0
rollup-plugin-visualizer:
specifier: 'catalog:'
version: 6.0.4(rollup@4.22.4)
storybook:
specifier: 'catalog:'
version: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
version: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
stylelint:
specifier: 'catalog:'
version: 16.24.0(typescript@5.9.2)
@@ -641,7 +671,7 @@ importers:
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.14.10)(@vitest/ui@3.2.4)(happy-dom@15.11.0)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.39.2)
vue-component-type-helpers:
specifier: 'catalog:'
version: 3.1.0
version: 3.1.1
vue-eslint-parser:
specifier: 'catalog:'
version: 10.2.0(eslint@9.35.0(jiti@2.4.2))
@@ -740,11 +770,11 @@ importers:
packages/shared-frontend-utils:
dependencies:
axios:
specifier: ^1.11.0
specifier: 'catalog:'
version: 1.11.0
devDependencies:
typescript:
specifier: ^5.9.2
specifier: 'catalog:'
version: 5.9.2
packages/tailwind-utils:
@@ -2177,6 +2207,21 @@ packages:
'@microsoft/tsdoc@0.15.1':
resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==}
'@mixpanel/rrdom@2.0.0-alpha.18.2':
resolution: {integrity: sha512-vX/tbnS14ZzzatC7vOyvAm9tOLU8tof0BuppBlphzEx1YHTSw8DQiAmyAc0AmXidchLV0W+cUHV/WsehPLh2hQ==}
'@mixpanel/rrweb-snapshot@2.0.0-alpha.18.2':
resolution: {integrity: sha512-2kSnjZZ3QZ9zOz/isOt8s54mXUUDgXk/u0eEi/rE0xBWDeuA0NHrBcqiMc+w4F/yWWUpo5F5zcuPeYpc6ufAsw==}
'@mixpanel/rrweb-types@2.0.0-alpha.18.2':
resolution: {integrity: sha512-ucIYe1mfJ2UksvXW+d3bOySTB2/0yUSqQJlUydvbBz6OO2Bhq3nJHyLXV9ExkgUMZm1ZyDcvvmNUd1+5tAXlpA==}
'@mixpanel/rrweb-utils@2.0.0-alpha.18.2':
resolution: {integrity: sha512-OomKIB6GTx5xvCLJ7iic2khT/t/tnCJUex13aEqsbSqIT/UzUUsqf+LTrgUK5ex+f6odmkCNjre2y5jvpNqn+g==}
'@mixpanel/rrweb@2.0.0-alpha.18.2':
resolution: {integrity: sha512-J3dVTEu6Z4p8di7y9KKvUooNuBjX97DdG6XGWoPEPi07A9512h9M8MEtvlY3mK0PGfuC0Mz5Pv/Ws6gjGYfKQg==}
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
@@ -2996,6 +3041,9 @@ packages:
'@types/chai@5.2.2':
resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
'@types/css-font-loading-module@0.0.7':
resolution: {integrity: sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@@ -3494,6 +3542,9 @@ packages:
'@webgpu/types@0.1.51':
resolution: {integrity: sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==}
'@xstate/fsm@1.6.5':
resolution: {integrity: sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==}
'@xterm/addon-fit@0.10.0':
resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==}
peerDependencies:
@@ -3776,6 +3827,10 @@ packages:
balanced-match@2.0.0:
resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==}
base64-arraybuffer@1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@@ -4886,9 +4941,6 @@ packages:
get-tsconfig@4.10.1:
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
get-tsconfig@4.7.5:
resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -5966,6 +6018,9 @@ packages:
mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
mixpanel-browser@2.71.0:
resolution: {integrity: sha512-jKmDXe68/oQFgk/9ns9Z36bA0CJ31PH8Y77XTLLGfJvhsUPbvu+7Se9e281NejZF6+OMqx7cE+zFxToozYyNrA==}
mkdirp@3.0.1:
resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
engines: {node: '>=10'}
@@ -5993,11 +6048,6 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
nanoid@3.3.8:
resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
nanoid@5.1.5:
resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==}
engines: {node: ^18 || >=20}
@@ -6345,10 +6395,6 @@ packages:
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
postcss@8.5.1:
resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==}
engines: {node: ^10 || ^12 || >=14}
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
@@ -6361,11 +6407,15 @@ packages:
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
engines: {node: '>=6.0.0'}
prettier@3.3.2:
resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==}
prettier@3.6.2:
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
engines: {node: '>=14'}
hasBin: true
pretty-bytes@7.1.0:
resolution: {integrity: sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==}
engines: {node: '>=20'}
pretty-format@27.5.1:
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
@@ -6668,6 +6718,19 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rollup-plugin-visualizer@6.0.4:
resolution: {integrity: sha512-q8Q7J/6YofkmaGW1sH/fPRAz37x/+pd7VBuaUU7lwvOS/YikuiiEU9jeb9PH8XHiq50XFrUsBbOxeAMYQ7KZkg==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
rolldown: 1.x || ^1.0.0-beta
rollup: 2.x || 3.x || 4.x
peerDependenciesMeta:
rolldown:
optional: true
rollup:
optional: true
rollup@4.22.4:
resolution: {integrity: sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -6822,6 +6885,10 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
source-map@0.7.6:
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
engines: {node: '>= 12'}
speakingurl@14.0.1:
resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
engines: {node: '>=0.10.0'}
@@ -7450,9 +7517,6 @@ packages:
vue-component-type-helpers@2.2.12:
resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==}
vue-component-type-helpers@3.1.0:
resolution: {integrity: sha512-cC1pYNRZkSS1iCvdlaMbbg2sjDwxX098FucEjtz9Yig73zYjWzQsnMe5M9H8dRNv55hAIDGUI29hF2BEUA4FMQ==}
vue-component-type-helpers@3.1.1:
resolution: {integrity: sha512-B0kHv7qX6E7+kdc5nsaqjdGZ1KwNKSUQDWGy7XkTYT7wFsOpkEyaJ1Vq79TjwrrtuLRgizrTV7PPuC4rRQo+vw==}
@@ -9469,6 +9533,29 @@ snapshots:
'@microsoft/tsdoc@0.15.1': {}
'@mixpanel/rrdom@2.0.0-alpha.18.2':
dependencies:
'@mixpanel/rrweb-snapshot': 2.0.0-alpha.18.2
'@mixpanel/rrweb-snapshot@2.0.0-alpha.18.2':
dependencies:
postcss: 8.5.6
'@mixpanel/rrweb-types@2.0.0-alpha.18.2': {}
'@mixpanel/rrweb-utils@2.0.0-alpha.18.2': {}
'@mixpanel/rrweb@2.0.0-alpha.18.2':
dependencies:
'@mixpanel/rrdom': 2.0.0-alpha.18.2
'@mixpanel/rrweb-snapshot': 2.0.0-alpha.18.2
'@mixpanel/rrweb-types': 2.0.0-alpha.18.2
'@mixpanel/rrweb-utils': 2.0.0-alpha.18.2
'@types/css-font-loading-module': 0.0.7
'@xstate/fsm': 1.6.5
base64-arraybuffer: 1.0.2
mitt: 3.0.1
'@napi-rs/wasm-runtime@0.2.12':
dependencies:
'@emnapi/core': 1.4.5
@@ -9645,7 +9732,7 @@ snapshots:
- typescript
- verdaccio
'@nx/storybook@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)':
'@nx/storybook@21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)':
dependencies:
'@nx/cypress': 21.4.1(@babel/traverse@7.28.3)(@zkochan/js-yaml@0.0.7)(eslint@9.35.0(jiti@2.4.2))(nx@21.4.1)(typescript@5.9.2)
'@nx/devkit': 21.4.1(nx@21.4.1)
@@ -9653,7 +9740,7 @@ snapshots:
'@nx/js': 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)
'@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.2)
semver: 7.7.2
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
tslib: 2.8.1
transitivePeerDependencies:
- '@babel/traverse'
@@ -10004,29 +10091,29 @@ snapshots:
'@sindresorhus/merge-streams@4.0.0': {}
'@storybook/addon-docs@9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))':
'@storybook/addon-docs@9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))':
dependencies:
'@mdx-js/react': 3.1.0(@types/react@19.1.9)(react@19.1.1)
'@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
'@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
'@storybook/icons': 1.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@storybook/react-dom-shim': 9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
'@storybook/react-dom-shim': 9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
ts-dedent: 2.2.0
transitivePeerDependencies:
- '@types/react'
'@storybook/builder-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))':
'@storybook/builder-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))':
dependencies:
'@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
'@storybook/csf-plugin': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
ts-dedent: 2.2.0
vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)
'@storybook/csf-plugin@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))':
'@storybook/csf-plugin@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))':
dependencies:
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
unplugin: 1.16.1
'@storybook/global@5.0.0': {}
@@ -10036,19 +10123,19 @@ snapshots:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
'@storybook/react-dom-shim@9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))':
'@storybook/react-dom-shim@9.1.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))':
dependencies:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
'@storybook/vue3-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))':
'@storybook/vue3-vite@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))':
dependencies:
'@storybook/builder-vite': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
'@storybook/vue3': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))
'@storybook/builder-vite': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
'@storybook/vue3': 9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))
find-package-json: 1.2.0
magic-string: 0.30.19
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
typescript: 5.9.2
vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)
vue-component-meta: 2.2.12(typescript@5.9.2)
@@ -10056,10 +10143,10 @@ snapshots:
transitivePeerDependencies:
- vue
'@storybook/vue3@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))':
'@storybook/vue3@9.1.1(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(vue@3.5.13(typescript@5.9.2))':
dependencies:
'@storybook/global': 5.0.0
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
type-fest: 2.19.0
vue: 3.5.13(typescript@5.9.2)
vue-component-type-helpers: 3.1.1
@@ -10324,7 +10411,7 @@ snapshots:
'@tiptap/extension-text-style': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
'@tiptap/pm': 2.10.4
'@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.3.2)':
'@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.6.2)':
dependencies:
'@babel/generator': 7.28.3
'@babel/parser': 7.28.4
@@ -10332,7 +10419,7 @@ snapshots:
'@babel/types': 7.28.4
javascript-natural-sort: 0.7.1
lodash: 4.17.21
prettier: 3.3.2
prettier: 3.6.2
optionalDependencies:
'@vue/compiler-sfc': 3.5.13
transitivePeerDependencies:
@@ -10357,6 +10444,8 @@ snapshots:
dependencies:
'@types/deep-eql': 4.0.2
'@types/css-font-loading-module@0.0.7': {}
'@types/debug@4.1.12':
dependencies:
'@types/ms': 2.1.0
@@ -10950,6 +11039,8 @@ snapshots:
'@webgpu/types@0.1.51': {}
'@xstate/fsm@1.6.5': {}
'@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)':
dependencies:
'@xterm/xterm': 5.5.0
@@ -11266,6 +11357,8 @@ snapshots:
balanced-match@2.0.0: {}
base64-arraybuffer@1.0.2: {}
base64-js@1.5.1: {}
better-opn@3.0.2:
@@ -12110,20 +12203,20 @@ snapshots:
- supports-color
optional: true
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.3.2):
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.4.2)))(eslint@9.35.0(jiti@2.4.2))(prettier@3.6.2):
dependencies:
eslint: 9.35.0(jiti@2.4.2)
prettier: 3.3.2
prettier: 3.6.2
prettier-linter-helpers: 1.0.0
synckit: 0.11.11
optionalDependencies:
eslint-config-prettier: 10.1.8(eslint@9.35.0(jiti@2.4.2))
eslint-plugin-storybook@9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2):
eslint-plugin-storybook@9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2):
dependencies:
'@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)
eslint: 9.35.0(jiti@2.4.2)
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
transitivePeerDependencies:
- supports-color
- typescript
@@ -12562,10 +12655,6 @@ snapshots:
dependencies:
resolve-pkg-maps: 1.0.0
get-tsconfig@4.7.5:
dependencies:
resolve-pkg-maps: 1.0.0
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -13838,6 +13927,10 @@ snapshots:
mitt@3.0.1: {}
mixpanel-browser@2.71.0:
dependencies:
'@mixpanel/rrweb': 2.0.0-alpha.18.2
mkdirp@3.0.1: {}
mlly@1.8.0:
@@ -13860,8 +13953,6 @@ snapshots:
nanoid@3.3.11: {}
nanoid@3.3.8: {}
nanoid@5.1.5: {}
napi-postinstall@0.3.3: {}
@@ -14241,14 +14332,14 @@ snapshots:
dependencies:
htmlparser2: 8.0.2
js-tokens: 9.0.1
postcss: 8.5.1
postcss-safe-parser: 6.0.0(postcss@8.5.1)
postcss: 8.5.6
postcss-safe-parser: 6.0.0(postcss@8.5.6)
postcss-resolve-nested-selector@0.1.6: {}
postcss-safe-parser@6.0.0(postcss@8.5.1):
postcss-safe-parser@6.0.0(postcss@8.5.6):
dependencies:
postcss: 8.5.1
postcss: 8.5.6
postcss-safe-parser@7.0.1(postcss@8.5.6):
dependencies:
@@ -14266,12 +14357,6 @@ snapshots:
postcss-value-parser@4.2.0: {}
postcss@8.5.1:
dependencies:
nanoid: 3.3.8
picocolors: 1.1.1
source-map-js: 1.2.1
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
@@ -14284,7 +14369,9 @@ snapshots:
dependencies:
fast-diff: 1.3.0
prettier@3.3.2: {}
prettier@3.6.2: {}
pretty-bytes@7.1.0: {}
pretty-format@27.5.1:
dependencies:
@@ -14733,6 +14820,15 @@ snapshots:
rfdc@1.4.1: {}
rollup-plugin-visualizer@6.0.4(rollup@4.22.4):
dependencies:
open: 8.4.2
picomatch: 4.0.3
source-map: 0.7.6
yargs: 17.7.2
optionalDependencies:
rollup: 4.22.4
rollup@4.22.4:
dependencies:
'@types/estree': 1.0.5
@@ -14924,6 +15020,8 @@ snapshots:
source-map@0.6.1: {}
source-map@0.7.6: {}
speakingurl@14.0.1: {}
sprintf-js@1.0.3: {}
@@ -14950,7 +15048,7 @@ snapshots:
internal-slot: 1.1.0
optional: true
storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)):
storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)):
dependencies:
'@storybook/global': 5.0.0
'@testing-library/jest-dom': 6.6.4
@@ -14965,7 +15063,7 @@ snapshots:
semver: 7.7.2
ws: 8.18.3
optionalDependencies:
prettier: 3.3.2
prettier: 3.6.2
transitivePeerDependencies:
- '@testing-library/dom'
- bufferutil
@@ -15287,7 +15385,7 @@ snapshots:
tsx@4.19.4:
dependencies:
esbuild: 0.25.5
get-tsconfig: 4.7.5
get-tsconfig: 4.10.1
optionalDependencies:
fsevents: 2.3.3
@@ -15639,7 +15737,7 @@ snapshots:
vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2):
dependencies:
esbuild: 0.21.5
postcss: 8.5.1
postcss: 8.5.6
rollup: 4.22.4
optionalDependencies:
'@types/node': 20.14.10
@@ -15704,8 +15802,6 @@ snapshots:
vue-component-type-helpers@2.2.12: {}
vue-component-type-helpers@3.1.0: {}
vue-component-type-helpers@3.1.1: {}
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)):

View File

@@ -94,6 +94,7 @@ catalog:
zod: ^3.23.8
zod-to-json-schema: ^3.24.1
zod-validation-error: ^3.3.0
mixpanel-browser: ^2.71.0
cleanupUnusedCatalogs: true

View File

@@ -86,6 +86,8 @@ import SplitButton from 'primevue/splitbutton'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { useCommandStore } from '@/stores/commandStore'
import {
useQueuePendingTaskCountStore,
@@ -141,10 +143,15 @@ const hasPendingTasks = computed(
const commandStore = useCommandStore()
const queuePrompt = async (e: Event) => {
const commandId =
'shiftKey' in e && e.shiftKey
? 'Comfy.QueuePromptFront'
: 'Comfy.QueuePrompt'
const isShiftPressed = 'shiftKey' in e && e.shiftKey
const commandId = isShiftPressed
? 'Comfy.QueuePromptFront'
: 'Comfy.QueuePrompt'
if (isCloud) {
useTelemetry()?.trackRunButton({ subscribe_to_run: false })
}
await commandStore.execute(commandId)
}
</script>

View File

@@ -17,7 +17,9 @@ import {
import type { Point } from '@/lib/litegraph/src/litegraph'
import { useAssetBrowserDialog } from '@/platform/assets/composables/useAssetBrowserDialog'
import { createModelNodeFromAsset } from '@/platform/assets/utils/createModelNodeFromAsset'
import { isCloud } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
@@ -447,6 +449,11 @@ export function useCoreCommands(): ComfyCommand[] {
category: 'essentials' as const,
function: async () => {
const batchCount = useQueueSettingsStore().batchCount
if (isCloud) {
useTelemetry()?.trackWorkflowExecution()
}
await app.queuePrompt(0, batchCount)
}
},
@@ -458,6 +465,11 @@ export function useCoreCommands(): ComfyCommand[] {
category: 'essentials' as const,
function: async () => {
const batchCount = useQueueSettingsStore().batchCount
if (isCloud) {
useTelemetry()?.trackWorkflowExecution()
}
await app.queuePrompt(-1, batchCount)
}
},

View File

@@ -15,6 +15,8 @@ import Button from 'primevue/button'
import { onBeforeUnmount, ref } from 'vue'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
withDefaults(
defineProps<{
@@ -82,6 +84,10 @@ const stopPolling = () => {
}
const handleSubscribe = async () => {
if (isCloud) {
useTelemetry()?.trackSubscription('subscribe_clicked')
}
isLoading.value = true
try {
await subscribe()

View File

@@ -10,7 +10,7 @@
severity="primary"
size="small"
data-testid="subscribe-to-run-button"
@click="showSubscriptionDialog"
@click="handleSubscribeToRun"
/>
</template>
@@ -18,6 +18,16 @@
import Button from 'primevue/button'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
const { showSubscriptionDialog } = useSubscription()
const handleSubscribeToRun = () => {
if (isCloud) {
useTelemetry()?.trackRunButton({ subscribe_to_run: true })
}
showSubscriptionDialog()
}
</script>

View File

@@ -7,6 +7,7 @@ import { COMFY_API_BASE_URL } from '@/config/comfyApi'
import { MONTHLY_SUBSCRIPTION_PRICE } from '@/config/subscriptionPricesConfig'
import { t } from '@/i18n'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { useDialogService } from '@/services/dialogService'
import {
FirebaseAuthStoreError,
@@ -78,6 +79,10 @@ export function useSubscription() {
}, reportError)
const showSubscriptionDialog = () => {
if (isCloud) {
useTelemetry()?.trackSubscription('modal_opened')
}
dialogService.showSubscriptionRequiredDialog()
}

View File

@@ -0,0 +1,42 @@
/**
* Telemetry Provider - OSS Build Safety
*
* CRITICAL: OSS Build Safety
* This module is conditionally compiled based on distribution. When building
* the open source version (DISTRIBUTION unset), this entire module and its dependencies
* are excluded through via tree-shaking.
*
* To verify OSS builds exclude this code:
* 1. `DISTRIBUTION= pnpm build` (OSS build)
* 2. `grep -RinE --include='*.js' 'trackWorkflow|trackEvent|mixpanel' dist/` (should find nothing)
* 3. Check dist/assets/*.js files contain no tracking code
*
* This approach maintains complete separation between cloud and OSS builds
* while ensuring the open source version contains no telemetry dependencies.
*/
import { isCloud } from '@/platform/distribution/types'
import { MixpanelTelemetryProvider } from './providers/cloud/MixpanelTelemetryProvider'
import type { TelemetryProvider } from './types'
// Singleton instance
let _telemetryProvider: TelemetryProvider | null = null
/**
* Telemetry factory - conditionally creates provider based on distribution
* Returns singleton instance.
*
* CRITICAL: This returns undefined in OSS builds. There is no telemetry provider
* for OSS builds and all tracking calls are no-ops.
*/
export function useTelemetry(): TelemetryProvider | null {
if (_telemetryProvider === null) {
// Use distribution check for tree-shaking
if (isCloud) {
_telemetryProvider = new MixpanelTelemetryProvider()
}
// For OSS builds, _telemetryProvider stays null
}
return _telemetryProvider
}

View File

@@ -0,0 +1,221 @@
import type { OverridedMixpanel } from 'mixpanel-browser'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore'
import type {
AuthMetadata,
ExecutionContext,
RunButtonProperties,
SurveyResponses,
TelemetryEventName,
TelemetryEventProperties,
TelemetryProvider,
TemplateMetadata
} from '../../types'
import { TelemetryEvents } from '../../types'
interface QueuedEvent {
eventName: TelemetryEventName
properties?: TelemetryEventProperties
}
/**
* Mixpanel Telemetry Provider - Cloud Build Implementation
*
* CRITICAL: OSS Build Safety
* This provider integrates with Mixpanel for cloud telemetry tracking.
* Entire file is tree-shaken away in OSS builds (DISTRIBUTION unset).
*
* To verify OSS builds exclude this code:
* 1. `DISTRIBUTION= pnpm build` (OSS build)
* 2. `grep -RinE --include='*.js' 'trackWorkflow|trackEvent|mixpanel' dist/` (should find nothing)
* 3. Check dist/assets/*.js files contain no tracking code
*/
export class MixpanelTelemetryProvider implements TelemetryProvider {
private isEnabled = true
private mixpanel: OverridedMixpanel | null = null
private eventQueue: QueuedEvent[] = []
private isInitialized = false
constructor() {
const token = __MIXPANEL_TOKEN__
if (token) {
try {
// Dynamic import to avoid bundling mixpanel in OSS builds
void import('mixpanel-browser')
.then((mixpanelModule) => {
this.mixpanel = mixpanelModule.default
this.mixpanel.init(token, {
debug: import.meta.env.DEV,
track_pageview: true,
api_host: 'https://mp.comfy.org',
cross_subdomain_cookie: true,
persistence: 'cookie',
loaded: () => {
this.isInitialized = true
this.flushEventQueue() // flush events that were queued while initializing
useCurrentUser().onUserResolved((user) => {
if (this.mixpanel && user.id) {
this.mixpanel.identify(user.id)
}
})
}
})
})
.catch((error) => {
console.error('Failed to load Mixpanel:', error)
this.isEnabled = false
})
} catch (error) {
console.error('Failed to initialize Mixpanel:', error)
this.isEnabled = false
}
} else {
console.warn('Mixpanel token not provided')
this.isEnabled = false
}
}
private flushEventQueue(): void {
if (!this.isInitialized || !this.mixpanel) {
return
}
while (this.eventQueue.length > 0) {
const event = this.eventQueue.shift()!
try {
this.mixpanel.track(event.eventName, event.properties || {})
} catch (error) {
console.error('Failed to track queued event:', error)
}
}
}
private trackEvent(
eventName: TelemetryEventName,
properties?: TelemetryEventProperties
): void {
if (!this.isEnabled) {
return
}
const event: QueuedEvent = { eventName, properties }
if (this.isInitialized && this.mixpanel) {
// Mixpanel is ready, track immediately
try {
this.mixpanel.track(eventName, properties || {})
} catch (error) {
console.error('Failed to track event:', error)
}
} else {
// Mixpanel not ready yet, queue the event
this.eventQueue.push(event)
}
}
trackAuth(metadata: AuthMetadata): void {
this.trackEvent(TelemetryEvents.USER_AUTH_COMPLETED, metadata)
}
trackSubscription(event: 'modal_opened' | 'subscribe_clicked'): void {
const eventName =
event === 'modal_opened'
? TelemetryEvents.SUBSCRIPTION_REQUIRED_MODAL_OPENED
: TelemetryEvents.SUBSCRIBE_NOW_BUTTON_CLICKED
this.trackEvent(eventName)
}
trackRunButton(options?: { subscribe_to_run?: boolean }): void {
const executionContext = this.getExecutionContext()
const runButtonProperties: RunButtonProperties = {
subscribe_to_run: options?.subscribe_to_run || false,
workflow_type: executionContext.is_template ? 'template' : 'custom',
workflow_name: executionContext.workflow_name ?? 'untitled'
}
this.trackEvent(TelemetryEvents.RUN_BUTTON_CLICKED, runButtonProperties)
}
trackSurvey(
stage: 'opened' | 'submitted',
responses?: SurveyResponses
): void {
const eventName =
stage === 'opened'
? TelemetryEvents.USER_SURVEY_OPENED
: TelemetryEvents.USER_SURVEY_SUBMITTED
this.trackEvent(eventName, responses)
}
trackEmailVerification(stage: 'opened' | 'requested' | 'completed'): void {
let eventName: TelemetryEventName
switch (stage) {
case 'opened':
eventName = TelemetryEvents.USER_EMAIL_VERIFY_OPENED
break
case 'requested':
eventName = TelemetryEvents.USER_EMAIL_VERIFY_REQUESTED
break
case 'completed':
eventName = TelemetryEvents.USER_EMAIL_VERIFY_COMPLETED
break
}
this.trackEvent(eventName)
}
trackTemplate(metadata: TemplateMetadata): void {
this.trackEvent(TelemetryEvents.TEMPLATE_WORKFLOW_OPENED, metadata)
}
trackWorkflowExecution(): void {
const context = this.getExecutionContext()
this.trackEvent(TelemetryEvents.WORKFLOW_EXECUTION_STARTED, context)
}
getExecutionContext(): ExecutionContext {
const workflowStore = useWorkflowStore()
const templatesStore = useWorkflowTemplatesStore()
const activeWorkflow = workflowStore.activeWorkflow
if (activeWorkflow?.filename) {
const isTemplate = templatesStore.knownTemplateNames.has(
activeWorkflow.filename
)
if (isTemplate) {
const template = templatesStore.getTemplateByName(
activeWorkflow.filename
)
return {
is_template: true,
workflow_name: activeWorkflow.filename,
template_source: template?.sourceModule,
template_category: template?.category,
template_tags: template?.tags,
template_models: template?.models,
template_use_case: template?.useCase,
template_license: template?.license
}
}
return {
is_template: false,
workflow_name: activeWorkflow.filename
}
}
return {
is_template: false,
workflow_name: undefined
}
}
}

View File

@@ -0,0 +1,138 @@
/**
* Telemetry Provider Interface
*
* CRITICAL: OSS Build Safety
* This module is excluded from OSS builds via conditional compilation.
* When DISTRIBUTION is unset (OSS builds), Vite's tree-shaking removes this code entirely,
* ensuring the open source build contains no telemetry dependencies.
*
* To verify OSS builds are clean:
* 1. `DISTRIBUTION= pnpm build` (OSS build)
* 2. `grep -RinE --include='*.js' 'trackWorkflow|trackEvent|mixpanel' dist/` (should find nothing)
* 3. Check dist/assets/*.js files contain no tracking code
*/
/**
* Authentication metadata for sign-up tracking
*/
export interface AuthMetadata {
method?: 'email' | 'google' | 'github'
is_new_user?: boolean
referrer_url?: string
utm_source?: string
utm_medium?: string
utm_campaign?: string
}
/**
* Survey response data for user profiling
*/
export interface SurveyResponses {
industry?: string
team_size?: string
use_case?: string
familiarity?: string
intended_use?: 'personal' | 'client' | 'inhouse'
}
/**
* Run button tracking properties
*/
export interface RunButtonProperties {
subscribe_to_run: boolean
workflow_type: 'template' | 'custom'
workflow_name: string
}
/**
* Execution context for workflow tracking
*/
export interface ExecutionContext {
is_template: boolean
workflow_name?: string
// Template metadata (only present when is_template = true)
template_source?: string
template_category?: string
template_tags?: string[]
template_models?: string[]
template_use_case?: string
template_license?: string
}
/**
* Template metadata for workflow tracking
*/
export interface TemplateMetadata {
workflow_name: string
template_source?: string
template_category?: string
template_tags?: string[]
template_models?: string[]
template_use_case?: string
template_license?: string
}
/**
* Core telemetry provider interface
*/
export interface TelemetryProvider {
// Authentication flow events
trackAuth(metadata: AuthMetadata): void
// Subscription flow events
trackSubscription(event: 'modal_opened' | 'subscribe_clicked'): void
trackRunButton(options?: { subscribe_to_run?: boolean }): void
// Survey flow events
trackSurvey(stage: 'opened' | 'submitted', responses?: SurveyResponses): void
// Email verification events
trackEmailVerification(stage: 'opened' | 'requested' | 'completed'): void
// Template workflow events
trackTemplate(metadata: TemplateMetadata): void
// Workflow execution events
trackWorkflowExecution(): void
}
/**
* Telemetry event constants
*/
export const TelemetryEvents = {
// Authentication Flow
USER_AUTH_COMPLETED: 'user_auth_completed',
// Subscription Flow
RUN_BUTTON_CLICKED: 'run_button_clicked',
SUBSCRIPTION_REQUIRED_MODAL_OPENED: 'subscription_required_modal_opened',
SUBSCRIBE_NOW_BUTTON_CLICKED: 'subscribe_now_button_clicked',
// Onboarding Survey
USER_SURVEY_OPENED: 'user_survey_opened',
USER_SURVEY_SUBMITTED: 'user_survey_submitted',
// Email Verification
USER_EMAIL_VERIFY_OPENED: 'user_email_verify_opened',
USER_EMAIL_VERIFY_REQUESTED: 'user_email_verify_requested',
USER_EMAIL_VERIFY_COMPLETED: 'user_email_verify_completed',
// Template Tracking
TEMPLATE_WORKFLOW_OPENED: 'template_workflow_opened',
// Workflow Execution Tracking
WORKFLOW_EXECUTION_STARTED: 'workflow_execution_started'
} as const
export type TelemetryEventName =
(typeof TelemetryEvents)[keyof typeof TelemetryEvents]
/**
* Union type for all possible telemetry event properties
*/
export type TelemetryEventProperties =
| AuthMetadata
| SurveyResponses
| TemplateMetadata
| ExecutionContext
| RunButtonProperties

View File

@@ -1,6 +1,8 @@
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore'
import type {
TemplateGroup,
@@ -78,7 +80,7 @@ export function useTemplateWorkflows() {
const fallback =
template.title ?? template.name ?? `${sourceModule} Template`
return sourceModule === 'default'
? template.localizedTitle ?? fallback
? (template.localizedTitle ?? fallback)
: fallback
}
@@ -128,6 +130,13 @@ export function useTemplateWorkflows() {
? t(`templateWorkflows.template.${id}`, id)
: id
if (isCloud) {
useTelemetry()?.trackTemplate({
workflow_name: workflowName,
template_source: actualSourceModule
})
}
dialogStore.closeDialog()
await app.loadGraphData(json, true, true, workflowName)
@@ -142,6 +151,13 @@ export function useTemplateWorkflows() {
? t(`templateWorkflows.template.${id}`, id)
: id
if (isCloud) {
useTelemetry()?.trackTemplate({
workflow_name: workflowName,
template_source: sourceModule
})
}
dialogStore.closeDialog()
await app.loadGraphData(json, true, true, workflowName)

View File

@@ -30,6 +30,11 @@ export const useWorkflowTemplatesStore = defineStore(
const customTemplates = shallowRef<{ [moduleName: string]: string[] }>({})
const coreTemplates = shallowRef<WorkflowTemplates[]>([])
const isLoaded = ref(false)
const knownTemplateNames = ref(new Set<string>())
const getTemplateByName = (name: string): EnhancedTemplate | undefined => {
return enhancedTemplates.value.find((template) => template.name === name)
}
// Store filter mappings for dynamic categories
type FilterData = {
@@ -432,6 +437,13 @@ export const useWorkflowTemplatesStore = defineStore(
customTemplates.value = await api.getWorkflowTemplates()
const locale = i18n.global.locale.value
coreTemplates.value = await api.getCoreWorkflowTemplates(locale)
const coreNames = coreTemplates.value.flatMap((category) =>
category.templates.map((template) => template.name)
)
const customNames = Object.values(customTemplates.value).flat()
knownTemplateNames.value = new Set([...coreNames, ...customNames])
isLoaded.value = true
}
} catch (error) {
@@ -446,7 +458,9 @@ export const useWorkflowTemplatesStore = defineStore(
templateFuse,
filterTemplatesByCategory,
isLoaded,
loadWorkflowTemplates
loadWorkflowTemplates,
knownTemplateNames,
getTemplateByName
}
}
)

View File

@@ -6,6 +6,7 @@ import {
browserLocalPersistence,
createUserWithEmailAndPassword,
deleteUser,
getAdditionalUserInfo,
onAuthStateChanged,
sendEmailVerification,
sendPasswordResetEmail,
@@ -22,6 +23,8 @@ import { useFirebaseAuth } from 'vuefire'
import { COMFY_API_BASE_URL } from '@/config/comfyApi'
import { t } from '@/i18n'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { useDialogService } from '@/services/dialogService'
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
import type { AuthHeader } from '@/types/authTypes'
@@ -246,36 +249,79 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
const login = async (
email: string,
password: string
): Promise<UserCredential> =>
executeAuthAction(
): Promise<UserCredential> => {
const result = await executeAuthAction(
(authInstance) =>
signInWithEmailAndPassword(authInstance, email, password),
{ createCustomer: true }
)
if (isCloud) {
useTelemetry()?.trackAuth({
method: 'email',
is_new_user: false
})
}
return result
}
const register = async (
email: string,
password: string
): Promise<UserCredential> => {
return executeAuthAction(
const result = await executeAuthAction(
(authInstance) =>
createUserWithEmailAndPassword(authInstance, email, password),
{ createCustomer: true }
)
if (isCloud) {
useTelemetry()?.trackAuth({
method: 'email',
is_new_user: true
})
}
return result
}
const loginWithGoogle = async (): Promise<UserCredential> =>
executeAuthAction(
const loginWithGoogle = async (): Promise<UserCredential> => {
const result = await executeAuthAction(
(authInstance) => signInWithPopup(authInstance, googleProvider),
{ createCustomer: true }
)
const loginWithGithub = async (): Promise<UserCredential> =>
executeAuthAction(
if (isCloud) {
const additionalUserInfo = getAdditionalUserInfo(result)
const isNewUser = additionalUserInfo?.isNewUser ?? false
useTelemetry()?.trackAuth({
method: 'google',
is_new_user: isNewUser
})
}
return result
}
const loginWithGithub = async (): Promise<UserCredential> => {
const result = await executeAuthAction(
(authInstance) => signInWithPopup(authInstance, githubProvider),
{ createCustomer: true }
)
if (isCloud) {
const additionalUserInfo = getAdditionalUserInfo(result)
const isNewUser = additionalUserInfo?.isNewUser ?? false
useTelemetry()?.trackAuth({
method: 'github',
is_new_user: isNewUser
})
}
return result
}
const logout = async (): Promise<void> =>
executeAuthAction((authInstance) => signOut(authInstance))

View File

@@ -31,6 +31,11 @@ vi.mock('@/scripts/app', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: vi.fn((key, fallback) => fallback || key)
}),
createI18n: () => ({
global: {
t: (key: string) => key
}
})
}))

View File

@@ -19,6 +19,10 @@ vi.mock('@/composables/auth/useCurrentUser', () => ({
}))
}))
vi.mock('@/platform/telemetry', () => ({
useTelemetry: vi.fn(() => null)
}))
vi.mock('@/composables/auth/useFirebaseAuthActions', () => ({
useFirebaseAuthActions: vi.fn(() => ({
reportError: mockReportError,

View File

@@ -0,0 +1,30 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('@/platform/distribution/types', () => ({
isCloud: false
}))
describe('useTelemetry', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should return null when not in cloud distribution', async () => {
const { useTelemetry } = await import('@/platform/telemetry')
const provider = useTelemetry()
// Should return null for OSS builds
expect(provider).toBeNull()
})
it('should return null consistently for OSS builds', async () => {
const { useTelemetry } = await import('@/platform/telemetry')
const provider1 = useTelemetry()
const provider2 = useTelemetry()
// Both should be null for OSS builds
expect(provider1).toBeNull()
expect(provider2).toBeNull()
})
})

View File

@@ -268,7 +268,8 @@ export default defineConfig({
__ALGOLIA_API_KEY__: JSON.stringify(process.env.ALGOLIA_API_KEY || ''),
__USE_PROD_CONFIG__: process.env.USE_PROD_CONFIG === 'true',
__DISTRIBUTION__: JSON.stringify(DISTRIBUTION),
__BUILD_FLAGS__: JSON.stringify(BUILD_FLAGS)
__BUILD_FLAGS__: JSON.stringify(BUILD_FLAGS),
__MIXPANEL_TOKEN__: JSON.stringify(process.env.MIXPANEL_TOKEN || '')
},
resolve: {