@budibase/server 2.7.19 → 2.7.20-alpha.2
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.d9b46807.css → index.36462e95.css} +1 -1
- package/builder/assets/{index.8b4ed657.js → index.77561524.js} +346 -346
- package/builder/index.html +2 -2
- package/dist/automation.js +421 -180
- package/dist/automation.js.map +3 -3
- package/dist/index.js +532 -283
- package/dist/index.js.map +3 -3
- package/dist/query.js +317 -142
- package/dist/query.js.map +3 -3
- package/package.json +11 -11
- package/src/api/controllers/backup.ts +22 -8
- package/src/api/controllers/datasource.ts +41 -24
- package/src/api/controllers/role.ts +5 -5
- package/src/api/controllers/routing.ts +3 -3
- package/src/api/routes/backup.ts +1 -1
- package/src/api/routes/tests/backup.spec.ts +18 -2
- package/src/automations/steps/sendSmtpEmail.ts +55 -4
- package/src/automations/tests/sendSmtpEmail.spec.ts +74 -0
- package/src/events/docUpdates/syncUsers.ts +4 -0
- package/src/integrations/googlesheets.ts +35 -18
- package/src/integrations/mongodb.ts +4 -2
- package/src/integrations/postgres.ts +21 -3
- package/src/middleware/currentapp.ts +1 -1
- package/src/sdk/app/backups/exports.ts +33 -5
- package/src/sdk/app/backups/imports.ts +21 -1
- package/src/sdk/app/datasources/datasources.ts +1 -0
- package/src/utilities/workerRequests.ts +20 -9
- package/src/automations/tests/sendSmtpEmail.spec.js +0 -71
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/server",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "2.7.
|
|
4
|
+
"version": "2.7.20-alpha.2",
|
|
5
5
|
"description": "Budibase Web Server",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
"license": "GPL-3.0",
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@apidevtools/swagger-parser": "10.0.3",
|
|
49
|
-
"@budibase/backend-core": "2.7.
|
|
50
|
-
"@budibase/client": "2.7.
|
|
51
|
-
"@budibase/pro": "2.7.
|
|
52
|
-
"@budibase/shared-core": "2.7.
|
|
53
|
-
"@budibase/string-templates": "2.7.
|
|
54
|
-
"@budibase/types": "2.7.
|
|
49
|
+
"@budibase/backend-core": "2.7.20-alpha.2",
|
|
50
|
+
"@budibase/client": "2.7.20-alpha.2",
|
|
51
|
+
"@budibase/pro": "2.7.20-alpha.2",
|
|
52
|
+
"@budibase/shared-core": "2.7.20-alpha.2",
|
|
53
|
+
"@budibase/string-templates": "2.7.20-alpha.2",
|
|
54
|
+
"@budibase/types": "2.7.20-alpha.2",
|
|
55
55
|
"@bull-board/api": "3.7.0",
|
|
56
56
|
"@bull-board/koa": "3.9.4",
|
|
57
57
|
"@elastic/elasticsearch": "7.10.0",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"koa2-ratelimit": "1.1.1",
|
|
98
98
|
"lodash": "4.17.21",
|
|
99
99
|
"memorystream": "0.3.1",
|
|
100
|
-
"mongodb": "
|
|
100
|
+
"mongodb": "5.6",
|
|
101
101
|
"mssql": "6.2.3",
|
|
102
102
|
"mysql2": "2.3.3",
|
|
103
103
|
"node-fetch": "2.6.7",
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
"socket.io": "4.6.1",
|
|
118
118
|
"svelte": "3.49.0",
|
|
119
119
|
"swagger-parser": "10.0.3",
|
|
120
|
-
"tar": "6.1.
|
|
120
|
+
"tar": "6.1.15",
|
|
121
121
|
"to-json-schema": "0.2.5",
|
|
122
122
|
"uuid": "3.3.2",
|
|
123
123
|
"validate.js": "0.13.1",
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
"@types/redis": "4.0.11",
|
|
151
151
|
"@types/server-destroy": "1.0.1",
|
|
152
152
|
"@types/supertest": "2.0.12",
|
|
153
|
-
"@types/tar": "6.1.
|
|
153
|
+
"@types/tar": "6.1.5",
|
|
154
154
|
"@typescript-eslint/parser": "5.45.0",
|
|
155
155
|
"apidoc": "0.50.4",
|
|
156
156
|
"babel-jest": "29.5.0",
|
|
@@ -195,5 +195,5 @@
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
},
|
|
198
|
-
"gitHead": "
|
|
198
|
+
"gitHead": "c8f23a7511efa6fd996aa2d28f2c864a8c14627d"
|
|
199
199
|
}
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import sdk from "../../sdk"
|
|
2
|
-
import { events, context } from "@budibase/backend-core"
|
|
2
|
+
import { events, context, db } from "@budibase/backend-core"
|
|
3
3
|
import { DocumentType } from "../../db/utils"
|
|
4
|
-
import {
|
|
4
|
+
import { Ctx } from "@budibase/types"
|
|
5
|
+
|
|
6
|
+
interface ExportAppDumpRequest {
|
|
7
|
+
excludeRows: boolean
|
|
8
|
+
encryptPassword?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function exportAppDump(ctx: Ctx<ExportAppDumpRequest>) {
|
|
12
|
+
const { appId } = ctx.query as any
|
|
13
|
+
const { excludeRows, encryptPassword } = ctx.request.body
|
|
14
|
+
|
|
15
|
+
const [app] = await db.getAppsByIDs([appId])
|
|
16
|
+
const appName = app.name
|
|
5
17
|
|
|
6
|
-
export async function exportAppDump(ctx: any) {
|
|
7
|
-
let { appId, excludeRows } = ctx.query
|
|
8
18
|
// remove the 120 second limit for the request
|
|
9
19
|
ctx.req.setTimeout(0)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const backupIdentifier = `${appName}-export-${new Date().getTime()}
|
|
20
|
+
|
|
21
|
+
const extension = encryptPassword ? "enc.tar.gz" : "tar.gz"
|
|
22
|
+
const backupIdentifier = `${appName}-export-${new Date().getTime()}.${extension}`
|
|
13
23
|
ctx.attachment(backupIdentifier)
|
|
14
|
-
ctx.body = await sdk.backups.streamExportApp(
|
|
24
|
+
ctx.body = await sdk.backups.streamExportApp({
|
|
25
|
+
appId,
|
|
26
|
+
excludeRows,
|
|
27
|
+
encryptPassword,
|
|
28
|
+
})
|
|
15
29
|
|
|
16
30
|
await context.doInAppContext(appId, async () => {
|
|
17
31
|
const appDb = context.getAppDB()
|
|
@@ -11,7 +11,7 @@ import { BuildSchemaErrors, InvalidColumns } from "../../constants"
|
|
|
11
11
|
import { getIntegration } from "../../integrations"
|
|
12
12
|
import { getDatasourceAndQuery } from "./row/utils"
|
|
13
13
|
import { invalidateDynamicVariables } from "../../threads/utils"
|
|
14
|
-
import { db as dbCore, context, events } from "@budibase/backend-core"
|
|
14
|
+
import { db as dbCore, context, events, cache } from "@budibase/backend-core"
|
|
15
15
|
import {
|
|
16
16
|
UserCtx,
|
|
17
17
|
Datasource,
|
|
@@ -25,9 +25,11 @@ import {
|
|
|
25
25
|
FetchDatasourceInfoResponse,
|
|
26
26
|
IntegrationBase,
|
|
27
27
|
DatasourcePlus,
|
|
28
|
+
SourceName,
|
|
28
29
|
} from "@budibase/types"
|
|
29
30
|
import sdk from "../../sdk"
|
|
30
31
|
import { builderSocket } from "../../websockets"
|
|
32
|
+
import { setupCreationAuth as googleSetupCreationAuth } from "../../integrations/googlesheets"
|
|
31
33
|
|
|
32
34
|
function getErrorTables(errors: any, errorType: string) {
|
|
33
35
|
return Object.entries(errors)
|
|
@@ -101,6 +103,22 @@ async function buildSchemaHelper(datasource: Datasource) {
|
|
|
101
103
|
return { tables: connector.tables, error }
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
async function buildFilteredSchema(datasource: Datasource, filter?: string[]) {
|
|
107
|
+
let { tables, error } = await buildSchemaHelper(datasource)
|
|
108
|
+
let finalTables = tables
|
|
109
|
+
if (filter) {
|
|
110
|
+
finalTables = {}
|
|
111
|
+
for (let key in tables) {
|
|
112
|
+
if (
|
|
113
|
+
filter.some((filter: any) => filter.toLowerCase() === key.toLowerCase())
|
|
114
|
+
) {
|
|
115
|
+
finalTables[key] = tables[key]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { tables: finalTables, error }
|
|
120
|
+
}
|
|
121
|
+
|
|
104
122
|
export async function fetch(ctx: UserCtx) {
|
|
105
123
|
// Get internal tables
|
|
106
124
|
const db = context.getAppDB()
|
|
@@ -172,43 +190,28 @@ export async function information(
|
|
|
172
190
|
}
|
|
173
191
|
const tableNames = await connector.getTableNames()
|
|
174
192
|
ctx.body = {
|
|
175
|
-
tableNames,
|
|
193
|
+
tableNames: tableNames.sort(),
|
|
176
194
|
}
|
|
177
195
|
}
|
|
178
196
|
|
|
179
197
|
export async function buildSchemaFromDb(ctx: UserCtx) {
|
|
180
198
|
const db = context.getAppDB()
|
|
181
|
-
const datasource = await sdk.datasources.get(ctx.params.datasourceId)
|
|
182
199
|
const tablesFilter = ctx.request.body.tablesFilter
|
|
200
|
+
const datasource = await sdk.datasources.get(ctx.params.datasourceId)
|
|
183
201
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (!datasource.entities) {
|
|
187
|
-
datasource.entities = {}
|
|
188
|
-
}
|
|
189
|
-
for (let key in tables) {
|
|
190
|
-
if (
|
|
191
|
-
tablesFilter.some(
|
|
192
|
-
(filter: any) => filter.toLowerCase() === key.toLowerCase()
|
|
193
|
-
)
|
|
194
|
-
) {
|
|
195
|
-
datasource.entities[key] = tables[key]
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
} else {
|
|
199
|
-
datasource.entities = tables
|
|
200
|
-
}
|
|
202
|
+
const { tables, error } = await buildFilteredSchema(datasource, tablesFilter)
|
|
203
|
+
datasource.entities = tables
|
|
201
204
|
|
|
202
205
|
setDefaultDisplayColumns(datasource)
|
|
203
206
|
const dbResp = await db.put(datasource)
|
|
204
207
|
datasource._rev = dbResp.rev
|
|
205
208
|
const cleanedDatasource = await sdk.datasources.removeSecretSingle(datasource)
|
|
206
209
|
|
|
207
|
-
const
|
|
210
|
+
const res: any = { datasource: cleanedDatasource }
|
|
208
211
|
if (error) {
|
|
209
|
-
|
|
212
|
+
res.error = error
|
|
210
213
|
}
|
|
211
|
-
ctx.body =
|
|
214
|
+
ctx.body = res
|
|
212
215
|
}
|
|
213
216
|
|
|
214
217
|
/**
|
|
@@ -306,12 +309,19 @@ export async function update(ctx: UserCtx<any, UpdateDatasourceResponse>) {
|
|
|
306
309
|
builderSocket?.emitDatasourceUpdate(ctx, datasource)
|
|
307
310
|
}
|
|
308
311
|
|
|
312
|
+
const preSaveAction: Partial<Record<SourceName, any>> = {
|
|
313
|
+
[SourceName.GOOGLE_SHEETS]: async (datasource: Datasource) => {
|
|
314
|
+
await googleSetupCreationAuth(datasource.config as any)
|
|
315
|
+
},
|
|
316
|
+
}
|
|
317
|
+
|
|
309
318
|
export async function save(
|
|
310
319
|
ctx: UserCtx<CreateDatasourceRequest, CreateDatasourceResponse>
|
|
311
320
|
) {
|
|
312
321
|
const db = context.getAppDB()
|
|
313
322
|
const plus = ctx.request.body.datasource.plus
|
|
314
323
|
const fetchSchema = ctx.request.body.fetchSchema
|
|
324
|
+
const tablesFilter = ctx.request.body.tablesFilter
|
|
315
325
|
|
|
316
326
|
const datasource = {
|
|
317
327
|
_id: generateDatasourceID({ plus }),
|
|
@@ -321,12 +331,19 @@ export async function save(
|
|
|
321
331
|
|
|
322
332
|
let schemaError = null
|
|
323
333
|
if (fetchSchema) {
|
|
324
|
-
const { tables, error } = await
|
|
334
|
+
const { tables, error } = await buildFilteredSchema(
|
|
335
|
+
datasource,
|
|
336
|
+
tablesFilter
|
|
337
|
+
)
|
|
325
338
|
schemaError = error
|
|
326
339
|
datasource.entities = tables
|
|
327
340
|
setDefaultDisplayColumns(datasource)
|
|
328
341
|
}
|
|
329
342
|
|
|
343
|
+
if (preSaveAction[datasource.source]) {
|
|
344
|
+
await preSaveAction[datasource.source](datasource)
|
|
345
|
+
}
|
|
346
|
+
|
|
330
347
|
const dbResp = await db.put(datasource)
|
|
331
348
|
await events.datasource.created(datasource)
|
|
332
349
|
datasource._rev = dbResp.rev
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
getUserMetadataParams,
|
|
5
5
|
InternalTables,
|
|
6
6
|
} from "../../db/utils"
|
|
7
|
-
import {
|
|
7
|
+
import { UserCtx, Database } from "@budibase/types"
|
|
8
8
|
|
|
9
9
|
const UpdateRolesOptions = {
|
|
10
10
|
CREATED: "created",
|
|
@@ -38,15 +38,15 @@ async function updateRolesOnUserTable(
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export async function fetch(ctx:
|
|
41
|
+
export async function fetch(ctx: UserCtx) {
|
|
42
42
|
ctx.body = await roles.getAllRoles()
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export async function find(ctx:
|
|
45
|
+
export async function find(ctx: UserCtx) {
|
|
46
46
|
ctx.body = await roles.getRole(ctx.params.roleId)
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export async function save(ctx:
|
|
49
|
+
export async function save(ctx: UserCtx) {
|
|
50
50
|
const db = context.getAppDB()
|
|
51
51
|
let { _id, name, inherits, permissionId } = ctx.request.body
|
|
52
52
|
let isCreate = false
|
|
@@ -72,7 +72,7 @@ export async function save(ctx: BBContext) {
|
|
|
72
72
|
ctx.message = `Role '${role.name}' created successfully.`
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
export async function destroy(ctx:
|
|
75
|
+
export async function destroy(ctx: UserCtx) {
|
|
76
76
|
const db = context.getAppDB()
|
|
77
77
|
const roleId = ctx.params.roleId
|
|
78
78
|
const role = await db.get(roleId)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getRoutingInfo } from "../../utilities/routing"
|
|
2
2
|
import { roles } from "@budibase/backend-core"
|
|
3
|
-
import {
|
|
3
|
+
import { UserCtx } from "@budibase/types"
|
|
4
4
|
|
|
5
5
|
const URL_SEPARATOR = "/"
|
|
6
6
|
|
|
@@ -56,11 +56,11 @@ async function getRoutingStructure() {
|
|
|
56
56
|
return { routes: routing.json }
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export async function fetch(ctx:
|
|
59
|
+
export async function fetch(ctx: UserCtx) {
|
|
60
60
|
ctx.body = await getRoutingStructure()
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export async function clientFetch(ctx:
|
|
63
|
+
export async function clientFetch(ctx: UserCtx) {
|
|
64
64
|
const routing = await getRoutingStructure()
|
|
65
65
|
let roleId = ctx.user?.role?._id
|
|
66
66
|
const roleIds = (await roles.getUserRoleHierarchy(roleId, {
|
package/src/api/routes/backup.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import tk from "timekeeper"
|
|
1
2
|
import * as setup from "./utilities"
|
|
2
3
|
import { events } from "@budibase/backend-core"
|
|
3
4
|
import sdk from "../../../sdk"
|
|
4
5
|
import { checkBuilderEndpoint } from "./utilities/TestFunctions"
|
|
6
|
+
import { mocks } from "@budibase/backend-core/tests"
|
|
5
7
|
|
|
6
8
|
describe("/backups", () => {
|
|
7
9
|
let request = setup.getRequest()
|
|
@@ -16,7 +18,7 @@ describe("/backups", () => {
|
|
|
16
18
|
describe("exportAppDump", () => {
|
|
17
19
|
it("should be able to export app", async () => {
|
|
18
20
|
const res = await request
|
|
19
|
-
.
|
|
21
|
+
.post(`/api/backups/export?appId=${config.getAppId()}`)
|
|
20
22
|
.set(config.defaultHeaders())
|
|
21
23
|
.expect(200)
|
|
22
24
|
expect(res.headers["content-type"]).toEqual("application/gzip")
|
|
@@ -26,10 +28,24 @@ describe("/backups", () => {
|
|
|
26
28
|
it("should apply authorization to endpoint", async () => {
|
|
27
29
|
await checkBuilderEndpoint({
|
|
28
30
|
config,
|
|
29
|
-
method: "
|
|
31
|
+
method: "POST",
|
|
30
32
|
url: `/api/backups/export?appId=${config.getAppId()}`,
|
|
31
33
|
})
|
|
32
34
|
})
|
|
35
|
+
|
|
36
|
+
it("should infer the app name from the app", async () => {
|
|
37
|
+
tk.freeze(mocks.date.MOCK_DATE)
|
|
38
|
+
|
|
39
|
+
const res = await request
|
|
40
|
+
.post(`/api/backups/export?appId=${config.getAppId()}`)
|
|
41
|
+
.set(config.defaultHeaders())
|
|
42
|
+
|
|
43
|
+
expect(res.headers["content-disposition"]).toEqual(
|
|
44
|
+
`attachment; filename="${
|
|
45
|
+
config.getApp()!.name
|
|
46
|
+
}-export-${mocks.date.MOCK_DATE.getTime()}.tar.gz"`
|
|
47
|
+
)
|
|
48
|
+
})
|
|
33
49
|
})
|
|
34
50
|
|
|
35
51
|
describe("calculateBackupStats", () => {
|
|
@@ -48,6 +48,35 @@ export const definition: AutomationStepSchema = {
|
|
|
48
48
|
type: AutomationIOType.STRING,
|
|
49
49
|
title: "HTML Contents",
|
|
50
50
|
},
|
|
51
|
+
addInvite: {
|
|
52
|
+
type: AutomationIOType.BOOLEAN,
|
|
53
|
+
title: "Add calendar invite",
|
|
54
|
+
},
|
|
55
|
+
startTime: {
|
|
56
|
+
type: AutomationIOType.DATE,
|
|
57
|
+
title: "Start Time",
|
|
58
|
+
dependsOn: "addInvite",
|
|
59
|
+
},
|
|
60
|
+
endTime: {
|
|
61
|
+
type: AutomationIOType.DATE,
|
|
62
|
+
title: "End Time",
|
|
63
|
+
dependsOn: "addInvite",
|
|
64
|
+
},
|
|
65
|
+
summary: {
|
|
66
|
+
type: AutomationIOType.STRING,
|
|
67
|
+
title: "Meeting Summary",
|
|
68
|
+
dependsOn: "addInvite",
|
|
69
|
+
},
|
|
70
|
+
location: {
|
|
71
|
+
type: AutomationIOType.STRING,
|
|
72
|
+
title: "Location",
|
|
73
|
+
dependsOn: "addInvite",
|
|
74
|
+
},
|
|
75
|
+
url: {
|
|
76
|
+
type: AutomationIOType.STRING,
|
|
77
|
+
title: "URL",
|
|
78
|
+
dependsOn: "addInvite",
|
|
79
|
+
},
|
|
51
80
|
},
|
|
52
81
|
required: ["to", "from", "subject", "contents"],
|
|
53
82
|
},
|
|
@@ -68,21 +97,43 @@ export const definition: AutomationStepSchema = {
|
|
|
68
97
|
}
|
|
69
98
|
|
|
70
99
|
export async function run({ inputs }: AutomationStepInput) {
|
|
71
|
-
let {
|
|
100
|
+
let {
|
|
101
|
+
to,
|
|
102
|
+
from,
|
|
103
|
+
subject,
|
|
104
|
+
contents,
|
|
105
|
+
cc,
|
|
106
|
+
bcc,
|
|
107
|
+
addInvite,
|
|
108
|
+
startTime,
|
|
109
|
+
endTime,
|
|
110
|
+
summary,
|
|
111
|
+
location,
|
|
112
|
+
url,
|
|
113
|
+
} = inputs
|
|
72
114
|
if (!contents) {
|
|
73
115
|
contents = "<h1>No content</h1>"
|
|
74
116
|
}
|
|
75
117
|
to = to || undefined
|
|
76
118
|
try {
|
|
77
|
-
let response = await sendSmtpEmail(
|
|
119
|
+
let response = await sendSmtpEmail({
|
|
78
120
|
to,
|
|
79
121
|
from,
|
|
80
122
|
subject,
|
|
81
123
|
contents,
|
|
82
124
|
cc,
|
|
83
125
|
bcc,
|
|
84
|
-
true
|
|
85
|
-
|
|
126
|
+
automation: true,
|
|
127
|
+
invite: addInvite
|
|
128
|
+
? {
|
|
129
|
+
startTime,
|
|
130
|
+
endTime,
|
|
131
|
+
summary,
|
|
132
|
+
location,
|
|
133
|
+
url,
|
|
134
|
+
}
|
|
135
|
+
: undefined,
|
|
136
|
+
})
|
|
86
137
|
return {
|
|
87
138
|
success: true,
|
|
88
139
|
response,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as workerRequests from "../../utilities/workerRequests"
|
|
2
|
+
|
|
3
|
+
jest.mock("../../utilities/workerRequests", () => ({
|
|
4
|
+
sendSmtpEmail: jest.fn(),
|
|
5
|
+
}))
|
|
6
|
+
|
|
7
|
+
function generateResponse(to: string, from: string) {
|
|
8
|
+
return {
|
|
9
|
+
success: true,
|
|
10
|
+
response: {
|
|
11
|
+
accepted: [to],
|
|
12
|
+
envelope: {
|
|
13
|
+
from: from,
|
|
14
|
+
to: [to],
|
|
15
|
+
},
|
|
16
|
+
message: `Email sent to ${to}.`,
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const setup = require("./utilities")
|
|
22
|
+
|
|
23
|
+
describe("test the outgoing webhook action", () => {
|
|
24
|
+
let inputs
|
|
25
|
+
let config = setup.getConfig()
|
|
26
|
+
beforeAll(async () => {
|
|
27
|
+
await config.init()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
afterAll(setup.afterAll)
|
|
31
|
+
|
|
32
|
+
it("should be able to run the action", async () => {
|
|
33
|
+
jest
|
|
34
|
+
.spyOn(workerRequests, "sendSmtpEmail")
|
|
35
|
+
.mockImplementationOnce(async () =>
|
|
36
|
+
generateResponse("user1@test.com", "admin@test.com")
|
|
37
|
+
)
|
|
38
|
+
const invite = {
|
|
39
|
+
startTime: new Date(),
|
|
40
|
+
endTime: new Date(),
|
|
41
|
+
summary: "summary",
|
|
42
|
+
location: "location",
|
|
43
|
+
url: "url",
|
|
44
|
+
}
|
|
45
|
+
inputs = {
|
|
46
|
+
to: "user1@test.com",
|
|
47
|
+
from: "admin@test.com",
|
|
48
|
+
subject: "hello",
|
|
49
|
+
contents: "testing",
|
|
50
|
+
cc: "cc",
|
|
51
|
+
bcc: "bcc",
|
|
52
|
+
addInvite: true,
|
|
53
|
+
...invite,
|
|
54
|
+
}
|
|
55
|
+
let resp = generateResponse(inputs.to, inputs.from)
|
|
56
|
+
const res = await setup.runStep(
|
|
57
|
+
setup.actions.SEND_EMAIL_SMTP.stepId,
|
|
58
|
+
inputs
|
|
59
|
+
)
|
|
60
|
+
expect(res.response).toEqual(resp)
|
|
61
|
+
expect(res.success).toEqual(true)
|
|
62
|
+
expect(workerRequests.sendSmtpEmail).toHaveBeenCalledTimes(1)
|
|
63
|
+
expect(workerRequests.sendSmtpEmail).toHaveBeenCalledWith({
|
|
64
|
+
to: "user1@test.com",
|
|
65
|
+
from: "admin@test.com",
|
|
66
|
+
subject: "hello",
|
|
67
|
+
contents: "testing",
|
|
68
|
+
cc: "cc",
|
|
69
|
+
bcc: "bcc",
|
|
70
|
+
invite,
|
|
71
|
+
automation: true,
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
})
|
|
@@ -26,6 +26,10 @@ export default function process(updateCb?: UpdateCallback) {
|
|
|
26
26
|
// if something not found - no changes to perform
|
|
27
27
|
if (err?.status === 404) {
|
|
28
28
|
return
|
|
29
|
+
}
|
|
30
|
+
// The user has already been sync in another process
|
|
31
|
+
else if (err?.status === 409) {
|
|
32
|
+
return
|
|
29
33
|
} else {
|
|
30
34
|
logging.logAlert("Failed to perform user/group app sync", err)
|
|
31
35
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ConnectionInfo,
|
|
3
|
+
Datasource,
|
|
3
4
|
DatasourceFeature,
|
|
4
5
|
DatasourceFieldType,
|
|
5
6
|
DatasourcePlus,
|
|
@@ -19,13 +20,15 @@ import { OAuth2Client } from "google-auth-library"
|
|
|
19
20
|
import { buildExternalTableId, finaliseExternalTables } from "./utils"
|
|
20
21
|
import { GoogleSpreadsheet, GoogleSpreadsheetRow } from "google-spreadsheet"
|
|
21
22
|
import fetch from "node-fetch"
|
|
22
|
-
import { configs, HTTPError } from "@budibase/backend-core"
|
|
23
|
-
import { dataFilters } from "@budibase/shared-core"
|
|
23
|
+
import { cache, configs, context, HTTPError } from "@budibase/backend-core"
|
|
24
|
+
import { dataFilters, utils } from "@budibase/shared-core"
|
|
24
25
|
import { GOOGLE_SHEETS_PRIMARY_KEY } from "../constants"
|
|
26
|
+
import sdk from "../sdk"
|
|
25
27
|
|
|
26
28
|
interface GoogleSheetsConfig {
|
|
27
29
|
spreadsheetId: string
|
|
28
30
|
auth: OAuthClientConfig
|
|
31
|
+
continueSetupId?: string
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
interface OAuthClientConfig {
|
|
@@ -72,7 +75,7 @@ const SCHEMA: Integration = {
|
|
|
72
75
|
},
|
|
73
76
|
datasource: {
|
|
74
77
|
spreadsheetId: {
|
|
75
|
-
display: "
|
|
78
|
+
display: "Spreadsheet URL",
|
|
76
79
|
type: DatasourceFieldType.STRING,
|
|
77
80
|
required: true,
|
|
78
81
|
},
|
|
@@ -207,6 +210,8 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
207
210
|
|
|
208
211
|
async connect() {
|
|
209
212
|
try {
|
|
213
|
+
await setupCreationAuth(this.config)
|
|
214
|
+
|
|
210
215
|
// Initialise oAuth client
|
|
211
216
|
let googleConfig = await configs.getGoogleDatasourceConfig()
|
|
212
217
|
if (!googleConfig) {
|
|
@@ -269,24 +274,24 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
269
274
|
}
|
|
270
275
|
|
|
271
276
|
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
|
|
272
|
-
// not fully configured yet
|
|
273
|
-
if (!this.config.auth) {
|
|
274
|
-
return
|
|
275
|
-
}
|
|
276
277
|
await this.connect()
|
|
277
278
|
const sheets = this.client.sheetsByIndex
|
|
278
279
|
const tables: Record<string, Table> = {}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
sheet.title
|
|
286
|
-
sheet.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
280
|
+
await utils.parallelForeach(
|
|
281
|
+
sheets,
|
|
282
|
+
async sheet => {
|
|
283
|
+
// must fetch rows to determine schema
|
|
284
|
+
await sheet.getRows({ limit: 0, offset: 0 })
|
|
285
|
+
|
|
286
|
+
const id = buildExternalTableId(datasourceId, sheet.title)
|
|
287
|
+
tables[sheet.title] = this.getTableSchema(
|
|
288
|
+
sheet.title,
|
|
289
|
+
sheet.headerValues,
|
|
290
|
+
id
|
|
291
|
+
)
|
|
292
|
+
},
|
|
293
|
+
10
|
|
294
|
+
)
|
|
290
295
|
const final = finaliseExternalTables(tables, entities)
|
|
291
296
|
this.tables = final.tables
|
|
292
297
|
this.schemaErrors = final.errors
|
|
@@ -566,6 +571,18 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
566
571
|
}
|
|
567
572
|
}
|
|
568
573
|
|
|
574
|
+
export async function setupCreationAuth(datasouce: GoogleSheetsConfig) {
|
|
575
|
+
if (datasouce.continueSetupId) {
|
|
576
|
+
const appId = context.getAppId()
|
|
577
|
+
const tokens = await cache.get(
|
|
578
|
+
`datasource:creation:${appId}:google:${datasouce.continueSetupId}`
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
datasouce.auth = tokens.tokens
|
|
582
|
+
delete datasouce.continueSetupId
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
569
586
|
export default {
|
|
570
587
|
schema: SCHEMA,
|
|
571
588
|
integration: GoogleSheetsIntegration,
|
|
@@ -351,7 +351,7 @@ const SCHEMA: Integration = getSchema()
|
|
|
351
351
|
|
|
352
352
|
class MongoIntegration implements IntegrationBase {
|
|
353
353
|
private config: MongoDBConfig
|
|
354
|
-
private client:
|
|
354
|
+
private client: MongoClient
|
|
355
355
|
|
|
356
356
|
constructor(config: MongoDBConfig) {
|
|
357
357
|
this.config = config
|
|
@@ -372,6 +372,8 @@ class MongoIntegration implements IntegrationBase {
|
|
|
372
372
|
response.connected = true
|
|
373
373
|
} catch (e: any) {
|
|
374
374
|
response.error = e.message as string
|
|
375
|
+
} finally {
|
|
376
|
+
await this.client.close()
|
|
375
377
|
}
|
|
376
378
|
return response
|
|
377
379
|
}
|
|
@@ -380,7 +382,7 @@ class MongoIntegration implements IntegrationBase {
|
|
|
380
382
|
return this.client.connect()
|
|
381
383
|
}
|
|
382
384
|
|
|
383
|
-
createObjectIds(json: any)
|
|
385
|
+
createObjectIds(json: any) {
|
|
384
386
|
const self = this
|
|
385
387
|
function interpolateObjectIds(json: any) {
|
|
386
388
|
for (let field of Object.keys(json)) {
|