Merge branch 'master' into cheeks-fixes
This commit is contained in:
commit
c869abab77
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.19.0",
|
"version": "2.19.1",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -2,15 +2,34 @@
|
||||||
import { Button, Select, Input, Label } from "@budibase/bbui"
|
import { Button, Select, Input, Label } from "@budibase/bbui"
|
||||||
import { onMount, createEventDispatcher } from "svelte"
|
import { onMount, createEventDispatcher } from "svelte"
|
||||||
import { flags } from "stores/backend"
|
import { flags } from "stores/backend"
|
||||||
|
import { helpers, REBOOT_CRON } from "@budibase/shared-core"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
let error
|
||||||
|
|
||||||
|
$: {
|
||||||
|
const exists = CRON_EXPRESSIONS.some(cron => cron.value === value)
|
||||||
|
const customIndex = CRON_EXPRESSIONS.findIndex(
|
||||||
|
cron => cron.label === "Custom"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!exists && customIndex === -1) {
|
||||||
|
CRON_EXPRESSIONS[0] = { label: "Custom", value: value }
|
||||||
|
} else if (exists && customIndex !== -1) {
|
||||||
|
CRON_EXPRESSIONS.splice(customIndex, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
if (e.detail === value) {
|
if (value !== REBOOT_CRON) {
|
||||||
|
error = helpers.cron.validate(e.detail).err
|
||||||
|
}
|
||||||
|
if (e.detail === value || error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
value = e.detail
|
value = e.detail
|
||||||
dispatch("change", e.detail)
|
dispatch("change", e.detail)
|
||||||
}
|
}
|
||||||
|
@ -41,7 +60,7 @@
|
||||||
if (!$flags.cloud) {
|
if (!$flags.cloud) {
|
||||||
CRON_EXPRESSIONS.push({
|
CRON_EXPRESSIONS.push({
|
||||||
label: "Every Budibase Reboot",
|
label: "Every Budibase Reboot",
|
||||||
value: "@reboot",
|
value: REBOOT_CRON,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -49,6 +68,7 @@
|
||||||
|
|
||||||
<div class="block-field">
|
<div class="block-field">
|
||||||
<Input
|
<Input
|
||||||
|
{error}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
{value}
|
{value}
|
||||||
on:blur={() => (touched = true)}
|
on:blur={() => (touched = true)}
|
||||||
|
@ -64,7 +84,7 @@
|
||||||
{#if presets}
|
{#if presets}
|
||||||
<Select
|
<Select
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
{value}
|
value={value || "Custom"}
|
||||||
secondary
|
secondary
|
||||||
extraThin
|
extraThin
|
||||||
label="Presets"
|
label="Presets"
|
||||||
|
|
|
@ -101,7 +101,13 @@
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
analytics.captureException(error)
|
analytics.captureException(error)
|
||||||
notifications.error("Error publishing app")
|
const baseMsg = "Error publishing app"
|
||||||
|
const message = error.message
|
||||||
|
if (message) {
|
||||||
|
notifications.error(`${baseMsg} - ${message}`)
|
||||||
|
} else {
|
||||||
|
notifications.error(baseMsg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
publishing = false
|
publishing = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,7 +341,10 @@ const exportDataHandler = async action => {
|
||||||
format: action.parameters.type,
|
format: action.parameters.type,
|
||||||
columns: action.parameters.columns,
|
columns: action.parameters.columns,
|
||||||
})
|
})
|
||||||
download(data, `${selection.tableId}.${action.parameters.type}`)
|
download(
|
||||||
|
new Blob([data], { type: "text/plain" }),
|
||||||
|
`${selection.tableId}.${action.parameters.type}`
|
||||||
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notificationStore.actions.error("There was an error exporting the data")
|
notificationStore.actions.error("There was an error exporting the data")
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,7 @@
|
||||||
"pouchdb-all-dbs": "1.1.1",
|
"pouchdb-all-dbs": "1.1.1",
|
||||||
"pouchdb-find": "7.2.2",
|
"pouchdb-find": "7.2.2",
|
||||||
"redis": "4",
|
"redis": "4",
|
||||||
|
"serialize-error": "^7.0.1",
|
||||||
"server-destroy": "1.0.1",
|
"server-destroy": "1.0.1",
|
||||||
"snowflake-promise": "^4.5.0",
|
"snowflake-promise": "^4.5.0",
|
||||||
"socket.io": "4.6.1",
|
"socket.io": "4.6.1",
|
||||||
|
|
|
@ -14,7 +14,6 @@ import { events } from "@budibase/backend-core"
|
||||||
import {
|
import {
|
||||||
BulkImportRequest,
|
BulkImportRequest,
|
||||||
BulkImportResponse,
|
BulkImportResponse,
|
||||||
DocumentType,
|
|
||||||
FetchTablesResponse,
|
FetchTablesResponse,
|
||||||
MigrateRequest,
|
MigrateRequest,
|
||||||
MigrateResponse,
|
MigrateResponse,
|
||||||
|
@ -25,7 +24,6 @@ import {
|
||||||
TableResponse,
|
TableResponse,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
UserCtx,
|
UserCtx,
|
||||||
SEPARATOR,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import { jsonFromCsvString } from "../../../utilities/csv"
|
import { jsonFromCsvString } from "../../../utilities/csv"
|
||||||
|
@ -77,9 +75,10 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
|
||||||
const table = ctx.request.body
|
const table = ctx.request.body
|
||||||
const isImport = table.rows
|
const isImport = table.rows
|
||||||
|
|
||||||
const savedTable = await pickApi({ table }).save(ctx)
|
let savedTable = await pickApi({ table }).save(ctx)
|
||||||
if (!table._id) {
|
if (!table._id) {
|
||||||
await events.table.created(savedTable)
|
await events.table.created(savedTable)
|
||||||
|
savedTable = sdk.tables.enrichViewSchemas(savedTable)
|
||||||
} else {
|
} else {
|
||||||
await events.table.updated(savedTable)
|
await events.table.updated(savedTable)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,15 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
|
||||||
} = {
|
} = {
|
||||||
_id: generateTableID(),
|
_id: generateTableID(),
|
||||||
...rest,
|
...rest,
|
||||||
type: "table",
|
// Ensure these fields are populated, even if not sent in the request
|
||||||
sourceType: TableSourceType.INTERNAL,
|
type: rest.type || "table",
|
||||||
views: {},
|
sourceType: rest.sourceType || TableSourceType.INTERNAL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!tableToSave.views) {
|
||||||
|
tableToSave.views = {}
|
||||||
|
}
|
||||||
|
|
||||||
const renaming = tableToSave._rename
|
const renaming = tableToSave._rename
|
||||||
delete tableToSave._rename
|
delete tableToSave._rename
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
FieldType,
|
FieldType,
|
||||||
INTERNAL_TABLE_SOURCE_ID,
|
INTERNAL_TABLE_SOURCE_ID,
|
||||||
InternalTable,
|
InternalTable,
|
||||||
|
NumberFieldMetadata,
|
||||||
RelationshipType,
|
RelationshipType,
|
||||||
Row,
|
Row,
|
||||||
SaveTableRequest,
|
SaveTableRequest,
|
||||||
|
@ -18,6 +19,12 @@ import * as setup from "./utilities"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import * as uuid from "uuid"
|
import * as uuid from "uuid"
|
||||||
|
|
||||||
|
import tk from "timekeeper"
|
||||||
|
import { mocks } from "@budibase/backend-core/tests"
|
||||||
|
import { TableToBuild } from "../../../tests/utilities/TestConfiguration"
|
||||||
|
|
||||||
|
tk.freeze(mocks.date.MOCK_DATE)
|
||||||
|
|
||||||
const { basicTable } = setup.structures
|
const { basicTable } = setup.structures
|
||||||
|
|
||||||
describe("/tables", () => {
|
describe("/tables", () => {
|
||||||
|
@ -60,6 +67,67 @@ describe("/tables", () => {
|
||||||
expect(events.table.created).toBeCalledWith(res.body)
|
expect(events.table.created).toBeCalledWith(res.body)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("creates all the passed fields", async () => {
|
||||||
|
const tableData: TableToBuild = {
|
||||||
|
name: "TestTable",
|
||||||
|
type: "table",
|
||||||
|
schema: {
|
||||||
|
autoId: {
|
||||||
|
name: "id",
|
||||||
|
type: FieldType.NUMBER,
|
||||||
|
subtype: AutoFieldSubType.AUTO_ID,
|
||||||
|
autocolumn: true,
|
||||||
|
constraints: {
|
||||||
|
type: "number",
|
||||||
|
presence: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
views: {
|
||||||
|
"table view": {
|
||||||
|
id: "viewId",
|
||||||
|
version: 2,
|
||||||
|
name: "table view",
|
||||||
|
tableId: "tableId",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const testTable = await config.createTable(tableData)
|
||||||
|
|
||||||
|
const expected: Table = {
|
||||||
|
...tableData,
|
||||||
|
type: "table",
|
||||||
|
views: {
|
||||||
|
"table view": {
|
||||||
|
...tableData.views!["table view"],
|
||||||
|
schema: {
|
||||||
|
autoId: {
|
||||||
|
autocolumn: true,
|
||||||
|
constraints: {
|
||||||
|
presence: false,
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
name: "id",
|
||||||
|
type: FieldType.NUMBER,
|
||||||
|
subtype: AutoFieldSubType.AUTO_ID,
|
||||||
|
visible: false,
|
||||||
|
} as NumberFieldMetadata,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sourceType: TableSourceType.INTERNAL,
|
||||||
|
sourceId: expect.any(String),
|
||||||
|
_rev: expect.stringMatching(/^1-.+/),
|
||||||
|
_id: expect.any(String),
|
||||||
|
createdAt: mocks.date.MOCK_DATE.toISOString(),
|
||||||
|
updatedAt: mocks.date.MOCK_DATE.toISOString(),
|
||||||
|
}
|
||||||
|
expect(testTable).toEqual(expected)
|
||||||
|
|
||||||
|
const persistedTable = await config.api.table.get(testTable._id!)
|
||||||
|
expect(persistedTable).toEqual(expected)
|
||||||
|
})
|
||||||
|
|
||||||
it("creates a table via data import", async () => {
|
it("creates a table via data import", async () => {
|
||||||
const table: SaveTableRequest = basicTable()
|
const table: SaveTableRequest = basicTable()
|
||||||
table.rows = [{ name: "test-name", description: "test-desc" }]
|
table.rows = [{ name: "test-name", description: "test-desc" }]
|
||||||
|
@ -152,6 +220,56 @@ describe("/tables", () => {
|
||||||
expect(res.body.name).toBeUndefined()
|
expect(res.body.name).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("updates only the passed fields", async () => {
|
||||||
|
const testTable = await config.createTable({
|
||||||
|
name: "TestTable",
|
||||||
|
type: "table",
|
||||||
|
schema: {
|
||||||
|
autoId: {
|
||||||
|
name: "id",
|
||||||
|
type: FieldType.NUMBER,
|
||||||
|
subtype: AutoFieldSubType.AUTO_ID,
|
||||||
|
autocolumn: true,
|
||||||
|
constraints: {
|
||||||
|
type: "number",
|
||||||
|
presence: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
views: {
|
||||||
|
view1: {
|
||||||
|
id: "viewId",
|
||||||
|
version: 2,
|
||||||
|
name: "table view",
|
||||||
|
tableId: "tableId",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await request
|
||||||
|
.post(`/api/tables`)
|
||||||
|
.send({
|
||||||
|
...testTable,
|
||||||
|
name: "UpdatedName",
|
||||||
|
})
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(response.body).toEqual({
|
||||||
|
...testTable,
|
||||||
|
name: "UpdatedName",
|
||||||
|
_rev: expect.stringMatching(/^2-.+/),
|
||||||
|
})
|
||||||
|
|
||||||
|
const persistedTable = await config.api.table.get(testTable._id!)
|
||||||
|
expect(persistedTable).toEqual({
|
||||||
|
...testTable,
|
||||||
|
name: "UpdatedName",
|
||||||
|
_rev: expect.stringMatching(/^2-.+/),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("user table", () => {
|
describe("user table", () => {
|
||||||
it("should add roleId and email field when adjusting user table schema", async () => {
|
it("should add roleId and email field when adjusting user table schema", async () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
|
@ -230,6 +348,7 @@ describe("/tables", () => {
|
||||||
|
|
||||||
describe("fetch", () => {
|
describe("fetch", () => {
|
||||||
let testTable: Table
|
let testTable: Table
|
||||||
|
const enrichViewSchemasMock = jest.spyOn(sdk.tables, "enrichViewSchemas")
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
testTable = await config.createTable(testTable)
|
testTable = await config.createTable(testTable)
|
||||||
|
@ -239,6 +358,10 @@ describe("/tables", () => {
|
||||||
delete testTable._rev
|
delete testTable._rev
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
enrichViewSchemasMock.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
it("returns all the tables for that instance in the response body", async () => {
|
it("returns all the tables for that instance in the response body", async () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/tables`)
|
.get(`/api/tables`)
|
||||||
|
@ -287,7 +410,7 @@ describe("/tables", () => {
|
||||||
|
|
||||||
it("should enrich the view schemas for viewsV2", async () => {
|
it("should enrich the view schemas for viewsV2", async () => {
|
||||||
const tableId = config.table!._id!
|
const tableId = config.table!._id!
|
||||||
jest.spyOn(sdk.tables, "enrichViewSchemas").mockImplementation(t => ({
|
enrichViewSchemasMock.mockImplementation(t => ({
|
||||||
...t,
|
...t,
|
||||||
views: {
|
views: {
|
||||||
view1: {
|
view1: {
|
||||||
|
@ -295,7 +418,7 @@ describe("/tables", () => {
|
||||||
name: "view1",
|
name: "view1",
|
||||||
schema: {},
|
schema: {},
|
||||||
id: "new_view_id",
|
id: "new_view_id",
|
||||||
tableId,
|
tableId: t._id!,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
@ -362,11 +485,7 @@ describe("/tables", () => {
|
||||||
let testTable: Table
|
let testTable: Table
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
testTable = await config.createTable(testTable)
|
testTable = await config.createTable()
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
delete testTable._rev
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns a success response when a table is deleted.", async () => {
|
it("returns a success response when a table is deleted.", async () => {
|
||||||
|
|
|
@ -16,9 +16,9 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../sdk"
|
import sdk from "../sdk"
|
||||||
import { automationsEnabled } from "../features"
|
import { automationsEnabled } from "../features"
|
||||||
|
import { helpers, REBOOT_CRON } from "@budibase/shared-core"
|
||||||
import tracer from "dd-trace"
|
import tracer from "dd-trace"
|
||||||
|
|
||||||
const REBOOT_CRON = "@reboot"
|
|
||||||
const WH_STEP_ID = definitions.WEBHOOK.stepId
|
const WH_STEP_ID = definitions.WEBHOOK.stepId
|
||||||
const CRON_STEP_ID = definitions.CRON.stepId
|
const CRON_STEP_ID = definitions.CRON.stepId
|
||||||
let Runner: Thread
|
let Runner: Thread
|
||||||
|
@ -198,6 +198,13 @@ export async function enableCronTrigger(appId: any, automation: Automation) {
|
||||||
!isRebootTrigger(automation) &&
|
!isRebootTrigger(automation) &&
|
||||||
trigger?.inputs.cron
|
trigger?.inputs.cron
|
||||||
) {
|
) {
|
||||||
|
const cronExp = trigger.inputs.cron
|
||||||
|
const validation = helpers.cron.validate(cronExp)
|
||||||
|
if (!validation.valid) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid automation CRON "${cronExp}" - ${validation.err.join(", ")}`
|
||||||
|
)
|
||||||
|
}
|
||||||
// make a job id rather than letting Bull decide, makes it easier to handle on way out
|
// make a job id rather than letting Bull decide, makes it easier to handle on way out
|
||||||
const jobId = `${appId}_cron_${newid()}`
|
const jobId = `${appId}_cron_${newid()}`
|
||||||
const job: any = await automationQueue.add(
|
const job: any = await automationQueue.add(
|
||||||
|
@ -205,7 +212,7 @@ export async function enableCronTrigger(appId: any, automation: Automation) {
|
||||||
automation,
|
automation,
|
||||||
event: { appId, timestamp: Date.now() },
|
event: { appId, timestamp: Date.now() },
|
||||||
},
|
},
|
||||||
{ repeat: { cron: trigger.inputs.cron }, jobId }
|
{ repeat: { cron: cronExp }, jobId }
|
||||||
)
|
)
|
||||||
// Assign cron job ID from bull so we can remove it later if the cron trigger is removed
|
// Assign cron job ID from bull so we can remove it later if the cron trigger is removed
|
||||||
trigger.cronJobId = job.id
|
trigger.cronJobId = job.id
|
||||||
|
|
|
@ -97,6 +97,7 @@ const environment = {
|
||||||
APP_MIGRATION_TIMEOUT: parseIntSafe(process.env.APP_MIGRATION_TIMEOUT),
|
APP_MIGRATION_TIMEOUT: parseIntSafe(process.env.APP_MIGRATION_TIMEOUT),
|
||||||
JS_RUNNER_MEMORY_LIMIT:
|
JS_RUNNER_MEMORY_LIMIT:
|
||||||
parseIntSafe(process.env.JS_RUNNER_MEMORY_LIMIT) || 64,
|
parseIntSafe(process.env.JS_RUNNER_MEMORY_LIMIT) || 64,
|
||||||
|
LOG_JS_ERRORS: process.env.LOG_JS_ERRORS,
|
||||||
}
|
}
|
||||||
|
|
||||||
// threading can cause memory issues with node-ts in development
|
// threading can cause memory issues with node-ts in development
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import vm from "vm"
|
import vm from "vm"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { setJSRunner } from "@budibase/string-templates"
|
import { setJSRunner, setOnErrorLog } from "@budibase/string-templates"
|
||||||
import { context, timers } from "@budibase/backend-core"
|
import { context, logging, timers } from "@budibase/backend-core"
|
||||||
import tracer from "dd-trace"
|
import tracer from "dd-trace"
|
||||||
|
import { serializeError } from "serialize-error"
|
||||||
|
|
||||||
type TrackerFn = <T>(f: () => T) => T
|
type TrackerFn = <T>(f: () => T) => T
|
||||||
|
|
||||||
|
@ -58,4 +59,10 @@ export function init() {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (env.LOG_JS_ERRORS) {
|
||||||
|
setOnErrorLog((error: Error) => {
|
||||||
|
logging.logWarn(JSON.stringify(serializeError(error)))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,11 +75,13 @@ export async function save(
|
||||||
if (!tableView) continue
|
if (!tableView) continue
|
||||||
|
|
||||||
if (viewsSdk.isV2(tableView)) {
|
if (viewsSdk.isV2(tableView)) {
|
||||||
table.views[view] = viewsSdk.syncSchema(
|
if (oldTable?.views && oldTable.views[view]) {
|
||||||
oldTable!.views![view] as ViewV2,
|
table.views[view] = viewsSdk.syncSchema(
|
||||||
table.schema,
|
oldTable.views[view] as ViewV2,
|
||||||
renaming
|
table.schema,
|
||||||
)
|
renaming
|
||||||
|
)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,12 +84,12 @@ type DefaultUserValues = {
|
||||||
csrfToken: string
|
csrfToken: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TableToBuild extends Omit<Table, "sourceId" | "sourceType"> {
|
export interface TableToBuild extends Omit<Table, "sourceId" | "sourceType"> {
|
||||||
sourceId?: string
|
sourceId?: string
|
||||||
sourceType?: TableSourceType
|
sourceType?: TableSourceType
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestConfiguration {
|
export default class TestConfiguration {
|
||||||
server: any
|
server: any
|
||||||
request: supertest.SuperTest<supertest.Test> | undefined
|
request: supertest.SuperTest<supertest.Test> | undefined
|
||||||
started: boolean
|
started: boolean
|
||||||
|
@ -912,4 +912,4 @@ class TestConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = TestConfiguration
|
module.exports = TestConfiguration
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { QueryVariable } from "./definitions"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import * as db from "../db"
|
import * as db from "../db"
|
||||||
import { redis, db as dbCore } from "@budibase/backend-core"
|
import { redis, db as dbCore } from "@budibase/backend-core"
|
||||||
|
import * as jsRunner from "../jsRunner"
|
||||||
|
|
||||||
const VARIABLE_TTL_SECONDS = 3600
|
const VARIABLE_TTL_SECONDS = 3600
|
||||||
let client: any
|
let client: any
|
||||||
|
@ -29,7 +30,9 @@ export function threadSetup() {
|
||||||
console.debug(`[${env.FORKED_PROCESS_NAME}] thread setup skipped`)
|
console.debug(`[${env.FORKED_PROCESS_NAME}] thread setup skipped`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug(`[${env.FORKED_PROCESS_NAME}] thread setup running`)
|
console.debug(`[${env.FORKED_PROCESS_NAME}] thread setup running`)
|
||||||
|
jsRunner.init()
|
||||||
db.init()
|
db.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
"check:types": "tsc -p tsconfig.json --noEmit --paths null"
|
"check:types": "tsc -p tsconfig.json --noEmit --paths null"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/types": "0.0.0"
|
"@budibase/types": "0.0.0",
|
||||||
|
"cron-validate": "1.4.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
|
|
|
@ -99,6 +99,8 @@ export const SocketSessionTTL = 60
|
||||||
export const ValidQueryNameRegex = /^[^()]*$/
|
export const ValidQueryNameRegex = /^[^()]*$/
|
||||||
export const ValidColumnNameRegex = /^[_a-zA-Z0-9\s]*$/g
|
export const ValidColumnNameRegex = /^[_a-zA-Z0-9\s]*$/g
|
||||||
|
|
||||||
|
export const REBOOT_CRON = "@reboot"
|
||||||
|
|
||||||
export const InvalidFileExtensions = [
|
export const InvalidFileExtensions = [
|
||||||
"7z",
|
"7z",
|
||||||
"action",
|
"action",
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
import cronValidate from "cron-validate"
|
||||||
|
|
||||||
|
const INPUT_CRON_START = "(Input cron: "
|
||||||
|
const ERROR_SWAPS = {
|
||||||
|
"smaller than lower limit": "less than",
|
||||||
|
"bigger than upper limit": "greater than",
|
||||||
|
daysOfMonth: "'days of the month'",
|
||||||
|
daysOfWeek: "'days of the week'",
|
||||||
|
years: "'years'",
|
||||||
|
months: "'months'",
|
||||||
|
hours: "'hours'",
|
||||||
|
minutes: "'minutes'",
|
||||||
|
seconds: "'seconds'",
|
||||||
|
}
|
||||||
|
|
||||||
|
function improveErrors(errors: string[]): string[] {
|
||||||
|
const finalErrors: string[] = []
|
||||||
|
|
||||||
|
for (let error of errors) {
|
||||||
|
if (error.includes(INPUT_CRON_START)) {
|
||||||
|
error = error.split(INPUT_CRON_START)[0].trim()
|
||||||
|
}
|
||||||
|
for (let [oldErr, newErr] of Object.entries(ERROR_SWAPS)) {
|
||||||
|
if (error.includes(oldErr)) {
|
||||||
|
error = error.replace(new RegExp(oldErr, "g"), newErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalErrors.push(error)
|
||||||
|
}
|
||||||
|
return finalErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validate(
|
||||||
|
cronExpression: string
|
||||||
|
): { valid: false; err: string[] } | { valid: true } {
|
||||||
|
const result = cronValidate(cronExpression, {
|
||||||
|
preset: "npm-node-cron",
|
||||||
|
override: {
|
||||||
|
useSeconds: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (!result.isValid()) {
|
||||||
|
return { valid: false, err: improveErrors(result.getError()) }
|
||||||
|
} else {
|
||||||
|
return { valid: true }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
export * from "./helpers"
|
export * from "./helpers"
|
||||||
export * from "./integrations"
|
export * from "./integrations"
|
||||||
|
export * as cron from "./cron"
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { expect, describe, it } from "vitest"
|
||||||
|
import { cron } from "../helpers"
|
||||||
|
|
||||||
|
describe("check valid and invalid crons", () => {
|
||||||
|
it("invalid - 0 0 0 11 *", () => {
|
||||||
|
expect(cron.validate("0 0 0 11 *")).toStrictEqual({
|
||||||
|
valid: false,
|
||||||
|
err: [expect.stringContaining("less than '1'")],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("invalid - 5 4 32 1 1", () => {
|
||||||
|
expect(cron.validate("5 4 32 1 1")).toStrictEqual({
|
||||||
|
valid: false,
|
||||||
|
err: [expect.stringContaining("greater than '31'")],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("valid - * * * * *", () => {
|
||||||
|
expect(cron.validate("* * * * *")).toStrictEqual({ valid: true })
|
||||||
|
})
|
||||||
|
})
|
|
@ -8,6 +8,9 @@ const { getJsHelperList } = require("./list")
|
||||||
let runJS
|
let runJS
|
||||||
module.exports.setJSRunner = runner => (runJS = runner)
|
module.exports.setJSRunner = runner => (runJS = runner)
|
||||||
|
|
||||||
|
let onErrorLog
|
||||||
|
module.exports.setOnErrorLog = delegate => (onErrorLog = delegate)
|
||||||
|
|
||||||
// Helper utility to strip square brackets from a value
|
// Helper utility to strip square brackets from a value
|
||||||
const removeSquareBrackets = value => {
|
const removeSquareBrackets = value => {
|
||||||
if (!value || typeof value !== "string") {
|
if (!value || typeof value !== "string") {
|
||||||
|
@ -56,6 +59,8 @@ module.exports.processJS = (handlebars, context) => {
|
||||||
const res = { data: runJS(js, sandboxContext) }
|
const res = { data: runJS(js, sandboxContext) }
|
||||||
return `{{${LITERAL_MARKER} js_result-${JSON.stringify(res)}}}`
|
return `{{${LITERAL_MARKER} js_result-${JSON.stringify(res)}}}`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
onErrorLog && onErrorLog(error)
|
||||||
|
|
||||||
if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
|
if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
|
||||||
return "Timed out while executing JS"
|
return "Timed out while executing JS"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
const vm = require("vm")
|
||||||
const handlebars = require("handlebars")
|
const handlebars = require("handlebars")
|
||||||
const { registerAll, registerMinimum } = require("./helpers/index")
|
const { registerAll, registerMinimum } = require("./helpers/index")
|
||||||
const processors = require("./processors")
|
const processors = require("./processors")
|
||||||
|
@ -365,6 +366,7 @@ module.exports.doesContainString = (template, string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.setJSRunner = javascript.setJSRunner
|
module.exports.setJSRunner = javascript.setJSRunner
|
||||||
|
module.exports.setOnErrorLog = javascript.setOnErrorLog
|
||||||
|
|
||||||
module.exports.convertToJS = hbs => {
|
module.exports.convertToJS = hbs => {
|
||||||
const blocks = exports.findHBSBlocks(hbs)
|
const blocks = exports.findHBSBlocks(hbs)
|
||||||
|
@ -401,3 +403,19 @@ const errors = require("./errors")
|
||||||
module.exports.JsErrorTimeout = errors.JsErrorTimeout
|
module.exports.JsErrorTimeout = errors.JsErrorTimeout
|
||||||
|
|
||||||
module.exports.helpersToRemoveForJs = helpersToRemoveForJs
|
module.exports.helpersToRemoveForJs = helpersToRemoveForJs
|
||||||
|
|
||||||
|
if (process && !process.env.NO_JS) {
|
||||||
|
/**
|
||||||
|
* Use polyfilled vm to run JS scripts in a browser Env
|
||||||
|
*/
|
||||||
|
javascript.setJSRunner((js, context) => {
|
||||||
|
context = {
|
||||||
|
...context,
|
||||||
|
alert: undefined,
|
||||||
|
setInterval: undefined,
|
||||||
|
setTimeout: undefined,
|
||||||
|
}
|
||||||
|
vm.createContext(context)
|
||||||
|
return vm.runInNewContext(js, context, { timeout: 1000 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import vm from "vm"
|
|
||||||
import templates from "./index.js"
|
import templates from "./index.js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,23 +19,8 @@ export const disableEscaping = templates.disableEscaping
|
||||||
export const findHBSBlocks = templates.findHBSBlocks
|
export const findHBSBlocks = templates.findHBSBlocks
|
||||||
export const convertToJS = templates.convertToJS
|
export const convertToJS = templates.convertToJS
|
||||||
export const setJSRunner = templates.setJSRunner
|
export const setJSRunner = templates.setJSRunner
|
||||||
|
export const setOnErrorLog = templates.setOnErrorLog
|
||||||
export const FIND_ANY_HBS_REGEX = templates.FIND_ANY_HBS_REGEX
|
export const FIND_ANY_HBS_REGEX = templates.FIND_ANY_HBS_REGEX
|
||||||
export const helpersToRemoveForJs = templates.helpersToRemoveForJs
|
export const helpersToRemoveForJs = templates.helpersToRemoveForJs
|
||||||
|
|
||||||
if (process && !process.env.NO_JS) {
|
|
||||||
/**
|
|
||||||
* Use polyfilled vm to run JS scripts in a browser Env
|
|
||||||
*/
|
|
||||||
setJSRunner((js, context) => {
|
|
||||||
context = {
|
|
||||||
...context,
|
|
||||||
alert: undefined,
|
|
||||||
setInterval: undefined,
|
|
||||||
setTimeout: undefined,
|
|
||||||
}
|
|
||||||
vm.createContext(context)
|
|
||||||
return vm.runInNewContext(js, context, { timeout: 1000 })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export * from "./errors.js"
|
export * from "./errors.js"
|
||||||
|
|
36
yarn.lock
36
yarn.lock
|
@ -1980,6 +1980,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
||||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||||
|
|
||||||
|
"@babel/runtime@^7.10.5":
|
||||||
|
version "7.23.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7"
|
||||||
|
integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
"@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.21.0", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
"@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.21.0", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||||
version "7.23.8"
|
version "7.23.8"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650"
|
||||||
|
@ -5431,6 +5438,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.200.tgz#435b6035c7eba9cdf1e039af8212c9e9281e7149"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.200.tgz#435b6035c7eba9cdf1e039af8212c9e9281e7149"
|
||||||
integrity sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==
|
integrity sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==
|
||||||
|
|
||||||
|
"@types/lodash@^4.14.165":
|
||||||
|
version "4.14.202"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8"
|
||||||
|
integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==
|
||||||
|
|
||||||
"@types/long@^4.0.0":
|
"@types/long@^4.0.0":
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
|
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
|
||||||
|
@ -8541,6 +8553,13 @@ cron-parser@^4.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
luxon "^3.2.1"
|
luxon "^3.2.1"
|
||||||
|
|
||||||
|
cron-validate@1.4.5:
|
||||||
|
version "1.4.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/cron-validate/-/cron-validate-1.4.5.tgz#eceb221f7558e6302e5f84c7b3a454fdf4d064c3"
|
||||||
|
integrity sha512-nKlOJEnYKudMn/aNyNH8xxWczlfpaazfWV32Pcx/2St51r2bxWbGhZD7uwzMcRhunA/ZNL+Htm/i0792Z59UMQ==
|
||||||
|
dependencies:
|
||||||
|
yup "0.32.9"
|
||||||
|
|
||||||
cross-spawn@^6.0.0:
|
cross-spawn@^6.0.0:
|
||||||
version "6.0.5"
|
version "6.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
||||||
|
@ -14440,7 +14459,7 @@ lock@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/lock/-/lock-1.1.0.tgz#53157499d1653b136ca66451071fca615703fa55"
|
resolved "https://registry.yarnpkg.com/lock/-/lock-1.1.0.tgz#53157499d1653b136ca66451071fca615703fa55"
|
||||||
integrity sha512-NZQIJJL5Rb9lMJ0Yl1JoVr9GSdo4HTPsUEWsSFzB8dE8DSoiLCVavWZPi7Rnlv/o73u6I24S/XYc/NmG4l8EKA==
|
integrity sha512-NZQIJJL5Rb9lMJ0Yl1JoVr9GSdo4HTPsUEWsSFzB8dE8DSoiLCVavWZPi7Rnlv/o73u6I24S/XYc/NmG4l8EKA==
|
||||||
|
|
||||||
lodash-es@^4.17.21:
|
lodash-es@^4.17.15, lodash-es@^4.17.21:
|
||||||
version "4.17.21"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||||
|
@ -14590,7 +14609,7 @@ lodash.xor@^4.5.0:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.xor/-/lodash.xor-4.5.0.tgz#4d48ed7e98095b0632582ba714d3ff8ae8fb1db6"
|
resolved "https://registry.yarnpkg.com/lodash.xor/-/lodash.xor-4.5.0.tgz#4d48ed7e98095b0632582ba714d3ff8ae8fb1db6"
|
||||||
integrity sha512-sVN2zimthq7aZ5sPGXnSz32rZPuqcparVW50chJQe+mzTYV+IsxSsl/2gnkWWE2Of7K3myBQBqtLKOUEHJKRsQ==
|
integrity sha512-sVN2zimthq7aZ5sPGXnSz32rZPuqcparVW50chJQe+mzTYV+IsxSsl/2gnkWWE2Of7K3myBQBqtLKOUEHJKRsQ==
|
||||||
|
|
||||||
lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.7.0:
|
lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.7.0:
|
||||||
version "4.17.21"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
@ -22020,6 +22039,19 @@ yocto-queue@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
|
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
|
||||||
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
|
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
|
||||||
|
|
||||||
|
yup@0.32.9:
|
||||||
|
version "0.32.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.9.tgz#9367bec6b1b0e39211ecbca598702e106019d872"
|
||||||
|
integrity sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.10.5"
|
||||||
|
"@types/lodash" "^4.14.165"
|
||||||
|
lodash "^4.17.20"
|
||||||
|
lodash-es "^4.17.15"
|
||||||
|
nanoclone "^0.2.1"
|
||||||
|
property-expr "^2.0.4"
|
||||||
|
toposort "^2.0.2"
|
||||||
|
|
||||||
yup@^0.32.11:
|
yup@^0.32.11:
|
||||||
version "0.32.11"
|
version "0.32.11"
|
||||||
resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5"
|
resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5"
|
||||||
|
|
Loading…
Reference in New Issue