forked from tsai/budibase
19 changed files with 412 additions and 150 deletions
@ -1,139 +0,0 @@ |
|||
import * as Sentry from "@sentry/browser" |
|||
import posthog from "posthog-js" |
|||
import api from "builderStore/api" |
|||
|
|||
let analyticsEnabled |
|||
const posthogConfigured = process.env.POSTHOG_TOKEN && process.env.POSTHOG_URL |
|||
const sentryConfigured = process.env.SENTRY_DSN |
|||
|
|||
const FEEDBACK_SUBMITTED_KEY = "budibase:feedback_submitted" |
|||
const APP_FIRST_STARTED_KEY = "budibase:first_run" |
|||
const feedbackHours = 12 |
|||
|
|||
async function activate() { |
|||
if (analyticsEnabled === undefined) { |
|||
// only the server knows the true NODE_ENV
|
|||
// this was an issue as NODE_ENV = 'cypress' on the server,
|
|||
// but 'production' on the client
|
|||
const response = await api.get("/api/analytics") |
|||
analyticsEnabled = (await response.json()).enabled === true |
|||
} |
|||
if (!analyticsEnabled) return |
|||
if (sentryConfigured) Sentry.init({ dsn: process.env.SENTRY_DSN }) |
|||
if (posthogConfigured) { |
|||
posthog.init(process.env.POSTHOG_TOKEN, { |
|||
autocapture: false, |
|||
capture_pageview: false, |
|||
api_host: process.env.POSTHOG_URL, |
|||
}) |
|||
posthog.set_config({ persistence: "cookie" }) |
|||
} |
|||
} |
|||
|
|||
function identify(id) { |
|||
if (!analyticsEnabled || !id) return |
|||
if (posthogConfigured) posthog.identify(id) |
|||
if (sentryConfigured) |
|||
Sentry.configureScope(scope => { |
|||
scope.setUser({ id: id }) |
|||
}) |
|||
} |
|||
|
|||
async function identifyByApiKey(apiKey) { |
|||
if (!analyticsEnabled) return true |
|||
try { |
|||
const response = await fetch( |
|||
`https://03gaine137.execute-api.eu-west-1.amazonaws.com/prod/account/id?api_key=${apiKey.trim()}` |
|||
) |
|||
if (response.status === 200) { |
|||
const id = await response.json() |
|||
|
|||
await api.put("/api/keys/userId", { value: id }) |
|||
identify(id) |
|||
return true |
|||
} |
|||
|
|||
return false |
|||
} catch (error) { |
|||
console.log(error) |
|||
} |
|||
} |
|||
|
|||
function captureException(err) { |
|||
if (!analyticsEnabled) return |
|||
Sentry.captureException(err) |
|||
captureEvent("Error", { error: err.message ? err.message : err }) |
|||
} |
|||
|
|||
function captureEvent(eventName, props = {}) { |
|||
if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return |
|||
props.sourceApp = "builder" |
|||
posthog.capture(eventName, props) |
|||
} |
|||
|
|||
if (!localStorage.getItem(APP_FIRST_STARTED_KEY)) { |
|||
localStorage.setItem(APP_FIRST_STARTED_KEY, Date.now()) |
|||
} |
|||
|
|||
const isFeedbackTimeElapsed = sinceDateStr => { |
|||
const sinceDate = parseFloat(sinceDateStr) |
|||
const feedbackMilliseconds = feedbackHours * 60 * 60 * 1000 |
|||
return Date.now() > sinceDate + feedbackMilliseconds |
|||
} |
|||
|
|||
function submitFeedback(values) { |
|||
if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return |
|||
localStorage.setItem(FEEDBACK_SUBMITTED_KEY, Date.now()) |
|||
|
|||
const prefixedValues = Object.entries(values).reduce((obj, [key, value]) => { |
|||
obj[`feedback_${key}`] = value |
|||
return obj |
|||
}, {}) |
|||
|
|||
posthog.capture("Feedback Submitted", prefixedValues) |
|||
} |
|||
|
|||
function requestFeedbackOnDeploy() { |
|||
if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return false |
|||
const lastSubmittedStr = localStorage.getItem(FEEDBACK_SUBMITTED_KEY) |
|||
if (!lastSubmittedStr) return true |
|||
return isFeedbackTimeElapsed(lastSubmittedStr) |
|||
} |
|||
|
|||
function highlightFeedbackIcon() { |
|||
if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return false |
|||
const lastSubmittedStr = localStorage.getItem(FEEDBACK_SUBMITTED_KEY) |
|||
if (lastSubmittedStr) return isFeedbackTimeElapsed(lastSubmittedStr) |
|||
const firstRunStr = localStorage.getItem(APP_FIRST_STARTED_KEY) |
|||
if (!firstRunStr) return false |
|||
return isFeedbackTimeElapsed(firstRunStr) |
|||
} |
|||
|
|||
// Opt In/Out
|
|||
const ifAnalyticsEnabled = func => () => { |
|||
if (analyticsEnabled && process.env.POSTHOG_TOKEN) { |
|||
return func() |
|||
} |
|||
} |
|||
const disabled = () => posthog.has_opted_out_capturing() |
|||
const optIn = () => posthog.opt_in_capturing() |
|||
const optOut = () => posthog.opt_out_capturing() |
|||
|
|||
export default { |
|||
activate, |
|||
identify, |
|||
identifyByApiKey, |
|||
captureException, |
|||
captureEvent, |
|||
requestFeedbackOnDeploy, |
|||
submitFeedback, |
|||
highlightFeedbackIcon, |
|||
disabled: () => { |
|||
if (analyticsEnabled == null) { |
|||
return true |
|||
} |
|||
return ifAnalyticsEnabled(disabled) |
|||
}, |
|||
optIn: ifAnalyticsEnabled(optIn), |
|||
optOut: ifAnalyticsEnabled(optOut), |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
export default class IntercomClient { |
|||
constructor(token) { |
|||
this.token = token |
|||
} |
|||
|
|||
init() { |
|||
if (!this.token) return |
|||
|
|||
const token = this.token |
|||
|
|||
var w = window |
|||
var ic = w.Intercom |
|||
if (typeof ic === "function") { |
|||
ic("reattach_activator") |
|||
ic("update", w.intercomSettings) |
|||
} else { |
|||
var d = document |
|||
var i = function () { |
|||
i.c(arguments) |
|||
} |
|||
i.q = [] |
|||
i.c = function (args) { |
|||
i.q.push(args) |
|||
} |
|||
w.Intercom = i |
|||
var l = function () { |
|||
var s = d.createElement("script") |
|||
s.type = "text/javascript" |
|||
s.async = true |
|||
s.src = "https://widget.intercom.io/widget/" + token |
|||
var x = d.getElementsByTagName("script")[0] |
|||
x.parentNode.insertBefore(s, x) |
|||
} |
|||
if (document.readyState === "complete") { |
|||
l() |
|||
} else if (w.attachEvent) { |
|||
w.attachEvent("onload", l) |
|||
} else { |
|||
w.addEventListener("load", l, false) |
|||
} |
|||
|
|||
this.initialised = true |
|||
} |
|||
} |
|||
|
|||
show(user = {}) { |
|||
if (!this.initialised) return |
|||
|
|||
return window.Intercom("boot", { |
|||
app_id: this.token, |
|||
...user |
|||
}) |
|||
} |
|||
|
|||
update() { |
|||
if (!this.initialised) return |
|||
|
|||
return window.Intercom("update") |
|||
} |
|||
|
|||
captureEvent(event, props = {}) { |
|||
if (!this.initialised) return |
|||
|
|||
window.Intercom("trackEvent", event, props); |
|||
} |
|||
} |
|||
@ -0,0 +1,144 @@ |
|||
import posthog from "posthog-js" |
|||
import { Events } from "./constants" |
|||
|
|||
// let analyticsEnabled
|
|||
// const posthogConfigured = process.env.POSTHOG_TOKEN && process.env.POSTHOG_URL
|
|||
|
|||
// const FEEDBACK_SUBMITTED_KEY = "budibase:feedback_submitted"
|
|||
// const APP_FIRST_STARTED_KEY = "budibase:first_run"
|
|||
// const feedbackHours = 12
|
|||
|
|||
export default class PosthogClient { |
|||
constructor(token, url) { |
|||
this.token = token |
|||
this.url = url |
|||
} |
|||
|
|||
init() { |
|||
if (!this.token || !this.url) return |
|||
|
|||
posthog.init(this.token, { |
|||
autocapture: false, |
|||
capture_pageview: false, |
|||
api_host: this.url, |
|||
}) |
|||
posthog.set_config({ persistence: "cookie" }) |
|||
|
|||
this.initialised = true |
|||
} |
|||
|
|||
disabled() { |
|||
return posthog.has_opted_out_capturing() |
|||
} |
|||
|
|||
optIn() { |
|||
return posthog.opt_in_capturing() |
|||
} |
|||
|
|||
optOut() { |
|||
return posthog.opt_out_capturing() |
|||
} |
|||
|
|||
identify(id) { |
|||
if (!this.initialised) return |
|||
|
|||
posthog.identify(id) |
|||
} |
|||
|
|||
updateUser(meta) { |
|||
if (!this.initialised) return |
|||
|
|||
posthog.people.set(meta) |
|||
} |
|||
|
|||
captureException(err) { |
|||
if (!this.initialised) return |
|||
|
|||
this.captureEvent("Error", { error: err.message ? err.message : err }) |
|||
} |
|||
|
|||
captureEvent(eventName, props) { |
|||
if (!this.initialised) return |
|||
|
|||
props.sourceApp = "builder" |
|||
posthog.capture(eventName, props) |
|||
} |
|||
|
|||
npsFeedback(values) { |
|||
if (!this.initialised) return |
|||
|
|||
localStorage.setItem(Events.NPS.SUBMITTED, Date.now()) |
|||
|
|||
const prefixedFeedback = {} |
|||
for (let key in values) { |
|||
prefixedFeedback[`feedback_${key}`] = values[key] |
|||
} |
|||
|
|||
posthog.capture(Events.NPS.SUBMITTED, prefixedFeedback) |
|||
} |
|||
} |
|||
|
|||
// function captureEvent(eventName, props = {}) {
|
|||
// if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return
|
|||
// props.sourceApp = "builder"
|
|||
// posthog.capture(eventName, props)
|
|||
// }
|
|||
|
|||
// if (!localStorage.getItem(APP_FIRST_STARTED_KEY)) {
|
|||
// localStorage.setItem(APP_FIRST_STARTED_KEY, Date.now())
|
|||
// }
|
|||
|
|||
// function submitFeedback(values) {
|
|||
// if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return
|
|||
// localStorage.setItem(FEEDBACK_SUBMITTED_KEY, Date.now())
|
|||
|
|||
// const prefixedValues = Object.entries(values).reduce((obj, [key, value]) => {
|
|||
// obj[`feedback_${key}`] = value
|
|||
// return obj
|
|||
// }, {})
|
|||
|
|||
// posthog.capture("Feedback Submitted", prefixedValues)
|
|||
// }
|
|||
|
|||
// function requestFeedbackOnDeploy() {
|
|||
// if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return false
|
|||
// const lastSubmittedStr = localStorage.getItem(FEEDBACK_SUBMITTED_KEY)
|
|||
// if (!lastSubmittedStr) return true
|
|||
// return isFeedbackTimeElapsed(lastSubmittedStr)
|
|||
// }
|
|||
|
|||
// function highlightFeedbackIcon() {
|
|||
// if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return false
|
|||
// const lastSubmittedStr = localStorage.getItem(FEEDBACK_SUBMITTED_KEY)
|
|||
// if (lastSubmittedStr) return isFeedbackTimeElapsed(lastSubmittedStr)
|
|||
// const firstRunStr = localStorage.getItem(APP_FIRST_STARTED_KEY)
|
|||
// if (!firstRunStr) return false
|
|||
// return isFeedbackTimeElapsed(firstRunStr)
|
|||
// }
|
|||
|
|||
// Opt In/Out
|
|||
// const ifAnalyticsEnabled = func => () => {
|
|||
// if (analyticsEnabled && process.env.POSTHOG_TOKEN) {
|
|||
// return func()
|
|||
// }
|
|||
// }
|
|||
// const disabled = () => posthog.has_opted_out_capturing()
|
|||
// const optIn = () => posthog.opt_in_capturing()
|
|||
// const optOut = () => posthog.opt_out_capturing()
|
|||
|
|||
// export default {
|
|||
// init,
|
|||
// identify,
|
|||
// captureException,
|
|||
// captureEvent,
|
|||
// submitFeedback,
|
|||
// highlightFeedbackIcon,
|
|||
// disabled: () => {
|
|||
// if (analyticsEnabled == null) {
|
|||
// return true
|
|||
// }
|
|||
// return ifAnalyticsEnabled(disabled)
|
|||
// },
|
|||
// optIn: ifAnalyticsEnabled(optIn),
|
|||
// optOut: ifAnalyticsEnabled(optOut),
|
|||
// }
|
|||
@ -0,0 +1,39 @@ |
|||
import * as Sentry from "@sentry/browser" |
|||
|
|||
export default class SentryClient { |
|||
constructor(dsn) { |
|||
this.dsn = dsn |
|||
} |
|||
|
|||
init() { |
|||
if (this.dsn) { |
|||
Sentry.init({ dsn: this.dsn }) |
|||
|
|||
this.initalised = true |
|||
} |
|||
} |
|||
|
|||
captureException(err) { |
|||
if (!this.initalised) return |
|||
|
|||
Sentry.captureException(err) |
|||
} |
|||
|
|||
identify(id) { |
|||
Sentry.configureScope(scope => { |
|||
scope.setUser({ id }) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
// export function init() {
|
|||
// if (process.env.SENTRY_DSN) {
|
|||
// Sentry.init({ dsn: process.env.SENTRY_DSN })
|
|||
// }
|
|||
// }
|
|||
|
|||
// export function captureException(err) {
|
|||
// // if (!analyticsEnabled) return
|
|||
// Sentry.captureException(err)
|
|||
// // captureEvent("Error", { error: err.message ? err.message : err })
|
|||
// }
|
|||
@ -0,0 +1,49 @@ |
|||
export const Events = { |
|||
BUILDER: { |
|||
STARTED: "Builder Started" |
|||
}, |
|||
COMPONENT: { |
|||
CREATED: "Added Component" |
|||
}, |
|||
DATASOURCE: { |
|||
CREATED: "Datasource Created", |
|||
UPDATED: "Datasource Updated", |
|||
}, |
|||
TABLE: { |
|||
CREATED: "Table Created", |
|||
}, |
|||
VIEW: { |
|||
CREATED: "View Created", |
|||
ADDED_FILTER: "Added View Filter", |
|||
ADDED_CALCULATE: "Added View Calculate" |
|||
}, |
|||
SCREEN: { |
|||
CREATED: "Screen Created" |
|||
}, |
|||
AUTOMATION: { |
|||
CREATED: "Added Component", |
|||
SAVED: "Automation Saved", |
|||
BLOCK_ADDED: "Added Automation Block", |
|||
}, |
|||
NPS: { |
|||
SUBMITTED: "budibase:feedback_submitted" |
|||
}, |
|||
APP: { |
|||
CREATED: "budibase:app_created", |
|||
PUBLISHED: "budibase:app_published", |
|||
UNPUBLISHED: "budibase:app_unpublished" |
|||
}, |
|||
ANALYTICS: { |
|||
OPT_IN: "budibase:analytics_opt_in", |
|||
OPT_OUT: "budibase:analytics_opt_out" |
|||
}, |
|||
USER: { |
|||
INVITE: "budibase:portal_user_invite" |
|||
}, |
|||
SMTP: { |
|||
SAVED: "budibase:smtp_saved" |
|||
}, |
|||
SSO: { |
|||
SAVED: "budibase:sso_saved" |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
import api from "builderStore/api" |
|||
import PosthogClient from "./PosthogClient" |
|||
import IntercomClient from "./IntercomClient" |
|||
import SentryClient from "./SentryClient" |
|||
import { Events } from "./constants" |
|||
|
|||
// const posthog = new PosthogClient(
|
|||
// process.env.POSTHOG_TOKEN,
|
|||
// process.env.POSTHOG_URL
|
|||
// )
|
|||
const posthog = new PosthogClient( |
|||
"phc_yGOn4i7jWKaCTapdGR6lfA4AvmuEQ2ijn5zAVSFYPlS", |
|||
"https://app.posthog.com" |
|||
) |
|||
// const sentry = new SentryClient(process.env.SENTRY_DSN)
|
|||
const sentry = new SentryClient("https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131") |
|||
// const intercom = new IntercomClient(process.env.INTERCOM_TOKEN)
|
|||
const intercom = new IntercomClient("qz2sxfuv") |
|||
|
|||
|
|||
class AnalyticsHub { |
|||
constructor() { |
|||
this.clients = [posthog, sentry, intercom] |
|||
} |
|||
|
|||
async activate() { |
|||
const analyticsStatus = await api.get("/api/analytics") |
|||
const json = await analyticsStatus.json() |
|||
|
|||
if (json.enabled) { |
|||
this.clients.forEach(client => client.init()) |
|||
} |
|||
|
|||
this.enabled = json.enabled |
|||
} |
|||
|
|||
optIn() { |
|||
this.captureEvent(Events.ANALYTICS.OPT_IN) |
|||
this.clients.forEach(client => client.optIn()) |
|||
} |
|||
|
|||
optOut() { |
|||
this.captureEvent(Events.ANALYTICS.OPT_OUT) |
|||
this.clients.forEach(client => client.optOut()) |
|||
} |
|||
|
|||
identify(id, metadata) { |
|||
posthog.identify(id) |
|||
if (metadata) { |
|||
posthog.updateUser(metadata) |
|||
} |
|||
sentry.identify(id) |
|||
} |
|||
|
|||
captureException(err) { |
|||
sentry.captureException(err) |
|||
} |
|||
|
|||
captureEvent(eventName, props = {}) { |
|||
posthog.captureEvent(eventName, props) |
|||
intercom.captureEvent(eventName, props) |
|||
} |
|||
|
|||
showChat(user) { |
|||
intercom.show(user) |
|||
} |
|||
|
|||
submitFeedback(values) { |
|||
posthog.npsFeedback(values) |
|||
} |
|||
} |
|||
|
|||
const analytics = new AnalyticsHub() |
|||
|
|||
export { Events } |
|||
export default analytics |
|||
Loading…
Reference in new issue