forked from tsai/budibase
80 changed files with 723 additions and 407 deletions
@ -0,0 +1,45 @@ |
|||||
|
import env from "../environment" |
||||
|
import * as tenancy from "../tenancy" |
||||
|
import * as dbUtils from "../db/utils" |
||||
|
import { Configs } from "../constants" |
||||
|
|
||||
|
export const enabled = async () => { |
||||
|
// cloud - always use the environment variable
|
||||
|
if (!env.SELF_HOSTED) { |
||||
|
return !!env.ENABLE_ANALYTICS |
||||
|
} |
||||
|
|
||||
|
// self host - prefer the settings doc
|
||||
|
// check for explicit true/false values to support
|
||||
|
// backwards compatibility where setting may not exist
|
||||
|
const settings = await getSettingsDoc() |
||||
|
if (settings?.config?.analyticsEnabled === false) { |
||||
|
return false |
||||
|
} else if (settings?.config?.analyticsEnabled === true) { |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
// fallback to the environment variable
|
||||
|
// explicitly check for 0 or false here, undefined or otherwise is treated as true
|
||||
|
const envEnabled: any = env.ENABLE_ANALYTICS |
||||
|
if (envEnabled === 0 || envEnabled === false) { |
||||
|
return false |
||||
|
} else { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const getSettingsDoc = async () => { |
||||
|
const db = tenancy.getGlobalDB() |
||||
|
let settings |
||||
|
try { |
||||
|
settings = await db.get( |
||||
|
dbUtils.generateConfigID({ type: Configs.SETTINGS }) |
||||
|
) |
||||
|
} catch (e: any) { |
||||
|
if (e.status !== 404) { |
||||
|
throw e |
||||
|
} |
||||
|
} |
||||
|
return settings |
||||
|
} |
||||
@ -1,9 +1,9 @@ |
|||||
import { Event } from "@budibase/types" |
import { Event } from "@budibase/types" |
||||
import { processors } from "./processors" |
import { processors } from "./processors" |
||||
|
import * as identification from "./identification" |
||||
|
|
||||
export const publishEvent = (event: Event, properties: any) => { |
export const publishEvent = async (event: Event, properties: any) => { |
||||
// in future this should use async events
|
// in future this should use async events via a distributed queue.
|
||||
// via a queue. For now we can use sync as
|
const identity = identification.getCurrentIdentity() |
||||
// this is non-blocking
|
await processors.processEvent(event, identity, properties) |
||||
processors.processEvent(event, properties) |
|
||||
} |
} |
||||
|
|||||
@ -0,0 +1,93 @@ |
|||||
|
import { |
||||
|
isCloudAccount, |
||||
|
isSSOAccount, |
||||
|
} from "./../../../types/src/documents/account/account" |
||||
|
import * as context from "../context" |
||||
|
import env from "../environment" |
||||
|
import { |
||||
|
Hosting, |
||||
|
User, |
||||
|
SessionUser, |
||||
|
Identity, |
||||
|
IdentityType, |
||||
|
Account, |
||||
|
AccountIdentity, |
||||
|
BudibaseIdentity, |
||||
|
} from "@budibase/types" |
||||
|
import { analyticsProcessor } from "./processors" |
||||
|
|
||||
|
export const getCurrentIdentity = (): Identity => { |
||||
|
const user: SessionUser | undefined = context.getUser() |
||||
|
const tenantId = context.getTenantId() |
||||
|
let id: string |
||||
|
|
||||
|
if (user) { |
||||
|
id = user._id |
||||
|
} else if (env.SELF_HOSTED) { |
||||
|
id = "installationId" // TODO
|
||||
|
} else { |
||||
|
id = context.getTenantId() |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
id, |
||||
|
tenantId, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const identifyUser = async (user: User) => { |
||||
|
const id = user._id as string |
||||
|
const tenantId = user.tenantId |
||||
|
const hosting = env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD |
||||
|
const type = IdentityType.USER |
||||
|
let builder = user.builder?.global |
||||
|
let admin = user.admin?.global |
||||
|
let authType = user.providerType ? user.providerType : "password" |
||||
|
|
||||
|
const identity: BudibaseIdentity = { |
||||
|
id, |
||||
|
tenantId, |
||||
|
hosting, |
||||
|
type, |
||||
|
builder, |
||||
|
admin, |
||||
|
authType, |
||||
|
} |
||||
|
|
||||
|
await identify(identity) |
||||
|
} |
||||
|
|
||||
|
export const identifyAccount = async (account: Account) => { |
||||
|
let id = account.accountId |
||||
|
const tenantId = account.tenantId |
||||
|
const hosting = account.hosting |
||||
|
let type = IdentityType.ACCOUNT |
||||
|
let authType = isSSOAccount(account) |
||||
|
? (account.providerType as string) |
||||
|
: "password" |
||||
|
|
||||
|
if (isCloudAccount(account)) { |
||||
|
if (account.budibaseUserId) { |
||||
|
// use the budibase user as the id if set
|
||||
|
id = account.budibaseUserId |
||||
|
type = IdentityType.USER |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const identity: AccountIdentity = { |
||||
|
id, |
||||
|
tenantId, |
||||
|
hosting, |
||||
|
type, |
||||
|
authType, |
||||
|
verified: account.verified, |
||||
|
profession: account.profession, |
||||
|
companySize: account.size, |
||||
|
} |
||||
|
|
||||
|
await identify(identity) |
||||
|
} |
||||
|
|
||||
|
const identify = async (identity: Identity) => { |
||||
|
await analyticsProcessor.identify(identity) |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
import { Event, Identity } from "@budibase/types" |
||||
|
import { EventProcessor } from "./types" |
||||
|
import env from "../../environment" |
||||
|
import * as analytics from "../analytics" |
||||
|
import PosthogProcessor from "./PosthogProcessor" |
||||
|
|
||||
|
export default class AnalyticsProcessor implements EventProcessor { |
||||
|
posthog: PosthogProcessor | undefined |
||||
|
|
||||
|
constructor() { |
||||
|
if (env.POSTHOG_TOKEN) { |
||||
|
this.posthog = new PosthogProcessor(env.POSTHOG_TOKEN) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async processEvent( |
||||
|
event: Event, |
||||
|
identity: Identity, |
||||
|
properties: any |
||||
|
): Promise<void> { |
||||
|
if (!(await analytics.enabled())) { |
||||
|
return |
||||
|
} |
||||
|
if (this.posthog) { |
||||
|
this.posthog.processEvent(event, identity, properties) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async identify(identity: Identity) { |
||||
|
if (!(await analytics.enabled())) { |
||||
|
return |
||||
|
} |
||||
|
if (this.posthog) { |
||||
|
this.posthog.identify(identity) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
shutdown() { |
||||
|
if (this.posthog) { |
||||
|
this.posthog.shutdown() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,2 +1,8 @@ |
|||||
|
import AnalyticsProcessor from "./AnalyticsProcessor" |
||||
|
import LoggingProcessor from "./LoggingProcessor" |
||||
import Processors from "./Processors" |
import Processors from "./Processors" |
||||
export const processors = new Processors() |
|
||||
|
export const analyticsProcessor = new AnalyticsProcessor() |
||||
|
const loggingProcessor = new LoggingProcessor() |
||||
|
|
||||
|
export const processors = new Processors([analyticsProcessor, loggingProcessor]) |
||||
|
|||||
@ -1,17 +1,17 @@ |
|||||
import { publishEvent } from "../events" |
import { publishEvent } from "../events" |
||||
import { Event, Account } from "@budibase/types" |
import { Event, Account } from "@budibase/types" |
||||
|
|
||||
export function created(account: Account) { |
export async function created(account: Account) { |
||||
const properties = {} |
const properties = {} |
||||
publishEvent(Event.ACCOUNT_CREATED, properties) |
await publishEvent(Event.ACCOUNT_CREATED, properties) |
||||
} |
} |
||||
|
|
||||
export function deleted(account: Account) { |
export async function deleted(account: Account) { |
||||
const properties = {} |
const properties = {} |
||||
publishEvent(Event.ACCOUNT_DELETED, properties) |
await publishEvent(Event.ACCOUNT_DELETED, properties) |
||||
} |
} |
||||
|
|
||||
export function verified(account: Account) { |
export async function verified(account: Account) { |
||||
const properties = {} |
const properties = {} |
||||
publishEvent(Event.ACCOUNT_VERIFIED, properties) |
await publishEvent(Event.ACCOUNT_VERIFIED, properties) |
||||
} |
} |
||||
|
|||||
@ -1,30 +1,35 @@ |
|||||
import { publishEvent } from "../events" |
import { publishEvent } from "../events" |
||||
import { Event, VersionCheckedEvent } from "@budibase/types" |
import { Event, VersionCheckedEvent } from "@budibase/types" |
||||
|
|
||||
export function nameUpdated() { |
export async function nameUpdated() { |
||||
const properties = {} |
const properties = {} |
||||
publishEvent(Event.ORG_NAME_UPDATED, properties) |
await publishEvent(Event.ORG_NAME_UPDATED, properties) |
||||
} |
} |
||||
|
|
||||
export function logoUpdated() { |
export async function logoUpdated() { |
||||
const properties = {} |
const properties = {} |
||||
publishEvent(Event.ORG_LOGO_UPDATED, properties) |
await publishEvent(Event.ORG_LOGO_UPDATED, properties) |
||||
} |
} |
||||
|
|
||||
export function platformURLUpdated() { |
export async function platformURLUpdated() { |
||||
const properties = {} |
const properties = {} |
||||
publishEvent(Event.ORG_PLATFORM_URL_UPDATED, properties) |
await publishEvent(Event.ORG_PLATFORM_URL_UPDATED, properties) |
||||
} |
} |
||||
|
|
||||
export function versionChecked(version: number) { |
export async function versionChecked(version: number) { |
||||
const properties: VersionCheckedEvent = { |
const properties: VersionCheckedEvent = { |
||||
version, |
version, |
||||
} |
} |
||||
publishEvent(Event.UPDATE_VERSION_CHECKED, properties) |
await publishEvent(Event.UPDATE_VERSION_CHECKED, properties) |
||||
} |
} |
||||
|
|
||||
// TODO
|
// TODO
|
||||
export function analyticsOptOut() { |
export async function analyticsOptOut() { |
||||
const properties = {} |
const properties = {} |
||||
publishEvent(Event.ANALYTICS_OPT_OUT, properties) |
await publishEvent(Event.ANALYTICS_OPT_OUT, properties) |
||||
|
} |
||||
|
|
||||
|
export async function analyticsOptIn() { |
||||
|
const properties = {} |
||||
|
await publishEvent(Event.ANALYTICS_OPT_OUT, properties) |
||||
} |
} |
||||
|
|||||
@ -1 +1,2 @@ |
|||||
export * from "./hosting" |
export * from "./hosting" |
||||
|
export * from "./sessions" |
||||
|
|||||
@ -0,0 +1,36 @@ |
|||||
|
import { User, Account } from "../documents" |
||||
|
import { Hosting } from "./hosting" |
||||
|
|
||||
|
/** |
||||
|
* Account portal user session. Used for self hosted accounts only. |
||||
|
*/ |
||||
|
export interface AccountUserSession { |
||||
|
_id: string |
||||
|
email: string |
||||
|
tenantId: string |
||||
|
accountPortalAccess: boolean |
||||
|
account: Account |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Budibase user session. |
||||
|
*/ |
||||
|
export interface BudibaseUserSession extends User { |
||||
|
_id: string // overwrite potentially undefined
|
||||
|
account?: Account |
||||
|
accountPortalAccess?: boolean |
||||
|
} |
||||
|
|
||||
|
export const isAccountSession = ( |
||||
|
user: AccountUserSession | BudibaseUserSession |
||||
|
): user is AccountUserSession => { |
||||
|
return user.account?.hosting === Hosting.SELF |
||||
|
} |
||||
|
|
||||
|
export const isUserSession = ( |
||||
|
user: AccountUserSession | BudibaseUserSession |
||||
|
): user is BudibaseUserSession => { |
||||
|
return !user.account || user.account?.hosting === Hosting.CLOUD |
||||
|
} |
||||
|
|
||||
|
export type SessionUser = AccountUserSession | BudibaseUserSession |
||||
@ -1,7 +1,82 @@ |
|||||
import { Hosting } from "../../core" |
import { Hosting } from "../../core" |
||||
|
|
||||
export interface Account { |
export interface CreateAccount { |
||||
accountId: string |
email: string |
||||
|
tenantId: string |
||||
hosting: Hosting |
hosting: Hosting |
||||
|
authType: AuthType |
||||
|
// optional fields - for sso based sign ups
|
||||
|
registrationStep?: string |
||||
|
// profile
|
||||
|
tenantName?: string |
||||
|
name?: string |
||||
|
size?: string |
||||
|
profession?: string |
||||
|
} |
||||
|
|
||||
|
export interface CreatePassswordAccount extends CreateAccount { |
||||
|
password: string |
||||
|
} |
||||
|
|
||||
|
export const isCreatePasswordAccount = ( |
||||
|
account: CreateAccount |
||||
|
): account is CreatePassswordAccount => account.authType === AuthType.PASSWORD |
||||
|
|
||||
|
export interface UpdateAccount { |
||||
|
stripeCustomerId?: string |
||||
|
licenseKey?: string |
||||
|
} |
||||
|
|
||||
|
export interface Account extends CreateAccount { |
||||
|
// generated
|
||||
|
accountId: string |
||||
|
createdAt: number |
||||
|
// registration
|
||||
verified: boolean |
verified: boolean |
||||
|
verificationSent: boolean |
||||
|
// licensing
|
||||
|
tier: string // deprecated
|
||||
|
stripeCustomerId?: string |
||||
|
licenseKey?: string |
||||
|
} |
||||
|
|
||||
|
export interface PasswordAccount extends Account { |
||||
|
password: string |
||||
|
} |
||||
|
|
||||
|
export const isPasswordAccount = ( |
||||
|
account: Account |
||||
|
): account is PasswordAccount => |
||||
|
account.authType === AuthType.PASSWORD && account.hosting === Hosting.SELF |
||||
|
|
||||
|
export interface CloudAccount extends Account { |
||||
|
password?: string |
||||
|
budibaseUserId: string |
||||
|
} |
||||
|
|
||||
|
export const isCloudAccount = (account: Account): account is CloudAccount => |
||||
|
account.hosting === Hosting.CLOUD |
||||
|
|
||||
|
export const isSelfHostAccount = (account: Account) => |
||||
|
account.hosting === Hosting.SELF |
||||
|
|
||||
|
export const isSSOAccount = (account: Account): account is SSOAccount => |
||||
|
account.authType === AuthType.SSO |
||||
|
|
||||
|
export interface SSOAccount extends Account { |
||||
|
pictureUrl?: string |
||||
|
provider?: string |
||||
|
providerType?: string |
||||
|
oauth2?: OAuthTokens |
||||
|
thirdPartyProfile: any // TODO: define what the google profile looks like
|
||||
|
} |
||||
|
|
||||
|
export enum AuthType { |
||||
|
SSO = "sso", |
||||
|
PASSWORD = "password", |
||||
|
} |
||||
|
|
||||
|
export interface OAuthTokens { |
||||
|
accessToken: string |
||||
|
refreshToken: string |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,29 @@ |
|||||
|
import { Hosting } from "../core" |
||||
|
|
||||
|
export enum IdentityType { |
||||
|
USER = "user", // cloud and self hosted users
|
||||
|
ACCOUNT = "account", // self hosted accounts
|
||||
|
TENANT = "tenant", // cloud and self hosted tenants
|
||||
|
} |
||||
|
|
||||
|
export interface Identity { |
||||
|
id: string |
||||
|
tenantId: string |
||||
|
} |
||||
|
|
||||
|
export interface UserIdentity extends Identity { |
||||
|
hosting: Hosting |
||||
|
type: IdentityType |
||||
|
authType: string |
||||
|
} |
||||
|
|
||||
|
export interface BudibaseIdentity extends UserIdentity { |
||||
|
builder?: boolean |
||||
|
admin?: boolean |
||||
|
} |
||||
|
|
||||
|
export interface AccountIdentity extends UserIdentity { |
||||
|
verified: boolean |
||||
|
profession: string | undefined |
||||
|
companySize: string | undefined |
||||
|
} |
||||
Loading…
Reference in new issue