diff --git a/forge/db/controllers/Invitation.js b/forge/db/controllers/Invitation.js index 0933468be0..141880247b 100644 --- a/forge/db/controllers/Invitation.js +++ b/forge/db/controllers/Invitation.js @@ -128,6 +128,14 @@ module.exports = { invitor, role }) + + // record acceptance in product analytics tool + app.product.capture(invitedUser.email, '$ff-invite-accepted', { + 'accepted-at': new Date().toISOString(), + 'invite-id': invitation.hashid + }, { + team: invitation.team.hashid + }) }, rejectInvitation: async (app, invitation, user) => { diff --git a/forge/forge.js b/forge/forge.js index 596ec68e77..fc15d05089 100644 --- a/forge/forge.js +++ b/forge/forge.js @@ -16,6 +16,7 @@ const housekeeper = require('./housekeeper') const license = require('./licensing') const notifications = require('./notifications') const postoffice = require('./postoffice') +const product = require('./product') const routes = require('./routes') const settings = require('./settings') const { finishSetup } = require('./setup') @@ -438,6 +439,8 @@ module.exports = async (options = {}) => { // Post Office : handles email await server.register(postoffice) await server.register(notifications) + // Product service handles reporting to PostHog + await server.register(product, runtimeConfig.telemetry.frontend?.posthog) // Comms : real-time communication broker await server.register(comms) // Containers: diff --git a/forge/product/index.js b/forge/product/index.js new file mode 100644 index 0000000000..27b833f6c9 --- /dev/null +++ b/forge/product/index.js @@ -0,0 +1,28 @@ +const fp = require('fastify-plugin') +const { PostHog } = require('posthog-node') + +module.exports = fp(async function (app, _opts) { + let client + if (_opts && _opts.apikey) { + const apiHost = _opts?.apiurl || 'https://app.posthog.com' + const apiKey = _opts?.apikey + const options = { + host: apiHost + } + + client = new PostHog(apiKey, options) + } + + function capture (distinctId, event, properties, groups) { + if (client) { + if (!properties) { + properties = {} + } + client.capture({ distinctId, event, properties, groups }) + } + } + + app.decorate('product', { + capture + }) +}, { name: 'app.product' }) diff --git a/forge/routes/ui/index.js b/forge/routes/ui/index.js index 3ca74a8158..a1fba70e1f 100644 --- a/forge/routes/ui/index.js +++ b/forge/routes/ui/index.js @@ -43,6 +43,7 @@ module.exports = async function (app) { } if (telemetry.frontend.posthog?.apikey) { + // add to frontend const apihost = telemetry.frontend.posthog.apiurl || 'https://app.posthog.com' const apikey = telemetry.frontend.posthog.apikey const options = { diff --git a/frontend/src/api/user.js b/frontend/src/api/user.js index 3f166898a5..6b972f62b5 100644 --- a/frontend/src/api/user.js +++ b/frontend/src/api/user.js @@ -104,12 +104,6 @@ const getTeamInvitations = async () => { } const acceptTeamInvitation = async (invitationId, teamId) => { return client.patch('/api/v1/user/invitations/' + invitationId).then(res => { - product.capture('$ff-invite-accepted', { - 'invite-id': invitationId, - 'accepted-at': (new Date()).toISOString() - }, { - team: teamId - }) return res.data }) } diff --git a/package-lock.json b/package-lock.json index b270c95c7c..326ba7340c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "pg": "^8.11.3", "pino": "^8.15.1", "pino-pretty": "^10.0.0", + "posthog-node": "^4.2.0", "qrcode": "^1.5.3", "random-words": "~2.0.0", "semver": "~7.6.0", @@ -18484,6 +18485,18 @@ "node": ">=0.10.0" } }, + "node_modules/posthog-node": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-4.2.0.tgz", + "integrity": "sha512-hgyCYMyzMvuF3qWMw6JvS8gT55v7Mtp5wKWcnDrw+nu39D0Tk9BXD7I0LOBp0lGlHEPaXCEVYUtviNKrhMALGA==", + "dependencies": { + "axios": "^1.7.4", + "rusha": "^0.8.14" + }, + "engines": { + "node": ">=15.0.0" + } + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -19840,6 +19853,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rusha": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/rusha/-/rusha-0.8.14.tgz", + "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==" + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -36771,6 +36789,15 @@ "xtend": "^4.0.0" } }, + "posthog-node": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-4.2.0.tgz", + "integrity": "sha512-hgyCYMyzMvuF3qWMw6JvS8gT55v7Mtp5wKWcnDrw+nu39D0Tk9BXD7I0LOBp0lGlHEPaXCEVYUtviNKrhMALGA==", + "requires": { + "axios": "^1.7.4", + "rusha": "^0.8.14" + } + }, "prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -37811,6 +37838,11 @@ "queue-microtask": "^1.2.2" } }, + "rusha": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/rusha/-/rusha-0.8.14.tgz", + "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==" + }, "rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", diff --git a/package.json b/package.json index 8a9f82e081..3736b40660 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "pg": "^8.11.3", "pino": "^8.15.1", "pino-pretty": "^10.0.0", + "posthog-node": "^4.2.0", "qrcode": "^1.5.3", "random-words": "~2.0.0", "semver": "~7.6.0",