Skip to content

Commit f80be50

Browse files
committed
feat(analytics): expand posthog event coverage with source tracking and lifecycle events
1 parent 6d029d7 commit f80be50

File tree

18 files changed

+303
-15
lines changed

18 files changed

+303
-15
lines changed

apps/sim/app/api/a2a/agents/[agentId]/route.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { generateAgentCard, generateSkillsFromWorkflow } from '@/lib/a2a/agent-c
77
import type { AgentCapabilities, AgentSkill } from '@/lib/a2a/types'
88
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
99
import { getRedisClient } from '@/lib/core/config/redis'
10+
import { captureServerEvent } from '@/lib/posthog/server'
1011
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
1112
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
1213

@@ -180,6 +181,17 @@ export async function DELETE(request: NextRequest, { params }: { params: Promise
180181

181182
logger.info(`Deleted A2A agent: ${agentId}`)
182183

184+
captureServerEvent(
185+
auth.userId,
186+
'a2a_agent_deleted',
187+
{
188+
agent_id: agentId,
189+
workflow_id: existingAgent.workflowId,
190+
workspace_id: existingAgent.workspaceId,
191+
},
192+
{ groups: { workspace: existingAgent.workspaceId } }
193+
)
194+
183195
return NextResponse.json({ success: true })
184196
} catch (error) {
185197
logger.error('Error deleting agent:', error)
@@ -251,6 +263,16 @@ export async function POST(request: NextRequest, { params }: { params: Promise<R
251263
}
252264

253265
logger.info(`Published A2A agent: ${agentId}`)
266+
captureServerEvent(
267+
auth.userId,
268+
'a2a_agent_published',
269+
{
270+
agent_id: agentId,
271+
workflow_id: existingAgent.workflowId,
272+
workspace_id: existingAgent.workspaceId,
273+
},
274+
{ groups: { workspace: existingAgent.workspaceId } }
275+
)
254276
return NextResponse.json({ success: true, isPublished: true })
255277
}
256278

@@ -273,6 +295,16 @@ export async function POST(request: NextRequest, { params }: { params: Promise<R
273295
}
274296

275297
logger.info(`Unpublished A2A agent: ${agentId}`)
298+
captureServerEvent(
299+
auth.userId,
300+
'a2a_agent_unpublished',
301+
{
302+
agent_id: agentId,
303+
workflow_id: existingAgent.workflowId,
304+
workspace_id: existingAgent.workspaceId,
305+
},
306+
{ groups: { workspace: existingAgent.workspaceId } }
307+
)
276308
return NextResponse.json({ success: true, isPublished: false })
277309
}
278310

apps/sim/app/api/mcp/servers/route.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,14 @@ export const POST = withMcpAuth('write')(
181181
// Silently fail
182182
}
183183

184+
const sourceParam = body.source as string | undefined
185+
const source =
186+
sourceParam === 'settings' || sourceParam === 'tool_input' ? sourceParam : undefined
187+
184188
captureServerEvent(
185189
userId,
186190
'mcp_server_connected',
187-
{ workspace_id: workspaceId, server_name: body.name, transport: body.transport },
191+
{ workspace_id: workspaceId, server_name: body.name, transport: body.transport, source },
188192
{
189193
groups: { workspace: workspaceId },
190194
setOnce: { first_mcp_connected_at: new Date().toISOString() },
@@ -225,6 +229,9 @@ export const DELETE = withMcpAuth('admin')(
225229
try {
226230
const { searchParams } = new URL(request.url)
227231
const serverId = searchParams.get('serverId')
232+
const sourceParam = searchParams.get('source')
233+
const source =
234+
sourceParam === 'settings' || sourceParam === 'tool_input' ? sourceParam : undefined
228235

229236
if (!serverId) {
230237
return createMcpErrorResponse(
@@ -256,7 +263,7 @@ export const DELETE = withMcpAuth('admin')(
256263
captureServerEvent(
257264
userId,
258265
'mcp_server_disconnected',
259-
{ workspace_id: workspaceId, server_name: deletedServer.name },
266+
{ workspace_id: workspaceId, server_name: deletedServer.name, source },
260267
{ groups: { workspace: workspaceId } }
261268
)
262269

apps/sim/app/api/skills/route.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { z } from 'zod'
44
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
55
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
66
import { generateRequestId } from '@/lib/core/utils/request'
7+
import { captureServerEvent } from '@/lib/posthog/server'
78
import { deleteSkill, listSkills, upsertSkills } from '@/lib/workflows/skills/operations'
89
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
910

@@ -23,6 +24,7 @@ const SkillSchema = z.object({
2324
})
2425
),
2526
workspaceId: z.string().optional(),
27+
source: z.enum(['settings', 'tool_input']).optional(),
2628
})
2729

2830
/** GET - Fetch all skills for a workspace */
@@ -75,7 +77,7 @@ export async function POST(req: NextRequest) {
7577
const body = await req.json()
7678

7779
try {
78-
const { skills, workspaceId } = SkillSchema.parse(body)
80+
const { skills, workspaceId, source } = SkillSchema.parse(body)
7981

8082
if (!workspaceId) {
8183
logger.warn(`[${requestId}] Missing workspaceId in request body`)
@@ -107,6 +109,12 @@ export async function POST(req: NextRequest) {
107109
resourceName: skill.name,
108110
description: `Created/updated skill "${skill.name}"`,
109111
})
112+
captureServerEvent(
113+
userId,
114+
'skill_created',
115+
{ skill_id: skill.id, skill_name: skill.name, workspace_id: workspaceId, source },
116+
{ groups: { workspace: workspaceId } }
117+
)
110118
}
111119

112120
return NextResponse.json({ success: true, data: resultSkills })
@@ -137,6 +145,9 @@ export async function DELETE(request: NextRequest) {
137145
const searchParams = request.nextUrl.searchParams
138146
const skillId = searchParams.get('id')
139147
const workspaceId = searchParams.get('workspaceId')
148+
const sourceParam = searchParams.get('source')
149+
const source =
150+
sourceParam === 'settings' || sourceParam === 'tool_input' ? sourceParam : undefined
140151

141152
try {
142153
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
@@ -180,6 +191,13 @@ export async function DELETE(request: NextRequest) {
180191
description: `Deleted skill`,
181192
})
182193

194+
captureServerEvent(
195+
userId,
196+
'skill_deleted',
197+
{ skill_id: skillId, workspace_id: workspaceId, source },
198+
{ groups: { workspace: workspaceId } }
199+
)
200+
183201
logger.info(`[${requestId}] Deleted skill: ${skillId}`)
184202
return NextResponse.json({ success: true })
185203
} catch (error) {

apps/sim/app/api/tools/custom/route.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const CustomToolSchema = z.object({
3535
})
3636
),
3737
workspaceId: z.string().optional(),
38+
source: z.enum(['settings', 'tool_input']).optional(),
3839
})
3940

4041
// GET - Fetch all custom tools for the workspace
@@ -136,7 +137,7 @@ export async function POST(req: NextRequest) {
136137

137138
try {
138139
// Validate the request body
139-
const { tools, workspaceId } = CustomToolSchema.parse(body)
140+
const { tools, workspaceId, source } = CustomToolSchema.parse(body)
140141

141142
if (!workspaceId) {
142143
logger.warn(`[${requestId}] Missing workspaceId in request body`)
@@ -172,7 +173,7 @@ export async function POST(req: NextRequest) {
172173
captureServerEvent(
173174
userId,
174175
'custom_tool_saved',
175-
{ tool_id: tool.id, workspace_id: workspaceId, tool_name: tool.title },
176+
{ tool_id: tool.id, workspace_id: workspaceId, tool_name: tool.title, source },
176177
{
177178
groups: { workspace: workspaceId },
178179
setOnce: { first_custom_tool_saved_at: new Date().toISOString() },
@@ -216,6 +217,9 @@ export async function DELETE(request: NextRequest) {
216217
const searchParams = request.nextUrl.searchParams
217218
const toolId = searchParams.get('id')
218219
const workspaceId = searchParams.get('workspaceId')
220+
const sourceParam = searchParams.get('source')
221+
const source =
222+
sourceParam === 'settings' || sourceParam === 'tool_input' ? sourceParam : undefined
219223

220224
if (!toolId) {
221225
logger.warn(`[${requestId}] Missing tool ID for deletion`)
@@ -293,7 +297,7 @@ export async function DELETE(request: NextRequest) {
293297
captureServerEvent(
294298
userId,
295299
'custom_tool_deleted',
296-
{ tool_id: toolId, workspace_id: toolWorkspaceId },
300+
{ tool_id: toolId, workspace_id: toolWorkspaceId, source },
297301
toolWorkspaceId ? { groups: { workspace: toolWorkspaceId } } : undefined
298302
)
299303

apps/sim/app/api/webhooks/[id]/route.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
88
import { validateInteger } from '@/lib/core/security/input-validation'
99
import { PlatformEvents } from '@/lib/core/telemetry'
1010
import { generateRequestId } from '@/lib/core/utils/request'
11+
import { captureServerEvent } from '@/lib/posthog/server'
1112
import { cleanupExternalWebhook } from '@/lib/webhooks/provider-subscriptions'
1213
import { authorizeWorkflowByWorkspacePermission } from '@/lib/workflows/utils'
1314

@@ -274,6 +275,19 @@ export async function DELETE(
274275
request,
275276
})
276277

278+
const wsId = webhookData.workflow.workspaceId || undefined
279+
captureServerEvent(
280+
userId,
281+
'webhook_trigger_deleted',
282+
{
283+
webhook_id: id,
284+
workflow_id: webhookData.workflow.id,
285+
provider: foundWebhook.provider || 'generic',
286+
workspace_id: wsId ?? '',
287+
},
288+
wsId ? { groups: { workspace: wsId } } : undefined
289+
)
290+
277291
return NextResponse.json({ success: true }, { status: 200 })
278292
} catch (error: any) {
279293
logger.error(`[${requestId}] Error deleting webhook`, {

apps/sim/app/api/webhooks/route.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { getSession } from '@/lib/auth'
99
import { PlatformEvents } from '@/lib/core/telemetry'
1010
import { generateRequestId } from '@/lib/core/utils/request'
1111
import { getProviderIdFromServiceId } from '@/lib/oauth'
12+
import { captureServerEvent } from '@/lib/posthog/server'
1213
import { resolveEnvVarsInObject } from '@/lib/webhooks/env-resolver'
1314
import {
1415
cleanupExternalWebhook,
@@ -763,6 +764,19 @@ export async function POST(request: NextRequest) {
763764
metadata: { provider, workflowId },
764765
request,
765766
})
767+
768+
const wsId = workflowRecord.workspaceId || undefined
769+
captureServerEvent(
770+
userId,
771+
'webhook_trigger_created',
772+
{
773+
webhook_id: savedWebhook.id,
774+
workflow_id: workflowId,
775+
provider: provider || 'generic',
776+
workspace_id: wsId ?? '',
777+
},
778+
wsId ? { groups: { workspace: wsId } } : undefined
779+
)
766780
}
767781

768782
const status = targetWebhookId ? 200 : 201

apps/sim/app/api/workflows/[id]/deploy/route.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,11 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
129129
const { id } = await params
130130

131131
try {
132-
const { error, session } = await validateWorkflowPermissions(id, requestId, 'admin')
132+
const {
133+
error,
134+
session,
135+
workflow: workflowData,
136+
} = await validateWorkflowPermissions(id, requestId, 'admin')
133137
if (error) {
134138
return createErrorResponse(error.message, error.status)
135139
}
@@ -159,6 +163,14 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
159163

160164
logger.info(`[${requestId}] Updated isPublicApi for workflow ${id} to ${isPublicApi}`)
161165

166+
const wsId = workflowData?.workspaceId
167+
captureServerEvent(
168+
session!.user.id,
169+
'workflow_public_api_toggled',
170+
{ workflow_id: id, workspace_id: wsId ?? '', is_public: isPublicApi },
171+
wsId ? { groups: { workspace: wsId } } : undefined
172+
)
173+
162174
return createSuccessResponse({ isPublicApi })
163175
} catch (error: unknown) {
164176
const message = error instanceof Error ? error.message : 'Failed to update deployment settings'
@@ -175,7 +187,11 @@ export async function DELETE(
175187
const { id } = await params
176188

177189
try {
178-
const { error, session } = await validateWorkflowPermissions(id, requestId, 'admin')
190+
const {
191+
error,
192+
session,
193+
workflow: workflowData,
194+
} = await validateWorkflowPermissions(id, requestId, 'admin')
179195
if (error) {
180196
return createErrorResponse(error.message, error.status)
181197
}
@@ -190,6 +206,14 @@ export async function DELETE(
190206
return createErrorResponse(result.error || 'Failed to undeploy workflow', 500)
191207
}
192208

209+
const wsId = workflowData?.workspaceId
210+
captureServerEvent(
211+
session!.user.id,
212+
'workflow_undeployed',
213+
{ workflow_id: id, workspace_id: wsId ?? '' },
214+
wsId ? { groups: { workspace: wsId } } : undefined
215+
)
216+
193217
return createSuccessResponse({
194218
isDeployed: false,
195219
deployedAt: null,

apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { and, eq } from 'drizzle-orm'
44
import type { NextRequest } from 'next/server'
55
import { z } from 'zod'
66
import { generateRequestId } from '@/lib/core/utils/request'
7+
import { captureServerEvent } from '@/lib/posthog/server'
78
import { performActivateVersion } from '@/lib/workflows/orchestration'
89
import { validateWorkflowPermissions } from '@/lib/workflows/utils'
910
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
@@ -174,6 +175,14 @@ export async function PATCH(
174175
}
175176
}
176177

178+
const wsId = (workflowData as { workspaceId?: string } | null)?.workspaceId
179+
captureServerEvent(
180+
actorUserId,
181+
'deployment_version_activated',
182+
{ workflow_id: id, workspace_id: wsId ?? '', version: versionNum },
183+
wsId ? { groups: { workspace: wsId } } : undefined
184+
)
185+
177186
return createSuccessResponse({
178187
success: true,
179188
deployedAt: activateResult.deployedAt,

apps/sim/app/api/workflows/[id]/restore/route.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
33
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
44
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
55
import { generateRequestId } from '@/lib/core/utils/request'
6+
import { captureServerEvent } from '@/lib/posthog/server'
67
import { restoreWorkflow } from '@/lib/workflows/lifecycle'
78
import { getWorkflowById } from '@/lib/workflows/utils'
89
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
@@ -58,6 +59,13 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
5859
request,
5960
})
6061

62+
captureServerEvent(
63+
auth.userId,
64+
'workflow_restored',
65+
{ workflow_id: workflowId, workspace_id: workflowData.workspaceId ?? '' },
66+
workflowData.workspaceId ? { groups: { workspace: workflowData.workspaceId } } : undefined
67+
)
68+
6169
return NextResponse.json({ success: true })
6270
} catch (error) {
6371
logger.error(`[${requestId}] Error restoring workflow ${workflowId}`, error)

apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/route.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { z } from 'zod'
77
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
88
import { getSession } from '@/lib/auth'
99
import { encryptSecret } from '@/lib/core/security/encryption'
10+
import { captureServerEvent } from '@/lib/posthog/server'
1011
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
1112
import { MAX_EMAIL_RECIPIENTS, MAX_WORKFLOW_IDS } from '../constants'
1213

@@ -342,6 +343,17 @@ export async function DELETE(request: NextRequest, { params }: RouteParams) {
342343
request,
343344
})
344345

346+
captureServerEvent(
347+
session.user.id,
348+
'notification_channel_deleted',
349+
{
350+
notification_id: notificationId,
351+
notification_type: deletedSubscription.notificationType,
352+
workspace_id: workspaceId,
353+
},
354+
{ groups: { workspace: workspaceId } }
355+
)
356+
345357
return NextResponse.json({ success: true })
346358
} catch (error) {
347359
logger.error('Error deleting notification', { error })

0 commit comments

Comments
 (0)