diff --git a/.github/workflows/deploy-qa.yml b/.github/workflows/deploy-qa.yml index 1339ad2eb97..2a30e44def5 100644 --- a/.github/workflows/deploy-qa.yml +++ b/.github/workflows/deploy-qa.yml @@ -3,7 +3,7 @@ name: Deploy QA on: push: branches: - - master + - v3-ui workflow_dispatch: jobs: diff --git a/lerna.json b/lerna.json index 5432f60e9eb..df50828b307 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "2.33.0", + "version": "2.33.2", "npmClient": "yarn", "packages": [ "packages/*", diff --git a/packages/backend-core/src/db/couch/DatabaseImpl.ts b/packages/backend-core/src/db/couch/DatabaseImpl.ts index 8ca20bf8e18..b807db0ee3a 100644 --- a/packages/backend-core/src/db/couch/DatabaseImpl.ts +++ b/packages/backend-core/src/db/couch/DatabaseImpl.ts @@ -10,6 +10,7 @@ import { DatabaseQueryOpts, DBError, Document, + FeatureFlag, isDocument, RowResponse, RowValue, @@ -456,7 +457,7 @@ export class DatabaseImpl implements Database { async destroy() { if ( - (await flags.isEnabled("SQS")) && + (await flags.isEnabled(FeatureFlag.SQS)) && (await this.exists(SQLITE_DESIGN_DOC_ID)) ) { // delete the design document, then run the cleanup operation diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index 2ab8c550ccf..7d3a9f18f56 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -54,30 +54,46 @@ function getPackageJsonFields(): { VERSION: string SERVICE_NAME: string } { - function findFileInAncestors( - fileName: string, - currentDir: string - ): string | null { - const filePath = `${currentDir}/${fileName}` - if (existsSync(filePath)) { - return filePath - } + function getParentFile(file: string) { + function findFileInAncestors( + fileName: string, + currentDir: string + ): string | null { + const filePath = `${currentDir}/${fileName}` + if (existsSync(filePath)) { + return filePath + } + + const parentDir = `${currentDir}/..` + if (parentDir === currentDir) { + // reached root directory + return null + } - const parentDir = `${currentDir}/..` - if (parentDir === currentDir) { - // reached root directory - return null + return findFileInAncestors(fileName, parentDir) } - return findFileInAncestors(fileName, parentDir) + const packageJsonFile = findFileInAncestors(file, process.cwd()) + const content = readFileSync(packageJsonFile!, "utf-8") + const parsedContent = JSON.parse(content) + return parsedContent + } + + let localVersion: string | undefined + if (isDev() && !isTest()) { + try { + const lerna = getParentFile("lerna.json") + localVersion = `${lerna.version}+local` + } catch { + // + } } try { - const packageJsonFile = findFileInAncestors("package.json", process.cwd()) - const content = readFileSync(packageJsonFile!, "utf-8") - const parsedContent = JSON.parse(content) + const parsedContent = getParentFile("package.json") return { - VERSION: process.env.BUDIBASE_VERSION || parsedContent.version, + VERSION: + localVersion || process.env.BUDIBASE_VERSION || parsedContent.version, SERVICE_NAME: parsedContent.name, } } catch { diff --git a/packages/backend-core/src/features/features.ts b/packages/backend-core/src/features/features.ts index e95472a784b..e2f8d9b6a15 100644 --- a/packages/backend-core/src/features/features.ts +++ b/packages/backend-core/src/features/features.ts @@ -267,12 +267,13 @@ export class FlagSet, T extends { [key: string]: V }> { // All of the machinery in this file is to make sure that flags have their // default values set correctly and their types flow through the system. export const flags = new FlagSet({ - DEFAULT_VALUES: Flag.boolean(env.isDev()), - AUTOMATION_BRANCHING: Flag.boolean(env.isDev()), - SQS: Flag.boolean(true), + [FeatureFlag.DEFAULT_VALUES]: Flag.boolean(env.isDev()), + [FeatureFlag.AUTOMATION_BRANCHING]: Flag.boolean(env.isDev()), + [FeatureFlag.SQS]: Flag.boolean(true), [FeatureFlag.AI_CUSTOM_CONFIGS]: Flag.boolean(env.isDev()), [FeatureFlag.ENRICHED_RELATIONSHIPS]: Flag.boolean(env.isDev()), [FeatureFlag.TABLES_DEFAULT_ADMIN]: Flag.boolean(env.isDev()), + [FeatureFlag.BUDIBASE_AI]: Flag.boolean(env.isDev()), }) type UnwrapPromise = T extends Promise ? U : T diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts index fa2d114d7dd..c14178cacb1 100644 --- a/packages/backend-core/src/security/roles.ts +++ b/packages/backend-core/src/security/roles.ts @@ -1,3 +1,4 @@ +import semver from "semver" import { BuiltinPermissionID, PermissionLevel } from "./permissions" import { prefixRoleID, @@ -7,9 +8,16 @@ import { doWithDB, } from "../db" import { getAppDB } from "../context" -import { Screen, Role as RoleDoc, RoleUIMetadata } from "@budibase/types" +import { + Screen, + Role as RoleDoc, + RoleUIMetadata, + Database, + App, +} from "@budibase/types" import cloneDeep from "lodash/fp/cloneDeep" -import { RoleColor } from "@budibase/shared-core" +import { RoleColor, helpers } from "@budibase/shared-core" +import { uniqBy } from "lodash" export const BUILTIN_ROLE_IDS = { ADMIN: "ADMIN", @@ -23,14 +31,6 @@ const BUILTIN_IDS = { BUILDER: "BUILDER", } -// exclude internal roles like builder -const EXTERNAL_BUILTIN_ROLE_IDS = [ - BUILTIN_IDS.ADMIN, - BUILTIN_IDS.POWER, - BUILTIN_IDS.BASIC, - BUILTIN_IDS.PUBLIC, -] - export const RoleIDVersion = { // original version, with a UUID based ID UUID: undefined, @@ -38,12 +38,20 @@ export const RoleIDVersion = { NAME: "name", } +function rolesInList(roleIds: string[], ids: string | string[]) { + if (Array.isArray(ids)) { + return ids.filter(id => roleIds.includes(id)).length === ids.length + } else { + return roleIds.includes(ids) + } +} + export class Role implements RoleDoc { _id: string _rev?: string name: string permissionId: string - inherits?: string + inherits?: string | string[] version?: string permissions: Record = {} uiMetadata?: RoleUIMetadata @@ -62,12 +70,70 @@ export class Role implements RoleDoc { this.version = RoleIDVersion.NAME } - addInheritance(inherits: string) { + addInheritance(inherits?: string | string[]) { + // make sure IDs are correct format + if (inherits && typeof inherits === "string") { + inherits = prefixRoleIDNoBuiltin(inherits) + } else if (inherits && Array.isArray(inherits)) { + inherits = inherits.map(prefixRoleIDNoBuiltin) + } this.inherits = inherits return this } } +export class RoleHierarchyTraversal { + allRoles: RoleDoc[] + opts?: { defaultPublic?: boolean } + + constructor(allRoles: RoleDoc[], opts?: { defaultPublic?: boolean }) { + this.allRoles = allRoles + this.opts = opts + } + + walk(role: RoleDoc): RoleDoc[] { + const opts = this.opts, + allRoles = this.allRoles + // this will be a full walked list of roles - which may contain duplicates + let roleList: RoleDoc[] = [] + if (!role || !role._id) { + return roleList + } + roleList.push(role) + if (Array.isArray(role.inherits)) { + for (let roleId of role.inherits) { + const foundRole = findRole(roleId, allRoles, opts) + if (foundRole) { + roleList = roleList.concat(this.walk(foundRole)) + } + } + } else { + const foundRoleIds: string[] = [] + let currentRole: RoleDoc | undefined = role + while ( + currentRole && + currentRole.inherits && + !rolesInList(foundRoleIds, currentRole.inherits) + ) { + if (Array.isArray(currentRole.inherits)) { + return roleList.concat(this.walk(currentRole)) + } else { + foundRoleIds.push(currentRole.inherits) + currentRole = findRole(currentRole.inherits, allRoles, opts) + if (currentRole) { + roleList.push(currentRole) + } + } + // loop now found - stop iterating + if (helpers.roles.checkForRoleInheritanceLoops(roleList)) { + break + } + } + } + return uniqBy(roleList, role => role._id) + } +} + const BUILTIN_ROLES = { ADMIN: new Role( BUILTIN_IDS.ADMIN, @@ -126,7 +192,15 @@ export function getBuiltinRoles(): { [key: string]: RoleDoc } { } export function isBuiltin(role: string) { - return getBuiltinRole(role) !== undefined + return Object.values(BUILTIN_ROLE_IDS).includes(role) +} + +export function prefixRoleIDNoBuiltin(roleId: string) { + if (isBuiltin(roleId)) { + return roleId + } else { + return prefixRoleID(roleId) + } } export function getBuiltinRole(roleId: string): Role | undefined { @@ -154,7 +228,11 @@ export function builtinRoleToNumber(id: string) { if (!role) { break } - role = builtins[role.inherits!] + if (Array.isArray(role.inherits)) { + throw new Error("Built-in roles don't support multi-inheritance") + } else { + role = builtins[role.inherits!] + } count++ } while (role !== null) return count @@ -170,12 +248,31 @@ export async function roleToNumber(id: string) { const hierarchy = (await getUserRoleHierarchy(id, { defaultPublic: true, })) as RoleDoc[] - for (let role of hierarchy) { - if (role?.inherits && isBuiltin(role.inherits)) { + const findNumber = (role: RoleDoc): number => { + if (!role.inherits) { + return 0 + } + if (Array.isArray(role.inherits)) { + // find the built-in roles, get their number, sort it, then get the last one + const highestBuiltin: number | undefined = role.inherits + .map(roleId => { + const foundRole = hierarchy.find(role => role._id === roleId) + if (foundRole) { + return findNumber(foundRole) + 1 + } + }) + .filter(number => number) + .sort() + .pop() + if (highestBuiltin != undefined) { + return highestBuiltin + } + } else if (isBuiltin(role.inherits)) { return builtinRoleToNumber(role.inherits) + 1 } + return 0 } - return 0 + return Math.max(...hierarchy.map(findNumber)) } /** @@ -193,17 +290,31 @@ export function lowerBuiltinRoleID(roleId1?: string, roleId2?: string): string { : roleId1 } +export function compareRoleIds(roleId1: string, roleId2: string) { + // make sure both role IDs are prefixed correctly + return prefixRoleID(roleId1) === prefixRoleID(roleId2) +} + +export function externalRole(role: RoleDoc): RoleDoc { + let _id: string | undefined + if (role._id) { + _id = getExternalRoleID(role._id) + } + return { + ...role, + _id, + inherits: getExternalRoleIDs(role.inherits, role.version), + } +} + /** - * Gets the role object, this is mainly useful for two purposes, to check if the level exists and - * to check if the role inherits any others. - * @param roleId The level ID to lookup. - * @param opts options for the function, like whether to halt errors, instead return public. - * @returns The role object, which may contain an "inherits" property. + * Given a list of roles, this will pick the role out, accounting for built ins. */ -export async function getRole( +export function findRole( roleId: string, + roles: RoleDoc[], opts?: { defaultPublic?: boolean } -): Promise { +): RoleDoc | undefined { // built in roles mostly come from the in-code implementation, // but can be extended by a doc stored about them (e.g. permissions) let role: RoleDoc | undefined = getBuiltinRole(roleId) @@ -211,22 +322,53 @@ export async function getRole( // make sure has the prefix (if it has it then it won't be added) roleId = prefixRoleID(roleId) } - try { - const db = getAppDB() - const dbRole = await db.get(getDBRoleID(roleId)) - role = Object.assign(role || {}, dbRole) - // finalise the ID - role._id = getExternalRoleID(role._id!, role.version) - } catch (err) { - if (!isBuiltin(roleId) && opts?.defaultPublic) { - return cloneDeep(BUILTIN_ROLES.PUBLIC) - } - // only throw an error if there is no role at all - if (!role || Object.keys(role).length === 0) { - throw err + const dbRole = roles.find( + role => role._id && compareRoleIds(role._id, roleId) + ) + if (!dbRole && !isBuiltin(roleId) && opts?.defaultPublic) { + return cloneDeep(BUILTIN_ROLES.PUBLIC) + } + // combine the roles + role = Object.assign(role || {}, dbRole) + // finalise the ID + if (role?._id) { + role._id = getExternalRoleID(role._id, role.version) + } + return Object.keys(role).length === 0 ? undefined : role +} + +/** + * Gets the role object, this is mainly useful for two purposes, to check if the level exists and + * to check if the role inherits any others. + * @param roleId The level ID to lookup. + * @param opts options for the function, like whether to halt errors, instead return public. + * @returns The role object, which may contain an "inherits" property. + */ +export async function getRole( + roleId: string, + opts?: { defaultPublic?: boolean } +): Promise { + const db = getAppDB() + const roleList = [] + if (!isBuiltin(roleId)) { + const role = await db.tryGet(getDBRoleID(roleId)) + if (role) { + roleList.push(role) } } - return role + return findRole(roleId, roleList, opts) +} + +export async function saveRoles(roles: RoleDoc[]) { + const db = getAppDB() + await db.bulkDocs( + roles + .filter(role => role._id) + .map(role => ({ + ...role, + _id: prefixRoleID(role._id!), + })) + ) } /** @@ -236,24 +378,18 @@ async function getAllUserRoles( userRoleId: string, opts?: { defaultPublic?: boolean } ): Promise { + const allRoles = await getAllRoles() // admins have access to all roles if (userRoleId === BUILTIN_IDS.ADMIN) { - return getAllRoles() + return allRoles } - let currentRole = await getRole(userRoleId, opts) - let roles = currentRole ? [currentRole] : [] - let roleIds = [userRoleId] + // get all the inherited roles - while ( - currentRole && - currentRole.inherits && - roleIds.indexOf(currentRole.inherits) === -1 - ) { - roleIds.push(currentRole.inherits) - currentRole = await getRole(currentRole.inherits) - if (currentRole) { - roles.push(currentRole) - } + const foundRole = findRole(userRoleId, allRoles, opts) + let roles: RoleDoc[] = [] + if (foundRole) { + const traversal = new RoleHierarchyTraversal(allRoles, opts) + roles = traversal.walk(foundRole) } return roles } @@ -319,7 +455,7 @@ export async function getAllRoles(appId?: string): Promise { } return internal(appDB) } - async function internal(db: any) { + async function internal(db: Database | undefined) { let roles: RoleDoc[] = [] if (db) { const body = await db.allDocs( @@ -334,8 +470,26 @@ export async function getAllRoles(appId?: string): Promise { } const builtinRoles = getBuiltinRoles() + // exclude internal roles like builder + let externalBuiltinRoles = [] + + if (!db || (await shouldIncludePowerRole(db))) { + externalBuiltinRoles = [ + BUILTIN_IDS.ADMIN, + BUILTIN_IDS.POWER, + BUILTIN_IDS.BASIC, + BUILTIN_IDS.PUBLIC, + ] + } else { + externalBuiltinRoles = [ + BUILTIN_IDS.ADMIN, + BUILTIN_IDS.BASIC, + BUILTIN_IDS.PUBLIC, + ] + } + // need to combine builtin with any DB record of them (for sake of permissions) - for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) { + for (let builtinRoleId of externalBuiltinRoles) { const builtinRole = builtinRoles[builtinRoleId] const dbBuiltin = roles.filter( dbRole => @@ -366,6 +520,18 @@ export async function getAllRoles(appId?: string): Promise { } } +async function shouldIncludePowerRole(db: Database) { + const app = await db.tryGet(DocumentType.APP_METADATA) + const creationVersion = app?.creationVersion + if (!creationVersion || !semver.valid(creationVersion)) { + // Old apps don't have creationVersion, so we should include it for backward compatibility + return true + } + + const isGreaterThan3x = semver.gte(creationVersion, "3.0.0") + return !isGreaterThan3x +} + export class AccessController { userHierarchies: { [key: string]: string[] } constructor() { @@ -390,7 +556,10 @@ export class AccessController { this.userHierarchies[userRoleId] = roleIds } - return roleIds?.indexOf(tryingRoleId) !== -1 + return ( + roleIds?.find(roleId => compareRoleIds(roleId, tryingRoleId)) !== + undefined + ) } async checkScreensAccess(screens: Screen[], userRoleId: string) { @@ -432,7 +601,7 @@ export function getDBRoleID(roleName: string) { export function getExternalRoleID(roleId: string, version?: string) { // for built-in roles we want to remove the DB role ID element (role_) if ( - roleId.startsWith(DocumentType.ROLE) && + roleId.startsWith(`${DocumentType.ROLE}${SEPARATOR}`) && (isBuiltin(roleId) || version === RoleIDVersion.NAME) ) { const parts = roleId.split(SEPARATOR) @@ -441,3 +610,16 @@ export function getExternalRoleID(roleId: string, version?: string) { } return roleId } + +export function getExternalRoleIDs( + roleIds: string | string[] | undefined, + version?: string +) { + if (!roleIds) { + return roleIds + } else if (typeof roleIds === "string") { + return getExternalRoleID(roleIds, version) + } else { + return roleIds.map(roleId => getExternalRoleID(roleId, version)) + } +} diff --git a/packages/backend-core/src/sql/sql.ts b/packages/backend-core/src/sql/sql.ts index b415a6f1b7b..949d7edf1bb 100644 --- a/packages/backend-core/src/sql/sql.ts +++ b/packages/backend-core/src/sql/sql.ts @@ -23,12 +23,14 @@ import { InternalSearchFilterOperator, JsonFieldMetadata, JsonTypes, + LogicalOperator, Operation, prefixed, QueryJson, QueryOptions, RangeOperator, RelationshipsJson, + SearchFilterKey, SearchFilters, SortOrder, SqlClient, @@ -96,6 +98,22 @@ function isSqs(table: Table): boolean { ) } +const allowEmptyRelationships: Record = { + [BasicOperator.EQUAL]: false, + [BasicOperator.NOT_EQUAL]: true, + [BasicOperator.EMPTY]: false, + [BasicOperator.NOT_EMPTY]: true, + [BasicOperator.FUZZY]: false, + [BasicOperator.STRING]: false, + [RangeOperator.RANGE]: false, + [ArrayOperator.CONTAINS]: false, + [ArrayOperator.NOT_CONTAINS]: true, + [ArrayOperator.CONTAINS_ANY]: false, + [ArrayOperator.ONE_OF]: false, + [LogicalOperator.AND]: false, + [LogicalOperator.OR]: false, +} + class InternalBuilder { private readonly client: SqlClient private readonly query: QueryJson @@ -405,31 +423,48 @@ class InternalBuilder { addRelationshipForFilter( query: Knex.QueryBuilder, + allowEmptyRelationships: boolean, filterKey: string, - whereCb: (query: Knex.QueryBuilder) => Knex.QueryBuilder + whereCb: (filterKey: string, query: Knex.QueryBuilder) => Knex.QueryBuilder ): Knex.QueryBuilder { const mainKnex = this.knex const { relationships, endpoint, tableAliases: aliases } = this.query const tableName = endpoint.entityId const fromAlias = aliases?.[tableName] || tableName - const matches = (possibleTable: string) => - filterKey.startsWith(`${possibleTable}`) + const matches = (value: string) => + filterKey.match(new RegExp(`^${value}\\.`)) if (!relationships) { return query } for (const relationship of relationships) { const relatedTableName = relationship.tableName const toAlias = aliases?.[relatedTableName] || relatedTableName + + const matchesTableName = matches(relatedTableName) || matches(toAlias) + const matchesRelationName = matches(relationship.column) + // this is the relationship which is being filtered if ( - (matches(relatedTableName) || matches(toAlias)) && + (matchesTableName || matchesRelationName) && relationship.to && relationship.tableName ) { - let subQuery = mainKnex + const joinTable = mainKnex .select(mainKnex.raw(1)) .from({ [toAlias]: relatedTableName }) + let subQuery = joinTable.clone() const manyToMany = validateManyToMany(relationship) + let updatedKey + + if (!matchesTableName) { + updatedKey = filterKey.replace( + new RegExp(`^${relationship.column}.`), + `${aliases![relationship.tableName]}.` + ) + } else { + updatedKey = filterKey + } + if (manyToMany) { const throughAlias = aliases?.[manyToMany.through] || relationship.through @@ -440,7 +475,6 @@ class InternalBuilder { subQuery = subQuery // add a join through the junction table .innerJoin(throughTable, function () { - // @ts-ignore this.on( `${toAlias}.${manyToMany.toPrimary}`, "=", @@ -460,18 +494,38 @@ class InternalBuilder { if (this.client === SqlClient.SQL_LITE) { subQuery = this.addJoinFieldCheck(subQuery, manyToMany) } + + query = query.where(q => { + q.whereExists(whereCb(updatedKey, subQuery)) + if (allowEmptyRelationships) { + q.orWhereNotExists( + joinTable.clone().innerJoin(throughTable, function () { + this.on( + `${fromAlias}.${manyToMany.fromPrimary}`, + "=", + `${throughAlias}.${manyToMany.from}` + ) + }) + ) + } + }) } else { + const toKey = `${toAlias}.${relationship.to}` + const foreignKey = `${fromAlias}.${relationship.from}` // "join" to the main table, making sure the ID matches that of the main subQuery = subQuery.where( - `${toAlias}.${relationship.to}`, + toKey, "=", - mainKnex.raw( - this.quotedIdentifier(`${fromAlias}.${relationship.from}`) - ) + mainKnex.raw(this.quotedIdentifier(foreignKey)) ) + + query = query.where(q => { + q.whereExists(whereCb(updatedKey, subQuery.clone())) + if (allowEmptyRelationships) { + q.orWhereNotExists(subQuery) + } + }) } - query = query.whereExists(whereCb(subQuery)) - break } } return query @@ -502,6 +556,7 @@ class InternalBuilder { } function iterate( structure: AnySearchFilter, + operation: SearchFilterKey, fn: ( query: Knex.QueryBuilder, key: string, @@ -558,9 +613,14 @@ class InternalBuilder { if (allOr) { query = query.or } - query = builder.addRelationshipForFilter(query, updatedKey, q => { - return handleRelationship(q, updatedKey, value) - }) + query = builder.addRelationshipForFilter( + query, + allowEmptyRelationships[operation], + updatedKey, + (updatedKey, q) => { + return handleRelationship(q, updatedKey, value) + } + ) } } } @@ -592,7 +652,7 @@ class InternalBuilder { return `[${value.join(",")}]` } if (this.client === SqlClient.POSTGRES) { - iterate(mode, (q, key, value) => { + iterate(mode, ArrayOperator.CONTAINS, (q, key, value) => { const wrap = any ? "" : "'" const op = any ? "\\?| array" : "@>" const fieldNames = key.split(/\./g) @@ -610,7 +670,7 @@ class InternalBuilder { this.client === SqlClient.MARIADB ) { const jsonFnc = any ? "JSON_OVERLAPS" : "JSON_CONTAINS" - iterate(mode, (q, key, value) => { + iterate(mode, ArrayOperator.CONTAINS, (q, key, value) => { return q[rawFnc]( `${not}COALESCE(${jsonFnc}(${key}, '${stringifyArray( value @@ -619,7 +679,7 @@ class InternalBuilder { }) } else { const andOr = mode === filters?.containsAny ? " OR " : " AND " - iterate(mode, (q, key, value) => { + iterate(mode, ArrayOperator.CONTAINS, (q, key, value) => { let statement = "" const identifier = this.quotedIdentifier(key) for (let i in value) { @@ -673,6 +733,7 @@ class InternalBuilder { const fnc = allOr ? "orWhereIn" : "whereIn" iterate( filters.oneOf, + ArrayOperator.ONE_OF, (q, key: string, array) => { if (this.client === SqlClient.ORACLE) { key = this.convertClobs(key) @@ -697,7 +758,7 @@ class InternalBuilder { ) } if (filters.string) { - iterate(filters.string, (q, key, value) => { + iterate(filters.string, BasicOperator.STRING, (q, key, value) => { const fnc = allOr ? "orWhere" : "where" // postgres supports ilike, nothing else does if (this.client === SqlClient.POSTGRES) { @@ -712,10 +773,10 @@ class InternalBuilder { }) } if (filters.fuzzy) { - iterate(filters.fuzzy, like) + iterate(filters.fuzzy, BasicOperator.FUZZY, like) } if (filters.range) { - iterate(filters.range, (q, key, value) => { + iterate(filters.range, RangeOperator.RANGE, (q, key, value) => { const isEmptyObject = (val: any) => { return ( val && @@ -781,7 +842,7 @@ class InternalBuilder { }) } if (filters.equal) { - iterate(filters.equal, (q, key, value) => { + iterate(filters.equal, BasicOperator.EQUAL, (q, key, value) => { const fnc = allOr ? "orWhereRaw" : "whereRaw" if (this.client === SqlClient.MS_SQL) { return q[fnc]( @@ -801,7 +862,7 @@ class InternalBuilder { }) } if (filters.notEqual) { - iterate(filters.notEqual, (q, key, value) => { + iterate(filters.notEqual, BasicOperator.NOT_EQUAL, (q, key, value) => { const fnc = allOr ? "orWhereRaw" : "whereRaw" if (this.client === SqlClient.MS_SQL) { return q[fnc]( @@ -822,13 +883,13 @@ class InternalBuilder { }) } if (filters.empty) { - iterate(filters.empty, (q, key) => { + iterate(filters.empty, BasicOperator.EMPTY, (q, key) => { const fnc = allOr ? "orWhereNull" : "whereNull" return q[fnc](key) }) } if (filters.notEmpty) { - iterate(filters.notEmpty, (q, key) => { + iterate(filters.notEmpty, BasicOperator.NOT_EMPTY, (q, key) => { const fnc = allOr ? "orWhereNotNull" : "whereNotNull" return q[fnc](key) }) @@ -1224,12 +1285,10 @@ class InternalBuilder { }) : undefined if (!throughTable) { - // @ts-ignore query = query.leftJoin(toTableWithSchema, function () { for (let relationship of columns) { const from = relationship.from, to = relationship.to - // @ts-ignore this.orOn(`${fromAlias}.${from}`, "=", `${toAlias}.${to}`) } }) @@ -1240,7 +1299,6 @@ class InternalBuilder { for (let relationship of columns) { const fromPrimary = relationship.fromPrimary const from = relationship.from - // @ts-ignore this.orOn( `${fromAlias}.${fromPrimary}`, "=", @@ -1252,7 +1310,6 @@ class InternalBuilder { for (let relationship of columns) { const toPrimary = relationship.toPrimary const to = relationship.to - // @ts-ignore this.orOn(`${toAlias}.${toPrimary}`, `${throughAlias}.${to}`) } }) diff --git a/packages/backend-core/src/sql/sqlTable.ts b/packages/backend-core/src/sql/sqlTable.ts index f5b02cc4e4b..84f4e290aa5 100644 --- a/packages/backend-core/src/sql/sqlTable.ts +++ b/packages/backend-core/src/sql/sqlTable.ts @@ -17,7 +17,7 @@ import SchemaBuilder = Knex.SchemaBuilder import CreateTableBuilder = Knex.CreateTableBuilder function isIgnoredType(type: FieldType) { - const ignored = [FieldType.LINK, FieldType.FORMULA] + const ignored = [FieldType.LINK, FieldType.FORMULA, FieldType.AI] return ignored.indexOf(type) !== -1 } @@ -144,6 +144,9 @@ function generateSchema( case FieldType.FORMULA: // This is allowed, but nothing to do on the external datasource break + case FieldType.AI: + // This is allowed, but nothing to do on the external datasource + break case FieldType.ATTACHMENTS: case FieldType.ATTACHMENT_SINGLE: case FieldType.SIGNATURE_SINGLE: diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte index f656a1ecb93..21cdc4b893e 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte @@ -19,6 +19,7 @@ AutomationEventType.ROW_DELETE, AutomationEventType.ROW_UPDATE, AutomationEventType.ROW_SAVE, + AutomationEventType.ROW_ACTION, ] /** diff --git a/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte b/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte index 365d3d358fb..35fdba970a4 100644 --- a/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte +++ b/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte @@ -1,4 +1,5 @@