Skip to content

Commit 6abff41

Browse files
feat(telemetry): add workflow_opened with open_source and missing node metrics (#6476)
- Adds app:workflow_opened and plumbs open_source across drag/drop, file-open button, workspace, and templates - Tracks missing_node_count and missing_node_types for both workflow_opened and workflow_imported - Reuses WorkflowOpenSource type for consistency; no breaking changes to loadGraphData callers (5th param remains options object; openSource optional) Validation - pnpm lint:fix - pnpm typecheck Notes - Telemetry only runs in cloud builds; OSS remains clean. - loadGraphData telemetry is centralized where missing_node_types is computed. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6476-feat-telemetry-add-workflow_opened-with-open_source-and-missing-node-metrics-29d6d73d365081f385c0da29958309da) by [Unito](https://www.unito.io) --------- Co-authored-by: bymyself <[email protected]>
1 parent a537124 commit 6abff41

File tree

5 files changed

+80
-26
lines changed

5 files changed

+80
-26
lines changed

src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,10 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
244244
this.trackEvent(TelemetryEvents.WORKFLOW_IMPORTED, metadata)
245245
}
246246

247+
trackWorkflowOpened(metadata: WorkflowImportMetadata): void {
248+
this.trackEvent(TelemetryEvents.WORKFLOW_OPENED, metadata)
249+
}
250+
247251
trackPageVisibilityChanged(metadata: PageVisibilityMetadata): void {
248252
this.trackEvent(TelemetryEvents.PAGE_VISIBILITY_CHANGED, metadata)
249253
}

src/platform/telemetry/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,22 @@ export interface CreditTopupMetadata {
113113
export interface WorkflowImportMetadata {
114114
missing_node_count: number
115115
missing_node_types: string[]
116+
/**
117+
* The source of the workflow open/import action
118+
*/
119+
open_source?: 'file_button' | 'file_drop' | 'template' | 'unknown'
116120
}
117121

122+
/**
123+
* Workflow open metadata
124+
*/
125+
/**
126+
* Enumerated sources for workflow open/import actions.
127+
*/
128+
export type WorkflowOpenSource = NonNullable<
129+
WorkflowImportMetadata['open_source']
130+
>
131+
118132
/**
119133
* Template library metadata
120134
*/
@@ -205,6 +219,7 @@ export interface TelemetryProvider {
205219

206220
// Workflow management events
207221
trackWorkflowImported(metadata: WorkflowImportMetadata): void
222+
trackWorkflowOpened(metadata: WorkflowImportMetadata): void
208223

209224
// Page visibility events
210225
trackPageVisibilityChanged(metadata: PageVisibilityMetadata): void
@@ -262,6 +277,7 @@ export const TelemetryEvents = {
262277

263278
// Workflow Management
264279
WORKFLOW_IMPORTED: 'app:workflow_imported',
280+
WORKFLOW_OPENED: 'app:workflow_opened',
265281

266282
// Page Visibility
267283
PAGE_VISIBILITY_CHANGED: 'app:page_visibility_changed',

src/platform/workflow/templates/composables/useTemplateWorkflows.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ export function useTemplateWorkflows() {
138138
}
139139

140140
dialogStore.closeDialog()
141-
await app.loadGraphData(json, true, true, workflowName)
141+
await app.loadGraphData(json, true, true, workflowName, {
142+
openSource: 'template'
143+
})
142144

143145
return true
144146
}
@@ -159,7 +161,9 @@ export function useTemplateWorkflows() {
159161
}
160162

161163
dialogStore.closeDialog()
162-
await app.loadGraphData(json, true, true, workflowName)
164+
await app.loadGraphData(json, true, true, workflowName, {
165+
openSource: 'template'
166+
})
163167

164168
return true
165169
} catch (error) {

src/scripts/app.ts

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
1919
import { isCloud } from '@/platform/distribution/types'
2020
import { useSettingStore } from '@/platform/settings/settingStore'
2121
import { useTelemetry } from '@/platform/telemetry'
22+
import type { WorkflowOpenSource } from '@/platform/telemetry/types'
2223
import { useToastStore } from '@/platform/updates/common/toastStore'
2324
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
2425
import { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
@@ -550,7 +551,7 @@ export class ComfyApp {
550551
event.dataTransfer.files.length &&
551552
event.dataTransfer.files[0].type !== 'image/bmp'
552553
) {
553-
await this.handleFile(event.dataTransfer.files[0])
554+
await this.handleFile(event.dataTransfer.files[0], 'file_drop')
554555
} else {
555556
// Try loading the first URI in the transfer list
556557
const validTypes = ['text/uri-list', 'text/x-moz-url']
@@ -561,7 +562,10 @@ export class ComfyApp {
561562
const uri = event.dataTransfer.getData(match)?.split('\n')?.[0]
562563
if (uri) {
563564
const blob = await (await fetch(uri)).blob()
564-
await this.handleFile(new File([blob], uri, { type: blob.type }))
565+
await this.handleFile(
566+
new File([blob], uri, { type: blob.type }),
567+
'file_drop'
568+
)
565569
}
566570
}
567571
}
@@ -1040,12 +1044,19 @@ export class ComfyApp {
10401044
clean: boolean = true,
10411045
restore_view: boolean = true,
10421046
workflow: string | null | ComfyWorkflow = null,
1043-
{
1044-
showMissingNodesDialog = true,
1045-
showMissingModelsDialog = true,
1046-
checkForRerouteMigration = false
1047+
options: {
1048+
showMissingNodesDialog?: boolean
1049+
showMissingModelsDialog?: boolean
1050+
checkForRerouteMigration?: boolean
1051+
openSource?: WorkflowOpenSource
10471052
} = {}
10481053
) {
1054+
const {
1055+
showMissingNodesDialog = true,
1056+
showMissingModelsDialog = true,
1057+
checkForRerouteMigration = false,
1058+
openSource
1059+
} = options
10491060
useWorkflowService().beforeLoadNewGraph()
10501061

10511062
if (clean !== false) {
@@ -1275,13 +1286,15 @@ export class ComfyApp {
12751286
missingNodeTypes
12761287
)
12771288

1278-
// Track workflow import with missing node information
1279-
useTelemetry()?.trackWorkflowImported({
1289+
const telemetryPayload = {
12801290
missing_node_count: missingNodeTypes.length,
12811291
missing_node_types: missingNodeTypes.map((node) =>
12821292
typeof node === 'string' ? node : node.type
1283-
)
1284-
})
1293+
),
1294+
open_source: openSource ?? 'unknown'
1295+
}
1296+
useTelemetry()?.trackWorkflowOpened(telemetryPayload)
1297+
useTelemetry()?.trackWorkflowImported(telemetryPayload)
12851298
await useWorkflowService().afterLoadNewGraph(
12861299
workflow,
12871300
this.graph.serialize() as unknown as ComfyWorkflowJSON
@@ -1399,7 +1412,7 @@ export class ComfyApp {
13991412
* Loads workflow data from the specified file
14001413
* @param {File} file
14011414
*/
1402-
async handleFile(file: File) {
1415+
async handleFile(file: File, openSource?: WorkflowOpenSource) {
14031416
const removeExt = (f: string) => {
14041417
if (!f) return f
14051418
const p = f.lastIndexOf('.')
@@ -1414,7 +1427,8 @@ export class ComfyApp {
14141427
JSON.parse(pngInfo.workflow),
14151428
true,
14161429
true,
1417-
fileName
1430+
fileName,
1431+
{ openSource }
14181432
)
14191433
} else if (pngInfo?.prompt) {
14201434
this.loadApiJson(JSON.parse(pngInfo.prompt), fileName)
@@ -1434,7 +1448,9 @@ export class ComfyApp {
14341448
const { workflow, prompt } = await getAvifMetadata(file)
14351449

14361450
if (workflow) {
1437-
this.loadGraphData(JSON.parse(workflow), true, true, fileName)
1451+
this.loadGraphData(JSON.parse(workflow), true, true, fileName, {
1452+
openSource
1453+
})
14381454
} else if (prompt) {
14391455
this.loadApiJson(JSON.parse(prompt), fileName)
14401456
} else {
@@ -1447,7 +1463,9 @@ export class ComfyApp {
14471463
const prompt = pngInfo?.prompt || pngInfo?.Prompt
14481464

14491465
if (workflow) {
1450-
this.loadGraphData(JSON.parse(workflow), true, true, fileName)
1466+
this.loadGraphData(JSON.parse(workflow), true, true, fileName, {
1467+
openSource
1468+
})
14511469
} else if (prompt) {
14521470
this.loadApiJson(JSON.parse(prompt), fileName)
14531471
} else {
@@ -1456,7 +1474,7 @@ export class ComfyApp {
14561474
} else if (file.type === 'audio/mpeg') {
14571475
const { workflow, prompt } = await getMp3Metadata(file)
14581476
if (workflow) {
1459-
this.loadGraphData(workflow, true, true, fileName)
1477+
this.loadGraphData(workflow, true, true, fileName, { openSource })
14601478
} else if (prompt) {
14611479
this.loadApiJson(prompt, fileName)
14621480
} else {
@@ -1465,7 +1483,7 @@ export class ComfyApp {
14651483
} else if (file.type === 'audio/ogg') {
14661484
const { workflow, prompt } = await getOggMetadata(file)
14671485
if (workflow) {
1468-
this.loadGraphData(workflow, true, true, fileName)
1486+
this.loadGraphData(workflow, true, true, fileName, { openSource })
14691487
} else if (prompt) {
14701488
this.loadApiJson(prompt, fileName)
14711489
} else {
@@ -1477,7 +1495,9 @@ export class ComfyApp {
14771495
const prompt = pngInfo?.prompt || pngInfo?.Prompt
14781496

14791497
if (workflow) {
1480-
this.loadGraphData(JSON.parse(workflow), true, true, fileName)
1498+
this.loadGraphData(JSON.parse(workflow), true, true, fileName, {
1499+
openSource
1500+
})
14811501
} else if (prompt) {
14821502
this.loadApiJson(JSON.parse(prompt), fileName)
14831503
} else {
@@ -1486,7 +1506,9 @@ export class ComfyApp {
14861506
} else if (file.type === 'video/webm') {
14871507
const webmInfo = await getFromWebmFile(file)
14881508
if (webmInfo.workflow) {
1489-
this.loadGraphData(webmInfo.workflow, true, true, fileName)
1509+
this.loadGraphData(webmInfo.workflow, true, true, fileName, {
1510+
openSource
1511+
})
14901512
} else if (webmInfo.prompt) {
14911513
this.loadApiJson(webmInfo.prompt, fileName)
14921514
} else {
@@ -1502,14 +1524,18 @@ export class ComfyApp {
15021524
) {
15031525
const mp4Info = await getFromIsobmffFile(file)
15041526
if (mp4Info.workflow) {
1505-
this.loadGraphData(mp4Info.workflow, true, true, fileName)
1527+
this.loadGraphData(mp4Info.workflow, true, true, fileName, {
1528+
openSource
1529+
})
15061530
} else if (mp4Info.prompt) {
15071531
this.loadApiJson(mp4Info.prompt, fileName)
15081532
}
15091533
} else if (file.type === 'image/svg+xml' || file.name?.endsWith('.svg')) {
15101534
const svgInfo = await getSvgMetadata(file)
15111535
if (svgInfo.workflow) {
1512-
this.loadGraphData(svgInfo.workflow, true, true, fileName)
1536+
this.loadGraphData(svgInfo.workflow, true, true, fileName, {
1537+
openSource
1538+
})
15131539
} else if (svgInfo.prompt) {
15141540
this.loadApiJson(svgInfo.prompt, fileName)
15151541
} else {
@@ -1521,7 +1547,9 @@ export class ComfyApp {
15211547
) {
15221548
const gltfInfo = await getGltfBinaryMetadata(file)
15231549
if (gltfInfo.workflow) {
1524-
this.loadGraphData(gltfInfo.workflow, true, true, fileName)
1550+
this.loadGraphData(gltfInfo.workflow, true, true, fileName, {
1551+
openSource
1552+
})
15251553
} else if (gltfInfo.prompt) {
15261554
this.loadApiJson(gltfInfo.prompt, fileName)
15271555
} else {
@@ -1544,7 +1572,8 @@ export class ComfyApp {
15441572
JSON.parse(readerResult),
15451573
true,
15461574
true,
1547-
fileName
1575+
fileName,
1576+
{ openSource }
15481577
)
15491578
}
15501579
}
@@ -1562,7 +1591,8 @@ export class ComfyApp {
15621591
JSON.parse(info.workflow),
15631592
true,
15641593
true,
1565-
fileName
1594+
fileName,
1595+
{ openSource }
15661596
)
15671597
// @ts-expect-error
15681598
} else if (info.prompt) {

src/scripts/ui.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ export class ComfyUI {
392392
parent: document.body,
393393
onchange: async () => {
394394
// @ts-expect-error fixme ts strict error
395-
await app.handleFile(fileInput.files[0])
395+
await app.handleFile(fileInput.files[0], 'file_button')
396396
fileInput.value = ''
397397
}
398398
})

0 commit comments

Comments
 (0)