Merge branch 'master' of github.com:budibase/budibase into sqs-auto-tests

This commit is contained in:
Sam Rose 2024-04-26 15:07:45 +01:00
commit 951b7d3e59
No known key found for this signature in database
32 changed files with 214 additions and 138 deletions

View File

@ -92,8 +92,6 @@ jobs:
test-libraries: test-libraries:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
REUSE_CONTAINERS: true
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -150,8 +148,6 @@ jobs:
test-server: test-server:
runs-on: budi-tubby-tornado-quad-core-150gb runs-on: budi-tubby-tornado-quad-core-150gb
env:
REUSE_CONTAINERS: true
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v4

View File

@ -1,16 +1,49 @@
import { GenericContainer, Wait } from "testcontainers" import {
GenericContainer,
Wait,
getContainerRuntimeClient,
} from "testcontainers"
import { ContainerInfo } from "dockerode"
import path from "path" import path from "path"
import lockfile from "proper-lockfile" import lockfile from "proper-lockfile"
async function getBudibaseContainers() {
const client = await getContainerRuntimeClient()
const conatiners = await client.container.list()
return conatiners.filter(
container =>
container.Labels["com.budibase"] === "true" &&
container.Labels["org.testcontainers"] === "true"
)
}
async function killContainers(containers: ContainerInfo[]) {
const client = await getContainerRuntimeClient()
for (const container of containers) {
const c = client.container.getById(container.Id)
await c.kill()
await c.remove()
}
}
export default async function setup() { export default async function setup() {
const lockPath = path.resolve(__dirname, "globalSetup.ts") const lockPath = path.resolve(__dirname, "globalSetup.ts")
if (process.env.REUSE_CONTAINERS) { // If you run multiple tests at the same time, it's possible for the CouchDB
// If you run multiple tests at the same time, it's possible for the CouchDB // shared container to get started multiple times despite having an
// shared container to get started multiple times despite having an // identical reuse hash. To avoid that, we do a filesystem-based lock so
// identical reuse hash. To avoid that, we do a filesystem-based lock so // that only one globalSetup.ts is running at a time.
// that only one globalSetup.ts is running at a time. lockfile.lockSync(lockPath)
lockfile.lockSync(lockPath)
} // Remove any containers that are older than 24 hours. This is to prevent
// containers getting full volumes or accruing any other problems from being
// left up for very long periods of time.
const threshold = new Date(Date.now() - 1000 * 60 * 60 * 24)
const containers = (await getBudibaseContainers()).filter(container => {
const created = new Date(container.Created * 1000)
return created < threshold
})
await killContainers(containers)
try { try {
let couchdb = new GenericContainer("budibase/couchdb:v3.2.1-sqs") let couchdb = new GenericContainer("budibase/couchdb:v3.2.1-sqs")
@ -28,20 +61,16 @@ export default async function setup() {
target: "/opt/couchdb/etc/local.d/test-couchdb.ini", target: "/opt/couchdb/etc/local.d/test-couchdb.ini",
}, },
]) ])
.withLabels({ "com.budibase": "true" })
.withReuse()
.withWaitStrategy( .withWaitStrategy(
Wait.forSuccessfulCommand( Wait.forSuccessfulCommand(
"curl http://budibase:budibase@localhost:5984/_up" "curl http://budibase:budibase@localhost:5984/_up"
).withStartupTimeout(20000) ).withStartupTimeout(20000)
) )
if (process.env.REUSE_CONTAINERS) {
couchdb = couchdb.withReuse()
}
await couchdb.start() await couchdb.start()
} finally { } finally {
if (process.env.REUSE_CONTAINERS) { lockfile.unlockSync(lockPath)
lockfile.unlockSync(lockPath)
}
} }
} }

View File

@ -59,7 +59,8 @@
"dev:all": "yarn run kill-all && lerna run --stream dev", "dev:all": "yarn run kill-all && lerna run --stream dev",
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream dev:built", "dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream dev:built",
"dev:docker": "./scripts/devDocker.sh", "dev:docker": "./scripts/devDocker.sh",
"test": "REUSE_CONTAINERS=1 lerna run --concurrency 1 --stream test --stream", "test": "lerna run --concurrency 1 --stream test --stream",
"test:containers:kill": "./scripts/killTestcontainers.sh",
"lint:eslint": "eslint packages --max-warnings=0", "lint:eslint": "eslint packages --max-warnings=0",
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\"", "lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\"",
"lint": "yarn run lint:eslint && yarn run lint:prettier", "lint": "yarn run lint:eslint && yarn run lint:prettier",

View File

@ -28,7 +28,11 @@ function getTestcontainers(): ContainerInfo[] {
.split("\n") .split("\n")
.filter(x => x.length > 0) .filter(x => x.length > 0)
.map(x => JSON.parse(x) as ContainerInfo) .map(x => JSON.parse(x) as ContainerInfo)
.filter(x => x.Labels.includes("org.testcontainers=true")) .filter(
x =>
x.Labels.includes("org.testcontainers=true") &&
x.Labels.includes("com.budibase=true")
)
} }
export function getContainerByImage(image: string) { export function getContainerByImage(image: string) {

View File

@ -29,7 +29,11 @@
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte" import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
import { getBindings } from "components/backend/DataTable/formula" import { getBindings } from "components/backend/DataTable/formula"
import JSONSchemaModal from "./JSONSchemaModal.svelte" import JSONSchemaModal from "./JSONSchemaModal.svelte"
import { FieldType, FieldSubtype, SourceName } from "@budibase/types" import {
FieldType,
BBReferenceFieldSubType,
SourceName,
} from "@budibase/types"
import RelationshipSelector from "components/common/RelationshipSelector.svelte" import RelationshipSelector from "components/common/RelationshipSelector.svelte"
import { RowUtils } from "@budibase/frontend-core" import { RowUtils } from "@budibase/frontend-core"
import ServerBindingPanel from "components/common/bindings/ServerBindingPanel.svelte" import ServerBindingPanel from "components/common/bindings/ServerBindingPanel.svelte"
@ -41,8 +45,6 @@
const NUMBER_TYPE = FieldType.NUMBER const NUMBER_TYPE = FieldType.NUMBER
const JSON_TYPE = FieldType.JSON const JSON_TYPE = FieldType.JSON
const DATE_TYPE = FieldType.DATETIME const DATE_TYPE = FieldType.DATETIME
const USER_TYPE = FieldSubtype.USER
const USERS_TYPE = FieldSubtype.USERS
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"] const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
@ -263,9 +265,9 @@
delete saveColumn.fieldName delete saveColumn.fieldName
} }
if (isUsersColumn(saveColumn)) { if (isUsersColumn(saveColumn)) {
if (saveColumn.subtype === USER_TYPE) { if (saveColumn.subtype === BBReferenceFieldSubType.USER) {
saveColumn.relationshipType = RelationshipType.ONE_TO_MANY saveColumn.relationshipType = RelationshipType.ONE_TO_MANY
} else if (saveColumn.subtype === USERS_TYPE) { } else if (saveColumn.subtype === BBReferenceFieldSubType.USERS) {
saveColumn.relationshipType = RelationshipType.MANY_TO_MANY saveColumn.relationshipType = RelationshipType.MANY_TO_MANY
} }
} }
@ -375,7 +377,7 @@
const isUsers = const isUsers =
editableColumn.type === FieldType.BB_REFERENCE && editableColumn.type === FieldType.BB_REFERENCE &&
editableColumn.subtype === FieldSubtype.USERS editableColumn.subtype === BBReferenceFieldSubType.USERS
if (!externalTable) { if (!externalTable) {
return [ return [
@ -485,7 +487,9 @@
function isUsersColumn(column) { function isUsersColumn(column) {
return ( return (
column.type === FieldType.BB_REFERENCE && column.type === FieldType.BB_REFERENCE &&
[FieldSubtype.USER, FieldSubtype.USERS].includes(column.subtype) [BBReferenceFieldSubType.USER, BBReferenceFieldSubType.USERS].includes(
column.subtype
)
) )
} }
@ -688,12 +692,14 @@
> >
{:else if isUsersColumn(editableColumn) && datasource?.source !== SourceName.GOOGLE_SHEETS} {:else if isUsersColumn(editableColumn) && datasource?.source !== SourceName.GOOGLE_SHEETS}
<Toggle <Toggle
value={editableColumn.subtype === FieldSubtype.USERS} value={editableColumn.subtype === BBReferenceFieldSubType.USERS}
on:change={e => on:change={e =>
handleTypeChange( handleTypeChange(
makeFieldId( makeFieldId(
FieldType.BB_REFERENCE, FieldType.BB_REFERENCE,
e.detail ? FieldSubtype.USERS : FieldSubtype.USER e.detail
? BBReferenceFieldSubType.USERS
: BBReferenceFieldSubType.USER
) )
)} )}
disabled={!isCreating} disabled={!isCreating}

View File

@ -1,5 +1,5 @@
<script> <script>
import { FieldType, FieldSubtype } from "@budibase/types" import { FieldType, BBReferenceFieldSubType } from "@budibase/types"
import { Select, Toggle, Multiselect } from "@budibase/bbui" import { Select, Toggle, Multiselect } from "@budibase/bbui"
import { DB_TYPE_INTERNAL } from "constants/backend" import { DB_TYPE_INTERNAL } from "constants/backend"
import { API } from "api" import { API } from "api"
@ -60,11 +60,11 @@
}, },
{ {
label: "User", label: "User",
value: `${FieldType.BB_REFERENCE}${FieldSubtype.USER}`, value: `${FieldType.BB_REFERENCE}${BBReferenceFieldSubType.USER}`,
}, },
{ {
label: "Users", label: "Users",
value: `${FieldType.BB_REFERENCE}${FieldSubtype.USERS}`, value: `${FieldType.BB_REFERENCE}${BBReferenceFieldSubType.USERS}`,
}, },
] ]

View File

@ -1,6 +1,6 @@
import { import {
FieldType, FieldType,
FieldSubtype, BBReferenceFieldSubType,
INTERNAL_TABLE_SOURCE_ID, INTERNAL_TABLE_SOURCE_ID,
AutoFieldSubType, AutoFieldSubType,
Hosting, Hosting,
@ -160,13 +160,13 @@ export const FIELDS = {
USER: { USER: {
name: "User", name: "User",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USER, subtype: BBReferenceFieldSubType.USER,
icon: TypeIconMap[FieldType.USER], icon: TypeIconMap[FieldType.USER],
}, },
USERS: { USERS: {
name: "Users", name: "Users",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS, subtype: BBReferenceFieldSubType.USERS,
icon: TypeIconMap[FieldType.USERS], icon: TypeIconMap[FieldType.USERS],
constraints: { constraints: {
type: "array", type: "array",

View File

@ -1,7 +1,7 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import RelationshipCell from "./RelationshipCell.svelte" import RelationshipCell from "./RelationshipCell.svelte"
import { FieldSubtype, RelationshipType } from "@budibase/types" import { BBReferenceFieldSubType, RelationshipType } from "@budibase/types"
export let api export let api
@ -13,13 +13,16 @@
// This is not really used, just adding some content to be able to render the relationship cell // This is not really used, just adding some content to be able to render the relationship cell
tableId: "external", tableId: "external",
relationshipType: relationshipType:
subtype === FieldSubtype.USER subtype === BBReferenceFieldSubType.USER
? RelationshipType.ONE_TO_MANY ? RelationshipType.ONE_TO_MANY
: RelationshipType.MANY_TO_MANY, : RelationshipType.MANY_TO_MANY,
} }
async function searchFunction(searchParams) { async function searchFunction(searchParams) {
if (subtype !== FieldSubtype.USER && subtype !== FieldSubtype.USERS) { if (
subtype !== BBReferenceFieldSubType.USER &&
subtype !== BBReferenceFieldSubType.USERS
) {
throw `Search for '${subtype}' not implemented` throw `Search for '${subtype}' not implemented`
} }

View File

@ -7,7 +7,11 @@
} from "@budibase/bbui" } from "@budibase/bbui"
import { getContext } from "svelte" import { getContext } from "svelte"
import { ValidColumnNameRegex } from "@budibase/shared-core" import { ValidColumnNameRegex } from "@budibase/shared-core"
import { FieldSubtype, FieldType, RelationshipType } from "@budibase/types" import {
BBReferenceFieldSubType,
FieldType,
RelationshipType,
} from "@budibase/types"
const { API, definition, rows } = getContext("grid") const { API, definition, rows } = getContext("grid")
@ -29,9 +33,9 @@
} }
const migrateUserColumn = async () => { const migrateUserColumn = async () => {
let subtype = FieldSubtype.USERS let subtype = BBReferenceFieldSubType.USERS
if (column.schema.relationshipType === RelationshipType.ONE_TO_MANY) { if (column.schema.relationshipType === RelationshipType.ONE_TO_MANY) {
subtype = FieldSubtype.USER subtype = BBReferenceFieldSubType.USER
} }
try { try {

View File

@ -4,7 +4,7 @@
export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core" export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core"
export { Feature as Features } from "@budibase/types" export { Feature as Features } from "@budibase/types"
import { BpmCorrelationKey } from "@budibase/shared-core" import { BpmCorrelationKey } from "@budibase/shared-core"
import { FieldType, FieldTypeSubtypes } from "@budibase/types" import { FieldType, BBReferenceFieldSubType } from "@budibase/types"
// Cookie names // Cookie names
export const Cookies = { export const Cookies = {
@ -134,7 +134,7 @@ export const TypeIconMap = {
[FieldType.USER]: "User", [FieldType.USER]: "User",
[FieldType.USERS]: "UserGroup", [FieldType.USERS]: "UserGroup",
[FieldType.BB_REFERENCE]: { [FieldType.BB_REFERENCE]: {
[FieldTypeSubtypes.BB_REFERENCE.USER]: "User", [BBReferenceFieldSubType.USER]: "User",
[FieldTypeSubtypes.BB_REFERENCE.USERS]: "UserGroup", [BBReferenceFieldSubType.USERS]: "UserGroup",
}, },
} }

@ -1 +1 @@
Subproject commit dff7b5a9dd1fd770f8a48fb8e6df1740be605f18 Subproject commit 479879246aac5dd3073cc695945c62c41fae5b0e

View File

@ -9,7 +9,7 @@ import { mocks } from "@budibase/backend-core/tests"
import { import {
Datasource, Datasource,
FieldSchema, FieldSchema,
FieldSubtype, BBReferenceFieldSubType,
FieldType, FieldType,
QueryPreview, QueryPreview,
RelationshipType, RelationshipType,
@ -337,7 +337,7 @@ describe("/datasources", () => {
[FieldType.BB_REFERENCE]: { [FieldType.BB_REFERENCE]: {
name: "bb_reference", name: "bb_reference",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS, subtype: BBReferenceFieldSubType.USERS,
}, },
} }

View File

@ -13,7 +13,7 @@ import {
DeleteRow, DeleteRow,
FieldSchema, FieldSchema,
FieldType, FieldType,
FieldTypeSubtypes, BBReferenceFieldSubType,
FormulaType, FormulaType,
INTERNAL_TABLE_SOURCE_ID, INTERNAL_TABLE_SOURCE_ID,
NumberFieldMetadata, NumberFieldMetadata,
@ -1015,12 +1015,12 @@ describe.each([
user: { user: {
name: "user", name: "user",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldTypeSubtypes.BB_REFERENCE.USER, subtype: BBReferenceFieldSubType.USER,
}, },
users: { users: {
name: "users", name: "users",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldTypeSubtypes.BB_REFERENCE.USERS, subtype: BBReferenceFieldSubType.USERS,
}, },
}), }),
() => config.createUser(), () => config.createUser(),

View File

@ -2,7 +2,7 @@ import { context, events } from "@budibase/backend-core"
import { import {
AutoFieldSubType, AutoFieldSubType,
Datasource, Datasource,
FieldSubtype, BBReferenceFieldSubType,
FieldType, FieldType,
INTERNAL_TABLE_SOURCE_ID, INTERNAL_TABLE_SOURCE_ID,
InternalTable, InternalTable,
@ -497,7 +497,7 @@ describe.each([
newColumn: { newColumn: {
name: "user column", name: "user column",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USER, subtype: BBReferenceFieldSubType.USER,
}, },
}) })
@ -562,7 +562,7 @@ describe.each([
newColumn: { newColumn: {
name: "user column", name: "user column",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS, subtype: BBReferenceFieldSubType.USERS,
}, },
}) })
@ -614,7 +614,7 @@ describe.each([
newColumn: { newColumn: {
name: "user column", name: "user column",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS, subtype: BBReferenceFieldSubType.USERS,
}, },
}) })
@ -669,7 +669,7 @@ describe.each([
newColumn: { newColumn: {
name: "user column", name: "user column",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS, subtype: BBReferenceFieldSubType.USERS,
}, },
}) })
@ -728,7 +728,7 @@ describe.each([
newColumn: { newColumn: {
name: "", name: "",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS, subtype: BBReferenceFieldSubType.USERS,
}, },
}, },
{ status: 400 } { status: 400 }
@ -743,7 +743,7 @@ describe.each([
newColumn: { newColumn: {
name: "_id", name: "_id",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS, subtype: BBReferenceFieldSubType.USERS,
}, },
}, },
{ status: 400 } { status: 400 }
@ -758,7 +758,7 @@ describe.each([
newColumn: { newColumn: {
name: "num", name: "num",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS, subtype: BBReferenceFieldSubType.USERS,
}, },
}, },
{ status: 400 } { status: 400 }
@ -772,12 +772,12 @@ describe.each([
oldColumn: { oldColumn: {
name: "not a column", name: "not a column",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS, subtype: BBReferenceFieldSubType.USERS,
}, },
newColumn: { newColumn: {
name: "new column", name: "new column",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldSubtype.USERS, subtype: BBReferenceFieldSubType.USERS,
}, },
}, },
{ status: 400 } { status: 400 }

View File

@ -12,7 +12,7 @@ import SqlTableQueryBuilder from "./sqlTable"
import { import {
BBReferenceFieldMetadata, BBReferenceFieldMetadata,
FieldSchema, FieldSchema,
FieldSubtype, BBReferenceFieldSubType,
FieldType, FieldType,
JsonFieldMetadata, JsonFieldMetadata,
Operation, Operation,
@ -767,7 +767,7 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
return ( return (
field.type === FieldType.JSON || field.type === FieldType.JSON ||
(field.type === FieldType.BB_REFERENCE && (field.type === FieldType.BB_REFERENCE &&
field.subtype === FieldSubtype.USERS) field.subtype === BBReferenceFieldSubType.USERS)
) )
} }

View File

@ -1,6 +1,6 @@
import { Knex, knex } from "knex" import { Knex, knex } from "knex"
import { import {
FieldSubtype, BBReferenceFieldSubType,
FieldType, FieldType,
NumberFieldMetadata, NumberFieldMetadata,
Operation, Operation,
@ -64,10 +64,10 @@ function generateSchema(
case FieldType.BB_REFERENCE: { case FieldType.BB_REFERENCE: {
const subtype = column.subtype const subtype = column.subtype
switch (subtype) { switch (subtype) {
case FieldSubtype.USER: case BBReferenceFieldSubType.USER:
schema.text(key) schema.text(key)
break break
case FieldSubtype.USERS: case BBReferenceFieldSubType.USERS:
schema.json(key) schema.json(key)
break break
default: default:

View File

@ -65,9 +65,7 @@ export async function rawQuery(ds: Datasource, sql: string): Promise<any> {
} }
export async function startContainer(container: GenericContainer) { export async function startContainer(container: GenericContainer) {
if (process.env.REUSE_CONTAINERS) { container = container.withReuse().withLabels({ "com.budibase": "true" })
container = container.withReuse()
}
const startedContainer = await container.start() const startedContainer = await container.start()

View File

@ -2,7 +2,7 @@ import { searchInputMapping } from "../utils"
import { db as dbCore } from "@budibase/backend-core" import { db as dbCore } from "@budibase/backend-core"
import { import {
FieldType, FieldType,
FieldTypeSubtypes, BBReferenceFieldSubType,
INTERNAL_TABLE_SOURCE_ID, INTERNAL_TABLE_SOURCE_ID,
RowSearchParams, RowSearchParams,
Table, Table,
@ -20,7 +20,7 @@ const tableWithUserCol: Table = {
user: { user: {
name: "user", name: "user",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldTypeSubtypes.BB_REFERENCE.USER, subtype: BBReferenceFieldSubType.USER,
}, },
}, },
} }
@ -35,7 +35,7 @@ const tableWithUsersCol: Table = {
user: { user: {
name: "user", name: "user",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldTypeSubtypes.BB_REFERENCE.USERS, subtype: BBReferenceFieldSubType.USERS,
}, },
}, },
} }

View File

@ -3,7 +3,7 @@ import {
Table, Table,
DocumentType, DocumentType,
SEPARATOR, SEPARATOR,
FieldSubtype, BBReferenceFieldSubType,
SearchFilters, SearchFilters,
SearchIndex, SearchIndex,
SearchResponse, SearchResponse,
@ -89,8 +89,8 @@ export function searchInputMapping(table: Table, options: RowSearchParams) {
case FieldType.BB_REFERENCE: { case FieldType.BB_REFERENCE: {
const subtype = column.subtype const subtype = column.subtype
switch (subtype) { switch (subtype) {
case FieldSubtype.USER: case BBReferenceFieldSubType.USER:
case FieldSubtype.USERS: case BBReferenceFieldSubType.USERS:
userColumnMapping(key, options) userColumnMapping(key, options)
break break
default: default:

View File

@ -2,7 +2,7 @@ import { BadRequestError, context, db as dbCore } from "@budibase/backend-core"
import { import {
BBReferenceFieldMetadata, BBReferenceFieldMetadata,
FieldSchema, FieldSchema,
FieldSubtype, BBReferenceFieldSubType,
InternalTable, InternalTable,
isBBReferenceField, isBBReferenceField,
isRelationshipField, isRelationshipField,
@ -96,7 +96,7 @@ function getColumnMigrator(
} }
if (oldColumn.relationshipType === RelationshipType.ONE_TO_MANY) { if (oldColumn.relationshipType === RelationshipType.ONE_TO_MANY) {
if (newColumn.subtype !== FieldSubtype.USER) { if (newColumn.subtype !== BBReferenceFieldSubType.USER) {
throw new BadRequestError( throw new BadRequestError(
`Column "${oldColumn.name}" is a one-to-many column but "${newColumn.name}" is not a single user column` `Column "${oldColumn.name}" is a one-to-many column but "${newColumn.name}" is not a single user column`
) )
@ -107,7 +107,7 @@ function getColumnMigrator(
oldColumn.relationshipType === RelationshipType.MANY_TO_MANY || oldColumn.relationshipType === RelationshipType.MANY_TO_MANY ||
oldColumn.relationshipType === RelationshipType.MANY_TO_ONE oldColumn.relationshipType === RelationshipType.MANY_TO_ONE
) { ) {
if (newColumn.subtype !== FieldSubtype.USERS) { if (newColumn.subtype !== BBReferenceFieldSubType.USERS) {
throw new BadRequestError( throw new BadRequestError(
`Column "${oldColumn.name}" is a ${oldColumn.relationshipType} column but "${newColumn.name}" is not a multi user column` `Column "${oldColumn.name}" is a ${oldColumn.relationshipType} column but "${newColumn.name}" is not a multi user column`
) )

View File

@ -1,13 +1,17 @@
import { cache, db as dbCore } from "@budibase/backend-core" import { cache, db as dbCore } from "@budibase/backend-core"
import { utils } from "@budibase/shared-core" import { utils } from "@budibase/shared-core"
import { FieldSubtype, DocumentType, SEPARATOR } from "@budibase/types" import {
BBReferenceFieldSubType,
DocumentType,
SEPARATOR,
} from "@budibase/types"
import { InvalidBBRefError } from "./errors" import { InvalidBBRefError } from "./errors"
const ROW_PREFIX = DocumentType.ROW + SEPARATOR const ROW_PREFIX = DocumentType.ROW + SEPARATOR
export async function processInputBBReferences( export async function processInputBBReferences(
value: string | string[] | { _id: string } | { _id: string }[], value: string | string[] | { _id: string } | { _id: string }[],
subtype: FieldSubtype.USER | FieldSubtype.USERS subtype: BBReferenceFieldSubType.USER | BBReferenceFieldSubType.USERS
): Promise<string | string[] | null> { ): Promise<string | string[] | null> {
let referenceIds: string[] = [] let referenceIds: string[] = []
@ -40,15 +44,18 @@ export async function processInputBBReferences(
}) })
switch (subtype) { switch (subtype) {
case FieldSubtype.USER: case BBReferenceFieldSubType.USER:
case FieldSubtype.USERS: { case BBReferenceFieldSubType.USERS: {
const { notFoundIds } = await cache.user.getUsers(referenceIds) const { notFoundIds } = await cache.user.getUsers(referenceIds)
if (notFoundIds?.length) { if (notFoundIds?.length) {
throw new InvalidBBRefError(notFoundIds[0], FieldSubtype.USER) throw new InvalidBBRefError(
notFoundIds[0],
BBReferenceFieldSubType.USER
)
} }
if (subtype === FieldSubtype.USERS) { if (subtype === BBReferenceFieldSubType.USERS) {
return referenceIds return referenceIds
} }
@ -61,7 +68,7 @@ export async function processInputBBReferences(
export async function processOutputBBReferences( export async function processOutputBBReferences(
value: string | string[], value: string | string[],
subtype: FieldSubtype.USER | FieldSubtype.USERS subtype: BBReferenceFieldSubType.USER | BBReferenceFieldSubType.USERS
) { ) {
if (value === null || value === undefined) { if (value === null || value === undefined) {
// Already processed or nothing to process // Already processed or nothing to process
@ -72,8 +79,8 @@ export async function processOutputBBReferences(
typeof value === "string" ? value.split(",").filter(id => !!id) : value typeof value === "string" ? value.split(",").filter(id => !!id) : value
switch (subtype) { switch (subtype) {
case FieldSubtype.USER: case BBReferenceFieldSubType.USER:
case FieldSubtype.USERS: { case BBReferenceFieldSubType.USERS: {
const { users } = await cache.user.getUsers(ids) const { users } = await cache.user.getUsers(ids)
if (!users.length) { if (!users.length) {
return undefined return undefined

View File

@ -1,7 +1,7 @@
import { FieldSubtype } from "@budibase/types" import { BBReferenceFieldSubType } from "@budibase/types"
export class InvalidBBRefError extends Error { export class InvalidBBRefError extends Error {
constructor(id: string, subtype: FieldSubtype) { constructor(id: string, subtype: BBReferenceFieldSubType) {
super(`Id "${id}" is not valid for the subtype "${subtype}"`) super(`Id "${id}" is not valid for the subtype "${subtype}"`)
} }
} }

View File

@ -1,6 +1,6 @@
import _ from "lodash" import _ from "lodash"
import * as backendCore from "@budibase/backend-core" import * as backendCore from "@budibase/backend-core"
import { FieldSubtype, User } from "@budibase/types" import { BBReferenceFieldSubType, User } from "@budibase/types"
import { import {
processInputBBReferences, processInputBBReferences,
processOutputBBReferences, processOutputBBReferences,
@ -63,7 +63,7 @@ describe("bbReferenceProcessor", () => {
const userId = user!._id! const userId = user!._id!
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences(userId, FieldSubtype.USER) processInputBBReferences(userId, BBReferenceFieldSubType.USER)
) )
expect(result).toEqual(userId) expect(result).toEqual(userId)
@ -76,9 +76,11 @@ describe("bbReferenceProcessor", () => {
await expect( await expect(
config.doInTenant(() => config.doInTenant(() =>
processInputBBReferences(userId, FieldSubtype.USER) processInputBBReferences(userId, BBReferenceFieldSubType.USER)
) )
).rejects.toThrow(new InvalidBBRefError(userId, FieldSubtype.USER)) ).rejects.toThrow(
new InvalidBBRefError(userId, BBReferenceFieldSubType.USER)
)
expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1) expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1)
expect(cacheGetUsersSpy).toHaveBeenCalledWith([userId]) expect(cacheGetUsersSpy).toHaveBeenCalledWith([userId])
}) })
@ -88,7 +90,7 @@ describe("bbReferenceProcessor", () => {
const userIdCsv = userIds.join(" , ") const userIdCsv = userIds.join(" , ")
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences(userIdCsv, FieldSubtype.USER) processInputBBReferences(userIdCsv, BBReferenceFieldSubType.USER)
) )
expect(result).toEqual(userIds.join(",")) expect(result).toEqual(userIds.join(","))
@ -108,16 +110,21 @@ describe("bbReferenceProcessor", () => {
await expect( await expect(
config.doInTenant(() => config.doInTenant(() =>
processInputBBReferences(userIdCsv, FieldSubtype.USER) processInputBBReferences(userIdCsv, BBReferenceFieldSubType.USER)
) )
).rejects.toThrow(new InvalidBBRefError(wrongId, FieldSubtype.USER)) ).rejects.toThrow(
new InvalidBBRefError(wrongId, BBReferenceFieldSubType.USER)
)
}) })
it("validate valid user object", async () => { it("validate valid user object", async () => {
const userId = _.sample(users)!._id! const userId = _.sample(users)!._id!
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences({ _id: userId }, FieldSubtype.USER) processInputBBReferences(
{ _id: userId },
BBReferenceFieldSubType.USER
)
) )
expect(result).toEqual(userId) expect(result).toEqual(userId)
@ -129,7 +136,7 @@ describe("bbReferenceProcessor", () => {
const userIds = _.sampleSize(users, 3).map(x => x._id!) const userIds = _.sampleSize(users, 3).map(x => x._id!)
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences(userIds, FieldSubtype.USER) processInputBBReferences(userIds, BBReferenceFieldSubType.USER)
) )
expect(result).toEqual(userIds.join(",")) expect(result).toEqual(userIds.join(","))
@ -139,7 +146,7 @@ describe("bbReferenceProcessor", () => {
it("empty strings will return null", async () => { it("empty strings will return null", async () => {
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences("", FieldSubtype.USER) processInputBBReferences("", BBReferenceFieldSubType.USER)
) )
expect(result).toEqual(null) expect(result).toEqual(null)
@ -147,7 +154,7 @@ describe("bbReferenceProcessor", () => {
it("empty arrays will return null", async () => { it("empty arrays will return null", async () => {
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences([], FieldSubtype.USER) processInputBBReferences([], BBReferenceFieldSubType.USER)
) )
expect(result).toEqual(null) expect(result).toEqual(null)
@ -157,7 +164,7 @@ describe("bbReferenceProcessor", () => {
const userId = _.sample(users)!._id! const userId = _.sample(users)!._id!
const userMetadataId = backendCore.db.generateUserMetadataID(userId) const userMetadataId = backendCore.db.generateUserMetadataID(userId)
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences(userMetadataId, FieldSubtype.USER) processInputBBReferences(userMetadataId, BBReferenceFieldSubType.USER)
) )
expect(result).toBe(userId) expect(result).toBe(userId)
}) })
@ -171,7 +178,7 @@ describe("bbReferenceProcessor", () => {
const userId = user._id! const userId = user._id!
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processOutputBBReferences(userId, FieldSubtype.USER) processOutputBBReferences(userId, BBReferenceFieldSubType.USER)
) )
expect(result).toEqual([ expect(result).toEqual([
@ -195,7 +202,7 @@ describe("bbReferenceProcessor", () => {
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processOutputBBReferences( processOutputBBReferences(
[userId1, userId2].join(","), [userId1, userId2].join(","),
FieldSubtype.USER BBReferenceFieldSubType.USER
) )
) )

View File

@ -2,7 +2,7 @@ import { inputProcessing } from ".."
import { generator, structures } from "@budibase/backend-core/tests" import { generator, structures } from "@budibase/backend-core/tests"
import { import {
FieldType, FieldType,
FieldTypeSubtypes, BBReferenceFieldSubType,
INTERNAL_TABLE_SOURCE_ID, INTERNAL_TABLE_SOURCE_ID,
Table, Table,
TableSourceType, TableSourceType,
@ -39,7 +39,7 @@ describe("rowProcessor - inputProcessing", () => {
}, },
user: { user: {
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldTypeSubtypes.BB_REFERENCE.USER, subtype: BBReferenceFieldSubType.USER,
name: "user", name: "user",
constraints: { constraints: {
presence: true, presence: true,
@ -93,7 +93,7 @@ describe("rowProcessor - inputProcessing", () => {
}, },
user: { user: {
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldTypeSubtypes.BB_REFERENCE.USER, subtype: BBReferenceFieldSubType.USER,
name: "user", name: "user",
constraints: { constraints: {
presence: false, presence: false,
@ -135,7 +135,7 @@ describe("rowProcessor - inputProcessing", () => {
}, },
user: { user: {
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldTypeSubtypes.BB_REFERENCE.USER, subtype: BBReferenceFieldSubType.USER,
name: "user", name: "user",
constraints: { constraints: {
presence: false, presence: false,

View File

@ -1,7 +1,6 @@
import { import {
FieldSubtype,
FieldType, FieldType,
FieldTypeSubtypes, BBReferenceFieldSubType,
INTERNAL_TABLE_SOURCE_ID, INTERNAL_TABLE_SOURCE_ID,
RowAttachment, RowAttachment,
Table, Table,
@ -42,7 +41,7 @@ describe("rowProcessor - outputProcessing", () => {
}, },
user: { user: {
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldTypeSubtypes.BB_REFERENCE.USER, subtype: BBReferenceFieldSubType.USER,
name: "user", name: "user",
constraints: { constraints: {
presence: false, presence: false,
@ -69,7 +68,7 @@ describe("rowProcessor - outputProcessing", () => {
).toHaveBeenCalledTimes(1) ).toHaveBeenCalledTimes(1)
expect(bbReferenceProcessor.processOutputBBReferences).toHaveBeenCalledWith( expect(bbReferenceProcessor.processOutputBBReferences).toHaveBeenCalledWith(
"123", "123",
FieldSubtype.USER BBReferenceFieldSubType.USER
) )
}) })
@ -175,7 +174,7 @@ describe("rowProcessor - outputProcessing", () => {
}, },
user: { user: {
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: FieldTypeSubtypes.BB_REFERENCE.USER, subtype: BBReferenceFieldSubType.USER,
name: "user", name: "user",
constraints: { constraints: {
presence: false, presence: false,

View File

@ -1,6 +1,6 @@
import { import {
FieldType, FieldType,
FieldSubtype, BBReferenceFieldSubType,
TableSchema, TableSchema,
FieldSchema, FieldSchema,
Row, Row,
@ -137,10 +137,10 @@ export function parse(rows: Rows, schema: TableSchema): Rows {
parsedRow[columnName] = undefined parsedRow[columnName] = undefined
} else { } else {
switch (columnSubtype) { switch (columnSubtype) {
case FieldSubtype.USER: case BBReferenceFieldSubType.USER:
parsedRow[columnName] = parsedValues[0]?._id parsedRow[columnName] = parsedValues[0]?._id
break break
case FieldSubtype.USERS: case BBReferenceFieldSubType.USERS:
parsedRow[columnName] = parsedValues.map(u => u._id) parsedRow[columnName] = parsedValues.map(u => u._id)
break break
default: default:
@ -164,11 +164,11 @@ export function parse(rows: Rows, schema: TableSchema): Rows {
function isValidBBReference( function isValidBBReference(
columnData: any, columnData: any,
columnSubtype: FieldSubtype.USER | FieldSubtype.USERS columnSubtype: BBReferenceFieldSubType.USER | BBReferenceFieldSubType.USERS
): boolean { ): boolean {
switch (columnSubtype) { switch (columnSubtype) {
case FieldSubtype.USER: case BBReferenceFieldSubType.USER:
case FieldSubtype.USERS: { case BBReferenceFieldSubType.USERS: {
if (typeof columnData !== "string") { if (typeof columnData !== "string") {
return false return false
} }
@ -177,7 +177,10 @@ function isValidBBReference(
return false return false
} }
if (columnSubtype === FieldSubtype.USER && userArray.length > 1) { if (
columnSubtype === BBReferenceFieldSubType.USER &&
userArray.length > 1
) {
return false return false
} }

View File

@ -1,6 +1,6 @@
import { import {
Datasource, Datasource,
FieldSubtype, BBReferenceFieldSubType,
FieldType, FieldType,
FormulaType, FormulaType,
SearchFilter, SearchFilter,
@ -22,7 +22,7 @@ const HBS_REGEX = /{{([^{].*?)}}/g
export const getValidOperatorsForType = ( export const getValidOperatorsForType = (
fieldType: { fieldType: {
type: FieldType type: FieldType
subtype?: FieldSubtype subtype?: BBReferenceFieldSubType
formulaType?: FormulaType formulaType?: FormulaType
}, },
field: string, field: string,
@ -68,9 +68,15 @@ export const getValidOperatorsForType = (
ops = numOps ops = numOps
} else if (type === FieldType.FORMULA && formulaType === FormulaType.STATIC) { } else if (type === FieldType.FORMULA && formulaType === FormulaType.STATIC) {
ops = stringOps.concat([Op.MoreThan, Op.LessThan]) ops = stringOps.concat([Op.MoreThan, Op.LessThan])
} else if (type === FieldType.BB_REFERENCE && subtype == FieldSubtype.USER) { } else if (
type === FieldType.BB_REFERENCE &&
subtype == BBReferenceFieldSubType.USER
) {
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In] ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In]
} else if (type === FieldType.BB_REFERENCE && subtype == FieldSubtype.USERS) { } else if (
type === FieldType.BB_REFERENCE &&
subtype == BBReferenceFieldSubType.USERS
) {
ops = [Op.Contains, Op.NotContains, Op.ContainsAny, Op.Empty, Op.NotEmpty] ops = [Op.Contains, Op.NotContains, Op.ContainsAny, Op.Empty, Op.NotEmpty]
} }

View File

@ -124,16 +124,3 @@ export interface Row extends Document {
_viewId?: string _viewId?: string
[key: string]: any [key: string]: any
} }
export enum FieldSubtype {
USER = "user",
USERS = "users",
}
// The 'as' are required for typescript not to type the outputs as generic FieldSubtype
export const FieldTypeSubtypes = {
BB_REFERENCE: {
USER: FieldSubtype.USER as FieldSubtype.USER,
USERS: FieldSubtype.USERS as FieldSubtype.USERS,
},
}

View File

@ -24,3 +24,8 @@ export enum FormulaType {
STATIC = "static", STATIC = "static",
DYNAMIC = "dynamic", DYNAMIC = "dynamic",
} }
export enum BBReferenceFieldSubType {
USER = "user",
USERS = "users",
}

View File

@ -1,9 +1,10 @@
// all added by grid/table when defining the // all added by grid/table when defining the
// column size, position and whether it can be viewed // column size, position and whether it can be viewed
import { FieldSubtype, FieldType } from "../row" import { FieldType } from "../row"
import { import {
AutoFieldSubType, AutoFieldSubType,
AutoReason, AutoReason,
BBReferenceFieldSubType,
FormulaType, FormulaType,
JsonFieldSubType, JsonFieldSubType,
RelationshipType, RelationshipType,
@ -109,7 +110,7 @@ export interface FormulaFieldMetadata extends BaseFieldSchema {
export interface BBReferenceFieldMetadata export interface BBReferenceFieldMetadata
extends Omit<BaseFieldSchema, "subtype"> { extends Omit<BaseFieldSchema, "subtype"> {
type: FieldType.BB_REFERENCE type: FieldType.BB_REFERENCE
subtype: FieldSubtype.USER | FieldSubtype.USERS subtype: BBReferenceFieldSubType.USER | BBReferenceFieldSubType.USERS
relationshipType?: RelationshipType relationshipType?: RelationshipType
} }

View File

@ -11,6 +11,7 @@ export enum PlanType {
/** @deprecated */ /** @deprecated */
BUSINESS = "business", BUSINESS = "business",
ENTERPRISE_BASIC = "enterprise_basic", ENTERPRISE_BASIC = "enterprise_basic",
ENTERPRISE_BASIC_TRIAL = "enterprise_basic_trial",
ENTERPRISE = "enterprise", ENTERPRISE = "enterprise",
} }

19
scripts/killTestcontainers.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# Find all Docker containers with the label "org.testcontainers=true"
containers=$(docker ps -q -f "label=org.testcontainers=true")
# Check if there are any containers to stop
if [ -z "$containers" ]; then
echo "No containers with label 'org.testcontainers=true' found."
else
# Stop the containers
echo "Stopping containers..."
docker stop $containers
# Remove the containers
echo "Removing containers..."
docker rm $containers
echo "Containers have been stopped and removed."
fi