Merge pull request #12587 from Budibase/instrument-formula-processing

Instrument formula processing in DataDog.
This commit is contained in:
Sam Rose 2023-12-15 09:31:05 +00:00 committed by GitHub
commit 2dd07b9768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 123 additions and 105 deletions

View File

@ -12,103 +12,112 @@ import { getCachedSelf } from "../utilities/global"
import env from "../environment" import env from "../environment"
import { isWebhookEndpoint } from "./utils" import { isWebhookEndpoint } from "./utils"
import { UserCtx, ContextUser } from "@budibase/types" import { UserCtx, ContextUser } from "@budibase/types"
import tracer from "dd-trace"
export default async (ctx: UserCtx, next: any) => { export default async (ctx: UserCtx, next: any) => {
// try to get the appID from the request return tracer.trace("currentapp middleware", {}, async span => {
let requestAppId = await utils.getAppIdFromCtx(ctx) // try to get the appID from the request
if (!requestAppId) { let requestAppId = await utils.getAppIdFromCtx(ctx)
return next() if (!requestAppId) {
} return next()
// deny access to application preview
if (!env.isTest()) {
if (
isDevAppID(requestAppId) &&
!isWebhookEndpoint(ctx) &&
!users.isBuilder(ctx.user, requestAppId)
) {
return ctx.redirect("/")
} }
}
let appId: string | undefined, if (requestAppId) {
roleId = roles.BUILTIN_ROLE_IDS.PUBLIC span?.addTags({ app_id: requestAppId })
if (!ctx.user?._id) { }
// not logged in, try to set a cookie for public apps
appId = requestAppId
} else if (requestAppId != null) {
// Different App ID means cookie needs reset, or if the same public user has logged in
const globalUser = await getCachedSelf(ctx, requestAppId)
appId = requestAppId
// retrieving global user gets the right role
roleId = globalUser.roleId || roleId
// Allow builders to specify their role via a header // deny access to application preview
const isBuilder = users.isBuilder(globalUser, appId) if (!env.isTest()) {
const isDevApp = appId && isDevAppID(appId) if (
const roleHeader = isDevAppID(requestAppId) &&
ctx.request && !isWebhookEndpoint(ctx) &&
(ctx.request.headers[constants.Header.PREVIEW_ROLE] as string) !users.isBuilder(ctx.user, requestAppId)
if (isBuilder && isDevApp && roleHeader) { ) {
// Ensure the role is valid by ensuring a definition exists return ctx.redirect("/")
try { }
if (roleHeader) { }
await roles.getRole(roleHeader)
roleId = roleHeader
// Delete admin and builder flags so that the specified role is honoured let appId: string | undefined,
ctx.user = users.removePortalUserPermissions(ctx.user) as ContextUser roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
if (!ctx.user?._id) {
// not logged in, try to set a cookie for public apps
appId = requestAppId
} else if (requestAppId != null) {
// Different App ID means cookie needs reset, or if the same public user has logged in
const globalUser = await getCachedSelf(ctx, requestAppId)
appId = requestAppId
// retrieving global user gets the right role
roleId = globalUser.roleId || roleId
// Allow builders to specify their role via a header
const isBuilder = users.isBuilder(globalUser, appId)
const isDevApp = appId && isDevAppID(appId)
const roleHeader =
ctx.request &&
(ctx.request.headers[constants.Header.PREVIEW_ROLE] as string)
if (isBuilder && isDevApp && roleHeader) {
// Ensure the role is valid by ensuring a definition exists
try {
if (roleHeader) {
await roles.getRole(roleHeader)
roleId = roleHeader
// Delete admin and builder flags so that the specified role is honoured
ctx.user = users.removePortalUserPermissions(
ctx.user
) as ContextUser
}
} catch (error) {
// Swallow error and do nothing
} }
} catch (error) {
// Swallow error and do nothing
} }
} }
}
// nothing more to do // nothing more to do
if (!appId) { if (!appId) {
return next() return next()
} }
const userId = ctx.user ? generateUserMetadataID(ctx.user._id!) : undefined const userId = ctx.user ? generateUserMetadataID(ctx.user._id!) : undefined
// if the user is not in the right tenant then make sure to wipe their cookie // if the user is not in the right tenant then make sure to wipe their cookie
// also cleanse any information about them that has been allocated // also cleanse any information about them that has been allocated
// this avoids apps making calls to say the worker which are cross tenant, // this avoids apps making calls to say the worker which are cross tenant,
// we simply remove the authentication // we simply remove the authentication
if ( if (
env.MULTI_TENANCY && env.MULTI_TENANCY &&
userId && userId &&
requestAppId && requestAppId &&
!tenancy.isUserInAppTenant(requestAppId, ctx.user) !tenancy.isUserInAppTenant(requestAppId, ctx.user)
) { ) {
// clear out the user // clear out the user
ctx.user = users.cleanseUserObject(ctx.user) as ContextUser ctx.user = users.cleanseUserObject(ctx.user) as ContextUser
ctx.isAuthenticated = false ctx.isAuthenticated = false
roleId = roles.BUILTIN_ROLE_IDS.PUBLIC roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
// remove the cookie, so future calls are public // remove the cookie, so future calls are public
await auth.platformLogout({ await auth.platformLogout({
ctx, ctx,
userId,
})
}
return context.doInAppContext(appId, async () => {
ctx.appId = appId
if (roleId) {
ctx.roleId = roleId
const globalId = ctx.user ? ctx.user._id : undefined
ctx.user = {
...ctx.user!,
// override userID with metadata one
_id: userId,
userId, userId,
globalId, })
roleId,
role: await roles.getRole(roleId, { defaultPublic: true }),
}
} }
return next() return context.doInAppContext(appId, async () => {
ctx.appId = appId
if (roleId) {
ctx.roleId = roleId
const globalId = ctx.user ? ctx.user._id : undefined
ctx.user = {
...ctx.user!,
// override userID with metadata one
_id: userId,
userId,
globalId,
roleId,
role: await roles.getRole(roleId, { defaultPublic: true }),
}
}
return next()
})
}) })
} }

View File

@ -11,6 +11,7 @@ import {
Row, Row,
Table, Table,
} from "@budibase/types" } from "@budibase/types"
import tracer from "dd-trace"
interface FormulaOpts { interface FormulaOpts {
dynamic?: boolean dynamic?: boolean
@ -50,33 +51,41 @@ export function processFormulas<T extends Row | Row[]>(
inputRows: T, inputRows: T,
{ dynamic, contextRows }: FormulaOpts = { dynamic: true } { dynamic, contextRows }: FormulaOpts = { dynamic: true }
): T { ): T {
const rows = Array.isArray(inputRows) ? inputRows : [inputRows] return tracer.trace("processFormulas", {}, span => {
if (rows) span?.addTags({ table_id: table._id })
for (let [column, schema] of Object.entries(table.schema)) { const rows = Array.isArray(inputRows) ? inputRows : [inputRows]
if (schema.type !== FieldTypes.FORMULA) { if (rows) {
continue for (let [column, schema] of Object.entries(table.schema)) {
} if (schema.type !== FieldTypes.FORMULA) {
continue
}
const isStatic = schema.formulaType === FormulaTypes.STATIC const isStatic = schema.formulaType === FormulaTypes.STATIC
if ( if (
schema.formula == null || schema.formula == null ||
(dynamic && isStatic) || (dynamic && isStatic) ||
(!dynamic && !isStatic) (!dynamic && !isStatic)
) { ) {
continue continue
} }
// iterate through rows and process formula // iterate through rows and process formula
for (let i = 0; i < rows.length; i++) { for (let i = 0; i < rows.length; i++) {
let row = rows[i] let row = rows[i]
let context = contextRows ? contextRows[i] : row let context = contextRows ? contextRows[i] : row
rows[i] = { let formula = schema.formula
...row, rows[i] = {
[column]: processStringSync(schema.formula, context), ...row,
[column]: tracer.trace("processStringSync", {}, span => {
span?.addTags({ column })
return processStringSync(formula, context)
}),
}
} }
} }
} }
return Array.isArray(inputRows) ? rows : rows[0] return Array.isArray(inputRows) ? rows : rows[0]
})
} }
/** /**