Merge branch 'master' of github.com:budibase/budibase into test-oracle
This commit is contained in:
commit
20bad903cc
|
@ -108,7 +108,7 @@ jobs:
|
|||
- name: Pull testcontainers images
|
||||
run: |
|
||||
docker pull testcontainers/ryuk:0.5.1 &
|
||||
docker pull budibase/couchdb:v3.2.1-sqs &
|
||||
docker pull budibase/couchdb:v3.3.3 &
|
||||
docker pull redis &
|
||||
|
||||
wait $(jobs -p)
|
||||
|
@ -162,17 +162,23 @@ jobs:
|
|||
node-version: 20.x
|
||||
cache: yarn
|
||||
|
||||
- name: Load dotenv
|
||||
id: dotenv
|
||||
uses: falti/dotenv-action@v1.1.3
|
||||
with:
|
||||
path: ./packages/server/datasource-sha.env
|
||||
|
||||
- name: Pull testcontainers images
|
||||
run: |
|
||||
docker pull mcr.microsoft.com/mssql/server:2022-latest &
|
||||
docker pull mysql:8.3 &
|
||||
docker pull postgres:16.1-bullseye &
|
||||
docker pull mongo:7.0-jammy &
|
||||
docker pull mariadb:lts &
|
||||
docker pull testcontainers/ryuk:0.5.1 &
|
||||
docker pull budibase/couchdb:v3.2.1-sqs &
|
||||
docker pull mcr.microsoft.com/mssql/server@${{ steps.dotenv.outputs.MSSQL_SHA }} &
|
||||
docker pull mysql@${{ steps.dotenv.outputs.MYSQL_SHA }} &
|
||||
docker pull postgres@${{ steps.dotenv.outputs.POSTGRES_SHA }} &
|
||||
docker pull mongo@${{ steps.dotenv.outputs.MONGODB_SHA }} &
|
||||
docker pull mariadb@${{ steps.dotenv.outputs.MARIADB_SHA }} &
|
||||
docker pull minio/minio &
|
||||
docker pull redis &
|
||||
docker pull testcontainers/ryuk:0.5.1 &
|
||||
docker pull budibase/couchdb:v3.3.3 &
|
||||
|
||||
wait $(jobs -p)
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ export default async function setup() {
|
|||
await killContainers(containers)
|
||||
|
||||
try {
|
||||
const couchdb = new GenericContainer("budibase/couchdb:v3.2.1-sqs")
|
||||
const couchdb = new GenericContainer("budibase/couchdb:v3.3.3")
|
||||
.withExposedPorts(5984, 4984)
|
||||
.withEnvironment({
|
||||
COUCHDB_PASSWORD: "budibase",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||
"version": "2.29.22",
|
||||
"version": "2.29.24",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*",
|
||||
|
|
13
nx.json
13
nx.json
|
@ -10,7 +10,18 @@
|
|||
},
|
||||
"targetDefaults": {
|
||||
"build": {
|
||||
"inputs": ["{workspaceRoot}/scripts/*", "{workspaceRoot}/lerna.json"]
|
||||
"inputs": [
|
||||
"{workspaceRoot}/scripts/*",
|
||||
"{workspaceRoot}/lerna.json",
|
||||
"{workspaceRoot}/.github/workflows/*"
|
||||
]
|
||||
},
|
||||
"test": {
|
||||
"inputs": [
|
||||
"{workspaceRoot}/scripts/*",
|
||||
"{workspaceRoot}/lerna.json",
|
||||
"{workspaceRoot}/.github/workflows/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"namedInputs": {
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
AutomationEventType,
|
||||
AutomationStepType,
|
||||
AutomationActionStepId,
|
||||
AutomationCustomIOType,
|
||||
} from "@budibase/types"
|
||||
import { FIELDS } from "constants/backend"
|
||||
import PropField from "./PropField.svelte"
|
||||
|
@ -394,7 +395,9 @@
|
|||
*/
|
||||
const onRowTriggerUpdate = async update => {
|
||||
if (
|
||||
["tableId", "filters", "meta"].some(key => Object.hasOwn(update, key))
|
||||
["tableId", AutomationCustomIOType.FILTERS, "meta"].some(key =>
|
||||
Object.hasOwn(update, key)
|
||||
)
|
||||
) {
|
||||
try {
|
||||
let updatedAutomation
|
||||
|
@ -744,7 +747,11 @@
|
|||
for (let [key, field] of properties) {
|
||||
// need to look for the builder definition (keyed separately, see saveFilters)
|
||||
const defKey = `${key}-def`
|
||||
if (field.customType === "filters" && inputs?.[defKey]) {
|
||||
if (
|
||||
(field.customType === AutomationCustomIOType.FILTERS ||
|
||||
field.customType === AutomationCustomIOType.TRIGGER_FILTER) &&
|
||||
inputs?.[defKey]
|
||||
) {
|
||||
filters = inputs[defKey]
|
||||
break
|
||||
}
|
||||
|
@ -846,7 +853,7 @@
|
|||
<Label>
|
||||
{label}
|
||||
</Label>
|
||||
{#if value.customType === "trigger_filter"}
|
||||
{#if value.customType === AutomationCustomIOType.TRIGGER_FILTER}
|
||||
<Icon
|
||||
hoverable
|
||||
on:click={() =>
|
||||
|
@ -977,7 +984,7 @@
|
|||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else if value.customType === "filters" || value.customType === "trigger_filter"}
|
||||
{:else if value.customType === AutomationCustomIOType.FILTERS || value.customType === AutomationCustomIOType.TRIGGER_FILTER}
|
||||
<ActionButton fullWidth on:click={drawer.show}
|
||||
>{filters.length > 0
|
||||
? "Update Filter"
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
export let listItemKey
|
||||
export let draggable = true
|
||||
export let focus
|
||||
export let bindings = []
|
||||
|
||||
let zoneType = generate()
|
||||
|
||||
|
@ -127,7 +126,6 @@
|
|||
anchor={anchors[draggableItem.id]}
|
||||
item={draggableItem.item}
|
||||
{...listTypeProps}
|
||||
{bindings}
|
||||
on:change={onItemChanged}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -64,7 +64,9 @@
|
|||
items={columns.sortable}
|
||||
listItemKey={"_id"}
|
||||
listType={FieldSetting}
|
||||
{bindings}
|
||||
listTypeProps={{
|
||||
bindings,
|
||||
}}
|
||||
/>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -106,12 +106,24 @@
|
|||
}
|
||||
const handleHashChange = () => {
|
||||
const { open: sidePanelOpen } = $sidePanelStore
|
||||
if (sidePanelOpen) {
|
||||
// only close if the sidepanel is open and theres no onload side panel actions on the screen.
|
||||
if (
|
||||
sidePanelOpen &&
|
||||
!$screenStore.activeScreen.onLoad?.some(
|
||||
item => item["##eventHandlerType"] === "Open Side Panel"
|
||||
)
|
||||
) {
|
||||
sidePanelStore.actions.close()
|
||||
}
|
||||
|
||||
const { open: modalOpen } = $modalStore
|
||||
if (modalOpen) {
|
||||
// only close if the modal is open and theres onload modals actions on the screen.
|
||||
if (
|
||||
modalOpen &&
|
||||
!$screenStore.activeScreen.onLoad?.some(
|
||||
item => item["##eventHandlerType"] === "Open Modal"
|
||||
)
|
||||
) {
|
||||
modalStore.actions.close()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
MSSQL_SHA=sha256:c4369c38385eba011c10906dc8892425831275bb035d5ce69656da8e29de50d8
|
||||
MYSQL_SHA=sha256:9de9d54fecee6253130e65154b930978b1fcc336bcc86dfd06e89b72a2588ebe
|
||||
POSTGRES_SHA=sha256:bd0d8e485d1aca439d39e5ea99b931160bd28d862e74c786f7508e9d0053090e
|
||||
MONGODB_SHA=sha256:afa36bca12295b5f9dae68a493c706113922bdab520e901bd5d6c9d7247a1d8d
|
||||
MARIADB_SHA=sha256:e59ba8783bf7bc02a4779f103bb0d8751ac0e10f9471089709608377eded7aa8
|
|
@ -16,7 +16,8 @@
|
|||
"build:isolated-vm-lib:snippets": "esbuild --minify --bundle src/jsRunner/bundles/snippets.ts --outfile=src/jsRunner/bundles/snippets.ivm.bundle.js --platform=node --format=iife --global-name=snippets",
|
||||
"build:isolated-vm-lib:string-templates": "esbuild --minify --bundle src/jsRunner/bundles/index-helpers.ts --outfile=src/jsRunner/bundles/index-helpers.ivm.bundle.js --platform=node --format=iife --external:handlebars --global-name=helpers",
|
||||
"build:isolated-vm-lib:bson": "esbuild --minify --bundle src/jsRunner/bundles/bsonPackage.ts --outfile=src/jsRunner/bundles/bson.ivm.bundle.js --platform=node --format=iife --global-name=bson",
|
||||
"build:isolated-vm-libs": "yarn build:isolated-vm-lib:string-templates && yarn build:isolated-vm-lib:bson && yarn build:isolated-vm-lib:snippets",
|
||||
"build:isolated-vm-lib:buffer": "esbuild --minify --bundle src/jsRunner/bundles/buffer.ts --outfile=src/jsRunner/bundles/buffer.ivm.bundle.js --platform=node --format=iife --global-name=buffer",
|
||||
"build:isolated-vm-libs": "yarn build:isolated-vm-lib:string-templates && yarn build:isolated-vm-lib:bson && yarn build:isolated-vm-lib:snippets && yarn build:isolated-vm-lib:buffer",
|
||||
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
||||
"debug": "yarn build && node --expose-gc --inspect=9222 dist/index.js",
|
||||
"jest": "NODE_OPTIONS=\"--no-node-snapshot $NODE_OPTIONS\" jest",
|
||||
|
@ -68,6 +69,7 @@
|
|||
"aws-sdk": "2.1030.0",
|
||||
"bcrypt": "5.1.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"buffer": "6.0.3",
|
||||
"bull": "4.10.1",
|
||||
"chokidar": "3.5.3",
|
||||
"content-disposition": "^0.5.4",
|
||||
|
|
|
@ -92,25 +92,6 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function find(ctx: UserCtx): Promise<Row> {
|
||||
const id = ctx.params.rowId
|
||||
const tableId = utils.getTableId(ctx)
|
||||
const row = await sdk.rows.external.getRow(tableId, id, {
|
||||
relationships: true,
|
||||
})
|
||||
|
||||
if (!row) {
|
||||
ctx.throw(404)
|
||||
}
|
||||
|
||||
const table = await sdk.tables.getTable(tableId)
|
||||
// Preserving links, as the outputProcessing does not support external rows yet and we don't need it in this use case
|
||||
return await outputProcessing(table, row, {
|
||||
squash: true,
|
||||
preserveLinks: true,
|
||||
})
|
||||
}
|
||||
|
||||
export async function destroy(ctx: UserCtx) {
|
||||
const tableId = utils.getTableId(ctx)
|
||||
const _id = ctx.request.body._id
|
||||
|
|
|
@ -117,7 +117,9 @@ export async function fetch(ctx: any) {
|
|||
|
||||
export async function find(ctx: UserCtx<void, GetRowResponse>) {
|
||||
const tableId = utils.getTableId(ctx)
|
||||
ctx.body = await pickApi(tableId).find(ctx)
|
||||
const rowId = ctx.params.rowId
|
||||
|
||||
ctx.body = await sdk.rows.find(tableId, rowId)
|
||||
}
|
||||
|
||||
function isDeleteRows(input: any): input is DeleteRows {
|
||||
|
@ -278,7 +280,8 @@ export async function downloadAttachment(ctx: UserCtx) {
|
|||
const { columnName } = ctx.params
|
||||
|
||||
const tableId = utils.getTableId(ctx)
|
||||
const row = await pickApi(tableId).find(ctx)
|
||||
const rowId = ctx.params.rowId
|
||||
const row = await sdk.rows.find(tableId, rowId)
|
||||
|
||||
const table = await sdk.tables.getTable(tableId)
|
||||
const columnSchema = table.schema[columnName]
|
||||
|
|
|
@ -32,7 +32,7 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
|||
try {
|
||||
oldRow = await outputProcessing(
|
||||
dbTable,
|
||||
await utils.findRow(ctx, tableId, inputs._id!)
|
||||
await utils.findRow(tableId, inputs._id!)
|
||||
)
|
||||
} catch (err) {
|
||||
if (isUserTable) {
|
||||
|
@ -96,15 +96,6 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
|||
return { ...result, oldRow }
|
||||
}
|
||||
|
||||
export async function find(ctx: UserCtx): Promise<Row> {
|
||||
const tableId = utils.getTableId(ctx),
|
||||
rowId = ctx.params.rowId
|
||||
const table = await sdk.tables.getTable(tableId)
|
||||
let row = await utils.findRow(ctx, tableId, rowId)
|
||||
row = await outputProcessing(table, row)
|
||||
return row
|
||||
}
|
||||
|
||||
export async function destroy(ctx: UserCtx) {
|
||||
const db = context.getAppDB()
|
||||
const tableId = utils.getTableId(ctx)
|
||||
|
@ -195,7 +186,7 @@ export async function fetchEnrichedRow(ctx: UserCtx) {
|
|||
sdk.tables.getTable(tableId),
|
||||
linkRows.getLinkDocuments({ tableId, rowId, fieldName }),
|
||||
])
|
||||
let row = await utils.findRow(ctx, tableId, rowId)
|
||||
let row = await utils.findRow(tableId, rowId)
|
||||
row = await outputProcessing(table, row)
|
||||
const linkVals = links as LinkDocumentValue[]
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { InternalTables } from "../../../../db/utils"
|
||||
import * as userController from "../../user"
|
||||
|
||||
import { context } from "@budibase/backend-core"
|
||||
import {
|
||||
Ctx,
|
||||
|
@ -8,7 +8,6 @@ import {
|
|||
RelationshipsJson,
|
||||
Row,
|
||||
Table,
|
||||
UserCtx,
|
||||
} from "@budibase/types"
|
||||
import {
|
||||
processDates,
|
||||
|
@ -24,6 +23,7 @@ import {
|
|||
import sdk from "../../../../sdk"
|
||||
import { processStringSync } from "@budibase/string-templates"
|
||||
import validateJs from "validate.js"
|
||||
import { getFullUser } from "../../../../utilities/users"
|
||||
|
||||
validateJs.extend(validateJs.validators.datetime, {
|
||||
parse: function (value: string) {
|
||||
|
@ -63,16 +63,12 @@ export async function processRelationshipFields(
|
|||
return row
|
||||
}
|
||||
|
||||
export async function findRow(ctx: UserCtx, tableId: string, rowId: string) {
|
||||
export async function findRow(tableId: string, rowId: string) {
|
||||
const db = context.getAppDB()
|
||||
let row: Row
|
||||
// TODO remove special user case in future
|
||||
if (tableId === InternalTables.USER_METADATA) {
|
||||
ctx.params = {
|
||||
id: rowId,
|
||||
}
|
||||
await userController.findMetadata(ctx)
|
||||
row = ctx.body
|
||||
row = await getFullUser(rowId)
|
||||
} else {
|
||||
row = await db.get(rowId)
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import { generator, mocks } from "@budibase/backend-core/tests"
|
|||
import _, { merge } from "lodash"
|
||||
import * as uuid from "uuid"
|
||||
import { Knex } from "knex"
|
||||
import { InternalTables } from "../../../db/utils"
|
||||
|
||||
const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString()
|
||||
tk.freeze(timestamp)
|
||||
|
@ -804,6 +805,23 @@ describe.each([
|
|||
status: 404,
|
||||
})
|
||||
})
|
||||
|
||||
isInternal &&
|
||||
it("can search row from user table", async () => {
|
||||
const res = await config.api.row.get(
|
||||
InternalTables.USER_METADATA,
|
||||
config.userMetadataId!
|
||||
)
|
||||
|
||||
expect(res).toEqual({
|
||||
...config.getUser(),
|
||||
_id: config.userMetadataId!,
|
||||
_rev: expect.any(String),
|
||||
roles: undefined,
|
||||
roleId: "ADMIN",
|
||||
tableId: InternalTables.USER_METADATA,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetch", () => {
|
||||
|
|
|
@ -25,7 +25,7 @@ export const definition: AutomationTriggerSchema = {
|
|||
},
|
||||
filters: {
|
||||
type: AutomationIOType.OBJECT,
|
||||
customType: AutomationCustomIOType.FILTERS,
|
||||
customType: AutomationCustomIOType.TRIGGER_FILTER,
|
||||
title: "Filtering",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import dotenv from "dotenv"
|
||||
import { join } from "path"
|
||||
|
||||
const path = join(__dirname, "..", "..", "..", "..", "datasource-sha.env")
|
||||
dotenv.config({
|
||||
path,
|
||||
})
|
||||
|
||||
export const MSSQL_IMAGE = `mcr.microsoft.com/mssql/server@${process.env.MSSQL_SHA}`
|
||||
export const MYSQL_IMAGE = `mysql@${process.env.MYSQL_SHA}`
|
||||
export const POSTGRES_IMAGE = `postgres@${process.env.POSTGRES_SHA}`
|
||||
export const MONGODB_IMAGE = `mongo@${process.env.MONGODB_SHA}`
|
||||
export const MARIADB_IMAGE = `mariadb@${process.env.MARIADB_SHA}`
|
|
@ -1,3 +1,4 @@
|
|||
import "./images"
|
||||
import { Datasource, SourceName } from "@budibase/types"
|
||||
import * as postgres from "./postgres"
|
||||
import * as mongodb from "./mongodb"
|
||||
|
@ -73,7 +74,11 @@ export async function knexClient(ds: Datasource) {
|
|||
|
||||
export async function startContainer(container: GenericContainer) {
|
||||
const imageName = (container as any).imageName.string as string
|
||||
const key = imageName.replaceAll("/", "-").replaceAll(":", "-")
|
||||
let key: string = imageName
|
||||
if (imageName.includes("@sha256")) {
|
||||
key = imageName.split("@")[0]
|
||||
}
|
||||
key = key.replaceAll("/", "-").replaceAll(":", "-")
|
||||
|
||||
container = container
|
||||
.withReuse()
|
||||
|
|
|
@ -4,6 +4,7 @@ import { AbstractWaitStrategy } from "testcontainers/build/wait-strategies/wait-
|
|||
import { generator, testContainerUtils } from "@budibase/backend-core/tests"
|
||||
import { startContainer } from "."
|
||||
import { knexClient } from "./mysql"
|
||||
import { MARIADB_IMAGE } from "./images"
|
||||
|
||||
let ports: Promise<testContainerUtils.Port[]>
|
||||
|
||||
|
@ -27,7 +28,7 @@ class MariaDBWaitStrategy extends AbstractWaitStrategy {
|
|||
export async function getDatasource(): Promise<Datasource> {
|
||||
if (!ports) {
|
||||
ports = startContainer(
|
||||
new GenericContainer("mariadb:lts")
|
||||
new GenericContainer(MARIADB_IMAGE)
|
||||
.withExposedPorts(3306)
|
||||
.withEnvironment({ MARIADB_ROOT_PASSWORD: "password" })
|
||||
.withWaitStrategy(new MariaDBWaitStrategy())
|
||||
|
|
|
@ -2,13 +2,14 @@ import { generator, testContainerUtils } from "@budibase/backend-core/tests"
|
|||
import { Datasource, SourceName } from "@budibase/types"
|
||||
import { GenericContainer, Wait } from "testcontainers"
|
||||
import { startContainer } from "."
|
||||
import { MONGODB_IMAGE } from "./images"
|
||||
|
||||
let ports: Promise<testContainerUtils.Port[]>
|
||||
|
||||
export async function getDatasource(): Promise<Datasource> {
|
||||
if (!ports) {
|
||||
ports = startContainer(
|
||||
new GenericContainer("mongo:7.0-jammy")
|
||||
new GenericContainer(MONGODB_IMAGE)
|
||||
.withExposedPorts(27017)
|
||||
.withEnvironment({
|
||||
MONGO_INITDB_ROOT_USERNAME: "mongo",
|
||||
|
|
|
@ -3,13 +3,14 @@ import { GenericContainer, Wait } from "testcontainers"
|
|||
import { generator, testContainerUtils } from "@budibase/backend-core/tests"
|
||||
import { startContainer } from "."
|
||||
import knex from "knex"
|
||||
import { MSSQL_IMAGE } from "./images"
|
||||
|
||||
let ports: Promise<testContainerUtils.Port[]>
|
||||
|
||||
export async function getDatasource(): Promise<Datasource> {
|
||||
if (!ports) {
|
||||
ports = startContainer(
|
||||
new GenericContainer("mcr.microsoft.com/mssql/server:2022-latest")
|
||||
new GenericContainer(MSSQL_IMAGE)
|
||||
.withExposedPorts(1433)
|
||||
.withEnvironment({
|
||||
ACCEPT_EULA: "Y",
|
||||
|
|
|
@ -4,6 +4,7 @@ import { AbstractWaitStrategy } from "testcontainers/build/wait-strategies/wait-
|
|||
import { generator, testContainerUtils } from "@budibase/backend-core/tests"
|
||||
import { startContainer } from "."
|
||||
import knex from "knex"
|
||||
import { MYSQL_IMAGE } from "./images"
|
||||
|
||||
let ports: Promise<testContainerUtils.Port[]>
|
||||
|
||||
|
@ -30,7 +31,7 @@ class MySQLWaitStrategy extends AbstractWaitStrategy {
|
|||
export async function getDatasource(): Promise<Datasource> {
|
||||
if (!ports) {
|
||||
ports = startContainer(
|
||||
new GenericContainer("mysql:8.3")
|
||||
new GenericContainer(MYSQL_IMAGE)
|
||||
.withExposedPorts(3306)
|
||||
.withEnvironment({ MYSQL_ROOT_PASSWORD: "password" })
|
||||
.withWaitStrategy(new MySQLWaitStrategy().withStartupTimeout(10000))
|
||||
|
|
|
@ -3,13 +3,14 @@ import { GenericContainer, Wait } from "testcontainers"
|
|||
import { generator, testContainerUtils } from "@budibase/backend-core/tests"
|
||||
import { startContainer } from "."
|
||||
import knex from "knex"
|
||||
import { POSTGRES_IMAGE } from "./images"
|
||||
|
||||
let ports: Promise<testContainerUtils.Port[]>
|
||||
|
||||
export async function getDatasource(): Promise<Datasource> {
|
||||
if (!ports) {
|
||||
ports = startContainer(
|
||||
new GenericContainer("postgres:16.1-bullseye")
|
||||
new GenericContainer(POSTGRES_IMAGE)
|
||||
.withExposedPorts(5432)
|
||||
.withEnvironment({ POSTGRES_PASSWORD: "password" })
|
||||
.withWaitStrategy(
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,3 @@
|
|||
const Buffer = require("buffer/").Buffer
|
||||
|
||||
export default Buffer
|
|
@ -4,12 +4,14 @@ export const enum BundleType {
|
|||
HELPERS = "helpers",
|
||||
BSON = "bson",
|
||||
SNIPPETS = "snippets",
|
||||
BUFFER = "buffer",
|
||||
}
|
||||
|
||||
const bundleSourceFile: Record<BundleType, string> = {
|
||||
[BundleType.HELPERS]: "./index-helpers.ivm.bundle.js",
|
||||
[BundleType.BSON]: "./bson.ivm.bundle.js",
|
||||
[BundleType.SNIPPETS]: "./snippets.ivm.bundle.js",
|
||||
[BundleType.BUFFER]: "./buffer.ivm.bundle.js",
|
||||
}
|
||||
const bundleSourceCode: Partial<Record<BundleType, string>> = {}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ export function init() {
|
|||
isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS,
|
||||
})
|
||||
.withHelpers()
|
||||
.withBuffer()
|
||||
.withSnippets(bbCtx?.snippets)
|
||||
|
||||
// Persist isolate in context so we can reuse it
|
||||
|
|
|
@ -71,6 +71,17 @@ describe("jsRunner (using isolated-vm)", () => {
|
|||
expect(result).toBeLessThanOrEqual(max)
|
||||
})
|
||||
})
|
||||
|
||||
describe("buffer", () => {
|
||||
it("handle a buffer", async () => {
|
||||
const base64 = Buffer.from("hello").toString("base64")
|
||||
const result = await processJS(
|
||||
`return Buffer.from("${base64}", "base64").toString("utf8")`
|
||||
)
|
||||
expect(result).toBeDefined()
|
||||
expect(result).toEqual("hello")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// the test cases here were extracted from templates/real world examples of JS in Budibase
|
||||
|
|
|
@ -86,6 +86,7 @@ export class IsolatedVM implements VM {
|
|||
}
|
||||
}`
|
||||
const helpersSource = loadBundle(BundleType.HELPERS)
|
||||
|
||||
const script = this.isolate.compileScriptSync(
|
||||
`${injectedRequire};${helpersSource};helpers=helpers.default`
|
||||
)
|
||||
|
@ -118,6 +119,19 @@ export class IsolatedVM implements VM {
|
|||
return this
|
||||
}
|
||||
|
||||
withBuffer() {
|
||||
const bufferSource = loadBundle(BundleType.BUFFER)
|
||||
const script = this.isolate.compileScriptSync(`
|
||||
${bufferSource};
|
||||
const Buffer = buffer.default;
|
||||
`)
|
||||
script.runSync(this.vm, { timeout: this.invocationTimeout, release: false })
|
||||
new Promise(() => {
|
||||
script.release()
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
withContext<T>(context: Record<string, any>, executeWithContext: () => T) {
|
||||
this.addToContext(context)
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { IncludeRelationship, Operation, Row } from "@budibase/types"
|
||||
import { HTTPError } from "@budibase/backend-core"
|
||||
import { handleRequest } from "../../../api/controllers/row/external"
|
||||
import { breakRowIdField } from "../../../integrations/utils"
|
||||
import sdk from "../../../sdk"
|
||||
|
@ -53,7 +54,7 @@ export async function save(
|
|||
|
||||
const rowId = response.row._id
|
||||
if (rowId) {
|
||||
const row = await sdk.rows.external.getRow(tableId, rowId, {
|
||||
const row = await getRow(tableId, rowId, {
|
||||
relationships: true,
|
||||
})
|
||||
return {
|
||||
|
@ -67,3 +68,20 @@ export async function save(
|
|||
return response
|
||||
}
|
||||
}
|
||||
|
||||
export async function find(tableId: string, rowId: string): Promise<Row> {
|
||||
const row = await getRow(tableId, rowId, {
|
||||
relationships: true,
|
||||
})
|
||||
|
||||
if (!row) {
|
||||
throw new HTTPError("Row not found", 404)
|
||||
}
|
||||
|
||||
const table = await sdk.tables.getTable(tableId)
|
||||
// Preserving links, as the outputProcessing does not support external rows yet and we don't need it in this use case
|
||||
return await outputProcessing(table, row, {
|
||||
squash: true,
|
||||
preserveLinks: true,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import { db } from "@budibase/backend-core"
|
||||
import { context, db } from "@budibase/backend-core"
|
||||
import { Row } from "@budibase/types"
|
||||
import sdk from "../../../sdk"
|
||||
import cloneDeep from "lodash/fp/cloneDeep"
|
||||
import { finaliseRow } from "../../../api/controllers/row/staticFormula"
|
||||
import { inputProcessing } from "../../../utilities/rowProcessor"
|
||||
import {
|
||||
inputProcessing,
|
||||
outputProcessing,
|
||||
} from "../../../utilities/rowProcessor"
|
||||
import * as linkRows from "../../../db/linkedRows"
|
||||
import { InternalTables } from "../../../db/utils"
|
||||
import { getFullUser } from "../../../utilities/users"
|
||||
|
||||
export async function save(
|
||||
tableId: string,
|
||||
|
@ -47,3 +52,26 @@ export async function save(
|
|||
updateFormula: true,
|
||||
})
|
||||
}
|
||||
|
||||
export async function find(tableId: string, rowId: string): Promise<Row> {
|
||||
const table = await sdk.tables.getTable(tableId)
|
||||
let row = await findRow(tableId, rowId)
|
||||
|
||||
row = await outputProcessing(table, row)
|
||||
return row
|
||||
}
|
||||
|
||||
async function findRow(tableId: string, rowId: string) {
|
||||
const db = context.getAppDB()
|
||||
let row: Row
|
||||
// TODO remove special user case in future
|
||||
if (tableId === InternalTables.USER_METADATA) {
|
||||
row = await getFullUser(rowId)
|
||||
} else {
|
||||
row = await db.get(rowId)
|
||||
}
|
||||
if (row.tableId !== tableId) {
|
||||
throw "Supplied tableId does not match the rows tableId"
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
|
|
@ -34,3 +34,7 @@ export async function save(
|
|||
) {
|
||||
return pickApi(tableId).save(tableId, row, userId)
|
||||
}
|
||||
|
||||
export async function find(tableId: string, rowId: string) {
|
||||
return pickApi(tableId).find(tableId, rowId)
|
||||
}
|
||||
|
|
|
@ -80,18 +80,31 @@ export async function startup(
|
|||
const address = server.address() as AddressInfo
|
||||
env._set("PORT", address.port)
|
||||
}
|
||||
|
||||
console.log("Emitting port event")
|
||||
eventEmitter.emitPort(env.PORT)
|
||||
|
||||
console.log("Initialising file system")
|
||||
fileSystem.init()
|
||||
|
||||
console.log("Initialising redis")
|
||||
await redis.init()
|
||||
|
||||
console.log("Initialising writethrough cache")
|
||||
cache.docWritethrough.init()
|
||||
|
||||
console.log("Initialising events")
|
||||
eventInit()
|
||||
|
||||
if (app && server) {
|
||||
console.log("Initialising websockets")
|
||||
initialiseWebsockets(app, server)
|
||||
}
|
||||
|
||||
// run migrations on startup if not done via http
|
||||
// not recommended in a clustered environment
|
||||
if (!env.HTTP_MIGRATIONS && !env.isTest()) {
|
||||
console.log("Running migrations")
|
||||
try {
|
||||
await migrations.migrate()
|
||||
} catch (e) {
|
||||
|
@ -107,12 +120,15 @@ export async function startup(
|
|||
env.PLUGINS_DIR &&
|
||||
fs.existsSync(env.PLUGINS_DIR)
|
||||
) {
|
||||
console.log("Monitoring plugin directory")
|
||||
watch()
|
||||
}
|
||||
|
||||
// check for version updates
|
||||
console.log("Checking for version updates")
|
||||
await installation.checkInstallVersion()
|
||||
|
||||
console.log("Initialising queues")
|
||||
// get the references to the queue promises, don't await as
|
||||
// they will never end, unless the processing stops
|
||||
let queuePromises = []
|
||||
|
@ -126,6 +142,7 @@ export async function startup(
|
|||
}
|
||||
queuePromises.push(initPro())
|
||||
if (app) {
|
||||
console.log("Initialising routes")
|
||||
// bring routes online as final step once everything ready
|
||||
await initRoutes(app)
|
||||
}
|
||||
|
@ -141,6 +158,7 @@ export async function startup(
|
|||
bbAdminEmail &&
|
||||
bbAdminPassword
|
||||
) {
|
||||
console.log("Initialising admin user")
|
||||
const tenantId = tenancy.getTenantId()
|
||||
await tenancy.doInTenant(tenantId, async () => {
|
||||
const exists = await users.doesUserExist(bbAdminEmail)
|
||||
|
@ -171,5 +189,6 @@ export async function startup(
|
|||
})
|
||||
}
|
||||
|
||||
console.log("Initialising JS runner")
|
||||
jsRunner.init()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { HelperFunctionBuiltin } from "../src/helpers/constants"
|
||||
import {
|
||||
HelperFunctionBuiltin,
|
||||
EXTERNAL_FUNCTION_COLLECTIONS,
|
||||
} from "../src/helpers/constants"
|
||||
import { readFileSync, writeFileSync } from "fs"
|
||||
import { marked } from "marked"
|
||||
import { join, dirname } from "path"
|
||||
|
@ -14,21 +17,6 @@ type HelperInfo = {
|
|||
tags?: any[]
|
||||
}
|
||||
|
||||
/**
|
||||
* full list of supported helpers can be found here:
|
||||
* https://github.com/budibase/handlebars-helpers
|
||||
*/
|
||||
|
||||
const COLLECTIONS = [
|
||||
"math",
|
||||
"array",
|
||||
"number",
|
||||
"url",
|
||||
"string",
|
||||
"comparison",
|
||||
"object",
|
||||
"uuid",
|
||||
]
|
||||
const FILENAME = join(__dirname, "..", "src", "manifest.json")
|
||||
const outputJSON: any = {}
|
||||
const ADDED_HELPERS = {
|
||||
|
@ -140,7 +128,7 @@ const excludeFunctions: Record<string, string[]> = { string: ["raw"] }
|
|||
*/
|
||||
function run() {
|
||||
const foundNames: string[] = []
|
||||
for (let collection of COLLECTIONS) {
|
||||
for (let collection of EXTERNAL_FUNCTION_COLLECTIONS) {
|
||||
const collectionFile = readFileSync(
|
||||
`${dirname(
|
||||
require.resolve("@budibase/handlebars-helpers")
|
||||
|
|
|
@ -58,7 +58,7 @@ function buildList(parts: string[], value: any) {
|
|||
if (!value) {
|
||||
return parts.length > 1 ? `${build()}` : build()
|
||||
} else {
|
||||
return parts.length === 0 ? value : `${value}, ${build()}`
|
||||
return parts.length === 0 ? value : `${build()}, ${value}`
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,22 @@ export const HelperFunctionBuiltin = [
|
|||
"with",
|
||||
]
|
||||
|
||||
/**
|
||||
* full list of supported helpers can be found here:
|
||||
* https://github.com/Budibase/handlebars-helpers
|
||||
*/
|
||||
export const EXTERNAL_FUNCTION_COLLECTIONS = [
|
||||
"math",
|
||||
"array",
|
||||
"number",
|
||||
"url",
|
||||
"string",
|
||||
"comparison",
|
||||
"object",
|
||||
"regex",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
export const HelperFunctionNames = {
|
||||
OBJECT: "object",
|
||||
ALL: "all",
|
||||
|
|
|
@ -2,26 +2,12 @@
|
|||
import helpers from "@budibase/handlebars-helpers"
|
||||
|
||||
import { date, duration } from "./date"
|
||||
import { HelperFunctionBuiltin } from "./constants"
|
||||
import {
|
||||
HelperFunctionBuiltin,
|
||||
EXTERNAL_FUNCTION_COLLECTIONS,
|
||||
} from "./constants"
|
||||
import Handlebars from "handlebars"
|
||||
|
||||
/**
|
||||
* full list of supported helpers can be found here:
|
||||
* https://github.com/Budibase/handlebars-helpers
|
||||
*/
|
||||
|
||||
const EXTERNAL_FUNCTION_COLLECTIONS = [
|
||||
"math",
|
||||
"array",
|
||||
"number",
|
||||
"url",
|
||||
"string",
|
||||
"comparison",
|
||||
"object",
|
||||
"regex",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
const ADDED_HELPERS = {
|
||||
date: date,
|
||||
duration: duration,
|
||||
|
@ -40,7 +26,7 @@ export function registerAll(handlebars: typeof Handlebars) {
|
|||
let hbsHelperInfo = helpers[collection]()
|
||||
for (let entry of Object.entries(hbsHelperInfo)) {
|
||||
const name = entry[0]
|
||||
// skip built in functions and ones seen already
|
||||
// skip built-in functions and ones seen already
|
||||
if (
|
||||
HelperFunctionBuiltin.indexOf(name) !== -1 ||
|
||||
externalNames.indexOf(name) !== -1
|
||||
|
|
|
@ -1312,6 +1312,26 @@
|
|||
"requiresBlock": false
|
||||
}
|
||||
},
|
||||
"regex": {
|
||||
"toRegex": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"example": "{{toRegex 'foo'}} -> /foo/",
|
||||
"description": "<p>Convert the given string to a regular expression.</p>\n",
|
||||
"requiresBlock": false
|
||||
},
|
||||
"test": {
|
||||
"args": [
|
||||
"str"
|
||||
],
|
||||
"numArgs": 1,
|
||||
"example": "{{test 'foobar' (toRegex 'foo')}} -> true",
|
||||
"description": "<p>Returns true if the given <code>str</code> matches the given regex. A regex can be passed on the context, or using the <a href=\"#toregex\">toRegex</a> helper as a subexpression.</p>\n",
|
||||
"requiresBlock": false
|
||||
}
|
||||
},
|
||||
"uuid": {
|
||||
"uuid": {
|
||||
"args": [],
|
||||
|
|
|
@ -93,10 +93,10 @@ describe("Test that the string processing works correctly", () => {
|
|||
|
||||
it("should handle a complex statement", () => {
|
||||
const response = convertToJS(
|
||||
"This is the average: {{ join ( avg val1 val2 val3 ) val4 }}"
|
||||
"This is the average: {{ join val1 ( avg val2 val3 val4 ) }}"
|
||||
)
|
||||
checkLines(response, [
|
||||
'const var1 = helpers.join(helpers.avg($("val1"), $("val2"), $("val3")), $("val4"));',
|
||||
'const var1 = helpers.join($("val1"), helpers.avg($("val2"), $("val3"), $("val4")));',
|
||||
"return `This is the average: ${var1}`;",
|
||||
])
|
||||
})
|
||||
|
@ -119,10 +119,10 @@ describe("Test that the string processing works correctly", () => {
|
|||
|
||||
it("should handle multiple complex statements", () => {
|
||||
const response = convertToJS(
|
||||
"average: {{ avg ( abs val1 ) val2 }} add: {{ add 1 2 }}"
|
||||
"average: {{ avg val1 ( abs val2 ) }} add: {{ add 1 2 }}"
|
||||
)
|
||||
checkLines(response, [
|
||||
'const var1 = helpers.avg(helpers.abs($("val1")), $("val2"));',
|
||||
'const var1 = helpers.avg($("val1"), helpers.abs($("val2")));',
|
||||
"const var2 = helpers.add(1, 2);",
|
||||
"return `average: ${var1} add: ${var2}`;",
|
||||
])
|
||||
|
|
38
yarn.lock
38
yarn.lock
|
@ -2038,7 +2038,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@budibase/backend-core@2.29.22":
|
||||
"@budibase/backend-core@2.29.24":
|
||||
version "0.0.0"
|
||||
dependencies:
|
||||
"@budibase/nano" "10.1.5"
|
||||
|
@ -2119,14 +2119,14 @@
|
|||
through2 "^2.0.0"
|
||||
|
||||
"@budibase/pro@npm:@budibase/pro@latest":
|
||||
version "2.29.22"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.29.22.tgz#2608b2a76be0426879068e5a61100d6b8dde6f3a"
|
||||
integrity sha512-flMVIpWQb9w3f4aiBSM73aLcYmfoPIf+kP8JXgRWO0k3nGrUGaMQNBKzXwC7soTkTrJCZjBh8uaY75AxTP2RdA==
|
||||
version "2.29.24"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.29.24.tgz#2dbd4c6c0f757aab7e17c413c6d6e4520086f9ac"
|
||||
integrity sha512-m1v24UD6O21Vbrfsuo5kC5oeg7FzjWO2w8TQMw1VvPKmdIqqclaKDPTPytxwllTMkapMDRNzM5cQzqnQ3yHf6A==
|
||||
dependencies:
|
||||
"@budibase/backend-core" "2.29.22"
|
||||
"@budibase/shared-core" "2.29.22"
|
||||
"@budibase/string-templates" "2.29.22"
|
||||
"@budibase/types" "2.29.22"
|
||||
"@budibase/backend-core" "2.29.24"
|
||||
"@budibase/shared-core" "2.29.24"
|
||||
"@budibase/string-templates" "2.29.24"
|
||||
"@budibase/types" "2.29.24"
|
||||
"@koa/router" "8.0.8"
|
||||
bull "4.10.1"
|
||||
joi "17.6.0"
|
||||
|
@ -2137,13 +2137,13 @@
|
|||
scim-patch "^0.8.1"
|
||||
scim2-parse-filter "^0.2.8"
|
||||
|
||||
"@budibase/shared-core@2.29.22":
|
||||
"@budibase/shared-core@2.29.24":
|
||||
version "0.0.0"
|
||||
dependencies:
|
||||
"@budibase/types" "0.0.0"
|
||||
cron-validate "1.4.5"
|
||||
|
||||
"@budibase/string-templates@2.29.22":
|
||||
"@budibase/string-templates@2.29.24":
|
||||
version "0.0.0"
|
||||
dependencies:
|
||||
"@budibase/handlebars-helpers" "^0.13.2"
|
||||
|
@ -2151,7 +2151,7 @@
|
|||
handlebars "^4.7.8"
|
||||
lodash.clonedeep "^4.5.0"
|
||||
|
||||
"@budibase/types@2.29.22":
|
||||
"@budibase/types@2.29.24":
|
||||
version "0.0.0"
|
||||
dependencies:
|
||||
scim-patch "^0.8.1"
|
||||
|
@ -7770,6 +7770,14 @@ buffer@4.9.2:
|
|||
ieee754 "^1.1.4"
|
||||
isarray "^1.0.0"
|
||||
|
||||
buffer@6.0.3, buffer@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
|
||||
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
|
||||
dependencies:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
buffer@^5.1.0, buffer@^5.2.0, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
|
||||
|
@ -7778,14 +7786,6 @@ buffer@^5.1.0, buffer@^5.2.0, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0:
|
|||
base64-js "^1.3.1"
|
||||
ieee754 "^1.1.13"
|
||||
|
||||
buffer@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
|
||||
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
|
||||
dependencies:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
bufferutil@^4.0.1:
|
||||
version "4.0.7"
|
||||
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad"
|
||||
|
|
Loading…
Reference in New Issue