@budibase/server 2.6.18 → 2.6.19-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builder/assets/{index.86c992bf.css → index.07382a47.css} +2 -2
- package/builder/assets/{index.69c5c1ea.js → index.b9eeb2a8.js} +310 -318
- package/builder/index.html +2 -2
- package/dist/api/controllers/datasource.js +70 -39
- package/dist/api/controllers/integration.js +2 -2
- package/dist/api/routes/datasource.js +1 -0
- package/dist/automations/steps/make.js +19 -5
- package/dist/automations/steps/zapier.js +19 -6
- package/dist/automations/utils.js +13 -7
- package/dist/db/dynamoClient.js +1 -1
- package/dist/integrations/airtable.js +28 -2
- package/dist/integrations/arangodb.js +19 -3
- package/dist/integrations/couchdb.js +16 -1
- package/dist/integrations/dynamodb.js +16 -0
- package/dist/integrations/elasticsearch.js +15 -0
- package/dist/integrations/firebase.js +15 -0
- package/dist/integrations/googlesheets.js +30 -1
- package/dist/integrations/index.js +5 -2
- package/dist/integrations/microsoftSqlServer.js +16 -0
- package/dist/integrations/mongodb.js +16 -0
- package/dist/integrations/mysql.js +21 -5
- package/dist/integrations/oracle.js +29 -0
- package/dist/integrations/postgres.js +26 -7
- package/dist/integrations/redis.js +20 -1
- package/dist/integrations/s3.js +23 -4
- package/dist/integrations/snowflake.js +15 -0
- package/dist/migrations/functions/backfill/app/queries.js +1 -2
- package/dist/sdk/app/datasources/datasources.js +10 -3
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/jest.config.ts +3 -3
- package/nodemon.json +7 -3
- package/package.json +10 -9
- package/src/api/controllers/datasource.ts +88 -49
- package/src/api/controllers/integration.ts +3 -3
- package/src/api/routes/datasource.ts +5 -0
- package/src/automations/steps/make.ts +18 -1
- package/src/automations/steps/zapier.ts +18 -1
- package/src/automations/tests/make.spec.ts +54 -0
- package/src/automations/tests/zapier.spec.ts +56 -0
- package/src/automations/utils.ts +13 -7
- package/src/db/dynamoClient.ts +1 -1
- package/src/integration-test/postgres.spec.ts +0 -1
- package/src/integrations/airtable.ts +31 -4
- package/src/integrations/arangodb.ts +18 -2
- package/src/integrations/couchdb.ts +18 -4
- package/src/integrations/dynamodb.ts +34 -5
- package/src/integrations/elasticsearch.ts +16 -1
- package/src/integrations/firebase.ts +15 -0
- package/src/integrations/googlesheets.ts +31 -2
- package/src/integrations/index.ts +12 -7
- package/src/integrations/microsoftSqlServer.ts +16 -0
- package/src/integrations/mongodb.ts +16 -0
- package/src/integrations/mysql.ts +27 -16
- package/src/integrations/oracle.ts +28 -6
- package/src/integrations/postgres.ts +21 -3
- package/src/integrations/redis.ts +26 -3
- package/src/integrations/s3.ts +19 -3
- package/src/integrations/snowflake.ts +20 -1
- package/src/migrations/functions/backfill/app/queries.ts +1 -1
- package/src/sdk/app/datasources/datasources.ts +7 -1
- package/tsconfig.json +1 -1
- package/src/automations/tests/zapier.spec.js +0 -27
package/jest.config.ts
CHANGED
|
@@ -20,9 +20,9 @@ const baseConfig: Config.InitialProjectOptions = {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// add pro sources if they exist
|
|
23
|
-
if (fs.existsSync("
|
|
24
|
-
baseConfig.moduleNameMapper["@budibase/pro"] =
|
|
25
|
-
"<rootDir
|
|
23
|
+
if (fs.existsSync("../pro/packages")) {
|
|
24
|
+
baseConfig.moduleNameMapper!["@budibase/pro"] =
|
|
25
|
+
"<rootDir>/../pro/packages/pro/src"
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const config: Config.InitialOptions = {
|
package/nodemon.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"watch": ["src", "../backend-core", "
|
|
2
|
+
"watch": ["src", "../backend-core", "../pro/packages/pro"],
|
|
3
3
|
"ext": "js,ts,json",
|
|
4
|
-
"ignore": [
|
|
4
|
+
"ignore": [
|
|
5
|
+
"src/**/*.spec.ts",
|
|
6
|
+
"src/**/*.spec.js",
|
|
7
|
+
"../backend-core/dist/**/*"
|
|
8
|
+
],
|
|
5
9
|
"exec": "ts-node src/index.ts"
|
|
6
|
-
}
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/server",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "2.6.
|
|
4
|
+
"version": "2.6.19-alpha.0",
|
|
5
5
|
"description": "Budibase Web Server",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
"license": "GPL-3.0",
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@apidevtools/swagger-parser": "10.0.3",
|
|
48
|
-
"@budibase/backend-core": "
|
|
49
|
-
"@budibase/client": "
|
|
50
|
-
"@budibase/pro": "2.6.
|
|
51
|
-
"@budibase/shared-core": "
|
|
52
|
-
"@budibase/string-templates": "
|
|
53
|
-
"@budibase/types": "
|
|
48
|
+
"@budibase/backend-core": "2.6.19-alpha.0",
|
|
49
|
+
"@budibase/client": "2.6.19-alpha.0",
|
|
50
|
+
"@budibase/pro": "2.6.19-alpha.0",
|
|
51
|
+
"@budibase/shared-core": "2.6.19-alpha.0",
|
|
52
|
+
"@budibase/string-templates": "2.6.19-alpha.0",
|
|
53
|
+
"@budibase/types": "2.6.19-alpha.0",
|
|
54
54
|
"@bull-board/api": "3.7.0",
|
|
55
55
|
"@bull-board/koa": "3.9.4",
|
|
56
56
|
"@elastic/elasticsearch": "7.10.0",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"mysql2": "2.3.3",
|
|
100
100
|
"node-fetch": "2.6.7",
|
|
101
101
|
"open": "8.4.0",
|
|
102
|
-
"pg": "8.
|
|
102
|
+
"pg": "8.10.0",
|
|
103
103
|
"posthog-node": "1.3.0",
|
|
104
104
|
"pouchdb": "7.3.0",
|
|
105
105
|
"pouchdb-adapter-memory": "7.2.2",
|
|
@@ -141,6 +141,7 @@
|
|
|
141
141
|
"@types/node": "14.18.20",
|
|
142
142
|
"@types/node-fetch": "2.6.1",
|
|
143
143
|
"@types/oracledb": "5.2.2",
|
|
144
|
+
"@types/pg": "8.6.6",
|
|
144
145
|
"@types/pouchdb": "6.4.0",
|
|
145
146
|
"@types/redis": "4.0.11",
|
|
146
147
|
"@types/server-destroy": "1.0.1",
|
|
@@ -176,5 +177,5 @@
|
|
|
176
177
|
"optionalDependencies": {
|
|
177
178
|
"oracledb": "5.3.0"
|
|
178
179
|
},
|
|
179
|
-
"gitHead": "
|
|
180
|
+
"gitHead": "82e867c0f5b41fe3af88333effeb88f29b8b4d23"
|
|
180
181
|
}
|
|
@@ -18,11 +18,71 @@ import {
|
|
|
18
18
|
Row,
|
|
19
19
|
CreateDatasourceResponse,
|
|
20
20
|
UpdateDatasourceResponse,
|
|
21
|
-
UpdateDatasourceRequest,
|
|
22
21
|
CreateDatasourceRequest,
|
|
22
|
+
VerifyDatasourceRequest,
|
|
23
|
+
VerifyDatasourceResponse,
|
|
24
|
+
IntegrationBase,
|
|
25
|
+
DatasourcePlus,
|
|
23
26
|
} from "@budibase/types"
|
|
24
27
|
import sdk from "../../sdk"
|
|
25
28
|
|
|
29
|
+
function getErrorTables(errors: any, errorType: string) {
|
|
30
|
+
return Object.entries(errors)
|
|
31
|
+
.filter(entry => entry[1] === errorType)
|
|
32
|
+
.map(([name]) => name)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function updateError(error: any, newError: any, tables: string[]) {
|
|
36
|
+
if (!error) {
|
|
37
|
+
error = ""
|
|
38
|
+
}
|
|
39
|
+
if (error.length > 0) {
|
|
40
|
+
error += "\n"
|
|
41
|
+
}
|
|
42
|
+
error += `${newError} ${tables.join(", ")}`
|
|
43
|
+
return error
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function getConnector(
|
|
47
|
+
datasource: Datasource
|
|
48
|
+
): Promise<IntegrationBase | DatasourcePlus> {
|
|
49
|
+
const Connector = await getIntegration(datasource.source)
|
|
50
|
+
// can't enrich if it doesn't have an ID yet
|
|
51
|
+
if (datasource._id) {
|
|
52
|
+
datasource = await sdk.datasources.enrich(datasource)
|
|
53
|
+
}
|
|
54
|
+
// Connect to the DB and build the schema
|
|
55
|
+
return new Connector(datasource.config)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function buildSchemaHelper(datasource: Datasource) {
|
|
59
|
+
const connector = (await getConnector(datasource)) as DatasourcePlus
|
|
60
|
+
await connector.buildSchema(datasource._id!, datasource.entities!)
|
|
61
|
+
|
|
62
|
+
const errors = connector.schemaErrors
|
|
63
|
+
let error = null
|
|
64
|
+
if (errors && Object.keys(errors).length > 0) {
|
|
65
|
+
const noKey = getErrorTables(errors, BuildSchemaErrors.NO_KEY)
|
|
66
|
+
const invalidCol = getErrorTables(errors, BuildSchemaErrors.INVALID_COLUMN)
|
|
67
|
+
if (noKey.length) {
|
|
68
|
+
error = updateError(
|
|
69
|
+
error,
|
|
70
|
+
"No primary key constraint found for the following:",
|
|
71
|
+
noKey
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
if (invalidCol.length) {
|
|
75
|
+
const invalidCols = Object.values(InvalidColumns).join(", ")
|
|
76
|
+
error = updateError(
|
|
77
|
+
error,
|
|
78
|
+
`Cannot use columns ${invalidCols} found in following:`,
|
|
79
|
+
invalidCol
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { tables: connector.tables, error }
|
|
84
|
+
}
|
|
85
|
+
|
|
26
86
|
export async function fetch(ctx: UserCtx) {
|
|
27
87
|
// Get internal tables
|
|
28
88
|
const db = context.getAppDB()
|
|
@@ -66,6 +126,33 @@ export async function fetch(ctx: UserCtx) {
|
|
|
66
126
|
ctx.body = [bbInternalDb, ...datasources]
|
|
67
127
|
}
|
|
68
128
|
|
|
129
|
+
export async function verify(
|
|
130
|
+
ctx: UserCtx<VerifyDatasourceRequest, VerifyDatasourceResponse>
|
|
131
|
+
) {
|
|
132
|
+
const { datasource } = ctx.request.body
|
|
133
|
+
let existingDatasource: undefined | Datasource
|
|
134
|
+
if (datasource._id) {
|
|
135
|
+
existingDatasource = await sdk.datasources.get(datasource._id)
|
|
136
|
+
}
|
|
137
|
+
let enrichedDatasource = datasource
|
|
138
|
+
if (existingDatasource) {
|
|
139
|
+
enrichedDatasource = sdk.datasources.mergeConfigs(
|
|
140
|
+
datasource,
|
|
141
|
+
existingDatasource
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
const connector = await getConnector(enrichedDatasource)
|
|
145
|
+
if (!connector.testConnection) {
|
|
146
|
+
ctx.throw(400, "Connection information verification not supported")
|
|
147
|
+
}
|
|
148
|
+
const response = await connector.testConnection()
|
|
149
|
+
|
|
150
|
+
ctx.body = {
|
|
151
|
+
connected: response.connected,
|
|
152
|
+
error: response.error,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
69
156
|
export async function buildSchemaFromDb(ctx: UserCtx) {
|
|
70
157
|
const db = context.getAppDB()
|
|
71
158
|
const datasource = await sdk.datasources.get(ctx.params.datasourceId)
|
|
@@ -311,51 +398,3 @@ export async function query(ctx: UserCtx) {
|
|
|
311
398
|
ctx.throw(400, err)
|
|
312
399
|
}
|
|
313
400
|
}
|
|
314
|
-
|
|
315
|
-
function getErrorTables(errors: any, errorType: string) {
|
|
316
|
-
return Object.entries(errors)
|
|
317
|
-
.filter(entry => entry[1] === errorType)
|
|
318
|
-
.map(([name]) => name)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function updateError(error: any, newError: any, tables: string[]) {
|
|
322
|
-
if (!error) {
|
|
323
|
-
error = ""
|
|
324
|
-
}
|
|
325
|
-
if (error.length > 0) {
|
|
326
|
-
error += "\n"
|
|
327
|
-
}
|
|
328
|
-
error += `${newError} ${tables.join(", ")}`
|
|
329
|
-
return error
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
async function buildSchemaHelper(datasource: Datasource) {
|
|
333
|
-
const Connector = await getIntegration(datasource.source)
|
|
334
|
-
datasource = await sdk.datasources.enrich(datasource)
|
|
335
|
-
// Connect to the DB and build the schema
|
|
336
|
-
const connector = new Connector(datasource.config)
|
|
337
|
-
await connector.buildSchema(datasource._id, datasource.entities)
|
|
338
|
-
|
|
339
|
-
const errors = connector.schemaErrors
|
|
340
|
-
let error = null
|
|
341
|
-
if (errors && Object.keys(errors).length > 0) {
|
|
342
|
-
const noKey = getErrorTables(errors, BuildSchemaErrors.NO_KEY)
|
|
343
|
-
const invalidCol = getErrorTables(errors, BuildSchemaErrors.INVALID_COLUMN)
|
|
344
|
-
if (noKey.length) {
|
|
345
|
-
error = updateError(
|
|
346
|
-
error,
|
|
347
|
-
"No primary key constraint found for the following:",
|
|
348
|
-
noKey
|
|
349
|
-
)
|
|
350
|
-
}
|
|
351
|
-
if (invalidCol.length) {
|
|
352
|
-
const invalidCols = Object.values(InvalidColumns).join(", ")
|
|
353
|
-
error = updateError(
|
|
354
|
-
error,
|
|
355
|
-
`Cannot use columns ${invalidCols} found in following:`,
|
|
356
|
-
invalidCol
|
|
357
|
-
)
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return { tables: connector.tables, error }
|
|
361
|
-
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getDefinitions } from "../../integrations"
|
|
1
|
+
import { getDefinition, getDefinitions } from "../../integrations"
|
|
2
2
|
import { BBContext } from "@budibase/types"
|
|
3
3
|
|
|
4
4
|
export async function fetch(ctx: BBContext) {
|
|
@@ -7,7 +7,7 @@ export async function fetch(ctx: BBContext) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export async function find(ctx: BBContext) {
|
|
10
|
-
const
|
|
10
|
+
const def = await getDefinition(ctx.params.type)
|
|
11
|
+
ctx.body = def
|
|
11
12
|
ctx.status = 200
|
|
12
|
-
ctx.body = defs[ctx.params.type]
|
|
13
13
|
}
|
|
@@ -26,6 +26,10 @@ export const definition: AutomationStepSchema = {
|
|
|
26
26
|
type: AutomationIOType.STRING,
|
|
27
27
|
title: "Webhook URL",
|
|
28
28
|
},
|
|
29
|
+
body: {
|
|
30
|
+
type: AutomationIOType.JSON,
|
|
31
|
+
title: "Payload",
|
|
32
|
+
},
|
|
29
33
|
value1: {
|
|
30
34
|
type: AutomationIOType.STRING,
|
|
31
35
|
title: "Input Value 1",
|
|
@@ -70,7 +74,19 @@ export const definition: AutomationStepSchema = {
|
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
export async function run({ inputs }: AutomationStepInput) {
|
|
73
|
-
|
|
77
|
+
//TODO - Remove deprecated values 1,2,3,4,5 after November 2023
|
|
78
|
+
const { url, value1, value2, value3, value4, value5, body } = inputs
|
|
79
|
+
|
|
80
|
+
let payload = {}
|
|
81
|
+
try {
|
|
82
|
+
payload = body?.value ? JSON.parse(body?.value) : {}
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return {
|
|
85
|
+
httpStatus: 400,
|
|
86
|
+
response: "Invalid payload JSON",
|
|
87
|
+
success: false,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
74
90
|
|
|
75
91
|
if (!url?.trim()?.length) {
|
|
76
92
|
return {
|
|
@@ -89,6 +105,7 @@ export async function run({ inputs }: AutomationStepInput) {
|
|
|
89
105
|
value3,
|
|
90
106
|
value4,
|
|
91
107
|
value5,
|
|
108
|
+
...payload,
|
|
92
109
|
}),
|
|
93
110
|
headers: {
|
|
94
111
|
"Content-Type": "application/json",
|
|
@@ -24,6 +24,10 @@ export const definition: AutomationStepSchema = {
|
|
|
24
24
|
type: AutomationIOType.STRING,
|
|
25
25
|
title: "Webhook URL",
|
|
26
26
|
},
|
|
27
|
+
body: {
|
|
28
|
+
type: AutomationIOType.JSON,
|
|
29
|
+
title: "Payload",
|
|
30
|
+
},
|
|
27
31
|
value1: {
|
|
28
32
|
type: AutomationIOType.STRING,
|
|
29
33
|
title: "Payload Value 1",
|
|
@@ -63,7 +67,19 @@ export const definition: AutomationStepSchema = {
|
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
export async function run({ inputs }: AutomationStepInput) {
|
|
66
|
-
|
|
70
|
+
//TODO - Remove deprecated values 1,2,3,4,5 after November 2023
|
|
71
|
+
const { url, value1, value2, value3, value4, value5, body } = inputs
|
|
72
|
+
|
|
73
|
+
let payload = {}
|
|
74
|
+
try {
|
|
75
|
+
payload = body?.value ? JSON.parse(body?.value) : {}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
return {
|
|
78
|
+
httpStatus: 400,
|
|
79
|
+
response: "Invalid payload JSON",
|
|
80
|
+
success: false,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
67
83
|
|
|
68
84
|
if (!url?.trim()?.length) {
|
|
69
85
|
return {
|
|
@@ -85,6 +101,7 @@ export async function run({ inputs }: AutomationStepInput) {
|
|
|
85
101
|
value3,
|
|
86
102
|
value4,
|
|
87
103
|
value5,
|
|
104
|
+
...payload,
|
|
88
105
|
}),
|
|
89
106
|
headers: {
|
|
90
107
|
"Content-Type": "application/json",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getConfig, afterAll, runStep, actions } from "./utilities"
|
|
2
|
+
|
|
3
|
+
describe("test the outgoing webhook action", () => {
|
|
4
|
+
let config = getConfig()
|
|
5
|
+
|
|
6
|
+
beforeAll(async () => {
|
|
7
|
+
await config.init()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
afterAll()
|
|
11
|
+
|
|
12
|
+
it("should be able to run the action", async () => {
|
|
13
|
+
const res = await runStep(actions.integromat.stepId, {
|
|
14
|
+
value1: "test",
|
|
15
|
+
url: "http://www.test.com",
|
|
16
|
+
})
|
|
17
|
+
expect(res.response.url).toEqual("http://www.test.com")
|
|
18
|
+
expect(res.response.method).toEqual("post")
|
|
19
|
+
expect(res.success).toEqual(true)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it("should add the payload props when a JSON string is provided", async () => {
|
|
23
|
+
const payload = `{"value1":1,"value2":2,"value3":3,"value4":4,"value5":5,"name":"Adam","age":9}`
|
|
24
|
+
const res = await runStep(actions.integromat.stepId, {
|
|
25
|
+
value1: "ONE",
|
|
26
|
+
value2: "TWO",
|
|
27
|
+
value3: "THREE",
|
|
28
|
+
value4: "FOUR",
|
|
29
|
+
value5: "FIVE",
|
|
30
|
+
body: {
|
|
31
|
+
value: payload,
|
|
32
|
+
},
|
|
33
|
+
url: "http://www.test.com",
|
|
34
|
+
})
|
|
35
|
+
expect(res.response.url).toEqual("http://www.test.com")
|
|
36
|
+
expect(res.response.method).toEqual("post")
|
|
37
|
+
expect(res.response.body).toEqual(payload)
|
|
38
|
+
expect(res.success).toEqual(true)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("should return a 400 if the JSON payload string is malformed", async () => {
|
|
42
|
+
const payload = `{ value1 1 }`
|
|
43
|
+
const res = await runStep(actions.integromat.stepId, {
|
|
44
|
+
value1: "ONE",
|
|
45
|
+
body: {
|
|
46
|
+
value: payload,
|
|
47
|
+
},
|
|
48
|
+
url: "http://www.test.com",
|
|
49
|
+
})
|
|
50
|
+
expect(res.httpStatus).toEqual(400)
|
|
51
|
+
expect(res.response).toEqual("Invalid payload JSON")
|
|
52
|
+
expect(res.success).toEqual(false)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getConfig, afterAll, runStep, actions } from "./utilities"
|
|
2
|
+
|
|
3
|
+
describe("test the outgoing webhook action", () => {
|
|
4
|
+
let config = getConfig()
|
|
5
|
+
|
|
6
|
+
beforeAll(async () => {
|
|
7
|
+
await config.init()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
afterAll()
|
|
11
|
+
|
|
12
|
+
it("should be able to run the action", async () => {
|
|
13
|
+
const res = await runStep(actions.zapier.stepId, {
|
|
14
|
+
value1: "test",
|
|
15
|
+
url: "http://www.test.com",
|
|
16
|
+
})
|
|
17
|
+
expect(res.response.url).toEqual("http://www.test.com")
|
|
18
|
+
expect(res.response.method).toEqual("post")
|
|
19
|
+
expect(res.success).toEqual(true)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it("should add the payload props when a JSON string is provided", async () => {
|
|
23
|
+
const payload = `{ "value1": 1, "value2": 2, "value3": 3, "value4": 4, "value5": 5, "name": "Adam", "age": 9 }`
|
|
24
|
+
const res = await runStep(actions.zapier.stepId, {
|
|
25
|
+
value1: "ONE",
|
|
26
|
+
value2: "TWO",
|
|
27
|
+
value3: "THREE",
|
|
28
|
+
value4: "FOUR",
|
|
29
|
+
value5: "FIVE",
|
|
30
|
+
body: {
|
|
31
|
+
value: payload,
|
|
32
|
+
},
|
|
33
|
+
url: "http://www.test.com",
|
|
34
|
+
})
|
|
35
|
+
expect(res.response.url).toEqual("http://www.test.com")
|
|
36
|
+
expect(res.response.method).toEqual("post")
|
|
37
|
+
expect(res.response.body).toEqual(
|
|
38
|
+
`{"platform":"budibase","value1":1,"value2":2,"value3":3,"value4":4,"value5":5,"name":"Adam","age":9}`
|
|
39
|
+
)
|
|
40
|
+
expect(res.success).toEqual(true)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it("should return a 400 if the JSON payload string is malformed", async () => {
|
|
44
|
+
const payload = `{ value1 1 }`
|
|
45
|
+
const res = await runStep(actions.zapier.stepId, {
|
|
46
|
+
value1: "ONE",
|
|
47
|
+
body: {
|
|
48
|
+
value: payload,
|
|
49
|
+
},
|
|
50
|
+
url: "http://www.test.com",
|
|
51
|
+
})
|
|
52
|
+
expect(res.httpStatus).toEqual(400)
|
|
53
|
+
expect(res.response).toEqual("Invalid payload JSON")
|
|
54
|
+
expect(res.success).toEqual(false)
|
|
55
|
+
})
|
|
56
|
+
})
|
package/src/automations/utils.ts
CHANGED
|
@@ -17,10 +17,16 @@ const CRON_STEP_ID = definitions.CRON.stepId
|
|
|
17
17
|
const Runner = new Thread(ThreadType.AUTOMATION)
|
|
18
18
|
|
|
19
19
|
function loggingArgs(job: AutomationJob) {
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
_logKey: "automation",
|
|
23
|
+
trigger: job.data.automation.definition.trigger.event,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
_logKey: "bull",
|
|
27
|
+
jobId: job.id,
|
|
28
|
+
},
|
|
29
|
+
]
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
export async function processEvent(job: AutomationJob) {
|
|
@@ -29,16 +35,16 @@ export async function processEvent(job: AutomationJob) {
|
|
|
29
35
|
const task = async () => {
|
|
30
36
|
try {
|
|
31
37
|
// need to actually await these so that an error can be captured properly
|
|
32
|
-
console.log("automation running", loggingArgs(job))
|
|
38
|
+
console.log("automation running", ...loggingArgs(job))
|
|
33
39
|
|
|
34
40
|
const runFn = () => Runner.run(job)
|
|
35
41
|
const result = await quotas.addAutomation(runFn, {
|
|
36
42
|
automationId,
|
|
37
43
|
})
|
|
38
|
-
console.log("automation completed", loggingArgs(job))
|
|
44
|
+
console.log("automation completed", ...loggingArgs(job))
|
|
39
45
|
return result
|
|
40
46
|
} catch (err) {
|
|
41
|
-
console.error(`automation was unable to run`, err, loggingArgs(job))
|
|
47
|
+
console.error(`automation was unable to run`, err, ...loggingArgs(job))
|
|
42
48
|
return { err }
|
|
43
49
|
}
|
|
44
50
|
}
|
package/src/db/dynamoClient.ts
CHANGED
|
@@ -140,7 +140,7 @@ export function init(endpoint: string) {
|
|
|
140
140
|
docClient = new AWS.DynamoDB.DocumentClient(docClientParams)
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
if (!env.isProd()) {
|
|
143
|
+
if (!env.isProd() && !env.isJest()) {
|
|
144
144
|
env._set("AWS_ACCESS_KEY_ID", "KEY_ID")
|
|
145
145
|
env._set("AWS_SECRET_ACCESS_KEY", "SECRET_KEY")
|
|
146
146
|
init("http://localhost:8333")
|
|
@@ -19,7 +19,6 @@ import _ from "lodash"
|
|
|
19
19
|
import { generator } from "@budibase/backend-core/tests"
|
|
20
20
|
import { utils } from "@budibase/backend-core"
|
|
21
21
|
import { GenericContainer } from "testcontainers"
|
|
22
|
-
import { generateRowIdField } from "../integrations/utils"
|
|
23
22
|
|
|
24
23
|
const config = setup.getConfig()!
|
|
25
24
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
ConnectionInfo,
|
|
3
|
+
DatasourceFeature,
|
|
3
4
|
DatasourceFieldType,
|
|
4
|
-
|
|
5
|
+
Integration,
|
|
5
6
|
IntegrationBase,
|
|
7
|
+
QueryType,
|
|
6
8
|
} from "@budibase/types"
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
import Airtable from "airtable"
|
|
9
11
|
|
|
10
12
|
interface AirtableConfig {
|
|
11
13
|
apiKey: string
|
|
@@ -18,6 +20,7 @@ const SCHEMA: Integration = {
|
|
|
18
20
|
"Airtable is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet.",
|
|
19
21
|
friendlyName: "Airtable",
|
|
20
22
|
type: "Spreadsheet",
|
|
23
|
+
features: [DatasourceFeature.CONNECTION_CHECKING],
|
|
21
24
|
datasource: {
|
|
22
25
|
apiKey: {
|
|
23
26
|
type: DatasourceFieldType.PASSWORD,
|
|
@@ -81,13 +84,37 @@ const SCHEMA: Integration = {
|
|
|
81
84
|
|
|
82
85
|
class AirtableIntegration implements IntegrationBase {
|
|
83
86
|
private config: AirtableConfig
|
|
84
|
-
private client
|
|
87
|
+
private client
|
|
85
88
|
|
|
86
89
|
constructor(config: AirtableConfig) {
|
|
87
90
|
this.config = config
|
|
88
91
|
this.client = new Airtable(config).base(config.base)
|
|
89
92
|
}
|
|
90
93
|
|
|
94
|
+
async testConnection(): Promise<ConnectionInfo> {
|
|
95
|
+
const mockTable = Date.now().toString()
|
|
96
|
+
try {
|
|
97
|
+
await this.client.makeRequest({
|
|
98
|
+
path: `/${mockTable}`,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return { connected: true }
|
|
102
|
+
} catch (e: any) {
|
|
103
|
+
if (
|
|
104
|
+
e.message ===
|
|
105
|
+
`Could not find table ${mockTable} in application ${this.config.base}`
|
|
106
|
+
) {
|
|
107
|
+
// The request managed to check the application, so the credentials are valid
|
|
108
|
+
return { connected: true }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
connected: false,
|
|
113
|
+
error: e.message as string,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
91
118
|
async create(query: { table: any; json: any }) {
|
|
92
119
|
const { table, json } = query
|
|
93
120
|
|
|
@@ -3,9 +3,11 @@ import {
|
|
|
3
3
|
DatasourceFieldType,
|
|
4
4
|
QueryType,
|
|
5
5
|
IntegrationBase,
|
|
6
|
+
DatasourceFeature,
|
|
7
|
+
ConnectionInfo,
|
|
6
8
|
} from "@budibase/types"
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
import { Database, aql } from "arangojs"
|
|
9
11
|
|
|
10
12
|
interface ArangodbConfig {
|
|
11
13
|
url: string
|
|
@@ -21,6 +23,7 @@ const SCHEMA: Integration = {
|
|
|
21
23
|
type: "Non-relational",
|
|
22
24
|
description:
|
|
23
25
|
"ArangoDB is a scalable open-source multi-model database natively supporting graph, document and search. All supported data models & access patterns can be combined in queries allowing for maximal flexibility. ",
|
|
26
|
+
features: [DatasourceFeature.CONNECTION_CHECKING],
|
|
24
27
|
datasource: {
|
|
25
28
|
url: {
|
|
26
29
|
type: DatasourceFieldType.STRING,
|
|
@@ -58,7 +61,7 @@ const SCHEMA: Integration = {
|
|
|
58
61
|
|
|
59
62
|
class ArangoDBIntegration implements IntegrationBase {
|
|
60
63
|
private config: ArangodbConfig
|
|
61
|
-
private client
|
|
64
|
+
private client
|
|
62
65
|
|
|
63
66
|
constructor(config: ArangodbConfig) {
|
|
64
67
|
const newConfig = {
|
|
@@ -74,6 +77,19 @@ class ArangoDBIntegration implements IntegrationBase {
|
|
|
74
77
|
this.client = new Database(newConfig)
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
async testConnection() {
|
|
81
|
+
const response: ConnectionInfo = {
|
|
82
|
+
connected: false,
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await this.client.get()
|
|
86
|
+
response.connected = true
|
|
87
|
+
} catch (e: any) {
|
|
88
|
+
response.error = e.message as string
|
|
89
|
+
}
|
|
90
|
+
return response
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
async read(query: { sql: any }) {
|
|
78
94
|
try {
|
|
79
95
|
const result = await this.client.query(query.sql)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ConnectionInfo,
|
|
3
|
+
DatasourceFeature,
|
|
2
4
|
DatasourceFieldType,
|
|
3
5
|
Document,
|
|
4
6
|
Integration,
|
|
@@ -18,6 +20,7 @@ const SCHEMA: Integration = {
|
|
|
18
20
|
type: "Non-relational",
|
|
19
21
|
description:
|
|
20
22
|
"Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.",
|
|
23
|
+
features: [DatasourceFeature.CONNECTION_CHECKING],
|
|
21
24
|
datasource: {
|
|
22
25
|
url: {
|
|
23
26
|
type: DatasourceFieldType.STRING,
|
|
@@ -61,21 +64,32 @@ const SCHEMA: Integration = {
|
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
class CouchDBIntegration implements IntegrationBase {
|
|
64
|
-
private
|
|
65
|
-
private readonly client: any
|
|
67
|
+
private readonly client: dbCore.DatabaseImpl
|
|
66
68
|
|
|
67
69
|
constructor(config: CouchDBConfig) {
|
|
68
|
-
this.config = config
|
|
69
70
|
this.client = dbCore.DatabaseWithConnection(config.database, config.url)
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
async testConnection() {
|
|
74
|
+
const response: ConnectionInfo = {
|
|
75
|
+
connected: false,
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const result = await this.query("exists", "validation error", {})
|
|
79
|
+
response.connected = result === true
|
|
80
|
+
} catch (e: any) {
|
|
81
|
+
response.error = e.message as string
|
|
82
|
+
}
|
|
83
|
+
return response
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
async query(
|
|
73
87
|
command: string,
|
|
74
88
|
errorMsg: string,
|
|
75
89
|
query: { json?: object; id?: string }
|
|
76
90
|
) {
|
|
77
91
|
try {
|
|
78
|
-
return await this.client[command](query.id || query.json)
|
|
92
|
+
return await (this.client as any)[command](query.id || query.json)
|
|
79
93
|
} catch (err) {
|
|
80
94
|
console.error(errorMsg, err)
|
|
81
95
|
throw err
|