From c4f45cd49a005ffab1efe2dbf03d5775ab92d37f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cosmin=20P=C3=A2rvulescu?= Date: Mon, 25 Sep 2023 12:37:21 +0300 Subject: [PATCH] chore(analytics): track in-app groups with groups metrics (#2678) --- .../connectIdentityGroupEmail.ts | 15 +++++++- .../acceptIdentityGroupMemberInvitation.ts | 12 ++++++ .../identity-groups/createIdentityGroup.ts | 8 ++-- .../identity-groups/deleteIdentityGroup.ts | 18 +++++++-- .../deleteIdentityGroupMembership.ts | 14 +++++++ .../purgeIdentityGroupMemberships.ts | 12 +++++- .../starbase/src/jsonrpc/methods/createApp.ts | 2 +- .../starbase/src/jsonrpc/methods/deleteApp.ts | 23 ++++++++--- .../src/jsonrpc/methods/publishApp.ts | 32 ++++++++++++++-- .../src/jsonrpc/methods/setAppPlan.ts | 38 ++++++++++++++----- 10 files changed, 144 insertions(+), 30 deletions(-) diff --git a/platform/account/src/jsonrpc/methods/identity-groups/connectIdentityGroupEmail.ts b/platform/account/src/jsonrpc/methods/identity-groups/connectIdentityGroupEmail.ts index 866f020576..67fc2c15bd 100644 --- a/platform/account/src/jsonrpc/methods/identity-groups/connectIdentityGroupEmail.ts +++ b/platform/account/src/jsonrpc/methods/identity-groups/connectIdentityGroupEmail.ts @@ -1,6 +1,6 @@ import { z } from 'zod' import { Context } from '../../../context' -import { InternalServerError, UnauthorizedError } from '@proofzero/errors' +import { UnauthorizedError } from '@proofzero/errors' import { AccountURNInput, IdentityGroupURNValidator, @@ -8,6 +8,7 @@ import { import { router } from '@proofzero/platform.core' import { EDGE_ACCOUNT } from '@proofzero/platform.account/src/constants' import { EDGE_MEMBER_OF_IDENTITY_GROUP } from '@proofzero/types/graph' +import { createAnalyticsEvent } from '@proofzero/utils/analytics' export const ConnectIdentityGroupEmailInputSchema = z.object({ identityGroupURN: IdentityGroupURNValidator, @@ -78,6 +79,18 @@ export const connectIdentityGroupEmail = async ({ dst: accountURN, }) + ctx.waitUntil?.( + createAnalyticsEvent({ + eventName: 'group_email_connected', + apiKey: ctx.POSTHOG_API_KEY, + distinctId: identityGroupURN, + properties: { + $groups: { group: identityGroupURN }, + connectedAccountURN: accountURN, + }, + }) + ) + return { existing: false, } diff --git a/platform/identity/src/jsonrpc/methods/identity-groups/acceptIdentityGroupMemberInvitation.ts b/platform/identity/src/jsonrpc/methods/identity-groups/acceptIdentityGroupMemberInvitation.ts index 6a037a7c84..ed6b1a32a8 100644 --- a/platform/identity/src/jsonrpc/methods/identity-groups/acceptIdentityGroupMemberInvitation.ts +++ b/platform/identity/src/jsonrpc/methods/identity-groups/acceptIdentityGroupMemberInvitation.ts @@ -7,6 +7,7 @@ import { InternalServerError } from '@proofzero/errors' import { router } from '@proofzero/platform.core' import { EDGE_MEMBER_OF_IDENTITY_GROUP } from '@proofzero/types/graph' import { IdentityURN } from '@proofzero/urns/identity' +import { createAnalyticsEvent } from '@proofzero/utils/analytics' export const AcceptIdentityGroupMemberInvitationInputSchema = z.object({ identityGroupURN: IdentityGroupURNValidator, @@ -68,4 +69,15 @@ export const acceptIdentityGroupMemberInvitation = async ({ tag: EDGE_MEMBER_OF_IDENTITY_GROUP, dst: identityGroupURN, }) + + ctx.waitUntil?.( + createAnalyticsEvent({ + eventName: 'group_invitation_accepted', + apiKey: ctx.POSTHOG_API_KEY, + distinctId: ctx.identityURN as IdentityURN, + properties: { + $groups: { group: identityGroupURN }, + }, + }) + ) } diff --git a/platform/identity/src/jsonrpc/methods/identity-groups/createIdentityGroup.ts b/platform/identity/src/jsonrpc/methods/identity-groups/createIdentityGroup.ts index 9b8d132dcf..a175165b50 100644 --- a/platform/identity/src/jsonrpc/methods/identity-groups/createIdentityGroup.ts +++ b/platform/identity/src/jsonrpc/methods/identity-groups/createIdentityGroup.ts @@ -47,12 +47,12 @@ export const createIdentityGroup = async ({ apiKey: ctx.POSTHOG_API_KEY, distinctId: ctx.identityURN as IdentityURN, properties: { - $groups: { group: groupURN }, + $groups: { group: baseGroupURN }, $group_type: 'group', - $group_key: groupURN, + $group_key: baseGroupURN, $group_set: { name: input.name, - groupURN: groupURN, + groupURN: baseGroupURN, date_joined: new Date().toISOString(), }, }, @@ -65,7 +65,7 @@ export const createIdentityGroup = async ({ apiKey: ctx.POSTHOG_API_KEY, distinctId: ctx.identityURN as IdentityURN, properties: { - $groups: { group: groupURN }, + $groups: { group: baseGroupURN }, }, }) ) diff --git a/platform/identity/src/jsonrpc/methods/identity-groups/deleteIdentityGroup.ts b/platform/identity/src/jsonrpc/methods/identity-groups/deleteIdentityGroup.ts index bcd51f4a6e..a2d06e3c48 100644 --- a/platform/identity/src/jsonrpc/methods/identity-groups/deleteIdentityGroup.ts +++ b/platform/identity/src/jsonrpc/methods/identity-groups/deleteIdentityGroup.ts @@ -7,6 +7,8 @@ import { IdentityGroupURNValidator } from '@proofzero/platform-middleware/inputV import { initIdentityGroupNodeByName } from '../../../nodes' import { BadRequestError } from '@proofzero/errors' import { EDGE_APPLICATION } from '@proofzero/platform.starbase/src/types' +import { createAnalyticsEvent } from '@proofzero/utils/analytics' +import { IdentityURN } from '@proofzero/urns/identity' export const DeleteIdentityGroupInputSchema = IdentityGroupURNValidator type DeleteIdentityGroupInput = z.infer @@ -67,11 +69,19 @@ export const deleteIdentityGroup = async ({ urn: identityGroupURN, }) - const DO = await initIdentityGroupNodeByName( - identityGroupURN, - ctx.IdentityGroup - ) + const DO = initIdentityGroupNodeByName(identityGroupURN, ctx.IdentityGroup) if (DO) { await DO.storage.deleteAll() } + + ctx.waitUntil?.( + createAnalyticsEvent({ + eventName: 'group_deleted', + apiKey: ctx.POSTHOG_API_KEY, + distinctId: ctx.identityURN as IdentityURN, + properties: { + $groups: { group: identityGroupURN }, + }, + }) + ) } diff --git a/platform/identity/src/jsonrpc/methods/identity-groups/deleteIdentityGroupMembership.ts b/platform/identity/src/jsonrpc/methods/identity-groups/deleteIdentityGroupMembership.ts index f136938d68..0237db7d9b 100644 --- a/platform/identity/src/jsonrpc/methods/identity-groups/deleteIdentityGroupMembership.ts +++ b/platform/identity/src/jsonrpc/methods/identity-groups/deleteIdentityGroupMembership.ts @@ -9,6 +9,8 @@ import { IdentityURNInput, } from '@proofzero/platform-middleware/inputValidators' import { InternalServerError } from '@proofzero/errors' +import { createAnalyticsEvent } from '@proofzero/utils/analytics' +import { IdentityURN } from '@proofzero/urns/identity' export const DeleteIdentityGroupMembershipInputSchema = z.object({ identityURN: IdentityURNInput, @@ -64,4 +66,16 @@ export const deleteIdentityGroupMembership = async ({ tag: EDGE_MEMBER_OF_IDENTITY_GROUP, dst: identityGroupURN, }) + + ctx.waitUntil?.( + createAnalyticsEvent({ + eventName: 'group_member_removed', + apiKey: ctx.POSTHOG_API_KEY, + distinctId: ctx.identityURN as IdentityURN, + properties: { + $groups: { group: identityGroupURN }, + removedIdentityURN: identityURN, + }, + }) + ) } diff --git a/platform/identity/src/jsonrpc/methods/identity-groups/purgeIdentityGroupMemberships.ts b/platform/identity/src/jsonrpc/methods/identity-groups/purgeIdentityGroupMemberships.ts index cbe37774bc..809457b429 100644 --- a/platform/identity/src/jsonrpc/methods/identity-groups/purgeIdentityGroupMemberships.ts +++ b/platform/identity/src/jsonrpc/methods/identity-groups/purgeIdentityGroupMemberships.ts @@ -2,9 +2,9 @@ import { router } from '@proofzero/platform.core' import { EDGE_MEMBER_OF_IDENTITY_GROUP } from '@proofzero/types/graph' import { Context } from '../../../context' -import { InternalServerError } from '@proofzero/errors' import { IdentityURN } from '@proofzero/urns/identity' import { initIdentityGroupNodeByName } from '../../../nodes' +import { createAnalyticsEvent } from '@proofzero/utils/analytics' export const purgeIdentityGroupMemberships = async ({ ctx, @@ -45,11 +45,19 @@ export const purgeIdentityGroupMemberships = async ({ urn: igu, }) - const DO = await initIdentityGroupNodeByName(igu, ctx.IdentityGroup) + const DO = initIdentityGroupNodeByName(igu, ctx.IdentityGroup) if (DO) { await DO.storage.deleteAll() } } }) ) + + ctx.waitUntil?.( + createAnalyticsEvent({ + eventName: 'group_memberships_purged', + apiKey: ctx.POSTHOG_API_KEY, + distinctId: ctx.identityURN as IdentityURN, + }) + ) } diff --git a/platform/starbase/src/jsonrpc/methods/createApp.ts b/platform/starbase/src/jsonrpc/methods/createApp.ts index 493f39b62f..23699faa3b 100644 --- a/platform/starbase/src/jsonrpc/methods/createApp.ts +++ b/platform/starbase/src/jsonrpc/methods/createApp.ts @@ -104,7 +104,7 @@ export const createApp = async ({ apiKey: ctx.POSTHOG_API_KEY, distinctId: ctx.identityURN, properties: { - $groups: { app: clientId }, + $groups: { app: clientId, group: input.identityGroupURN }, }, }) ) diff --git a/platform/starbase/src/jsonrpc/methods/deleteApp.ts b/platform/starbase/src/jsonrpc/methods/deleteApp.ts index ad464c7de7..600c336686 100644 --- a/platform/starbase/src/jsonrpc/methods/deleteApp.ts +++ b/platform/starbase/src/jsonrpc/methods/deleteApp.ts @@ -2,13 +2,12 @@ import { z } from 'zod' import { router } from '@proofzero/platform.core' import { Context } from '../context' import { getApplicationNodeByClientId } from '../../nodes/application' -import { BadRequestError } from '@proofzero/errors' +import { BadRequestError, InternalServerError } from '@proofzero/errors' import { ApplicationURNSpace } from '@proofzero/urns/application' import { AppClientIdParamSchema } from '../validators/app' -import { EDGE_APPLICATION } from '../../types' -import { EDGE_HAS_REFERENCE_TO } from '@proofzero/types/graph' import { createAnalyticsEvent } from '@proofzero/utils/analytics' -import type { IdentityURN } from '@proofzero/urns/identity' +import { IdentityURNSpace, type IdentityURN } from '@proofzero/urns/identity' +import { EDGE_APPLICATION } from '../../types' export const DeleteAppInput = AppClientIdParamSchema @@ -48,6 +47,15 @@ export const deleteApp = async ({ }), ]) + const appOwnershipEdge = dstEdges.find( + (edge) => edge.tag === EDGE_APPLICATION + ) + if (!appOwnershipEdge) { + throw new InternalServerError({ + message: 'App ownership edge not found', + }) + } + await Promise.all( srcEdges.concat(dstEdges).map((e) => caller.edges.removeEdge({ @@ -69,7 +77,12 @@ export const deleteApp = async ({ eventName: 'identity_deleted_app', distinctId: ctx.identityURN as IdentityURN, properties: { - $groups: { app: input.clientId }, + $groups: { + app: input.clientId, + group: IdentityURNSpace.is(appOwnershipEdge.src.baseUrn) + ? appOwnershipEdge.src.baseUrn + : undefined, + }, }, }) ) diff --git a/platform/starbase/src/jsonrpc/methods/publishApp.ts b/platform/starbase/src/jsonrpc/methods/publishApp.ts index f640f99f74..dc229b3cbd 100644 --- a/platform/starbase/src/jsonrpc/methods/publishApp.ts +++ b/platform/starbase/src/jsonrpc/methods/publishApp.ts @@ -9,6 +9,9 @@ import { createAnalyticsEvent } from '@proofzero/utils/analytics' import { Context } from '../context' import { getApplicationNodeByClientId } from '../../nodes/application' import type { IdentityURN } from '@proofzero/urns/identity' +import { EDGE_APPLICATION } from '../../types' +import { InternalServerError } from '@proofzero/errors' +import { IdentityGroupURNSpace } from '@proofzero/urns/identity-group' export const PublishAppInput = z.object({ clientId: z.string(), @@ -58,16 +61,37 @@ export const publishApp = async ({ await appDO.class.publish(input.published) - ctx.waitUntil?.( - createAnalyticsEvent({ + const buildAnalyticsEvent = async () => { + const { edges: ownershipEdges } = await caller.edges.getEdges({ + query: { + tag: EDGE_APPLICATION, + dst: { baseUrn: appURN }, + }, + }) + if (ownershipEdges.length === 0) { + throw new InternalServerError({ + message: 'App ownership edge not found', + }) + } + + await createAnalyticsEvent({ distinctId: ctx.identityURN as IdentityURN, eventName: input.published ? 'identity_published_app' : 'identity_unpublished_app', apiKey: ctx.POSTHOG_API_KEY, - properties: { $groups: { app: input.clientId } }, + properties: { + $groups: { + app: input.clientId, + group: IdentityGroupURNSpace.is(ownershipEdges[0].src.baseUrn) + ? ownershipEdges[0].src.baseUrn + : undefined, + }, + }, }) - ) + } + + ctx.waitUntil?.(buildAnalyticsEvent()) return { published: true, diff --git a/platform/starbase/src/jsonrpc/methods/setAppPlan.ts b/platform/starbase/src/jsonrpc/methods/setAppPlan.ts index a7b4779d6a..82dfa7c785 100644 --- a/platform/starbase/src/jsonrpc/methods/setAppPlan.ts +++ b/platform/starbase/src/jsonrpc/methods/setAppPlan.ts @@ -8,6 +8,9 @@ import { ServicePlanType } from '@proofzero/types/billing' import { EDGE_PAYS_APP } from '@proofzero/types/graph' import { IdentityRefURNValidator } from '@proofzero/platform-middleware/inputValidators' import { createAnalyticsEvent } from '@proofzero/utils/analytics' +import { EDGE_APPLICATION } from '../../types' +import { InternalServerError } from '@proofzero/errors' +import { IdentityGroupURNSpace } from '@proofzero/urns/identity-group' export const SetAppPlanInput = AppClientIdParamSchema.extend({ URN: IdentityRefURNValidator, @@ -62,10 +65,10 @@ export const setAppPlan = async ({ }) } - // This is the way how we can update group properties - // https://posthog.com/tutorials/frontend-vs-backend-group-analytics - ctx.waitUntil?.( - createAnalyticsEvent({ + const buildAnalyticsEvent = async () => { + // This is the way how we can update group properties + // https://posthog.com/tutorials/frontend-vs-backend-group-analytics + await createAnalyticsEvent({ eventName: '$groupidentify', apiKey: ctx.POSTHOG_API_KEY, distinctId: input.URN, @@ -77,16 +80,33 @@ export const setAppPlan = async ({ }, }, }) - ) - ctx.waitUntil?.( - createAnalyticsEvent({ + const { edges: ownershipEdges } = await caller.edges.getEdges({ + query: { + tag: EDGE_APPLICATION, + dst: { baseUrn: appURN }, + }, + }) + if (ownershipEdges.length === 0) { + throw new InternalServerError({ + message: 'App ownership edge not found', + }) + } + + await createAnalyticsEvent({ eventName: `app_set_${plan}_plan`, apiKey: ctx.POSTHOG_API_KEY, distinctId: input.URN, properties: { - $groups: { app: clientId }, + $groups: { + app: clientId, + group: IdentityGroupURNSpace.is(ownershipEdges[0].src.baseUrn) + ? ownershipEdges[0].src.baseUrn + : undefined, + }, }, }) - ) + } + + ctx.waitUntil?.(buildAnalyticsEvent()) }