1
0
Fork 0
mirror of synced 2024-07-11 01:06:04 +12:00

Merge branch 'develop' of github.com:Budibase/budibase into spreadsheet-integration

This commit is contained in:
Andrew Kingston 2023-04-18 21:00:42 +01:00
commit 9b2b071b88
77 changed files with 2243 additions and 400 deletions

View file

@ -56,6 +56,7 @@ jobs:
run: yarn install:pro $BRANCH $BASE_BRANCH run: yarn install:pro $BRANCH $BASE_BRANCH
- run: yarn - run: yarn
- run: yarn bootstrap - run: yarn bootstrap
- run: yarn build
- run: yarn test - run: yarn test
- uses: codecov/codecov-action@v3 - uses: codecov/codecov-action@v3
with: with:

View file

@ -1,5 +1,5 @@
{ {
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"npmClient": "yarn", "npmClient": "yarn",
"useWorkspaces": true, "useWorkspaces": true,
"packages": ["packages/*"], "packages": ["packages/*"],

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "name": "@budibase/backend-core",
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"description": "Budibase backend core libraries used in server and worker", "description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js", "main": "dist/src/index.js",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
@ -24,7 +24,7 @@
"dependencies": { "dependencies": {
"@budibase/nano": "10.1.2", "@budibase/nano": "10.1.2",
"@budibase/pouchdb-replication-stream": "1.2.10", "@budibase/pouchdb-replication-stream": "1.2.10",
"@budibase/types": "2.5.6-alpha.3", "@budibase/types": "2.5.6-alpha.5",
"@shopify/jest-koa-mocks": "5.0.1", "@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",
"aws-cloudfront-sign": "2.2.0", "aws-cloudfront-sign": "2.2.0",

View file

@ -1,5 +1,5 @@
export * as correlation from "./correlation/correlation" export * as correlation from "./correlation/correlation"
export { default as logger } from "./pino/logger" export { logger, disableLogger } from "./pino/logger"
export * from "./alerts" export * from "./alerts"
// turn off or on context logging i.e. tenantId, appId etc // turn off or on context logging i.e. tenantId, appId etc

View file

@ -5,6 +5,17 @@ import * as correlation from "../correlation"
import { IdentityType } from "@budibase/types" import { IdentityType } from "@budibase/types"
import { LOG_CONTEXT } from "../index" import { LOG_CONTEXT } from "../index"
// CORE LOGGERS - for disabling
const BUILT_INS = {
log: console.log,
error: console.error,
info: console.info,
warn: console.warn,
trace: console.trace,
debug: console.debug,
}
// LOGGER // LOGGER
const pinoOptions: LoggerOptions = { const pinoOptions: LoggerOptions = {
@ -31,6 +42,15 @@ if (env.isDev()) {
export const logger = pino(pinoOptions) export const logger = pino(pinoOptions)
export function disableLogger() {
console.log = BUILT_INS.log
console.error = BUILT_INS.error
console.info = BUILT_INS.info
console.warn = BUILT_INS.warn
console.trace = BUILT_INS.trace
console.debug = BUILT_INS.debug
}
// CONSOLE OVERRIDES // CONSOLE OVERRIDES
interface MergingObject { interface MergingObject {
@ -166,5 +186,3 @@ const getIdentity = () => {
} }
return identity return identity
} }
export default logger

View file

@ -1,5 +1,5 @@
import env from "../../environment" import env from "../../environment"
import logger from "./logger" import { logger } from "./logger"
import { IncomingMessage } from "http" import { IncomingMessage } from "http"
const pino = require("koa-pino-logger") const pino = require("koa-pino-logger")
import { Options } from "pino-http" import { Options } from "pino-http"

View file

@ -0,0 +1,83 @@
import { validate } from "../utils"
import fetch from "node-fetch"
import { PluginType } from "@budibase/types"
const repoUrl =
"https://raw.githubusercontent.com/Budibase/budibase-skeleton/master"
const automationLink = `${repoUrl}/automation/schema.json.hbs`
const componentLink = `${repoUrl}/component/schema.json.hbs`
const datasourceLink = `${repoUrl}/datasource/schema.json.hbs`
async function getSchema(link: string) {
const response = await fetch(link)
if (response.status > 300) {
return
}
const text = await response.text()
return JSON.parse(text)
}
async function runTest(opts: { link?: string; schema?: any }) {
let error
try {
let schema = opts.schema
if (opts.link) {
schema = await getSchema(opts.link)
}
validate(schema)
} catch (err) {
error = err
}
return error
}
describe("it should be able to validate an automation schema", () => {
it("should return automation skeleton schema is valid", async () => {
const error = await runTest({ link: automationLink })
expect(error).toBeUndefined()
})
it("should fail given invalid automation schema", async () => {
const error = await runTest({
schema: {
type: PluginType.AUTOMATION,
schema: {},
},
})
expect(error).toBeDefined()
})
})
describe("it should be able to validate a component schema", () => {
it("should return component skeleton schema is valid", async () => {
const error = await runTest({ link: componentLink })
expect(error).toBeUndefined()
})
it("should fail given invalid component schema", async () => {
const error = await runTest({
schema: {
type: PluginType.COMPONENT,
schema: {},
},
})
expect(error).toBeDefined()
})
})
describe("it should be able to validate a datasource schema", () => {
it("should return datasource skeleton schema is valid", async () => {
const error = await runTest({ link: datasourceLink })
expect(error).toBeUndefined()
})
it("should fail given invalid datasource schema", async () => {
const error = await runTest({
schema: {
type: PluginType.DATASOURCE,
schema: {},
},
})
expect(error).toBeDefined()
})
})

View file

@ -1,4 +1,12 @@
import { DatasourceFieldType, QueryType, PluginType } from "@budibase/types" import {
DatasourceFieldType,
QueryType,
PluginType,
AutomationStepType,
AutomationStepIdArray,
AutomationIOType,
AutomationCustomIOType,
} from "@budibase/types"
import joi from "joi" import joi from "joi"
const DATASOURCE_TYPES = [ const DATASOURCE_TYPES = [
@ -19,7 +27,7 @@ function runJoi(validator: joi.Schema, schema: any) {
function validateComponent(schema: any) { function validateComponent(schema: any) {
const validator = joi.object({ const validator = joi.object({
type: joi.string().allow("component").required(), type: joi.string().allow(PluginType.COMPONENT).required(),
metadata: joi.object().unknown(true).required(), metadata: joi.object().unknown(true).required(),
hash: joi.string().optional(), hash: joi.string().optional(),
version: joi.string().optional(), version: joi.string().optional(),
@ -53,7 +61,7 @@ function validateDatasource(schema: any) {
.required() .required()
const validator = joi.object({ const validator = joi.object({
type: joi.string().allow("datasource").required(), type: joi.string().allow(PluginType.DATASOURCE).required(),
metadata: joi.object().unknown(true).required(), metadata: joi.object().unknown(true).required(),
hash: joi.string().optional(), hash: joi.string().optional(),
version: joi.string().optional(), version: joi.string().optional(),
@ -82,6 +90,55 @@ function validateDatasource(schema: any) {
runJoi(validator, schema) runJoi(validator, schema)
} }
function validateAutomation(schema: any) {
const basePropsValidator = joi.object().pattern(joi.string(), {
type: joi
.string()
.allow(...Object.values(AutomationIOType))
.required(),
customType: joi.string().allow(...Object.values(AutomationCustomIOType)),
title: joi.string(),
description: joi.string(),
enum: joi.array().items(joi.string()),
pretty: joi.array().items(joi.string()),
})
const stepSchemaValidator = joi
.object({
properties: basePropsValidator,
required: joi.array().items(joi.string()),
})
.concat(basePropsValidator)
.required()
const validator = joi.object({
type: joi.string().allow(PluginType.AUTOMATION).required(),
metadata: joi.object().unknown(true).required(),
hash: joi.string().optional(),
version: joi.string().optional(),
schema: joi.object({
name: joi.string().required(),
tagline: joi.string().required(),
icon: joi.string().required(),
description: joi.string().required(),
type: joi
.string()
.allow(AutomationStepType.ACTION, AutomationStepType.LOGIC)
.required(),
stepId: joi
.string()
.disallow(...AutomationStepIdArray)
.required(),
inputs: joi.object().optional(),
schema: joi
.object({
inputs: stepSchemaValidator,
outputs: stepSchemaValidator,
})
.required(),
}),
})
runJoi(validator, schema)
}
export function validate(schema: any) { export function validate(schema: any) {
switch (schema?.type) { switch (schema?.type) {
case PluginType.COMPONENT: case PluginType.COMPONENT:
@ -90,6 +147,9 @@ export function validate(schema: any) {
case PluginType.DATASOURCE: case PluginType.DATASOURCE:
validateDatasource(schema) validateDatasource(schema)
break break
case PluginType.AUTOMATION:
validateAutomation(schema)
break
default: default:
throw new Error(`Unknown plugin type - check schema.json: ${schema.type}`) throw new Error(`Unknown plugin type - check schema.json: ${schema.type}`)
} }

View file

@ -1,7 +1,7 @@
const mockFetch = jest.fn((url: any, opts: any) => { const mockFetch = jest.fn((url: any, opts: any) => {
const fetch = jest.requireActual("node-fetch") const fetch = jest.requireActual("node-fetch")
const env = jest.requireActual("../../../../src/environment").default const env = jest.requireActual("../../../../src/environment").default
if (url.includes(env.COUCH_DB_URL)) { if (url.includes(env.COUCH_DB_URL) || url.includes("raw.github")) {
return fetch(url, opts) return fetch(url, opts)
} }
return undefined return undefined

View file

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",
@ -38,8 +38,8 @@
], ],
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "1.2.1", "@adobe/spectrum-css-workflow-icons": "1.2.1",
"@budibase/shared-core": "2.5.6-alpha.3", "@budibase/shared-core": "2.5.6-alpha.5",
"@budibase/string-templates": "2.5.6-alpha.3", "@budibase/string-templates": "2.5.6-alpha.5",
"@spectrum-css/accordion": "3.0.24", "@spectrum-css/accordion": "3.0.24",
"@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actionbutton": "1.0.1",
"@spectrum-css/actiongroup": "1.0.1", "@spectrum-css/actiongroup": "1.0.1",

View file

@ -9,6 +9,7 @@
<TooltipWrapper {tooltip} {size}> <TooltipWrapper {tooltip} {size}>
<label <label
data-testid="label"
class:muted class:muted
for="" for=""
class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`} class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}

View file

@ -9,6 +9,7 @@
</script> </script>
<p <p
data-testid="typography-body"
style={` style={`
${weight ? `font-weight:${weight};` : ""} ${weight ? `font-weight:${weight};` : ""}
${textAlign ? `text-align:${textAlign};` : ""} ${textAlign ? `text-align:${textAlign};` : ""}

View file

@ -9,6 +9,7 @@
</script> </script>
<h1 <h1
data-testid="typography-heading"
style={textAlign ? `text-align:${textAlign}` : ``} style={textAlign ? `text-align:${textAlign}` : ``}
class:noPadding class:noPadding
class="spectrum-Heading spectrum-Heading--size{size} spectrum-Heading--{weight}" class="spectrum-Heading spectrum-Heading--size{size} spectrum-Heading--{weight}"

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -9,7 +9,7 @@
"dev:builder": "routify -c dev:vite", "dev:builder": "routify -c dev:vite",
"dev:vite": "vite --host 0.0.0.0", "dev:vite": "vite --host 0.0.0.0",
"rollup": "rollup -c -w", "rollup": "rollup -c -w",
"test": "jest" "test": "vitest"
}, },
"jest": { "jest": {
"globals": { "globals": {
@ -58,11 +58,11 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "2.5.6-alpha.3", "@budibase/bbui": "2.5.6-alpha.5",
"@budibase/client": "2.5.6-alpha.3", "@budibase/client": "2.5.6-alpha.5",
"@budibase/frontend-core": "2.5.6-alpha.3", "@budibase/frontend-core": "2.5.6-alpha.5",
"@budibase/shared-core": "2.5.6-alpha.3", "@budibase/shared-core": "2.5.6-alpha.5",
"@budibase/string-templates": "2.5.6-alpha.3", "@budibase/string-templates": "2.5.6-alpha.5",
"@fortawesome/fontawesome-svg-core": "^6.2.1", "@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-brands-svg-icons": "^6.2.1", "@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1", "@fortawesome/free-solid-svg-icons": "^6.2.1",
@ -93,13 +93,14 @@
"@roxi/routify": "2.18.5", "@roxi/routify": "2.18.5",
"@sveltejs/vite-plugin-svelte": "1.0.1", "@sveltejs/vite-plugin-svelte": "1.0.1",
"@testing-library/jest-dom": "^5.11.10", "@testing-library/jest-dom": "^5.11.10",
"@testing-library/svelte": "^3.0.0", "@testing-library/svelte": "^3.2.2",
"babel-jest": "^26.6.3", "babel-jest": "^26.6.3",
"cypress": "^9.3.1", "cypress": "^9.3.1",
"cypress-multi-reporters": "^1.6.0", "cypress-multi-reporters": "^1.6.0",
"cypress-terminal-report": "^1.4.1", "cypress-terminal-report": "^1.4.1",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^26.6.3", "jest": "^26.6.3",
"jsdom": "^21.1.1",
"mochawesome": "^7.1.3", "mochawesome": "^7.1.3",
"mochawesome-merge": "^4.2.1", "mochawesome-merge": "^4.2.1",
"mochawesome-report-generator": "^6.2.0", "mochawesome-report-generator": "^6.2.0",
@ -113,7 +114,8 @@
"ts-node": "10.8.1", "ts-node": "10.8.1",
"tsconfig-paths": "4.0.0", "tsconfig-paths": "4.0.0",
"typescript": "4.7.3", "typescript": "4.7.3",
"vite": "^3.0.8" "vite": "^3.0.8",
"vitest": "^0.29.2"
}, },
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072" "gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
} }

View file

@ -1,13 +1,13 @@
import * as jsonpatch from "fast-json-patch/index.mjs" import * as jsonpatch from "fast-json-patch/index.mjs"
import { writable, derived, get } from "svelte/store" import { writable, derived, get } from "svelte/store"
const Operations = { export const Operations = {
Add: "Add", Add: "Add",
Delete: "Delete", Delete: "Delete",
Change: "Change", Change: "Change",
} }
const initialState = { export const initialState = {
history: [], history: [],
position: 0, position: 0,
loading: false, loading: false,

View file

@ -0,0 +1,345 @@
import { it, expect, describe, beforeEach, vi } from "vitest"
import { Operations, initialState, createHistoryStore } from "./history"
import { writable, derived, get } from "svelte/store"
import * as jsonpatch from "fast-json-patch/index.mjs"
vi.mock("svelte/store", () => {
return {
writable: vi.fn(),
derived: vi.fn(),
get: vi.fn(),
}
})
vi.mock("fast-json-patch/index.mjs", () => {
return {
compare: vi.fn(),
deepClone: vi.fn(),
applyPatch: vi.fn(),
}
})
describe("admin store", () => {
beforeEach(ctx => {
vi.clearAllMocks()
ctx.writableReturn = { update: vi.fn(), subscribe: vi.fn(), set: vi.fn() }
writable.mockReturnValue(ctx.writableReturn)
ctx.derivedReturn = { subscribe: vi.fn() }
derived.mockReturnValue(ctx.derivedReturn)
ctx.getDoc = vi.fn().mockReturnValue({})
ctx.selectDoc = vi.fn().mockReturnValue({})
ctx.beforeAction = vi.fn()
ctx.afterAction = vi.fn()
ctx.returnedStore = createHistoryStore({
getDoc: ctx.getDoc,
selectDoc: ctx.selectDoc,
beforeAction: ctx.beforeAction,
afterAction: ctx.afterAction,
})
})
describe("init", () => {
it("inits the writable store with the default config", () => {
expect(writable).toHaveBeenCalledTimes(1)
expect(writable).toHaveBeenCalledWith(initialState)
})
it("inits the derived store with the initial writable store and an update function", () => {
expect(derived).toHaveBeenCalledTimes(1)
expect(derived.calls[0][1]({ position: 0, history: [] })).toEqual({
position: 0,
history: [],
canUndo: false,
canRedo: false,
})
})
it("returns the created store and methods to manipulate it", ctx => {
expect(ctx.returnedStore).toEqual({
subscribe: expect.toBe(ctx.derivedReturn.subscribe),
wrapSaveDoc: expect.toBeFunc(),
wrapDeleteDoc: expect.toBeFunc(),
reset: expect.toBeFunc(),
undo: expect.toBeFunc(),
redo: expect.toBeFunc(),
})
})
})
describe("wrapSaveDoc", () => {
beforeEach(async ctx => {
ctx.saveFn = vi.fn().mockResolvedValue("fn")
ctx.doc = { _id: "id" }
ctx.oldDoc = { _id: "oldDoc" }
ctx.newDoc = { _id: "newDoc" }
ctx.getDoc.mockReturnValue(ctx.oldDoc)
jsonpatch.deepClone.mockReturnValue(ctx.newDoc)
vi.stubGlobal("Math", { random: vi.fn() })
ctx.forwardPatch = { foo: 1 }
ctx.backwardsPatch = { bar: 2 }
jsonpatch.compare.mockReturnValueOnce(ctx.forwardPatch)
jsonpatch.compare.mockReturnValueOnce(ctx.backwardsPatch)
Math.random.mockReturnValue(1234)
const wrappedSaveFn = ctx.returnedStore.wrapSaveDoc(ctx.saveFn)
await wrappedSaveFn(ctx.doc, null)
})
it("sets the state to loading", ctx => {
expect(ctx.writableReturn.update.calls[0][0]({})).toEqual({
loading: true,
})
})
it("retrieves the old doc", ctx => {
expect(ctx.getDoc).toHaveBeenCalledTimes(1)
expect(ctx.getDoc).toHaveBeenCalledWith("id")
})
it("clones the new doc", ctx => {
expect(ctx.saveFn).toHaveBeenCalledTimes(1)
expect(ctx.saveFn).toHaveBeenCalledWith(ctx.doc)
expect(jsonpatch.deepClone).toHaveBeenCalledTimes(1)
expect(jsonpatch.deepClone).toHaveBeenCalledWith("fn")
})
it("creates the undo/redo patches", ctx => {
expect(jsonpatch.compare).toHaveBeenCalledTimes(2)
expect(jsonpatch.compare.calls[0]).toEqual([ctx.oldDoc, ctx.doc])
expect(jsonpatch.compare.calls[1]).toEqual([ctx.doc, ctx.oldDoc])
})
it("saves the operation", ctx => {
expect(
ctx.writableReturn.update.calls[1][0]({
history: [],
position: 0,
})
).toEqual({
history: [
{
type: Operations.Change,
backwardsPatch: ctx.backwardsPatch,
forwardPatch: ctx.forwardPatch,
doc: ctx.newDoc,
id: 1234,
},
],
position: 1,
})
})
it("sets the state to not loading", ctx => {
expect(ctx.writableReturn.update).toHaveBeenCalledTimes(3)
expect(ctx.writableReturn.update.calls[2][0]({})).toEqual({
loading: false,
})
})
})
describe("wrapDeleteDoc", () => {
beforeEach(async ctx => {
ctx.deleteFn = vi.fn().mockResolvedValue("fn")
ctx.doc = { _id: "id" }
ctx.oldDoc = { _id: "oldDoc" }
jsonpatch.deepClone.mockReturnValue(ctx.oldDoc)
vi.stubGlobal("Math", { random: vi.fn() })
Math.random.mockReturnValue(1235)
const wrappedDeleteDoc = ctx.returnedStore.wrapDeleteDoc(ctx.deleteFn)
await wrappedDeleteDoc(ctx.doc, null)
})
it("sets the state to loading", ctx => {
expect(ctx.writableReturn.update.calls[0][0]({})).toEqual({
loading: true,
})
})
it("clones the doc", ctx => {
expect(jsonpatch.deepClone).toHaveBeenCalledTimes(1)
expect(jsonpatch.deepClone).toHaveBeenCalledWith(ctx.doc)
})
it("calls the delete fn with the doc", ctx => {
expect(ctx.deleteFn).toHaveBeenCalledTimes(1)
expect(ctx.deleteFn).toHaveBeenCalledWith(ctx.doc)
})
it("saves the operation", ctx => {
expect(
ctx.writableReturn.update.calls[1][0]({
history: [],
position: 0,
})
).toEqual({
history: [
{
type: Operations.Delete,
doc: ctx.oldDoc,
id: 1235,
},
],
position: 1,
})
})
it("sets the state to not loading", ctx => {
expect(ctx.writableReturn.update).toHaveBeenCalledTimes(3)
expect(ctx.writableReturn.update.calls[2][0]({})).toEqual({
loading: false,
})
})
})
describe("reset", () => {
beforeEach(ctx => {
ctx.returnedStore.reset()
})
it("sets the store to the initial state", ctx => {
expect(ctx.writableReturn.set).toHaveBeenCalledTimes(1)
expect(ctx.writableReturn.set).toHaveBeenCalledWith(initialState)
})
})
describe("undo", () => {
beforeEach(async ctx => {
ctx.history = [
{ type: Operations.Delete, doc: { _id: 1236, _rev: "rev" } },
]
ctx.derivedReturn = {
subscribe: vi.fn(),
canUndo: true,
history: ctx.history,
position: 1,
loading: false,
}
get.mockReturnValue(ctx.derivedReturn)
jsonpatch.deepClone.mockReturnValueOnce(ctx.history[0].doc)
ctx.newDoc = { _id: 1337 }
ctx.saveFn = vi.fn().mockResolvedValue(ctx.newDoc)
jsonpatch.deepClone.mockReturnValueOnce(ctx.newDoc)
// We need to create a wrapped saveFn before undo can be invoked
ctx.returnedStore.wrapSaveDoc(ctx.saveFn)
await ctx.returnedStore.undo()
})
it("sets the state to loading", ctx => {
expect(ctx.writableReturn.update.calls[0][0]({})).toEqual({
loading: true,
})
})
it("calls the beforeAction", ctx => {
expect(ctx.beforeAction).toHaveBeenCalledTimes(1)
expect(ctx.beforeAction).toHaveBeenCalledWith(ctx.history[0])
})
it("sets the state to the previous position", ctx => {
expect(
ctx.writableReturn.update.calls[1][0]({ history: [], position: 1 })
).toEqual({ history: [], position: 0 })
})
it("clones the doc", ctx => {
expect(jsonpatch.deepClone).toHaveBeenCalledWith(ctx.history[0].doc)
})
it("deletes the doc's rev", ctx => {
expect(ctx.history[0].doc._rev).toBe(undefined)
})
it("calls the wrappedSaveFn", ctx => {
expect(jsonpatch.deepClone).toHaveBeenCalledWith(ctx.newDoc)
expect(ctx.saveFn).toHaveBeenCalledWith(ctx.history[0].doc)
})
it("calls selectDoc", ctx => {
expect(ctx.selectDoc).toHaveBeenCalledWith(ctx.newDoc._id)
})
it("sets the state to not loading", ctx => {
expect(ctx.writableReturn.update.calls[5][0]({})).toEqual({
loading: false,
})
})
it("calls the afterAction", ctx => {
expect(ctx.afterAction).toHaveBeenCalledTimes(1)
expect(ctx.afterAction).toHaveBeenCalledWith(ctx.history[0])
})
})
describe("redo", () => {
beforeEach(async ctx => {
ctx.history = [
{ type: Operations.Delete, doc: { _id: 1236, _rev: "rev" } },
]
ctx.derivedReturn = {
subscribe: vi.fn(),
canRedo: true,
history: ctx.history,
position: 0,
loading: false,
}
get.mockReturnValue(ctx.derivedReturn)
ctx.latestDoc = { _id: 1337 }
ctx.getDoc.mockReturnValue(ctx.latestDoc)
// We need to create a wrapped deleteFn before redo can be invoked
ctx.deleteFn = vi.fn().mockResolvedValue(ctx.newDoc)
ctx.returnedStore.wrapDeleteDoc(ctx.deleteFn)
await ctx.returnedStore.redo()
})
it("sets the state to loading", ctx => {
expect(ctx.writableReturn.update.calls[0][0]({})).toEqual({
loading: true,
})
})
it("calls the beforeAction", ctx => {
expect(ctx.beforeAction).toHaveBeenCalledTimes(1)
expect(ctx.beforeAction).toHaveBeenCalledWith(ctx.history[0])
})
it("sets the state to the next position", ctx => {
expect(
ctx.writableReturn.update.calls[1][0]({ history: [], position: 0 })
).toEqual({ history: [], position: 1 })
})
it("calls the wrappedDeleteFn", ctx => {
expect(ctx.deleteFn).toHaveBeenCalledWith(ctx.latestDoc)
})
it("sets the state to not loading", ctx => {
expect(ctx.writableReturn.update.calls[5][0]({})).toEqual({
loading: false,
})
})
it("calls the afterAction", ctx => {
expect(ctx.afterAction).toHaveBeenCalledTimes(1)
expect(ctx.afterAction).toHaveBeenCalledWith(ctx.history[0])
})
})
})

View file

@ -26,7 +26,7 @@
const external = actions.reduce((acc, elm) => { const external = actions.reduce((acc, elm) => {
const [k, v] = elm const [k, v] = elm
if (!v.internal) { if (!v.internal && !v.custom) {
acc[k] = v acc[k] = v
} }
return acc return acc
@ -41,6 +41,15 @@
return acc return acc
}, {}) }, {})
const plugins = actions.reduce((acc, elm) => {
const [k, v] = elm
if (v.custom) {
acc[k] = v
}
return acc
}, {})
console.log(plugins)
const selectAction = action => { const selectAction = action => {
actionVal = action actionVal = action
selectedAction = action.name selectedAction = action.name
@ -116,6 +125,26 @@
{/each} {/each}
</div> </div>
</Layout> </Layout>
{#if Object.keys(plugins).length}
<Layout noPadding gap="XS">
<Detail size="S">Plugins</Detail>
<div class="item-list">
{#each Object.entries(plugins) as [idx, action]}
<div
class="item"
class:selected={selectedAction === action.name}
on:click={() => selectAction(action)}
>
<div class="item-body">
<Icon name={action.icon} />
<Body size="XS">{action.name}</Body>
</div>
</div>
{/each}
</div>
</Layout>
{/if}
</ModalContent> </ModalContent>
<style> <style>
@ -126,7 +155,7 @@
} }
.item-list { .item-list {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); grid-template-columns: repeat(2, minmax(150px, 1fr));
grid-gap: var(--spectrum-alias-grid-baseline); grid-gap: var(--spectrum-alias-grid-baseline);
} }

View file

@ -0,0 +1,37 @@
import { it, expect, describe, beforeEach } from "vitest"
import { render, screen } from "@testing-library/svelte"
import "@testing-library/jest-dom"
import EditableLabel from "./EditableLabel.svelte"
describe("EditableLabel", () => {
describe('type of "heading"', () => {
beforeEach(() => {
render(EditableLabel, { type: "heading", value: "foo" })
})
it("renders a heading", () => {
expect(screen.getByTestId("typography-heading")).toBeInTheDocument()
})
})
describe('type of "body"', () => {
beforeEach(() => {
render(EditableLabel, { type: "body", value: "foo" })
})
it("renders a body", () => {
expect(screen.getByTestId("typography-body")).toBeInTheDocument()
})
})
describe("any other type", () => {
beforeEach(() => {
render(EditableLabel, { type: "", value: "foo" })
})
it("renders a label", () => {
expect(screen.getByTestId("label")).toBeInTheDocument()
})
})
})

View file

@ -1,3 +1,4 @@
import { expect, describe, it } from "vitest"
import { breakQueryString, buildQueryString } from "../data/utils" import { breakQueryString, buildQueryString } from "../data/utils"
describe("check query string utils", () => { describe("check query string utils", () => {
@ -26,11 +27,15 @@ describe("check query string utils", () => {
it("should be able to build with a binding", () => { it("should be able to build with a binding", () => {
const queryString = buildQueryString(obj2) const queryString = buildQueryString(obj2)
expect(queryString).toBe("key1={{ binding.awd }}&key2={{ binding.sed }}%20%20") expect(queryString).toBe(
"key1={{ binding.awd }}&key2={{ binding.sed }}%20%20"
)
}) })
it("should be able to break with a binding", () => { it("should be able to break with a binding", () => {
const broken = breakQueryString("key1={{ binding.awd }}&key2={{ binding.sed }}%20%20") const broken = breakQueryString(
"key1={{ binding.awd }}&key2={{ binding.sed }}%20%20"
)
expect(broken.key1).toBe(obj2.key1) expect(broken.key1).toBe(obj2.key1)
expect(broken.key2).toBe(obj2.key2) expect(broken.key2).toBe(obj2.key2)
}) })

View file

@ -1,7 +1,7 @@
const { duplicateName } = require("../duplicate") import { expect, describe, it } from "vitest"
import { duplicateName } from "../duplicate"
describe("duplicate", () => { describe("duplicate", () => {
describe("duplicates a name ", () => { describe("duplicates a name ", () => {
it("with a single existing", async () => { it("with a single existing", async () => {
const names = ["foo"] const names = ["foo"]

View file

@ -3,8 +3,7 @@ import { API } from "api"
import { auth } from "stores/portal" import { auth } from "stores/portal"
import { banner } from "@budibase/bbui" import { banner } from "@budibase/bbui"
export function createAdminStore() { export const DEFAULT_CONFIG = {
const DEFAULT_CONFIG = {
loaded: false, loaded: false,
multiTenancy: false, multiTenancy: false,
cloud: false, cloud: false,
@ -20,6 +19,7 @@ export function createAdminStore() {
}, },
} }
export function createAdminStore() {
const admin = writable(DEFAULT_CONFIG) const admin = writable(DEFAULT_CONFIG)
async function init() { async function init() {

View file

@ -0,0 +1,268 @@
import { it, expect, describe, beforeEach, vi } from "vitest"
import { DEFAULT_CONFIG, createAdminStore } from "./admin"
import { writable, get } from "svelte/store"
import { API } from "api"
import { auth } from "stores/portal"
import { banner } from "@budibase/bbui"
vi.mock("stores/portal", () => {
return { auth: vi.fn() }
})
// explict mock that is overwritten later so that the singleton admin store doesn't throw an error when partially mocked
vi.mock("svelte/store", () => {
return {
writable: vi.fn(() => ({
subscribe: vi.fn(),
update: vi.fn(),
})),
get: vi.fn(),
}
})
vi.mock("api", () => {
return {
API: {
checkImportComplete: vi.fn(),
getEnvironment: vi.fn(),
getSystemStatus: vi.fn(),
getChecklist: vi.fn(),
},
}
})
vi.mock("@budibase/bbui", () => {
return { banner: { showStatus: vi.fn() } }
})
describe("admin store", () => {
beforeEach(ctx => {
vi.clearAllMocks()
ctx.writableReturn = { update: vi.fn(), subscribe: vi.fn() }
writable.mockReturnValue(ctx.writableReturn)
ctx.returnedStore = createAdminStore()
})
it("inits the writable store with the default config", () => {
expect(writable).toHaveBeenCalledTimes(1)
expect(writable).toHaveBeenCalledWith(DEFAULT_CONFIG)
})
it("returns the created store", ctx => {
expect(ctx.returnedStore).toEqual({
subscribe: expect.toBe(ctx.writableReturn.subscribe),
init: expect.toBeFunc(),
checkImportComplete: expect.toBeFunc(),
unload: expect.toBeFunc(),
getChecklist: expect.toBeFunc(),
})
})
describe("init method", () => {
beforeEach(async ctx => {
let getMockIndex = 0
ctx.getMockValues = [
{ tenantId: "tenantId" },
{ cloud: true },
{ status: { health: { passing: false } } },
]
get.mockImplementation(() => {
const value = ctx.getMockValues[getMockIndex]
getMockIndex++
return value
})
API.getChecklist.mockReturnValue("checklist")
API.getEnvironment.mockReturnValue({
multiTenancy: true,
cloud: true,
disableAccountPortal: true,
accountPortalUrl: "url",
isDev: true,
})
API.getSystemStatus.mockReturnValue("status")
})
describe("getCheckList", () => {
beforeEach(async ctx => {
await ctx.returnedStore.init()
})
it("adds the checklist to the store", ctx => {
expect(get).toHaveBeenNthCalledWith(1, auth)
expect(API.getChecklist).toHaveBeenCalledTimes(1)
expect(API.getChecklist).toHaveBeenCalledWith("tenantId")
expect(ctx.writableReturn.update.calls[0][0]({ foo: "foo" })).toEqual({
foo: "foo",
checklist: "checklist",
})
})
})
describe("getEnvironment", () => {
beforeEach(async ctx => {
await ctx.returnedStore.init()
})
it("adds the environment to the store", ctx => {
expect(API.getEnvironment).toHaveBeenCalledTimes(1)
expect(API.getEnvironment).toHaveBeenCalledWith()
expect(ctx.writableReturn.update.calls[1][0]({ foo: "foo" })).toEqual({
foo: "foo",
multiTenancy: true,
cloud: true,
disableAccountPortal: true,
accountPortalUrl: "url",
isDev: true,
})
})
})
describe("system status", () => {
describe("non cloud", () => {
beforeEach(async ctx => {
ctx.getMockValues[1].cloud = false
await ctx.returnedStore.init()
})
it("getSystemStatus", () => {
expect(API.getSystemStatus).toHaveBeenCalledTimes(0)
})
it("checkStatus", () => {
expect(get).toHaveBeenCalledTimes(2)
expect(banner.showStatus).toHaveBeenCalledTimes(0)
})
})
describe("cloud with healthy admin status", () => {
beforeEach(async ctx => {
ctx.getMockValues[1].cloud = true
ctx.getMockValues[2].status.health.passing = true
await ctx.returnedStore.init()
})
it("getSystemStatus", ctx => {
expect(get).toHaveBeenNthCalledWith(2, ctx.writableReturn)
expect(API.getSystemStatus).toHaveBeenCalledTimes(1)
expect(API.getEnvironment).toHaveBeenCalledWith()
expect(ctx.writableReturn.update.calls[2][0]({ foo: "foo" })).toEqual(
{ foo: "foo", status: "status" }
)
})
it("checkStatus", ctx => {
expect(get).toHaveBeenCalledTimes(3)
expect(get).toHaveBeenNthCalledWith(3, ctx.writableReturn)
expect(banner.showStatus).toHaveBeenCalledTimes(0)
})
})
describe("cloud with unhealthy admin status", () => {
beforeEach(async ctx => {
ctx.getMockValues[1].cloud = true
ctx.getMockValues[2].status.health.passing = false
await ctx.returnedStore.init()
})
it("getSystemStatus", ctx => {
expect(get).toHaveBeenNthCalledWith(2, ctx.writableReturn)
expect(API.getSystemStatus).toHaveBeenCalledTimes(1)
expect(API.getEnvironment).toHaveBeenCalledWith()
expect(ctx.writableReturn.update.calls[2][0]({ foo: "foo" })).toEqual(
{ foo: "foo", status: "status" }
)
})
it("checkStatus", ctx => {
expect(get).toHaveBeenCalledTimes(3)
expect(get).toHaveBeenNthCalledWith(3, ctx.writableReturn)
expect(banner.showStatus).toHaveBeenCalledTimes(1)
expect(banner.showStatus).toHaveBeenCalledWith()
})
})
})
describe("getEnvironment", () => {
beforeEach(async ctx => {
await ctx.returnedStore.init()
})
it("marks the store as loaded", ctx => {
expect(ctx.writableReturn.update.calls[3][0]({ foo: "foo" })).toEqual({
foo: "foo",
loaded: true,
})
})
})
})
describe("checkImportComplete", () => {
describe("import complete", () => {
beforeEach(async ctx => {
API.checkImportComplete.mockReturnValue({ imported: true })
await ctx.returnedStore.checkImportComplete()
})
it("updates the store's importComplete parameter", ctx => {
expect(ctx.writableReturn.update.calls[0][0]({ foo: "foo" })).toEqual({
foo: "foo",
importComplete: true,
})
})
})
describe("import not complete", () => {
beforeEach(async ctx => {
// Can be null
API.checkImportComplete.mockReturnValue(null)
await ctx.returnedStore.checkImportComplete()
})
it("updates the store's importComplete parameter", ctx => {
expect(ctx.writableReturn.update.calls[0][0]({ foo: "foo" })).toEqual({
foo: "foo",
importComplete: false,
})
})
})
})
describe("unload", () => {
beforeEach(ctx => {
ctx.returnedStore.unload()
})
it("sets the store's loaded parameter to false", ctx => {
expect(ctx.writableReturn.update.calls[0][0]({ loaded: true })).toEqual({
loaded: false,
})
})
})
describe("getChecklist", () => {
beforeEach(async ctx => {
get.mockReturnValue({ tenantId: "tenantId" })
API.getChecklist.mockReturnValue("checklist")
await ctx.returnedStore.getChecklist()
})
it("updates the store with the new checklist", ctx => {
expect(get).toHaveBeenNthCalledWith(1, auth)
expect(API.getChecklist).toHaveBeenCalledTimes(1)
expect(API.getChecklist).toHaveBeenCalledWith("tenantId")
expect(ctx.writableReturn.update.calls[0][0]({ foo: "foo" })).toEqual({
foo: "foo",
checklist: "checklist",
})
})
})
})

View file

@ -0,0 +1,173 @@
import { it, expect, describe, beforeEach, vi } from "vitest"
import { createBackupsStore } from "./backups"
import { writable } from "svelte/store"
import { API } from "api"
vi.mock("svelte/store", () => {
return {
writable: vi.fn(() => ({
subscribe: vi.fn(),
update: vi.fn(),
})),
}
})
vi.mock("api", () => {
return {
API: {
searchBackups: vi.fn(() => "searchBackupsReturn"),
restoreBackup: vi.fn(() => "restoreBackupReturn"),
deleteBackup: vi.fn(() => "deleteBackupReturn"),
createManualBackup: vi.fn(() => "createManualBackupReturn"),
updateBackup: vi.fn(() => "updateBackupReturn"),
},
}
})
describe("backups store", () => {
beforeEach(ctx => {
vi.clearAllMocks()
ctx.writableReturn = { update: vi.fn(), subscribe: vi.fn() }
writable.mockReturnValue(ctx.writableReturn)
ctx.returnedStore = createBackupsStore()
})
it("inits the writable store with the default config", () => {
expect(writable).toHaveBeenCalledTimes(1)
expect(writable).toHaveBeenCalledWith({})
})
describe("createManualBackup", () => {
beforeEach(async ctx => {
ctx.appId = "appId"
ctx.value = await ctx.returnedStore.createManualBackup(ctx.appId)
})
it("calls and returns the API createManualBackup method", ctx => {
expect(API.createManualBackup).toHaveBeenCalledTimes(1)
expect(API.createManualBackup).toHaveBeenCalledWith(ctx.appId)
expect(ctx.value).toBe("createManualBackupReturn")
})
})
describe("searchBackups", () => {
beforeEach(async ctx => {
ctx.appId = "appId"
ctx.trigger = "trigger"
ctx.type = "type"
ctx.page = "page"
ctx.startDate = "startDate"
ctx.endDate = "endDate"
ctx.value = await ctx.returnedStore.searchBackups({
appId: ctx.appId,
trigger: ctx.trigger,
type: ctx.type,
page: ctx.page,
startDate: ctx.startDate,
endDate: ctx.endDate,
})
})
it("calls and returns the API searchBackups method", ctx => {
expect(API.searchBackups).toHaveBeenCalledTimes(1)
expect(API.searchBackups).toHaveBeenCalledWith({
appId: ctx.appId,
trigger: ctx.trigger,
type: ctx.type,
page: ctx.page,
startDate: ctx.startDate,
endDate: ctx.endDate,
})
expect(ctx.value).toBe("searchBackupsReturn")
})
})
describe("selectBackup", () => {
beforeEach(ctx => {
ctx.backupId = "backupId"
ctx.returnedStore.selectBackup(ctx.backupId)
})
it("sets the state with the selected backup", ctx => {
expect(ctx.writableReturn.update).toHaveBeenCalledTimes(1)
expect(ctx.writableReturn.update.calls[0][0]({})).toEqual({
selectedBackup: ctx.backupId,
})
})
})
describe("deleteBackup", () => {
beforeEach(async ctx => {
ctx.appId = "appId"
ctx.backupId = "backupId"
ctx.value = await ctx.returnedStore.deleteBackup({
appId: ctx.appId,
backupId: ctx.backupId,
})
})
it("calls and returns the API deleteBackup method", ctx => {
expect(API.deleteBackup).toHaveBeenCalledTimes(1)
expect(API.deleteBackup).toHaveBeenCalledWith({
appId: ctx.appId,
backupId: ctx.backupId,
})
expect(ctx.value).toBe("deleteBackupReturn")
})
})
describe("restoreBackup", () => {
beforeEach(async ctx => {
ctx.appId = "appId"
ctx.backupId = "backupId"
ctx.$name = "name" // `name` is used by some sort of internal ctx thing and is readonly
ctx.value = await ctx.returnedStore.restoreBackup({
appId: ctx.appId,
backupId: ctx.backupId,
name: ctx.$name,
})
})
it("calls and returns the API restoreBackup method", ctx => {
expect(API.restoreBackup).toHaveBeenCalledTimes(1)
expect(API.restoreBackup).toHaveBeenCalledWith({
appId: ctx.appId,
backupId: ctx.backupId,
name: ctx.$name,
})
expect(ctx.value).toBe("restoreBackupReturn")
})
})
describe("updateBackup", () => {
beforeEach(async ctx => {
ctx.appId = "appId"
ctx.backupId = "backupId"
ctx.$name = "name" // `name` is used by some sort of internal ctx thing and is readonly
ctx.value = await ctx.returnedStore.updateBackup({
appId: ctx.appId,
backupId: ctx.backupId,
name: ctx.$name,
})
})
it("calls and returns the API updateBackup method", ctx => {
expect(API.updateBackup).toHaveBeenCalledTimes(1)
expect(API.updateBackup).toHaveBeenCalledWith({
appId: ctx.appId,
backupId: ctx.backupId,
name: ctx.$name,
})
expect(ctx.value).toBe("updateBackupReturn")
})
})
describe("subscribe", () => {
it("calls and returns the API updateBackup method", ctx => {
expect(ctx.returnedStore.subscribe).toBe(ctx.writableReturn.subscribe)
})
})
})

View file

@ -15,6 +15,11 @@ export default defineConfig(({ mode }) => {
const isProduction = mode === "production" const isProduction = mode === "production"
const env = loadEnv(mode, process.cwd()) const env = loadEnv(mode, process.cwd())
return { return {
test: {
setupFiles: ["./vitest.setup.js"],
globals: true,
environment: "jsdom",
},
server: { server: {
fs: { fs: {
strict: false, strict: false,

View file

@ -0,0 +1,28 @@
import { expect } from "vitest"
expect.extend({
toBeFunc: received => {
if (typeof received === "function") {
return {
pass: true,
}
}
return {
message: () => `expected ${received} to be a function`,
pass: false,
}
},
toBe: (received, expected) => {
if (received === expected) {
return {
pass: true,
}
}
return {
message: () => `expected ${received} to be ${expected}`,
pass: false,
}
},
})

View file

@ -7,3 +7,6 @@ envoy.yaml
*.tar.gz *.tar.gz
prebuilds/ prebuilds/
dist/ dist/
budibase-automation/
budibase-component/
budibase-datasource/

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "dist/index.js", "main": "dist/index.js",
"bin": { "bin": {
@ -29,9 +29,9 @@
"outputPath": "build" "outputPath": "build"
}, },
"dependencies": { "dependencies": {
"@budibase/backend-core": "2.5.6-alpha.3", "@budibase/backend-core": "2.5.6-alpha.5",
"@budibase/string-templates": "2.5.6-alpha.3", "@budibase/string-templates": "2.5.6-alpha.5",
"@budibase/types": "2.5.6-alpha.3", "@budibase/types": "2.5.6-alpha.5",
"axios": "0.21.2", "axios": "0.21.2",
"chalk": "4.1.0", "chalk": "4.1.0",
"cli-progress": "3.11.2", "cli-progress": "3.11.2",

View file

@ -1,4 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
import { logging } from "@budibase/backend-core"
logging.disableLogger()
import "./prebuilds" import "./prebuilds"
import "./environment" import "./environment"
import { getCommands } from "./options" import { getCommands } from "./options"

View file

@ -54,7 +54,7 @@ async function init(opts: PluginOpts) {
if (!type || !PLUGIN_TYPE_ARR.includes(type)) { if (!type || !PLUGIN_TYPE_ARR.includes(type)) {
console.log( console.log(
error( error(
"Please provide a type to init, either 'component' or 'datasource'." "Please provide a type to init, either 'component', 'datasource' or 'automation'."
) )
) )
return return

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,11 +19,11 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "2.5.6-alpha.3", "@budibase/bbui": "2.5.6-alpha.5",
"@budibase/frontend-core": "2.5.6-alpha.3", "@budibase/frontend-core": "2.5.6-alpha.5",
"@budibase/shared-core": "2.5.6-alpha.3", "@budibase/shared-core": "2.5.6-alpha.5",
"@budibase/string-templates": "2.5.6-alpha.3", "@budibase/string-templates": "2.5.6-alpha.5",
"@budibase/types": "2.5.6-alpha.3", "@budibase/types": "2.5.6-alpha.5",
"@spectrum-css/button": "^3.0.3", "@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3", "@spectrum-css/divider": "^1.0.3",

View file

@ -1,13 +1,13 @@
{ {
"name": "@budibase/frontend-core", "name": "@budibase/frontend-core",
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"description": "Budibase frontend core libraries used in builder and client", "description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"dependencies": { "dependencies": {
"@budibase/bbui": "2.5.6-alpha.3", "@budibase/bbui": "2.5.6-alpha.5",
"@budibase/shared-core": "2.5.6-alpha.3", "@budibase/shared-core": "2.5.6-alpha.5",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"socket.io-client": "^4.6.1", "socket.io-client": "^4.6.1",

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/sdk", "name": "@budibase/sdk",
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"description": "Budibase Public API SDK", "description": "Budibase Public API SDK",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",

View file

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -45,12 +45,12 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "10.0.3", "@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "2.5.6-alpha.3", "@budibase/backend-core": "2.5.6-alpha.5",
"@budibase/client": "2.5.6-alpha.3", "@budibase/client": "2.5.6-alpha.5",
"@budibase/pro": "2.5.6-alpha.3", "@budibase/pro": "2.5.6-alpha.5",
"@budibase/shared-core": "2.5.6-alpha.3", "@budibase/shared-core": "2.5.6-alpha.5",
"@budibase/string-templates": "2.5.6-alpha.3", "@budibase/string-templates": "2.5.6-alpha.5",
"@budibase/types": "2.5.6-alpha.3", "@budibase/types": "2.5.6-alpha.5",
"@bull-board/api": "3.7.0", "@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4", "@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",

View file

@ -16,9 +16,15 @@ import { setTestFlag, clearTestFlag } from "../../utilities/redis"
import { context, cache, events } from "@budibase/backend-core" import { context, cache, events } from "@budibase/backend-core"
import { automations } from "@budibase/pro" import { automations } from "@budibase/pro"
import { Automation, BBContext } from "@budibase/types" import { Automation, BBContext } from "@budibase/types"
import { getActionDefinitions as actionDefs } from "../../automations/actions"
const ACTION_DEFS = removeDeprecated(actions.ACTION_DEFINITIONS) async function getActionDefinitions() {
const TRIGGER_DEFS = removeDeprecated(triggers.TRIGGER_DEFINITIONS) return removeDeprecated(await actionDefs())
}
function getTriggerDefinitions() {
return removeDeprecated(triggers.TRIGGER_DEFINITIONS)
}
/************************* /*************************
* * * *
@ -228,17 +234,17 @@ export async function clearLogError(ctx: BBContext) {
} }
export async function getActionList(ctx: BBContext) { export async function getActionList(ctx: BBContext) {
ctx.body = ACTION_DEFS ctx.body = await getActionDefinitions()
} }
export async function getTriggerList(ctx: BBContext) { export async function getTriggerList(ctx: BBContext) {
ctx.body = TRIGGER_DEFS ctx.body = getTriggerDefinitions()
} }
export async function getDefinitionList(ctx: BBContext) { export async function getDefinitionList(ctx: BBContext) {
ctx.body = { ctx.body = {
trigger: TRIGGER_DEFS, trigger: getTriggerDefinitions(),
action: ACTION_DEFS, action: await getActionDefinitions(),
} }
} }

View file

@ -1,31 +1,11 @@
import { npmUpload, urlUpload, githubUpload, fileUpload } from "./uploaders" import { npmUpload, urlUpload, githubUpload } from "./uploaders"
import { import { plugins as pluginCore } from "@budibase/backend-core"
plugins as pluginCore, import { PluginType, FileType, PluginSource } from "@budibase/types"
db as dbCore,
tenancy,
objectStore,
} from "@budibase/backend-core"
import { PluginType, FileType, PluginSource, Plugin } from "@budibase/types"
import env from "../../../environment" import env from "../../../environment"
import { clientAppSocket } from "../../../websockets" import { clientAppSocket } from "../../../websockets"
import sdk from "../../../sdk"
import { sdk as pro } from "@budibase/pro" import { sdk as pro } from "@budibase/pro"
export async function getPlugins(type?: PluginType) {
const db = tenancy.getGlobalDB()
const response = await db.allDocs(
dbCore.getPluginParams(null, {
include_docs: true,
})
)
let plugins = response.rows.map((row: any) => row.doc) as Plugin[]
plugins = objectStore.enrichPluginURLs(plugins)
if (type) {
return plugins.filter((plugin: Plugin) => plugin.schema?.type === type)
} else {
return plugins
}
}
export async function upload(ctx: any) { export async function upload(ctx: any) {
const plugins: FileType[] = const plugins: FileType[] =
ctx.request.files.file.length > 1 ctx.request.files.file.length > 1
@ -35,7 +15,7 @@ export async function upload(ctx: any) {
let docs = [] let docs = []
// can do single or multiple plugins // can do single or multiple plugins
for (let plugin of plugins) { for (let plugin of plugins) {
const doc = await processUploadedPlugin(plugin, PluginSource.FILE) const doc = await sdk.plugins.processUploaded(plugin, PluginSource.FILE)
docs.push(doc) docs.push(doc)
} }
ctx.body = { ctx.body = {
@ -105,7 +85,7 @@ export async function create(ctx: any) {
} }
export async function fetch(ctx: any) { export async function fetch(ctx: any) {
ctx.body = await getPlugins() ctx.body = await sdk.plugins.fetch()
} }
export async function destroy(ctx: any) { export async function destroy(ctx: any) {
@ -119,20 +99,3 @@ export async function destroy(ctx: any) {
ctx.throw(400, err.message) ctx.throw(400, err.message)
} }
} }
export async function processUploadedPlugin(
plugin: FileType,
source?: PluginSource
) {
const { metadata, directory } = await fileUpload(plugin)
pluginCore.validate(metadata?.schema)
// Only allow components in cloud
if (!env.SELF_HOSTED && metadata?.schema?.type !== PluginType.COMPONENT) {
throw new Error("Only component plugins are supported outside of self-host")
}
const doc = await pro.plugins.storePlugin(metadata, directory, source)
clientAppSocket.emit("plugin-update", { name: doc.name, hash: doc.hash })
return doc
}

View file

@ -7,7 +7,7 @@ const {
const setup = require("./utilities") const setup = require("./utilities")
const { basicAutomation, newAutomation, automationTrigger, automationStep } = setup.structures const { basicAutomation, newAutomation, automationTrigger, automationStep } = setup.structures
const MAX_RETRIES = 4 const MAX_RETRIES = 4
const { TRIGGER_DEFINITIONS, ACTION_DEFINITIONS } = require("../../../automations") const { TRIGGER_DEFINITIONS, BUILTIN_ACTION_DEFINITIONS } = require("../../../automations")
const { events } = require("@budibase/backend-core") const { events } = require("@budibase/backend-core")
@ -55,7 +55,7 @@ describe("/automations", () => {
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
let definitionsLength = Object.keys(ACTION_DEFINITIONS).length let definitionsLength = Object.keys(BUILTIN_ACTION_DEFINITIONS).length
definitionsLength-- // OUTGOING_WEBHOOK is deprecated definitionsLength-- // OUTGOING_WEBHOOK is deprecated
expect(Object.keys(res.body.action).length).toBeGreaterThanOrEqual(definitionsLength) expect(Object.keys(res.body.action).length).toBeGreaterThanOrEqual(definitionsLength)

View file

@ -15,7 +15,14 @@ import * as delay from "./steps/delay"
import * as queryRow from "./steps/queryRows" import * as queryRow from "./steps/queryRows"
import * as loop from "./steps/loop" import * as loop from "./steps/loop"
import env from "../environment" import env from "../environment"
import { AutomationStepSchema, AutomationStepInput } from "@budibase/types" import {
AutomationStepSchema,
AutomationStepInput,
PluginType,
AutomationStep,
} from "@budibase/types"
import sdk from "../sdk"
import { getAutomationPlugin } from "../utilities/fileSystem"
const ACTION_IMPLS: Record< const ACTION_IMPLS: Record<
string, string,
@ -38,7 +45,8 @@ const ACTION_IMPLS: Record<
zapier: zapier.run, zapier: zapier.run,
integromat: integromat.run, integromat: integromat.run,
} }
export const ACTION_DEFINITIONS: Record<string, AutomationStepSchema> = { export const BUILTIN_ACTION_DEFINITIONS: Record<string, AutomationStepSchema> =
{
SEND_EMAIL_SMTP: sendSmtpEmail.definition, SEND_EMAIL_SMTP: sendSmtpEmail.definition,
CREATE_ROW: createRow.definition, CREATE_ROW: createRow.definition,
UPDATE_ROW: updateRow.definition, UPDATE_ROW: updateRow.definition,
@ -66,12 +74,36 @@ if (env.SELF_HOSTED) {
// @ts-ignore // @ts-ignore
ACTION_IMPLS["EXECUTE_BASH"] = bash.run ACTION_IMPLS["EXECUTE_BASH"] = bash.run
// @ts-ignore // @ts-ignore
ACTION_DEFINITIONS["EXECUTE_BASH"] = bash.definition BUILTIN_ACTION_DEFINITIONS["EXECUTE_BASH"] = bash.definition
}
export async function getActionDefinitions() {
const actionDefinitions = BUILTIN_ACTION_DEFINITIONS
if (env.SELF_HOSTED) {
const plugins = await sdk.plugins.fetch(PluginType.AUTOMATION)
for (let plugin of plugins) {
const schema = plugin.schema.schema as AutomationStep
actionDefinitions[schema.stepId] = {
...schema,
custom: true,
}
}
}
return actionDefinitions
} }
/* istanbul ignore next */ /* istanbul ignore next */
export async function getAction(actionName: string) { export async function getAction(stepId: string) {
if (ACTION_IMPLS[actionName] != null) { if (ACTION_IMPLS[stepId] != null) {
return ACTION_IMPLS[actionName] return ACTION_IMPLS[stepId]
}
// must be a plugin
if (env.SELF_HOSTED) {
const plugins = await sdk.plugins.fetch(PluginType.AUTOMATION)
const found = plugins.find(plugin => plugin.schema.schema.stepId === stepId)
if (!found) {
throw new Error(`Unable to find action implementation for "${stepId}"`)
}
return (await getAutomationPlugin(found)).action
} }
} }

View file

@ -6,7 +6,7 @@ import BullQueue from "bull"
export { automationQueue } from "./bullboard" export { automationQueue } from "./bullboard"
export { shutdown } from "./bullboard" export { shutdown } from "./bullboard"
export { TRIGGER_DEFINITIONS } from "./triggers" export { TRIGGER_DEFINITIONS } from "./triggers"
export { ACTION_DEFINITIONS } from "./actions" export { BUILTIN_ACTION_DEFINITIONS, getActionDefinitions } from "./actions"
/** /**
* This module is built purely to kick off the worker farm and manage the inputs/outputs * This module is built purely to kick off the worker farm and manage the inputs/outputs

View file

@ -4,8 +4,11 @@ import * as automationUtils from "../automationUtils"
import environment from "../../environment" import environment from "../../environment"
import { import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationCustomIOType,
AutomationIOType,
AutomationStepInput, AutomationStepInput,
AutomationStepSchema,
AutomationStepType,
} from "@budibase/types" } from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
@ -13,7 +16,7 @@ export const definition: AutomationStepSchema = {
tagline: "Execute a bash command", tagline: "Execute a bash command",
icon: "JourneyEvent", icon: "JourneyEvent",
description: "Run a bash script", description: "Run a bash script",
type: "ACTION", type: AutomationStepType.ACTION,
internal: true, internal: true,
stepId: AutomationActionStepId.EXECUTE_BASH, stepId: AutomationActionStepId.EXECUTE_BASH,
inputs: {}, inputs: {},
@ -21,8 +24,8 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
code: { code: {
type: "string", type: AutomationIOType.STRING,
customType: "code", customType: AutomationCustomIOType.CODE,
title: "Code", title: "Code",
}, },
}, },
@ -31,17 +34,17 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
stdout: { stdout: {
type: "string", type: AutomationIOType.STRING,
description: "Standard output of your bash command or script", description: "Standard output of your bash command or script",
}, },
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the command was successful", description: "Whether the command was successful",
}, },
}, },
},
required: ["stdout"], required: ["stdout"],
}, },
},
} }
export async function run({ inputs, context }: AutomationStepInput) { export async function run({ inputs, context }: AutomationStepInput) {

View file

@ -3,8 +3,11 @@ import { cleanUpRow, getError } from "../automationUtils"
import { buildCtx } from "./utils" import { buildCtx } from "./utils"
import { import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationCustomIOType,
AutomationIOType,
AutomationStepInput, AutomationStepInput,
AutomationStepSchema,
AutomationStepType,
} from "@budibase/types" } from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
@ -12,7 +15,7 @@ export const definition: AutomationStepSchema = {
tagline: "Create a {{inputs.enriched.table.name}} row", tagline: "Create a {{inputs.enriched.table.name}} row",
icon: "TableRowAddBottom", icon: "TableRowAddBottom",
description: "Add a row to your database", description: "Add a row to your database",
type: "ACTION", type: AutomationStepType.ACTION,
internal: true, internal: true,
stepId: AutomationActionStepId.CREATE_ROW, stepId: AutomationActionStepId.CREATE_ROW,
inputs: {}, inputs: {},
@ -20,14 +23,14 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
row: { row: {
type: "object", type: AutomationIOType.OBJECT,
properties: { properties: {
tableId: { tableId: {
type: "string", type: AutomationIOType.STRING,
customType: "table", customType: AutomationCustomIOType.TABLE,
}, },
}, },
customType: "row", customType: AutomationCustomIOType.ROW,
title: "Table", title: "Table",
required: ["tableId"], required: ["tableId"],
}, },
@ -37,24 +40,24 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
row: { row: {
type: "object", type: AutomationIOType.OBJECT,
customType: "row", customType: AutomationCustomIOType.ROW,
description: "The new row", description: "The new row",
}, },
response: { response: {
type: "object", type: AutomationIOType.OBJECT,
description: "The response from the table", description: "The response from the table",
}, },
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the row creation was successful", description: "Whether the row creation was successful",
}, },
id: { id: {
type: "string", type: AutomationIOType.STRING,
description: "The identifier of the new row", description: "The identifier of the new row",
}, },
revision: { revision: {
type: "string", type: AutomationIOType.STRING,
description: "The revision of the new row", description: "The revision of the new row",
}, },
}, },

View file

@ -1,8 +1,10 @@
import { wait } from "../../utilities" import { wait } from "../../utilities"
import { import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationIOType,
AutomationStepInput, AutomationStepInput,
AutomationStepSchema,
AutomationStepType,
} from "@budibase/types" } from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
@ -17,7 +19,7 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
time: { time: {
type: "number", type: AutomationIOType.NUMBER,
title: "Delay in milliseconds", title: "Delay in milliseconds",
}, },
}, },
@ -26,14 +28,14 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the delay was successful", description: "Whether the delay was successful",
}, },
}, },
required: ["success"], required: ["success"],
}, },
}, },
type: "LOGIC", type: AutomationStepType.LOGIC,
} }
export async function run({ inputs }: AutomationStepInput) { export async function run({ inputs }: AutomationStepInput) {

View file

@ -3,8 +3,11 @@ import { buildCtx } from "./utils"
import { getError } from "../automationUtils" import { getError } from "../automationUtils"
import { import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema,
AutomationStepInput, AutomationStepInput,
AutomationStepSchema,
AutomationStepType,
AutomationIOType,
AutomationCustomIOType,
} from "@budibase/types" } from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
@ -12,7 +15,7 @@ export const definition: AutomationStepSchema = {
icon: "TableRowRemoveCenter", icon: "TableRowRemoveCenter",
name: "Delete Row", name: "Delete Row",
tagline: "Delete a {{inputs.enriched.table.name}} row", tagline: "Delete a {{inputs.enriched.table.name}} row",
type: "ACTION", type: AutomationStepType.ACTION,
stepId: AutomationActionStepId.DELETE_ROW, stepId: AutomationActionStepId.DELETE_ROW,
internal: true, internal: true,
inputs: {}, inputs: {},
@ -20,12 +23,12 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
tableId: { tableId: {
type: "string", type: AutomationIOType.STRING,
customType: "table", customType: AutomationCustomIOType.TABLE,
title: "Table", title: "Table",
}, },
id: { id: {
type: "string", type: AutomationIOType.STRING,
title: "Row ID", title: "Row ID",
}, },
}, },
@ -34,16 +37,16 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
row: { row: {
type: "object", type: AutomationIOType.OBJECT,
customType: "row", customType: AutomationCustomIOType.ROW,
description: "The deleted row", description: "The deleted row",
}, },
response: { response: {
type: "object", type: AutomationIOType.OBJECT,
description: "The response from the table", description: "The response from the table",
}, },
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the deletion was successful", description: "Whether the deletion was successful",
}, },
}, },

View file

@ -4,6 +4,8 @@ import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationStepSchema,
AutomationStepInput, AutomationStepInput,
AutomationStepType,
AutomationIOType,
} from "@budibase/types" } from "@budibase/types"
const DEFAULT_USERNAME = "Budibase Automate" const DEFAULT_USERNAME = "Budibase Automate"
@ -15,26 +17,26 @@ export const definition: AutomationStepSchema = {
description: "Send a message to a Discord server", description: "Send a message to a Discord server",
icon: "ri-discord-line", icon: "ri-discord-line",
stepId: AutomationActionStepId.discord, stepId: AutomationActionStepId.discord,
type: "ACTION", type: AutomationStepType.ACTION,
internal: false, internal: false,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
properties: { properties: {
url: { url: {
type: "string", type: AutomationIOType.STRING,
title: "Discord Webhook URL", title: "Discord Webhook URL",
}, },
username: { username: {
type: "string", type: AutomationIOType.STRING,
title: "Bot Name", title: "Bot Name",
}, },
avatar_url: { avatar_url: {
type: "string", type: AutomationIOType.STRING,
title: "Bot Avatar URL", title: "Bot Avatar URL",
}, },
content: { content: {
type: "string", type: AutomationIOType.STRING,
title: "Message", title: "Message",
}, },
}, },
@ -43,15 +45,15 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
httpStatus: { httpStatus: {
type: "number", type: AutomationIOType.NUMBER,
description: "The HTTP status code of the request", description: "The HTTP status code of the request",
}, },
response: { response: {
type: "string", type: AutomationIOType.STRING,
description: "The response from the Discord Webhook", description: "The response from the Discord Webhook",
}, },
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the message sent successfully", description: "Whether the message sent successfully",
}, },
}, },

View file

@ -3,8 +3,11 @@ import { buildCtx } from "./utils"
import * as automationUtils from "../automationUtils" import * as automationUtils from "../automationUtils"
import { import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationCustomIOType,
AutomationIOType,
AutomationStepInput, AutomationStepInput,
AutomationStepSchema,
AutomationStepType,
} from "@budibase/types" } from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
@ -12,7 +15,7 @@ export const definition: AutomationStepSchema = {
tagline: "Execute Data Connector", tagline: "Execute Data Connector",
icon: "Data", icon: "Data",
description: "Execute a query in an external data connector", description: "Execute a query in an external data connector",
type: "ACTION", type: AutomationStepType.ACTION,
stepId: AutomationActionStepId.EXECUTE_QUERY, stepId: AutomationActionStepId.EXECUTE_QUERY,
internal: true, internal: true,
inputs: {}, inputs: {},
@ -20,14 +23,14 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
query: { query: {
type: "object", type: AutomationIOType.OBJECT,
properties: { properties: {
queryId: { queryId: {
type: "string", type: AutomationIOType.STRING,
customType: "query", customType: AutomationCustomIOType.QUERY,
}, },
}, },
customType: "queryParams", customType: AutomationCustomIOType.QUERY_PARAMS,
title: "Parameters", title: "Parameters",
required: ["queryId"], required: ["queryId"],
}, },
@ -37,22 +40,22 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
response: { response: {
type: "object", type: AutomationIOType.OBJECT,
description: "The response from the datasource execution", description: "The response from the datasource execution",
}, },
info: { info: {
type: "object", type: AutomationIOType.OBJECT,
description: description:
"Some query types may return extra data, like headers from a REST query", "Some query types may return extra data, like headers from a REST query",
}, },
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the action was successful", description: "Whether the action was successful",
}, },
}, },
},
required: ["response", "success"], required: ["response", "success"],
}, },
},
} }
export async function run({ inputs, appId, emitter }: AutomationStepInput) { export async function run({ inputs, appId, emitter }: AutomationStepInput) {

View file

@ -3,8 +3,11 @@ import { buildCtx } from "./utils"
import * as automationUtils from "../automationUtils" import * as automationUtils from "../automationUtils"
import { import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationCustomIOType,
AutomationIOType,
AutomationStepInput, AutomationStepInput,
AutomationStepSchema,
AutomationStepType,
} from "@budibase/types" } from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
@ -12,7 +15,7 @@ export const definition: AutomationStepSchema = {
tagline: "Execute JavaScript Code", tagline: "Execute JavaScript Code",
icon: "Code", icon: "Code",
description: "Run a piece of JavaScript code in your automation", description: "Run a piece of JavaScript code in your automation",
type: "ACTION", type: AutomationStepType.ACTION,
internal: true, internal: true,
stepId: AutomationActionStepId.EXECUTE_SCRIPT, stepId: AutomationActionStepId.EXECUTE_SCRIPT,
inputs: {}, inputs: {},
@ -20,8 +23,8 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
code: { code: {
type: "string", type: AutomationIOType.STRING,
customType: "code", customType: AutomationCustomIOType.CODE,
title: "Code", title: "Code",
}, },
}, },
@ -30,17 +33,17 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
value: { value: {
type: "string", type: AutomationIOType.STRING,
description: "The result of the return statement", description: "The result of the return statement",
}, },
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the action was successful", description: "Whether the action was successful",
}, },
}, },
},
required: ["success"], required: ["success"],
}, },
},
} }
export async function run({ export async function run({

View file

@ -2,6 +2,8 @@ import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationStepSchema,
AutomationStepInput, AutomationStepInput,
AutomationStepType,
AutomationIOType,
} from "@budibase/types" } from "@budibase/types"
export const FilterConditions = { export const FilterConditions = {
@ -24,7 +26,7 @@ export const definition: AutomationStepSchema = {
icon: "Branch2", icon: "Branch2",
description: description:
"Conditionally halt automations which do not meet certain conditions", "Conditionally halt automations which do not meet certain conditions",
type: "LOGIC", type: AutomationStepType.LOGIC,
internal: true, internal: true,
stepId: AutomationActionStepId.FILTER, stepId: AutomationActionStepId.FILTER,
inputs: { inputs: {
@ -34,17 +36,17 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
field: { field: {
type: "string", type: AutomationIOType.STRING,
title: "Reference Value", title: "Reference Value",
}, },
condition: { condition: {
type: "string", type: AutomationIOType.STRING,
title: "Condition", title: "Condition",
enum: Object.values(FilterConditions), enum: Object.values(FilterConditions),
pretty: Object.values(PrettyFilterConditions), pretty: Object.values(PrettyFilterConditions),
}, },
value: { value: {
type: "string", type: AutomationIOType.STRING,
title: "Comparison Value", title: "Comparison Value",
}, },
}, },
@ -53,11 +55,11 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the action was successful", description: "Whether the action was successful",
}, },
result: { result: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the logic block passed", description: "Whether the logic block passed",
}, },
}, },

View file

@ -4,6 +4,8 @@ import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationStepSchema,
AutomationStepInput, AutomationStepInput,
AutomationStepType,
AutomationIOType,
} from "@budibase/types" } from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
@ -13,34 +15,34 @@ export const definition: AutomationStepSchema = {
"Performs a webhook call to Integromat and gets the response (if configured)", "Performs a webhook call to Integromat and gets the response (if configured)",
icon: "ri-shut-down-line", icon: "ri-shut-down-line",
stepId: AutomationActionStepId.integromat, stepId: AutomationActionStepId.integromat,
type: "ACTION", type: AutomationStepType.ACTION,
internal: false, internal: false,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
properties: { properties: {
url: { url: {
type: "string", type: AutomationIOType.STRING,
title: "Webhook URL", title: "Webhook URL",
}, },
value1: { value1: {
type: "string", type: AutomationIOType.STRING,
title: "Input Value 1", title: "Input Value 1",
}, },
value2: { value2: {
type: "string", type: AutomationIOType.STRING,
title: "Input Value 2", title: "Input Value 2",
}, },
value3: { value3: {
type: "string", type: AutomationIOType.STRING,
title: "Input Value 3", title: "Input Value 3",
}, },
value4: { value4: {
type: "string", type: AutomationIOType.STRING,
title: "Input Value 4", title: "Input Value 4",
}, },
value5: { value5: {
type: "string", type: AutomationIOType.STRING,
title: "Input Value 5", title: "Input Value 5",
}, },
}, },
@ -49,15 +51,15 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether call was successful", description: "Whether call was successful",
}, },
httpStatus: { httpStatus: {
type: "number", type: AutomationIOType.NUMBER,
description: "The HTTP status code returned", description: "The HTTP status code returned",
}, },
response: { response: {
type: "object", type: AutomationIOType.OBJECT,
description: "The webhook response - this can have properties", description: "The webhook response - this can have properties",
}, },
}, },

View file

@ -1,4 +1,10 @@
import { AutomationActionStepId, AutomationStepSchema } from "@budibase/types" import {
AutomationActionStepId,
AutomationCustomIOType,
AutomationIOType,
AutomationStepSchema,
AutomationStepType,
} from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
name: "Looping", name: "Looping",
@ -12,19 +18,19 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
option: { option: {
customType: "loopOption", customType: AutomationCustomIOType.LOOP_OPTION,
title: "Input type", title: "Input type",
}, },
binding: { binding: {
type: "string", type: AutomationIOType.STRING,
title: "Binding / Value", title: "Binding / Value",
}, },
iterations: { iterations: {
type: "number", type: AutomationIOType.NUMBER,
title: "Max loop iterations", title: "Max loop iterations",
}, },
failure: { failure: {
type: "string", type: AutomationIOType.STRING,
title: "Failure Condition", title: "Failure Condition",
}, },
}, },
@ -33,20 +39,20 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
items: { items: {
customType: "item", customType: AutomationCustomIOType.ITEM,
description: "The item currently being executed", description: "The item currently being executed",
}, },
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the message loop was successfully", description: "Whether the message loop was successfully",
}, },
iterations: { iterations: {
type: "number", type: AutomationIOType.NUMBER,
descriptions: "The amount of times the block ran", description: "The amount of times the block ran",
}, },
}, },
required: ["success", "items", "iterations"], required: ["success", "items", "iterations"],
}, },
}, },
type: "LOGIC", type: AutomationStepType.LOGIC,
} }

View file

@ -3,8 +3,11 @@ import { getFetchResponse } from "./utils"
import * as automationUtils from "../automationUtils" import * as automationUtils from "../automationUtils"
import { import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationCustomIOType,
AutomationIOType,
AutomationStepInput, AutomationStepInput,
AutomationStepSchema,
AutomationStepType,
} from "@budibase/types" } from "@budibase/types"
enum RequestType { enum RequestType {
@ -27,7 +30,7 @@ export const definition: AutomationStepSchema = {
tagline: "Send a {{inputs.requestMethod}} request", tagline: "Send a {{inputs.requestMethod}} request",
icon: "Send", icon: "Send",
description: "Send a request of specified method to a URL", description: "Send a request of specified method to a URL",
type: "ACTION", type: AutomationStepType.ACTION,
internal: true, internal: true,
stepId: AutomationActionStepId.OUTGOING_WEBHOOK, stepId: AutomationActionStepId.OUTGOING_WEBHOOK,
inputs: { inputs: {
@ -40,23 +43,23 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
requestMethod: { requestMethod: {
type: "string", type: AutomationIOType.STRING,
enum: Object.values(RequestType), enum: Object.values(RequestType),
title: "Request method", title: "Request method",
}, },
url: { url: {
type: "string", type: AutomationIOType.STRING,
title: "URL", title: "URL",
}, },
requestBody: { requestBody: {
type: "string", type: AutomationIOType.STRING,
title: "JSON Body", title: "JSON Body",
customType: "wide", customType: AutomationCustomIOType.WIDE,
}, },
headers: { headers: {
type: "string", type: AutomationIOType.STRING,
title: "Headers", title: "Headers",
customType: "wide", customType: AutomationCustomIOType.WIDE,
}, },
}, },
required: ["requestMethod", "url"], required: ["requestMethod", "url"],
@ -64,15 +67,15 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
response: { response: {
type: "object", type: AutomationIOType.OBJECT,
description: "The response from the webhook", description: "The response from the webhook",
}, },
httpStatus: { httpStatus: {
type: "number", type: AutomationIOType.NUMBER,
description: "The HTTP status code returned", description: "The HTTP status code returned",
}, },
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the action was successful", description: "Whether the action was successful",
}, },
}, },

View file

@ -5,8 +5,11 @@ import { buildCtx } from "./utils"
import * as automationUtils from "../automationUtils" import * as automationUtils from "../automationUtils"
import { import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationCustomIOType,
AutomationIOType,
AutomationStepInput, AutomationStepInput,
AutomationStepSchema,
AutomationStepType,
SearchFilters, SearchFilters,
Table, Table,
} from "@budibase/types" } from "@budibase/types"
@ -36,7 +39,7 @@ export const definition: AutomationStepSchema = {
icon: "Search", icon: "Search",
name: "Query rows", name: "Query rows",
tagline: "Query rows from {{inputs.enriched.table.name}} table", tagline: "Query rows from {{inputs.enriched.table.name}} table",
type: "ACTION", type: AutomationStepType.ACTION,
stepId: AutomationActionStepId.QUERY_ROWS, stepId: AutomationActionStepId.QUERY_ROWS,
internal: true, internal: true,
inputs: {}, inputs: {},
@ -44,35 +47,35 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
tableId: { tableId: {
type: "string", type: AutomationIOType.STRING,
customType: "table", customType: AutomationCustomIOType.TABLE,
title: "Table", title: "Table",
}, },
filters: { filters: {
type: "object", type: AutomationIOType.OBJECT,
customType: "filters", customType: AutomationCustomIOType.FILTERS,
title: "Filtering", title: "Filtering",
}, },
sortColumn: { sortColumn: {
type: "string", type: AutomationIOType.STRING,
title: "Sort Column", title: "Sort Column",
customType: "column", customType: AutomationCustomIOType.COLUMN,
}, },
sortOrder: { sortOrder: {
type: "string", type: AutomationIOType.STRING,
title: "Sort Order", title: "Sort Order",
enum: Object.values(SortOrder), enum: Object.values(SortOrder),
pretty: Object.values(SortOrderPretty), pretty: Object.values(SortOrderPretty),
}, },
limit: { limit: {
type: "number", type: AutomationIOType.NUMBER,
title: "Limit", title: "Limit",
customType: "queryLimit", customType: AutomationCustomIOType.QUERY_LIMIT,
}, },
onEmptyFilter: { onEmptyFilter: {
pretty: Object.values(EmptyFilterOptionPretty), pretty: Object.values(EmptyFilterOptionPretty),
enum: Object.values(EmptyFilterOption), enum: Object.values(EmptyFilterOption),
type: "string", type: AutomationIOType.STRING,
title: "When Filter Empty", title: "When Filter Empty",
}, },
}, },
@ -81,12 +84,12 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
rows: { rows: {
type: "array", type: AutomationIOType.ARRAY,
customType: "rows", customType: AutomationCustomIOType.ROWS,
description: "The rows that were found", description: "The rows that were found",
}, },
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the query was successful", description: "Whether the query was successful",
}, },
}, },

View file

@ -4,6 +4,8 @@ import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationStepSchema,
AutomationStepInput, AutomationStepInput,
AutomationStepType,
AutomationIOType,
} from "@budibase/types" } from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
@ -11,7 +13,7 @@ export const definition: AutomationStepSchema = {
tagline: "Send SMTP email to {{inputs.to}}", tagline: "Send SMTP email to {{inputs.to}}",
icon: "Email", icon: "Email",
name: "Send Email (SMTP)", name: "Send Email (SMTP)",
type: "ACTION", type: AutomationStepType.ACTION,
internal: true, internal: true,
stepId: AutomationActionStepId.SEND_EMAIL_SMTP, stepId: AutomationActionStepId.SEND_EMAIL_SMTP,
inputs: {}, inputs: {},
@ -19,27 +21,27 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
to: { to: {
type: "string", type: AutomationIOType.STRING,
title: "Send To", title: "Send To",
}, },
from: { from: {
type: "string", type: AutomationIOType.STRING,
title: "Send From", title: "Send From",
}, },
cc: { cc: {
type: "string", type: AutomationIOType.STRING,
title: "CC", title: "CC",
}, },
bcc: { bcc: {
type: "string", type: AutomationIOType.STRING,
title: "BCC", title: "BCC",
}, },
subject: { subject: {
type: "string", type: AutomationIOType.STRING,
title: "Email Subject", title: "Email Subject",
}, },
contents: { contents: {
type: "string", type: AutomationIOType.STRING,
title: "HTML Contents", title: "HTML Contents",
}, },
}, },
@ -48,11 +50,11 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the email was sent", description: "Whether the email was sent",
}, },
response: { response: {
type: "object", type: AutomationIOType.OBJECT,
description: "A response from the email client, this may be an error", description: "A response from the email client, this may be an error",
}, },
}, },

View file

@ -2,6 +2,8 @@ import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationStepSchema,
AutomationStepInput, AutomationStepInput,
AutomationStepType,
AutomationIOType,
} from "@budibase/types" } from "@budibase/types"
/** /**
@ -15,7 +17,7 @@ export const definition: AutomationStepSchema = {
tagline: "Console log a value in the backend", tagline: "Console log a value in the backend",
icon: "Monitoring", icon: "Monitoring",
description: "Logs the given text to the server (using console.log)", description: "Logs the given text to the server (using console.log)",
type: "ACTION", type: AutomationStepType.ACTION,
internal: true, internal: true,
stepId: AutomationActionStepId.SERVER_LOG, stepId: AutomationActionStepId.SERVER_LOG,
inputs: { inputs: {
@ -25,7 +27,7 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
text: { text: {
type: "string", type: AutomationIOType.STRING,
title: "Log", title: "Log",
}, },
}, },
@ -34,11 +36,11 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the action was successful", description: "Whether the action was successful",
}, },
message: { message: {
type: "string", type: AutomationIOType.STRING,
description: "What was output", description: "What was output",
}, },
}, },

View file

@ -4,6 +4,8 @@ import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationStepSchema,
AutomationStepInput, AutomationStepInput,
AutomationStepType,
AutomationIOType,
} from "@budibase/types" } from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
@ -12,18 +14,18 @@ export const definition: AutomationStepSchema = {
description: "Send a message to Slack", description: "Send a message to Slack",
icon: "ri-slack-line", icon: "ri-slack-line",
stepId: AutomationActionStepId.slack, stepId: AutomationActionStepId.slack,
type: "ACTION", type: AutomationStepType.ACTION,
internal: false, internal: false,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
properties: { properties: {
url: { url: {
type: "string", type: AutomationIOType.STRING,
title: "Incoming Webhook URL", title: "Incoming Webhook URL",
}, },
text: { text: {
type: "string", type: AutomationIOType.STRING,
title: "Message", title: "Message",
}, },
}, },
@ -32,15 +34,15 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
httpStatus: { httpStatus: {
type: "number", type: AutomationIOType.NUMBER,
description: "The HTTP status code of the request", description: "The HTTP status code of the request",
}, },
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the message sent successfully", description: "Whether the message sent successfully",
}, },
response: { response: {
type: "string", type: AutomationIOType.STRING,
description: "The response from the Slack Webhook", description: "The response from the Slack Webhook",
}, },
}, },

View file

@ -3,8 +3,11 @@ import * as automationUtils from "../automationUtils"
import { buildCtx } from "./utils" import { buildCtx } from "./utils"
import { import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationCustomIOType,
AutomationIOType,
AutomationStepInput, AutomationStepInput,
AutomationStepSchema,
AutomationStepType,
} from "@budibase/types" } from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
@ -12,7 +15,7 @@ export const definition: AutomationStepSchema = {
tagline: "Update a {{inputs.enriched.table.name}} row", tagline: "Update a {{inputs.enriched.table.name}} row",
icon: "Refresh", icon: "Refresh",
description: "Update a row in your database", description: "Update a row in your database",
type: "ACTION", type: AutomationStepType.ACTION,
internal: true, internal: true,
stepId: AutomationActionStepId.UPDATE_ROW, stepId: AutomationActionStepId.UPDATE_ROW,
inputs: {}, inputs: {},
@ -20,16 +23,16 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
meta: { meta: {
type: "object", type: AutomationIOType.OBJECT,
title: "Field settings", title: "Field settings",
}, },
row: { row: {
type: "object", type: AutomationIOType.OBJECT,
customType: "row", customType: AutomationCustomIOType.ROW,
title: "Table", title: "Table",
}, },
rowId: { rowId: {
type: "string", type: AutomationIOType.STRING,
title: "Row ID", title: "Row ID",
}, },
}, },
@ -38,24 +41,24 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
row: { row: {
type: "object", type: AutomationIOType.OBJECT,
customType: "row", customType: AutomationCustomIOType.ROW,
description: "The updated row", description: "The updated row",
}, },
response: { response: {
type: "object", type: AutomationIOType.OBJECT,
description: "The response from the table", description: "The response from the table",
}, },
success: { success: {
type: "boolean", type: AutomationIOType.BOOLEAN,
description: "Whether the action was successful", description: "Whether the action was successful",
}, },
id: { id: {
type: "string", type: AutomationIOType.STRING,
description: "The identifier of the updated row", description: "The identifier of the updated row",
}, },
revision: { revision: {
type: "string", type: AutomationIOType.STRING,
description: "The revision of the updated row", description: "The revision of the updated row",
}, },
}, },

View file

@ -4,12 +4,14 @@ import {
AutomationActionStepId, AutomationActionStepId,
AutomationStepSchema, AutomationStepSchema,
AutomationStepInput, AutomationStepInput,
AutomationStepType,
AutomationIOType,
} from "@budibase/types" } from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
name: "Zapier Webhook", name: "Zapier Webhook",
stepId: AutomationActionStepId.zapier, stepId: AutomationActionStepId.zapier,
type: "ACTION", type: AutomationStepType.ACTION,
internal: false, internal: false,
description: "Trigger a Zapier Zap via webhooks", description: "Trigger a Zapier Zap via webhooks",
tagline: "Trigger a Zapier Zap", tagline: "Trigger a Zapier Zap",
@ -19,27 +21,27 @@ export const definition: AutomationStepSchema = {
inputs: { inputs: {
properties: { properties: {
url: { url: {
type: "string", type: AutomationIOType.STRING,
title: "Webhook URL", title: "Webhook URL",
}, },
value1: { value1: {
type: "string", type: AutomationIOType.STRING,
title: "Payload Value 1", title: "Payload Value 1",
}, },
value2: { value2: {
type: "string", type: AutomationIOType.STRING,
title: "Payload Value 2", title: "Payload Value 2",
}, },
value3: { value3: {
type: "string", type: AutomationIOType.STRING,
title: "Payload Value 3", title: "Payload Value 3",
}, },
value4: { value4: {
type: "string", type: AutomationIOType.STRING,
title: "Payload Value 4", title: "Payload Value 4",
}, },
value5: { value5: {
type: "string", type: AutomationIOType.STRING,
title: "Payload Value 5", title: "Payload Value 5",
}, },
}, },
@ -48,11 +50,11 @@ export const definition: AutomationStepSchema = {
outputs: { outputs: {
properties: { properties: {
httpStatus: { httpStatus: {
type: "number", type: AutomationIOType.NUMBER,
description: "The HTTP status code of the request", description: "The HTTP status code of the request",
}, },
response: { response: {
type: "string", type: AutomationIOType.STRING,
description: "The response from Zapier", description: "The response from Zapier",
}, },
}, },

View file

@ -1,6 +1,6 @@
import TestConfig from "../../../tests/utilities/TestConfiguration" import TestConfig from "../../../tests/utilities/TestConfiguration"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import { ACTION_DEFINITIONS, getAction } from "../../actions" import { BUILTIN_ACTION_DEFINITIONS, getAction } from "../../actions"
import emitter from "../../../events/index" import emitter from "../../../events/index"
import env from "../../../environment" import env from "../../../environment"
@ -57,4 +57,4 @@ export async function runStep(stepId: string, inputs: any, stepContext?: any) {
} }
export const apiKey = "test" export const apiKey = "test"
export const actions = ACTION_DEFINITIONS export const actions = BUILTIN_ACTION_DEFINITIONS

View file

@ -1,4 +1,7 @@
import { import {
AutomationCustomIOType,
AutomationIOType,
AutomationStepType,
AutomationTriggerSchema, AutomationTriggerSchema,
AutomationTriggerStepId, AutomationTriggerStepId,
} from "@budibase/types" } from "@budibase/types"
@ -15,8 +18,8 @@ export const definition: AutomationTriggerSchema = {
inputs: { inputs: {
properties: { properties: {
fields: { fields: {
type: "object", type: AutomationIOType.OBJECT,
customType: "triggerSchema", customType: AutomationCustomIOType.TRIGGER_SCHEMA,
title: "Fields", title: "Fields",
}, },
}, },
@ -25,13 +28,13 @@ export const definition: AutomationTriggerSchema = {
outputs: { outputs: {
properties: { properties: {
fields: { fields: {
type: "object", type: AutomationIOType.OBJECT,
description: "Fields submitted from the app frontend", description: "Fields submitted from the app frontend",
customType: "triggerSchema", customType: AutomationCustomIOType.TRIGGER_SCHEMA,
}, },
}, },
required: ["fields"], required: ["fields"],
}, },
}, },
type: "TRIGGER", type: AutomationStepType.TRIGGER,
} }

View file

@ -1,4 +1,7 @@
import { import {
AutomationCustomIOType,
AutomationIOType,
AutomationStepType,
AutomationTriggerSchema, AutomationTriggerSchema,
AutomationTriggerStepId, AutomationTriggerStepId,
} from "@budibase/types" } from "@budibase/types"
@ -15,8 +18,8 @@ export const definition: AutomationTriggerSchema = {
inputs: { inputs: {
properties: { properties: {
cron: { cron: {
type: "string", type: AutomationIOType.STRING,
customType: "cron", customType: AutomationCustomIOType.CRON,
title: "Expression", title: "Expression",
}, },
}, },
@ -25,12 +28,12 @@ export const definition: AutomationTriggerSchema = {
outputs: { outputs: {
properties: { properties: {
timestamp: { timestamp: {
type: "number", type: AutomationIOType.NUMBER,
description: "Timestamp the cron was executed", description: "Timestamp the cron was executed",
}, },
}, },
required: ["timestamp"], required: ["timestamp"],
}, },
}, },
type: "TRIGGER", type: AutomationStepType.TRIGGER,
} }

View file

@ -1,4 +1,7 @@
import { import {
AutomationCustomIOType,
AutomationIOType,
AutomationStepType,
AutomationTriggerSchema, AutomationTriggerSchema,
AutomationTriggerStepId, AutomationTriggerStepId,
} from "@budibase/types" } from "@budibase/types"
@ -15,8 +18,8 @@ export const definition: AutomationTriggerSchema = {
inputs: { inputs: {
properties: { properties: {
tableId: { tableId: {
type: "string", type: AutomationIOType.STRING,
customType: "table", customType: AutomationCustomIOType.TABLE,
title: "Table", title: "Table",
}, },
}, },
@ -25,13 +28,13 @@ export const definition: AutomationTriggerSchema = {
outputs: { outputs: {
properties: { properties: {
row: { row: {
type: "object", type: AutomationIOType.OBJECT,
customType: "row", customType: AutomationCustomIOType.ROW,
description: "The row that was deleted", description: "The row that was deleted",
}, },
}, },
required: ["row"], required: ["row"],
}, },
}, },
type: "TRIGGER", type: AutomationStepType.TRIGGER,
} }

View file

@ -1,4 +1,7 @@
import { import {
AutomationCustomIOType,
AutomationIOType,
AutomationStepType,
AutomationTriggerSchema, AutomationTriggerSchema,
AutomationTriggerStepId, AutomationTriggerStepId,
} from "@budibase/types" } from "@budibase/types"
@ -15,8 +18,8 @@ export const definition: AutomationTriggerSchema = {
inputs: { inputs: {
properties: { properties: {
tableId: { tableId: {
type: "string", type: AutomationIOType.STRING,
customType: "table", customType: AutomationCustomIOType.TABLE,
title: "Table", title: "Table",
}, },
}, },
@ -25,21 +28,21 @@ export const definition: AutomationTriggerSchema = {
outputs: { outputs: {
properties: { properties: {
row: { row: {
type: "object", type: AutomationIOType.OBJECT,
customType: "row", customType: AutomationCustomIOType.ROW,
description: "The new row that was created", description: "The new row that was created",
}, },
id: { id: {
type: "string", type: AutomationIOType.STRING,
description: "Row ID - can be used for updating", description: "Row ID - can be used for updating",
}, },
revision: { revision: {
type: "string", type: AutomationIOType.STRING,
description: "Revision of row", description: "Revision of row",
}, },
}, },
required: ["row", "id"], required: ["row", "id"],
}, },
}, },
type: "TRIGGER", type: AutomationStepType.TRIGGER,
} }

View file

@ -1,4 +1,7 @@
import { import {
AutomationCustomIOType,
AutomationIOType,
AutomationStepType,
AutomationTriggerSchema, AutomationTriggerSchema,
AutomationTriggerStepId, AutomationTriggerStepId,
} from "@budibase/types" } from "@budibase/types"
@ -15,8 +18,8 @@ export const definition: AutomationTriggerSchema = {
inputs: { inputs: {
properties: { properties: {
tableId: { tableId: {
type: "string", type: AutomationIOType.STRING,
customType: "table", customType: AutomationCustomIOType.TABLE,
title: "Table", title: "Table",
}, },
}, },
@ -25,21 +28,21 @@ export const definition: AutomationTriggerSchema = {
outputs: { outputs: {
properties: { properties: {
row: { row: {
type: "object", type: AutomationIOType.OBJECT,
customType: "row", customType: AutomationCustomIOType.ROW,
description: "The row that was updated", description: "The row that was updated",
}, },
id: { id: {
type: "string", type: AutomationIOType.STRING,
description: "Row ID - can be used for updating", description: "Row ID - can be used for updating",
}, },
revision: { revision: {
type: "string", type: AutomationIOType.STRING,
description: "Revision of row", description: "Revision of row",
}, },
}, },
required: ["row", "id"], required: ["row", "id"],
}, },
}, },
type: "TRIGGER", type: AutomationStepType.TRIGGER,
} }

View file

@ -1,4 +1,7 @@
import { import {
AutomationCustomIOType,
AutomationIOType,
AutomationStepType,
AutomationTriggerSchema, AutomationTriggerSchema,
AutomationTriggerStepId, AutomationTriggerStepId,
} from "@budibase/types" } from "@budibase/types"
@ -15,13 +18,13 @@ export const definition: AutomationTriggerSchema = {
inputs: { inputs: {
properties: { properties: {
schemaUrl: { schemaUrl: {
type: "string", type: AutomationIOType.STRING,
customType: "webhookUrl", customType: AutomationCustomIOType.WEBHOOK_URL,
title: "Schema URL", title: "Schema URL",
}, },
triggerUrl: { triggerUrl: {
type: "string", type: AutomationIOType.STRING,
customType: "webhookUrl", customType: AutomationCustomIOType.WEBHOOK_URL,
title: "Trigger URL", title: "Trigger URL",
}, },
}, },
@ -30,12 +33,12 @@ export const definition: AutomationTriggerSchema = {
outputs: { outputs: {
properties: { properties: {
body: { body: {
type: "object", type: AutomationIOType.OBJECT,
description: "Body of the request which hit the webhook", description: "Body of the request which hit the webhook",
}, },
}, },
required: ["body"], required: ["body"],
}, },
}, },
type: "TRIGGER", type: AutomationStepType.TRIGGER,
} }

View file

@ -14,11 +14,11 @@ import firebase from "./firebase"
import redis from "./redis" import redis from "./redis"
import snowflake from "./snowflake" import snowflake from "./snowflake"
import oracle from "./oracle" import oracle from "./oracle"
import { getPlugins } from "../api/controllers/plugin"
import { SourceName, Integration, PluginType } from "@budibase/types" import { SourceName, Integration, PluginType } from "@budibase/types"
import { getDatasourcePlugin } from "../utilities/fileSystem" import { getDatasourcePlugin } from "../utilities/fileSystem"
import env from "../environment" import env from "../environment"
import { cloneDeep } from "lodash" import { cloneDeep } from "lodash"
import sdk from "../sdk"
const DEFINITIONS: { [key: string]: Integration } = { const DEFINITIONS: { [key: string]: Integration } = {
[SourceName.POSTGRES]: postgres.schema, [SourceName.POSTGRES]: postgres.schema,
@ -79,7 +79,7 @@ export async function getDefinition(source: SourceName): Promise<Integration> {
export async function getDefinitions() { export async function getDefinitions() {
const pluginSchemas: { [key: string]: Integration } = {} const pluginSchemas: { [key: string]: Integration } = {}
if (env.SELF_HOSTED) { if (env.SELF_HOSTED) {
const plugins = await getPlugins(PluginType.DATASOURCE) const plugins = await sdk.plugins.fetch(PluginType.DATASOURCE)
// extract the actual schema from each custom // extract the actual schema from each custom
for (let plugin of plugins) { for (let plugin of plugins) {
const sourceId = plugin.name const sourceId = plugin.name
@ -103,7 +103,7 @@ export async function getIntegration(integration: string) {
return INTEGRATIONS[integration] return INTEGRATIONS[integration]
} }
if (env.SELF_HOSTED) { if (env.SELF_HOSTED) {
const plugins = await getPlugins(PluginType.DATASOURCE) const plugins = await sdk.plugins.fetch(PluginType.DATASOURCE)
for (let plugin of plugins) { for (let plugin of plugins) {
if (plugin.name === integration) { if (plugin.name === integration) {
// need to use commonJS require due to its dynamic runtime nature // need to use commonJS require due to its dynamic runtime nature

View file

@ -6,6 +6,7 @@ import { default as datasources } from "./app/datasources"
import { default as queries } from "./app/queries" import { default as queries } from "./app/queries"
import { default as rows } from "./app/rows" import { default as rows } from "./app/rows"
import { default as users } from "./users" import { default as users } from "./users"
import { default as plugins } from "./plugins"
const sdk = { const sdk = {
backups, backups,
@ -16,6 +17,7 @@ const sdk = {
users, users,
datasources, datasources,
queries, queries,
plugins,
} }
// default export for TS // default export for TS

View file

@ -0,0 +1,5 @@
import * as plugins from "./plugins"
export default {
...plugins,
}

View file

@ -0,0 +1,41 @@
import { FileType, Plugin, PluginSource, PluginType } from "@budibase/types"
import {
db as dbCore,
objectStore,
plugins as pluginCore,
tenancy,
} from "@budibase/backend-core"
import { fileUpload } from "../../api/controllers/plugin/file"
import env from "../../environment"
import { ClientAppSocket } from "../../websocket"
import { sdk as pro } from "@budibase/pro"
export async function fetch(type?: PluginType) {
const db = tenancy.getGlobalDB()
const response = await db.allDocs(
dbCore.getPluginParams(null, {
include_docs: true,
})
)
let plugins = response.rows.map((row: any) => row.doc) as Plugin[]
plugins = objectStore.enrichPluginURLs(plugins)
if (type) {
return plugins.filter((plugin: Plugin) => plugin.schema?.type === type)
} else {
return plugins
}
}
export async function processUploaded(plugin: FileType, source?: PluginSource) {
const { metadata, directory } = await fileUpload(plugin)
pluginCore.validate(metadata?.schema)
// Only allow components in cloud
if (!env.SELF_HOSTED && metadata?.schema?.type !== PluginType.COMPONENT) {
throw new Error("Only component plugins are supported outside of self-host")
}
const doc = await pro.plugins.storePlugin(metadata, directory, source)
ClientAppSocket.emit("plugin-update", { name: doc.name, hash: doc.hash })
return doc
}

View file

@ -1,18 +1,22 @@
import { permissions, roles } from "@budibase/backend-core" import { permissions, roles, utils } from "@budibase/backend-core"
import { createHomeScreen } from "../../constants/screens" import { createHomeScreen } from "../../constants/screens"
import { EMPTY_LAYOUT } from "../../constants/layouts" import { EMPTY_LAYOUT } from "../../constants/layouts"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { ACTION_DEFINITIONS, TRIGGER_DEFINITIONS } from "../../automations" import {
BUILTIN_ACTION_DEFINITIONS,
TRIGGER_DEFINITIONS,
} from "../../automations"
import { import {
Automation, Automation,
AutomationActionStepId, AutomationActionStepId,
AutomationStep,
AutomationStepType,
AutomationTrigger,
AutomationTriggerStepId, AutomationTriggerStepId,
Datasource, Datasource,
SourceName, SourceName,
} from "@budibase/types" } from "@budibase/types"
const { v4: uuidv4 } = require("uuid")
export function basicTable() { export function basicTable() {
return { return {
name: "TestTable", name: "TestTable",
@ -71,19 +75,19 @@ export function view(tableId: string) {
} }
export function automationStep( export function automationStep(
actionDefinition = ACTION_DEFINITIONS.CREATE_ROW actionDefinition = BUILTIN_ACTION_DEFINITIONS.CREATE_ROW
) { ): AutomationStep {
return { return {
id: uuidv4(), id: utils.newid(),
...actionDefinition, ...actionDefinition,
} }
} }
export function automationTrigger( export function automationTrigger(
triggerDefinition = TRIGGER_DEFINITIONS.ROW_SAVED triggerDefinition = TRIGGER_DEFINITIONS.ROW_SAVED
) { ): AutomationTrigger {
return { return {
id: uuidv4(), id: utils.newid(),
...triggerDefinition, ...triggerDefinition,
} }
} }
@ -106,7 +110,7 @@ export function newAutomation({ steps, trigger }: any = {}) {
return automation return automation
} }
export function basicAutomation(appId?: string) { export function basicAutomation(appId?: string): Automation {
return { return {
name: "My Automation", name: "My Automation",
screenId: "kasdkfldsafkl", screenId: "kasdkfldsafkl",
@ -119,18 +123,22 @@ export function basicAutomation(appId?: string) {
tagline: "test", tagline: "test",
icon: "test", icon: "test",
description: "test", description: "test",
type: "trigger", type: AutomationStepType.TRIGGER,
id: "test", id: "test",
inputs: {}, inputs: {},
schema: { schema: {
inputs: {}, inputs: {
outputs: {}, properties: {},
},
outputs: {
properties: {},
},
}, },
}, },
steps: [], steps: [],
}, },
type: "automation", type: "automation",
appId, appId: appId!,
} }
} }
@ -154,7 +162,7 @@ export function loopAutomation(tableId: string, loopOpts?: any): Automation {
inputs: { inputs: {
tableId, tableId,
}, },
schema: ACTION_DEFINITIONS.QUERY_ROWS.schema, schema: BUILTIN_ACTION_DEFINITIONS.QUERY_ROWS.schema,
}, },
{ {
id: "c", id: "c",
@ -163,7 +171,7 @@ export function loopAutomation(tableId: string, loopOpts?: any): Automation {
internal: true, internal: true,
inputs: loopOpts, inputs: loopOpts,
blockToLoop: "d", blockToLoop: "d",
schema: ACTION_DEFINITIONS.LOOP.schema, schema: BUILTIN_ACTION_DEFINITIONS.LOOP.schema,
}, },
{ {
id: "d", id: "d",
@ -173,7 +181,7 @@ export function loopAutomation(tableId: string, loopOpts?: any): Automation {
inputs: { inputs: {
text: "log statement", text: "log statement",
}, },
schema: ACTION_DEFINITIONS.SERVER_LOG.schema, schema: BUILTIN_ACTION_DEFINITIONS.SERVER_LOG.schema,
}, },
], ],
trigger: { trigger: {

View file

@ -27,8 +27,8 @@ import { processObject } from "@budibase/string-templates"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import * as sdkUtils from "../sdk/utils" import * as sdkUtils from "../sdk/utils"
import env from "../environment" import env from "../environment"
const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId const FILTER_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.FILTER.stepId
const LOOP_STEP_ID = actions.ACTION_DEFINITIONS.LOOP.stepId const LOOP_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.LOOP.stepId
const CRON_STEP_ID = triggerDefs.CRON.stepId const CRON_STEP_ID = triggerDefs.CRON.stepId
const STOPPED_STATUS = { success: true, status: AutomationStatus.STOPPED } const STOPPED_STATUS = { success: true, status: AutomationStatus.STOPPED }

View file

@ -5,6 +5,7 @@ import { join } from "path"
import { objectStore } from "@budibase/backend-core" import { objectStore } from "@budibase/backend-core"
const DATASOURCE_PATH = join(budibaseTempDir(), "datasource") const DATASOURCE_PATH = join(budibaseTempDir(), "datasource")
const AUTOMATION_PATH = join(budibaseTempDir(), "automation")
export const getPluginMetadata = async (path: string) => { export const getPluginMetadata = async (path: string) => {
let metadata: any = {} let metadata: any = {}
@ -33,12 +34,12 @@ export const getPluginMetadata = async (path: string) => {
return { metadata, directory: path } return { metadata, directory: path }
} }
export const getDatasourcePlugin = async (plugin: Plugin) => { async function getPluginImpl(path: string, plugin: Plugin) {
const hash = plugin.schema?.hash const hash = plugin.schema?.hash
if (!fs.existsSync(DATASOURCE_PATH)) { if (!fs.existsSync(path)) {
fs.mkdirSync(DATASOURCE_PATH) fs.mkdirSync(path)
} }
const filename = join(DATASOURCE_PATH, plugin.name) const filename = join(path, plugin.name)
const metadataName = `${filename}.bbmetadata` const metadataName = `${filename}.bbmetadata`
if (fs.existsSync(filename)) { if (fs.existsSync(filename)) {
const currentHash = fs.readFileSync(metadataName, "utf8") const currentHash = fs.readFileSync(metadataName, "utf8")
@ -62,3 +63,11 @@ export const getDatasourcePlugin = async (plugin: Plugin) => {
return require(filename) return require(filename)
} }
export const getDatasourcePlugin = async (plugin: Plugin) => {
return getPluginImpl(DATASOURCE_PATH, plugin)
}
export const getAutomationPlugin = async (plugin: Plugin) => {
return getPluginImpl(AUTOMATION_PATH, plugin)
}

View file

@ -3,7 +3,7 @@ import env from "./environment"
import chokidar from "chokidar" import chokidar from "chokidar"
import fs from "fs" import fs from "fs"
import { constants, tenancy } from "@budibase/backend-core" import { constants, tenancy } from "@budibase/backend-core"
import { processUploadedPlugin } from "./api/controllers/plugin" import pluginsSdk from "./sdk/plugins"
export function watch() { export function watch() {
const watchPath = path.join(env.PLUGINS_DIR, "./**/*.tar.gz") const watchPath = path.join(env.PLUGINS_DIR, "./**/*.tar.gz")
@ -27,7 +27,7 @@ export function watch() {
const split = path.split("/") const split = path.split("/")
const name = split[split.length - 1] const name = split[split.length - 1]
console.log("Importing plugin:", path) console.log("Importing plugin:", path)
await processUploadedPlugin({ name, path }) await pluginsSdk.processUploaded({ name, path })
} catch (err: any) { } catch (err: any) {
const message = err?.message ? err?.message : err const message = err?.message ? err?.message : err
console.error("Failed to import plugin:", message) console.error("Failed to import plugin:", message)

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/shared-core", "name": "@budibase/shared-core",
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"description": "Shared data utils", "description": "Shared data utils",
"main": "dist/cjs/src/index.js", "main": "dist/cjs/src/index.js",
"types": "dist/mjs/src/index.d.ts", "types": "dist/mjs/src/index.d.ts",
@ -20,7 +20,7 @@
"dev:builder": "yarn prebuild && concurrently \"tsc -p tsconfig.build.json --watch\" \"tsc -p tsconfig-cjs.build.json --watch\"" "dev:builder": "yarn prebuild && concurrently \"tsc -p tsconfig.build.json --watch\" \"tsc -p tsconfig-cjs.build.json --watch\""
}, },
"dependencies": { "dependencies": {
"@budibase/types": "2.5.6-alpha.3" "@budibase/types": "2.5.6-alpha.5"
}, },
"devDependencies": { "devDependencies": {
"concurrently": "^7.6.0", "concurrently": "^7.6.0",

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/types", "name": "@budibase/types",
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"description": "Budibase types", "description": "Budibase types",
"main": "dist/cjs/index.js", "main": "dist/cjs/index.js",
"types": "dist/mjs/index.d.ts", "types": "dist/mjs/index.d.ts",

View file

@ -1,6 +1,32 @@
import { Document } from "../document" import { Document } from "../document"
import { EventEmitter } from "events" import { EventEmitter } from "events"
export enum AutomationIOType {
OBJECT = "object",
STRING = "string",
BOOLEAN = "boolean",
NUMBER = "number",
ARRAY = "array",
}
export enum AutomationCustomIOType {
TABLE = "table",
ROW = "row",
ROWS = "rows",
WIDE = "wide",
QUERY = "query",
QUERY_PARAMS = "queryParams",
QUERY_LIMIT = "queryLimit",
LOOP_OPTION = "loopOption",
ITEM = "item",
CODE = "code",
FILTERS = "filters",
COLUMN = "column",
TRIGGER_SCHEMA = "triggerSchema",
CRON = "cron",
WEBHOOK_URL = "webhookUrl",
}
export enum AutomationTriggerStepId { export enum AutomationTriggerStepId {
ROW_SAVED = "ROW_SAVED", ROW_SAVED = "ROW_SAVED",
ROW_UPDATED = "ROW_UPDATED", ROW_UPDATED = "ROW_UPDATED",
@ -10,6 +36,12 @@ export enum AutomationTriggerStepId {
CRON = "CRON", CRON = "CRON",
} }
export enum AutomationStepType {
LOGIC = "LOGIC",
ACTION = "ACTION",
TRIGGER = "TRIGGER",
}
export enum AutomationActionStepId { export enum AutomationActionStepId {
SEND_EMAIL_SMTP = "SEND_EMAIL_SMTP", SEND_EMAIL_SMTP = "SEND_EMAIL_SMTP",
CREATE_ROW = "CREATE_ROW", CREATE_ROW = "CREATE_ROW",
@ -31,14 +63,43 @@ export enum AutomationActionStepId {
integromat = "integromat", integromat = "integromat",
} }
export const AutomationStepIdArray = [
...Object.values(AutomationActionStepId),
...Object.values(AutomationTriggerStepId),
]
export interface Automation extends Document { export interface Automation extends Document {
definition: { definition: {
steps: AutomationStep[] steps: AutomationStep[]
trigger: AutomationTrigger trigger: AutomationTrigger
} }
screenId?: string
uiTree?: any
appId: string appId: string
live?: boolean live?: boolean
name: string name: string
internal?: boolean
type?: string
}
interface BaseIOStructure {
type?: AutomationIOType
customType?: AutomationCustomIOType
title?: string
description?: string
enum?: string[]
pretty?: string[]
properties?: {
[key: string]: BaseIOStructure
}
required?: string[]
}
interface InputOutputBlock {
properties: {
[key: string]: BaseIOStructure
}
required?: string[]
} }
export interface AutomationStepSchema { export interface AutomationStepSchema {
@ -46,7 +107,7 @@ export interface AutomationStepSchema {
tagline: string tagline: string
icon: string icon: string
description: string description: string
type: string type: AutomationStepType
internal?: boolean internal?: boolean
deprecated?: boolean deprecated?: boolean
stepId: AutomationTriggerStepId | AutomationActionStepId stepId: AutomationTriggerStepId | AutomationActionStepId
@ -55,14 +116,10 @@ export interface AutomationStepSchema {
[key: string]: any [key: string]: any
} }
schema: { schema: {
inputs: { inputs: InputOutputBlock
[key: string]: any outputs: InputOutputBlock
}
outputs: {
[key: string]: any
}
required?: string[]
} }
custom?: boolean
} }
export interface AutomationStep extends AutomationStepSchema { export interface AutomationStep extends AutomationStepSchema {

View file

@ -3,6 +3,7 @@ import { Document } from "../document"
export enum PluginType { export enum PluginType {
DATASOURCE = "datasource", DATASOURCE = "datasource",
COMPONENT = "component", COMPONENT = "component",
AUTOMATION = "automation",
} }
export enum PluginSource { export enum PluginSource {

View file

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "2.5.6-alpha.3", "version": "2.5.6-alpha.5",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -37,10 +37,10 @@
"author": "Budibase", "author": "Budibase",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@budibase/backend-core": "2.5.6-alpha.3", "@budibase/backend-core": "2.5.6-alpha.5",
"@budibase/pro": "2.5.6-alpha.3", "@budibase/pro": "2.5.6-alpha.5",
"@budibase/string-templates": "2.5.6-alpha.3", "@budibase/string-templates": "2.5.6-alpha.5",
"@budibase/types": "2.5.6-alpha.3", "@budibase/types": "2.5.6-alpha.5",
"@koa/router": "8.0.8", "@koa/router": "8.0.8",
"@sentry/node": "6.17.7", "@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",

639
yarn.lock

File diff suppressed because it is too large Load diff