mirror of https://github.com/Budibase/budibase.git
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 { processors } from "./processors" |
|||
import * as identification from "./identification" |
|||
|
|||
export const publishEvent = (event: Event, properties: any) => { |
|||
// in future this should use async events
|
|||
// via a queue. For now we can use sync as
|
|||
// this is non-blocking
|
|||
processors.processEvent(event, properties) |
|||
export const publishEvent = async (event: Event, properties: any) => { |
|||
// in future this should use async events via a distributed queue.
|
|||
const identity = identification.getCurrentIdentity() |
|||
await processors.processEvent(event, identity, 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" |
|||
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 { Event, Account } from "@budibase/types" |
|||
|
|||
export function created(account: Account) { |
|||
export async function created(account: Account) { |
|||
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 = {} |
|||
publishEvent(Event.ACCOUNT_DELETED, properties) |
|||
await publishEvent(Event.ACCOUNT_DELETED, properties) |
|||
} |
|||
|
|||
export function verified(account: Account) { |
|||
export async function verified(account: Account) { |
|||
const properties = {} |
|||
publishEvent(Event.ACCOUNT_VERIFIED, properties) |
|||
await publishEvent(Event.ACCOUNT_VERIFIED, properties) |
|||
} |
|||
|
|||
@ -1,30 +1,35 @@ |
|||
import { publishEvent } from "../events" |
|||
import { Event, VersionCheckedEvent } from "@budibase/types" |
|||
|
|||
export function nameUpdated() { |
|||
export async function nameUpdated() { |
|||
const properties = {} |
|||
publishEvent(Event.ORG_NAME_UPDATED, properties) |
|||
await publishEvent(Event.ORG_NAME_UPDATED, properties) |
|||
} |
|||
|
|||
export function logoUpdated() { |
|||
export async function logoUpdated() { |
|||
const properties = {} |
|||
publishEvent(Event.ORG_LOGO_UPDATED, properties) |
|||
await publishEvent(Event.ORG_LOGO_UPDATED, properties) |
|||
} |
|||
|
|||
export function platformURLUpdated() { |
|||
export async function platformURLUpdated() { |
|||
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 = { |
|||
version, |
|||
} |
|||
publishEvent(Event.UPDATE_VERSION_CHECKED, properties) |
|||
await publishEvent(Event.UPDATE_VERSION_CHECKED, properties) |
|||
} |
|||
|
|||
// TODO
|
|||
export function analyticsOptOut() { |
|||
export async function analyticsOptOut() { |
|||
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 "./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" |
|||
|
|||
export interface Account { |
|||
accountId: string |
|||
export interface CreateAccount { |
|||
email: string |
|||
tenantId: string |
|||
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 |
|||
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