timestamp support, logging updates, migration progress indicators, identification updates

This commit is contained in:
Rory Powell 2022-05-25 00:15:52 +01:00
parent b3f04e7e8f
commit 6a4c601ae8
14 changed files with 94 additions and 24 deletions

View File

@ -6,6 +6,7 @@ const {
updateAppId, updateAppId,
doInAppContext, doInAppContext,
doInUserContext, doInUserContext,
doInTenant,
} = require("./src/context") } = require("./src/context")
module.exports = { module.exports = {
@ -16,4 +17,5 @@ module.exports = {
updateAppId, updateAppId,
doInAppContext, doInAppContext,
doInUserContext, doInUserContext,
doInTenant,
} }

View File

@ -2,8 +2,12 @@ import { Event } from "@budibase/types"
import { processors } from "./processors" import { processors } from "./processors"
import * as identification from "./identification" import * as identification from "./identification"
export const publishEvent = async (event: Event, properties: any) => { export const publishEvent = async (
event: Event,
properties: any,
timestamp?: string | number
) => {
// in future this should use async events via a distributed queue. // in future this should use async events via a distributed queue.
const identity = await identification.getCurrentIdentity() const identity = await identification.getCurrentIdentity()
await processors.processEvent(event, identity, properties) await processors.processEvent(event, identity, properties, timestamp)
} }

View File

@ -23,18 +23,22 @@ export const getCurrentIdentity = async (): Promise<Identity> => {
const user: SessionUser | undefined = context.getUser() const user: SessionUser | undefined = context.getUser()
let tenantId = context.getTenantId() let tenantId = context.getTenantId()
let id: string let id: string
let type: IdentityType
if (user) { if (user) {
id = user._id id = user._id
type = IdentityType.USER
} else { } else {
const global = await getGlobalIdentifiers(tenantId) const global = await getGlobalIdentifiers(tenantId)
id = global.id id = global.id
tenantId = global.tenantId tenantId = global.tenantId
type = IdentityType.TENANT
} }
return { return {
id, id,
tenantId, tenantId,
type,
} }
} }
@ -113,14 +117,13 @@ export const identifyAccount = async (account: Account) => {
let id = account.accountId let id = account.accountId
const tenantId = account.tenantId const tenantId = account.tenantId
const hosting = account.hosting const hosting = account.hosting
let type = IdentityType.ACCOUNT let type = IdentityType.USER
let providerType = isSSOAccount(account) ? account.providerType : undefined let providerType = isSSOAccount(account) ? account.providerType : undefined
if (isCloudAccount(account)) { if (isCloudAccount(account)) {
if (account.budibaseUserId) { if (account.budibaseUserId) {
// use the budibase user as the id if set // use the budibase user as the id if set
id = account.budibaseUserId id = account.budibaseUserId
type = IdentityType.USER
} }
} }

View File

@ -16,13 +16,14 @@ export default class AnalyticsProcessor implements EventProcessor {
async processEvent( async processEvent(
event: Event, event: Event,
identity: Identity, identity: Identity,
properties: any properties: any,
timestamp?: string
): Promise<void> { ): Promise<void> {
if (!(await analytics.enabled())) { if (!(await analytics.enabled())) {
return return
} }
if (this.posthog) { if (this.posthog) {
this.posthog.processEvent(event, identity, properties) this.posthog.processEvent(event, identity, properties, timestamp)
} }
} }

View File

@ -1,5 +1,6 @@
import { Event, Identity } from "@budibase/types" import { Event, Identity } from "@budibase/types"
import { EventProcessor } from "./types" import { EventProcessor } from "./types"
import env from "../../environment"
export default class LoggingProcessor implements EventProcessor { export default class LoggingProcessor implements EventProcessor {
async processEvent( async processEvent(
@ -7,8 +8,11 @@ export default class LoggingProcessor implements EventProcessor {
identity: Identity, identity: Identity,
properties: any properties: any
): Promise<void> { ): Promise<void> {
if (env.SELF_HOSTED && !env.isDev()) {
return
}
console.log( console.log(
`[audit] [tenant=${identity.tenantId}] [identity=${identity.id}] ${event}` `[audit] [tenant=${identity.tenantId}] [identityType=${identity.type}] [identity=${identity.id}] ${event}`
) )
} }

View File

@ -15,9 +15,14 @@ export default class PosthogProcessor implements EventProcessor {
async processEvent( async processEvent(
event: Event, event: Event,
identity: Identity, identity: Identity,
properties: any properties: any,
timestamp?: string | number
): Promise<void> { ): Promise<void> {
this.posthog.capture({ distinctId: identity.id, event, properties }) const payload: any = { distinctId: identity.id, event, properties }
if (timestamp) {
payload.timestamp = new Date(timestamp)
}
this.posthog.capture(payload)
} }
async identify(identity: Identity) { async identify(identity: Identity) {

View File

@ -12,10 +12,11 @@ export default class Processor implements EventProcessor {
async processEvent( async processEvent(
event: Event, event: Event,
identity: Identity, identity: Identity,
properties: any properties: any,
timestamp?: string | number
): Promise<void> { ): Promise<void> {
for (const eventProcessor of this.processors) { for (const eventProcessor of this.processors) {
await eventProcessor.processEvent(event, identity, properties) await eventProcessor.processEvent(event, identity, properties, timestamp)
} }
} }

View File

@ -6,6 +6,11 @@ export enum EventProcessorType {
} }
export interface EventProcessor { export interface EventProcessor {
processEvent(event: Event, identity: Identity, properties: any): Promise<void> processEvent(
event: Event,
identity: Identity,
properties: any,
timestamp?: string | number
): Promise<void>
shutdown(): void shutdown(): void
} }

View File

@ -31,6 +31,13 @@ exports.runMigration = async (migration, options = {}) => {
const tenantId = getTenantId() const tenantId = getTenantId()
const migrationType = migration.type const migrationType = migration.type
const migrationName = migration.name const migrationName = migration.name
const silent = migration.silent
const log = message => {
if (!silent) {
console.log(message)
}
}
// get the db to store the migration in // get the db to store the migration in
let dbNames let dbNames
@ -45,8 +52,14 @@ exports.runMigration = async (migration, options = {}) => {
) )
} }
const length = dbNames.length
let count = 0
// run the migration against each db // run the migration against each db
for (const dbName of dbNames) { for (const dbName of dbNames) {
count++
const lengthStatement = length > 1 ? `[${count}/${length}]` : ""
await doWithDB(dbName, async db => { await doWithDB(dbName, async db => {
try { try {
const doc = await exports.getMigrationsDoc(db) const doc = await exports.getMigrationsDoc(db)
@ -58,7 +71,7 @@ exports.runMigration = async (migration, options = {}) => {
options.force[migrationType] && options.force[migrationType] &&
options.force[migrationType].includes(migrationName) options.force[migrationType].includes(migrationName)
) { ) {
console.log( log(
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Forcing` `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Forcing`
) )
} else { } else {
@ -67,12 +80,12 @@ exports.runMigration = async (migration, options = {}) => {
} }
} }
console.log( log(
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Running` `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Running ${lengthStatement}`
) )
// run the migration with tenant context // run the migration with tenant context
await migration.fn(db) await migration.fn(db)
console.log( log(
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Complete` `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Complete`
) )
@ -91,7 +104,6 @@ exports.runMigration = async (migration, options = {}) => {
} }
exports.runMigrations = async (migrations, options = {}) => { exports.runMigrations = async (migrations, options = {}) => {
console.log("Running migrations")
let tenantIds let tenantIds
if (environment.MULTI_TENANCY) { if (environment.MULTI_TENANCY) {
if (!options.tenantIds || !options.tenantIds.length) { if (!options.tenantIds || !options.tenantIds.length) {
@ -105,8 +117,19 @@ exports.runMigrations = async (migrations, options = {}) => {
tenantIds = [DEFAULT_TENANT_ID] tenantIds = [DEFAULT_TENANT_ID]
} }
if (tenantIds.length > 1) {
console.log(`Checking migrations for ${tenantIds.length} tenants`)
} else {
console.log("Checking migrations")
}
let count = 0
// for all tenants // for all tenants
for (const tenantId of tenantIds) { for (const tenantId of tenantIds) {
count++
if (tenantIds.length > 1) {
console.log(`Progress [${count}/${tenantIds.length}]`)
}
// for all migrations // for all migrations
for (const migration of migrations) { for (const migration of migrations) {
// run the migration // run the migration

View File

@ -6,6 +6,7 @@ import * as queries from "./app/queries"
import * as roles from "./app/roles" import * as roles from "./app/roles"
import * as tables from "./app/tables" import * as tables from "./app/tables"
import * as screens from "./app/screens" import * as screens from "./app/screens"
import * as global from "./global"
/** /**
* Date: * Date:
@ -16,6 +17,13 @@ import * as screens from "./app/screens"
*/ */
export const run = async (appDb: any) => { export const run = async (appDb: any) => {
if (await global.isComplete()) {
// make sure new apps aren't backfilled
// return if the global migration for this tenant is complete
// which runs after the app migrations
return
}
await apps.backfill(appDb) await apps.backfill(appDb)
await automations.backfill(appDb) await automations.backfill(appDb)
await datasources.backfill(appDb) await datasources.backfill(appDb)

View File

@ -1,6 +1,7 @@
import * as users from "./global/users" import * as users from "./global/users"
import * as rows from "./global/rows" import * as rows from "./global/rows"
import * as configs from "./global/configs" import * as configs from "./global/configs"
import { tenancy, events, migrations } from "@budibase/backend-core"
/** /**
* Date: * Date:
@ -11,7 +12,16 @@ import * as configs from "./global/configs"
*/ */
export const run = async (db: any) => { export const run = async (db: any) => {
const tenantId = tenancy.getTenantId()
await events.identification.identifyTenant(tenantId)
await users.backfill(db) await users.backfill(db)
await rows.backfill() await rows.backfill()
await configs.backfill(db) await configs.backfill(db)
} }
export const isComplete = async (): Promise<boolean> => {
const globalDb = tenancy.getGlobalDB()
const migrationsDoc = await migrations.getMigrationsDoc(globalDb)
return !!migrationsDoc.event_global_backfill
}

View File

@ -19,6 +19,7 @@ export const backfill = async (globalDb: any) => {
const users = await getUsers(globalDb) const users = await getUsers(globalDb)
for (const user of users) { for (const user of users) {
await events.identification.identifyUser(user)
await events.user.created(user) await events.user.created(user)
if (user.admin?.global) { if (user.admin?.global) {

View File

@ -7,12 +7,14 @@ import * as appUrls from "./functions/appUrls"
import * as developerQuota from "./functions/developerQuota" import * as developerQuota from "./functions/developerQuota"
import * as publishedAppsQuota from "./functions/publishedAppsQuota" import * as publishedAppsQuota from "./functions/publishedAppsQuota"
import * as backfill from "./functions/backfill" import * as backfill from "./functions/backfill"
import env from "../environment"
export interface Migration { export interface Migration {
type: string type: string
name: string name: string
opts?: object opts?: object
fn: Function fn: Function
silent?: boolean
} }
/** /**
@ -58,16 +60,18 @@ export const MIGRATIONS: Migration[] = [
name: "published_apps_quota", name: "published_apps_quota",
fn: publishedAppsQuota.run, fn: publishedAppsQuota.run,
}, },
{
type: migrations.MIGRATION_TYPES.GLOBAL,
name: "event_global_backfill",
fn: backfill.global.run,
},
{ {
type: migrations.MIGRATION_TYPES.APP, type: migrations.MIGRATION_TYPES.APP,
name: "event_app_backfill", name: "event_app_backfill",
opts: { all: true }, opts: { all: true },
fn: backfill.app.run, fn: backfill.app.run,
silent: !!env.SELF_HOSTED, // reduce noisy logging
},
{
type: migrations.MIGRATION_TYPES.GLOBAL,
name: "event_global_backfill",
fn: backfill.global.run,
silent: !!env.SELF_HOSTED, // reduce noisy logging
}, },
] ]

View File

@ -2,18 +2,17 @@ import { Hosting } from "../core"
export enum IdentityType { export enum IdentityType {
USER = "user", // cloud and self hosted users USER = "user", // cloud and self hosted users
ACCOUNT = "account", // self hosted accounts
TENANT = "tenant", // cloud and self hosted tenants TENANT = "tenant", // cloud and self hosted tenants
} }
export interface Identity { export interface Identity {
id: string id: string
tenantId: string tenantId: string
type: IdentityType
} }
export interface TenantIdentity extends Identity { export interface TenantIdentity extends Identity {
hosting: Hosting hosting: Hosting
type: IdentityType
} }
export interface UserIdentity extends TenantIdentity { export interface UserIdentity extends TenantIdentity {