@budibase/server 1.0.27-alpha.1 → 1.0.27-alpha.10
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/__mocks__/node-fetch.ts +18 -0
- package/builder/assets/{index.4a13c4cb.css → index.0dce895b.css} +1 -1
- package/builder/assets/index.39751e34.js +1348 -0
- package/builder/index.html +2 -2
- package/coverage/clover.xml +787 -637
- package/coverage/coverage-final.json +27 -27
- package/coverage/lcov-report/api/controllers/analytics.js.html +1 -1
- package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/controllers/application.js.html +54 -54
- package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
- package/coverage/lcov-report/api/controllers/automation.js.html +1 -1
- package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
- package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
- package/coverage/lcov-report/api/controllers/component.js.html +4 -4
- package/coverage/lcov-report/api/controllers/datasource.js.html +49 -49
- package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
- package/coverage/lcov-report/api/controllers/hosting.js.html +1 -1
- package/coverage/lcov-report/api/controllers/index.html +20 -20
- package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
- package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
- package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
- package/coverage/lcov-report/api/controllers/permission.js.html +1 -1
- package/coverage/lcov-report/api/controllers/query/index.html +19 -19
- package/coverage/lcov-report/api/controllers/query/index.js.html +100 -121
- package/coverage/lcov-report/api/controllers/query/validation.js.html +6 -3
- package/coverage/lcov-report/api/controllers/role.js.html +1 -1
- package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/index.html +22 -22
- package/coverage/lcov-report/api/controllers/row/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/internal.js.html +38 -11
- package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +7 -4
- package/coverage/lcov-report/api/controllers/row/utils.js.html +29 -20
- package/coverage/lcov-report/api/controllers/screen.js.html +1 -1
- package/coverage/lcov-report/api/controllers/script.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/external.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/index.html +1 -1
- package/coverage/lcov-report/api/controllers/table/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/internal.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/utils.js.html +1 -1
- package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
- package/coverage/lcov-report/api/controllers/user.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
- package/coverage/lcov-report/api/controllers/webhook.js.html +1 -1
- package/coverage/lcov-report/api/index.html +1 -1
- package/coverage/lcov-report/api/index.js.html +5 -5
- package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
- package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/routes/application.js.html +1 -1
- package/coverage/lcov-report/api/routes/auth.js.html +1 -1
- package/coverage/lcov-report/api/routes/automation.js.html +1 -1
- package/coverage/lcov-report/api/routes/backup.js.html +1 -1
- package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
- package/coverage/lcov-report/api/routes/component.js.html +1 -1
- package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
- package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
- package/coverage/lcov-report/api/routes/dev.js.html +1 -1
- package/coverage/lcov-report/api/routes/hosting.js.html +1 -1
- package/coverage/lcov-report/api/routes/index.html +1 -1
- package/coverage/lcov-report/api/routes/index.js.html +1 -1
- package/coverage/lcov-report/api/routes/integration.js.html +1 -1
- package/coverage/lcov-report/api/routes/layout.js.html +1 -1
- package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
- package/coverage/lcov-report/api/routes/permission.js.html +1 -1
- package/coverage/lcov-report/api/routes/query.js.html +24 -3
- package/coverage/lcov-report/api/routes/role.js.html +1 -1
- package/coverage/lcov-report/api/routes/routing.js.html +1 -1
- package/coverage/lcov-report/api/routes/row.js.html +1 -1
- package/coverage/lcov-report/api/routes/screen.js.html +1 -1
- package/coverage/lcov-report/api/routes/script.js.html +1 -1
- package/coverage/lcov-report/api/routes/static.js.html +1 -1
- package/coverage/lcov-report/api/routes/table.js.html +1 -1
- package/coverage/lcov-report/api/routes/templates.js.html +1 -1
- package/coverage/lcov-report/api/routes/user.js.html +1 -1
- package/coverage/lcov-report/api/routes/view.js.html +1 -1
- package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/actions.js.html +1 -1
- package/coverage/lcov-report/automations/automationUtils.js.html +1 -1
- package/coverage/lcov-report/automations/bullboard.js.html +1 -1
- package/coverage/lcov-report/automations/index.html +1 -1
- package/coverage/lcov-report/automations/index.js.html +1 -1
- package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
- package/coverage/lcov-report/automations/steps/createRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
- package/coverage/lcov-report/automations/steps/deleteRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeQuery.js.html +31 -7
- package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
- package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
- package/coverage/lcov-report/automations/steps/index.html +11 -11
- package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
- package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +1 -1
- package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
- package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
- package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
- package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
- package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
- package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/triggers.js.html +1 -1
- package/coverage/lcov-report/automations/utils.js.html +5 -5
- package/coverage/lcov-report/constants/index.html +1 -1
- package/coverage/lcov-report/constants/index.js.html +1 -1
- package/coverage/lcov-report/constants/layouts.js.html +1 -1
- package/coverage/lcov-report/constants/screens.js.html +1 -1
- package/coverage/lcov-report/db/client.js.html +1 -1
- package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
- package/coverage/lcov-report/db/index.html +1 -1
- package/coverage/lcov-report/db/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
- package/coverage/lcov-report/db/newid.js.html +2 -2
- package/coverage/lcov-report/db/utils.js.html +18 -18
- package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
- package/coverage/lcov-report/events/index.html +1 -1
- package/coverage/lcov-report/events/index.js.html +1 -1
- package/coverage/lcov-report/events/utils.js.html +1 -1
- package/coverage/lcov-report/index.html +63 -63
- package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
- package/coverage/lcov-report/integrations/tests/index.html +1 -1
- package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
- package/coverage/lcov-report/middleware/authorized.js.html +10 -10
- package/coverage/lcov-report/middleware/builder.js.html +8 -8
- package/coverage/lcov-report/middleware/currentapp.js.html +28 -28
- package/coverage/lcov-report/middleware/index.html +1 -1
- package/coverage/lcov-report/middleware/joi-validator.js.html +9 -9
- package/coverage/lcov-report/middleware/resourceId.js.html +17 -17
- package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
- package/coverage/lcov-report/middleware/usageQuota.js.html +1 -1
- package/coverage/lcov-report/middleware/utils.js.html +2 -2
- package/coverage/lcov-report/threads/automation.js.html +1 -1
- package/coverage/lcov-report/threads/index.html +31 -31
- package/coverage/lcov-report/threads/index.js.html +11 -11
- package/coverage/lcov-report/threads/query.js.html +367 -91
- package/coverage/lcov-report/threads/utils.js.html +356 -11
- package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
- package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
- package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
- package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
- package/coverage/lcov-report/utilities/global.js.html +13 -13
- package/coverage/lcov-report/utilities/index.html +1 -1
- package/coverage/lcov-report/utilities/index.js.html +9 -9
- package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +2 -2
- package/coverage/lcov-report/utilities/queue/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.html +19 -19
- package/coverage/lcov-report/utilities/rowProcessor/index.js.html +235 -10
- package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
- package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
- package/coverage/lcov-report/utilities/security.js.html +1 -1
- package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
- package/coverage/lcov-report/utilities/users.js.html +1 -1
- package/coverage/lcov-report/utilities/workerRequests.js.html +1 -1
- package/coverage/lcov.info +1348 -1077
- package/dist/api/controllers/query/import/sources/base/index.js +2 -1
- package/dist/api/controllers/query/import/sources/curl.js +11 -5
- package/dist/api/controllers/query/import/sources/openapi2.js +8 -5
- package/dist/api/controllers/query/index.js +45 -47
- package/dist/api/controllers/query/validation.js +2 -1
- package/dist/api/controllers/row/internal.js +14 -6
- package/dist/api/controllers/row/internalSearch.js +2 -1
- package/dist/api/controllers/row/utils.js +13 -6
- package/dist/api/routes/query.js +3 -1
- package/dist/automations/steps/executeQuery.js +8 -1
- package/dist/integrations/base/sqlTable.js +1 -1
- package/dist/integrations/rest.js +3 -3
- package/dist/package.json +5 -4
- package/dist/tests/utilities/TestConfiguration.js +7 -0
- package/dist/threads/query.js +148 -52
- package/dist/threads/utils.js +105 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utilities/fileSystem/utilities.js +2 -1
- package/dist/utilities/rowProcessor/index.js +76 -0
- package/package.json +6 -5
- package/src/api/controllers/query/import/sources/base/index.ts +3 -1
- package/src/api/controllers/query/import/sources/curl.ts +12 -4
- package/src/api/controllers/query/import/sources/openapi2.ts +9 -5
- package/src/api/controllers/query/import/sources/tests/curl/curl.spec.js +2 -3
- package/src/api/controllers/query/import/sources/tests/openapi2/openapi2.spec.js +6 -7
- package/src/api/controllers/query/import/tests/index.spec.js +0 -6
- package/src/api/controllers/query/index.js +48 -55
- package/src/api/controllers/query/validation.js +2 -1
- package/src/api/controllers/row/internal.js +14 -5
- package/src/api/controllers/row/internalSearch.js +2 -1
- package/src/api/controllers/row/utils.js +11 -8
- package/src/api/routes/query.js +8 -1
- package/src/api/routes/tests/query.spec.js +88 -0
- package/src/automations/steps/executeQuery.js +9 -1
- package/src/definitions/datasource.ts +10 -0
- package/src/integrations/base/sqlTable.ts +1 -1
- package/src/integrations/rest.ts +6 -3
- package/src/tests/utilities/TestConfiguration.js +10 -0
- package/src/threads/query.js +148 -56
- package/src/threads/utils.js +115 -0
- package/src/utilities/fileSystem/utilities.js +2 -0
- package/src/utilities/rowProcessor/index.js +75 -0
- package/builder/assets/index.00c4135e.js +0 -1320
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Mock out postgres for this
|
|
2
2
|
jest.mock("pg")
|
|
3
|
+
jest.mock("node-fetch")
|
|
3
4
|
|
|
4
5
|
// Mock isProdAppID to we can later mock the implementation and pretend we are
|
|
5
6
|
// using prod app IDs
|
|
@@ -10,6 +11,7 @@ authDb.isProdAppID = mockIsProdAppID
|
|
|
10
11
|
|
|
11
12
|
const setup = require("./utilities")
|
|
12
13
|
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
|
14
|
+
const { checkCacheForDynamicVariable } = require("../../../threads/utils")
|
|
13
15
|
const { basicQuery, basicDatasource } = setup.structures
|
|
14
16
|
|
|
15
17
|
describe("/queries", () => {
|
|
@@ -226,4 +228,90 @@ describe("/queries", () => {
|
|
|
226
228
|
.expect(400)
|
|
227
229
|
})
|
|
228
230
|
})
|
|
231
|
+
|
|
232
|
+
describe("test variables", () => {
|
|
233
|
+
async function restDatasource(cfg) {
|
|
234
|
+
return await config.createDatasource({
|
|
235
|
+
datasource: {
|
|
236
|
+
...basicDatasource().datasource,
|
|
237
|
+
source: "REST",
|
|
238
|
+
config: cfg || {},
|
|
239
|
+
},
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function dynamicVariableDatasource() {
|
|
244
|
+
const datasource = await restDatasource()
|
|
245
|
+
const basedOnQuery = await config.createQuery({
|
|
246
|
+
...basicQuery(datasource._id),
|
|
247
|
+
fields: {
|
|
248
|
+
path: "www.google.com",
|
|
249
|
+
},
|
|
250
|
+
})
|
|
251
|
+
await config.updateDatasource({
|
|
252
|
+
...datasource,
|
|
253
|
+
config: {
|
|
254
|
+
dynamicVariables: [
|
|
255
|
+
{ queryId: basedOnQuery._id, name: "variable3", value: "{{ data.0.[value] }}" }
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
return { datasource, query: basedOnQuery }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function preview(datasource, fields) {
|
|
263
|
+
return await request
|
|
264
|
+
.post(`/api/queries/preview`)
|
|
265
|
+
.send({
|
|
266
|
+
datasourceId: datasource._id,
|
|
267
|
+
parameters: {},
|
|
268
|
+
fields,
|
|
269
|
+
queryVerb: "read",
|
|
270
|
+
})
|
|
271
|
+
.set(config.defaultHeaders())
|
|
272
|
+
.expect("Content-Type", /json/)
|
|
273
|
+
.expect(200)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
it("should work with static variables", async () => {
|
|
277
|
+
const datasource = await restDatasource({
|
|
278
|
+
staticVariables: {
|
|
279
|
+
variable: "google",
|
|
280
|
+
variable2: "1",
|
|
281
|
+
},
|
|
282
|
+
})
|
|
283
|
+
const res = await preview(datasource, {
|
|
284
|
+
path: "www.{{ variable }}.com",
|
|
285
|
+
queryString: "test={{ variable2 }}",
|
|
286
|
+
})
|
|
287
|
+
// these responses come from the mock
|
|
288
|
+
expect(res.body.schemaFields).toEqual(["url", "opts", "value"])
|
|
289
|
+
expect(res.body.rows[0].url).toEqual("http://www.google.com?test=1")
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it("should work with dynamic variables", async () => {
|
|
293
|
+
const { datasource } = await dynamicVariableDatasource()
|
|
294
|
+
const res = await preview(datasource, {
|
|
295
|
+
path: "www.google.com",
|
|
296
|
+
queryString: "test={{ variable3 }}",
|
|
297
|
+
})
|
|
298
|
+
expect(res.body.schemaFields).toEqual(["url", "opts", "value"])
|
|
299
|
+
expect(res.body.rows[0].url).toContain("doctype html")
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it("check that it automatically retries on fail with cached dynamics", async () => {
|
|
303
|
+
const { datasource, query: base } = await dynamicVariableDatasource()
|
|
304
|
+
// preview once to cache
|
|
305
|
+
await preview(datasource, { path: "www.google.com", queryString: "test={{ variable3 }}" })
|
|
306
|
+
// check its in cache
|
|
307
|
+
const contents = await checkCacheForDynamicVariable(base._id, "variable3")
|
|
308
|
+
expect(contents.rows.length).toEqual(1)
|
|
309
|
+
const res = await preview(datasource, {
|
|
310
|
+
path: "www.failonce.com",
|
|
311
|
+
queryString: "test={{ variable3 }}",
|
|
312
|
+
})
|
|
313
|
+
expect(res.body.schemaFields).toEqual(["fails", "url", "opts"])
|
|
314
|
+
expect(res.body.rows[0].fails).toEqual(1)
|
|
315
|
+
})
|
|
316
|
+
})
|
|
229
317
|
})
|
|
@@ -35,6 +35,11 @@ exports.definition = {
|
|
|
35
35
|
type: "object",
|
|
36
36
|
description: "The response from the datasource execution",
|
|
37
37
|
},
|
|
38
|
+
info: {
|
|
39
|
+
type: "object",
|
|
40
|
+
description:
|
|
41
|
+
"Some query types may return extra data, like headers from a REST query",
|
|
42
|
+
},
|
|
38
43
|
success: {
|
|
39
44
|
type: "boolean",
|
|
40
45
|
description: "Whether the action was successful",
|
|
@@ -68,13 +73,16 @@ exports.run = async function ({ inputs, appId, emitter }) {
|
|
|
68
73
|
|
|
69
74
|
try {
|
|
70
75
|
await queryController.execute(ctx)
|
|
76
|
+
const { data, ...rest } = ctx.body
|
|
71
77
|
return {
|
|
72
|
-
response:
|
|
78
|
+
response: data,
|
|
79
|
+
info: rest,
|
|
73
80
|
success: true,
|
|
74
81
|
}
|
|
75
82
|
} catch (err) {
|
|
76
83
|
return {
|
|
77
84
|
success: false,
|
|
85
|
+
info: {},
|
|
78
86
|
response: automationUtils.getError(err),
|
|
79
87
|
}
|
|
80
88
|
}
|
|
@@ -240,6 +240,16 @@ export interface RestConfig {
|
|
|
240
240
|
[key: string]: any
|
|
241
241
|
}
|
|
242
242
|
authConfigs: AuthConfig[]
|
|
243
|
+
staticVariables: {
|
|
244
|
+
[key: string]: string
|
|
245
|
+
}
|
|
246
|
+
dynamicVariables: [
|
|
247
|
+
{
|
|
248
|
+
name: string
|
|
249
|
+
queryId: string
|
|
250
|
+
value: string
|
|
251
|
+
}
|
|
252
|
+
]
|
|
243
253
|
}
|
|
244
254
|
|
|
245
255
|
export interface Query {
|
package/src/integrations/rest.ts
CHANGED
|
@@ -48,7 +48,10 @@ module RestModule {
|
|
|
48
48
|
const { performance } = require("perf_hooks")
|
|
49
49
|
const FormData = require("form-data")
|
|
50
50
|
const { URLSearchParams } = require("url")
|
|
51
|
-
const {
|
|
51
|
+
const {
|
|
52
|
+
parseStringPromise: xmlParser,
|
|
53
|
+
Builder: XmlBuilder,
|
|
54
|
+
} = require("xml2js")
|
|
52
55
|
|
|
53
56
|
const SCHEMA: Integration = {
|
|
54
57
|
docs: "https://github.com/node-fetch/node-fetch",
|
|
@@ -168,7 +171,7 @@ module RestModule {
|
|
|
168
171
|
getUrl(path: string, queryString: string): string {
|
|
169
172
|
const main = `${path}?${queryString}`
|
|
170
173
|
let complete = main
|
|
171
|
-
if (this.config.url && !main.startsWith(
|
|
174
|
+
if (this.config.url && !main.startsWith("http")) {
|
|
172
175
|
complete = !this.config.url ? main : `${this.config.url}/${main}`
|
|
173
176
|
}
|
|
174
177
|
if (!complete.startsWith("http")) {
|
|
@@ -211,7 +214,7 @@ module RestModule {
|
|
|
211
214
|
break
|
|
212
215
|
case BodyTypes.XML:
|
|
213
216
|
if (object != null) {
|
|
214
|
-
string =
|
|
217
|
+
string = new XmlBuilder().buildObject(object)
|
|
215
218
|
}
|
|
216
219
|
input.body = string
|
|
217
220
|
input.headers["Content-Type"] = "application/xml"
|
|
@@ -316,6 +316,16 @@ class TestConfiguration {
|
|
|
316
316
|
return this.datasource
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
async updateDatasource(datasource) {
|
|
320
|
+
const response = await this._req(
|
|
321
|
+
datasource,
|
|
322
|
+
{ datasourceId: datasource._id },
|
|
323
|
+
controllers.datasource.update
|
|
324
|
+
)
|
|
325
|
+
this.datasource = response.datasource
|
|
326
|
+
return this.datasource
|
|
327
|
+
}
|
|
328
|
+
|
|
319
329
|
async createQuery(config = null) {
|
|
320
330
|
if (!this.datasource && !config) {
|
|
321
331
|
throw "No data source created for query."
|
package/src/threads/query.js
CHANGED
|
@@ -1,77 +1,169 @@
|
|
|
1
|
-
require("./utils")
|
|
1
|
+
const threadUtils = require("./utils")
|
|
2
|
+
threadUtils.threadSetup()
|
|
2
3
|
const ScriptRunner = require("../utilities/scriptRunner")
|
|
3
4
|
const { integrations } = require("../integrations")
|
|
5
|
+
const { processStringSync } = require("@budibase/string-templates")
|
|
6
|
+
const CouchDB = require("../db")
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
class QueryRunner {
|
|
9
|
+
constructor(input, flags = { noRecursiveQuery: false }) {
|
|
10
|
+
this.appId = input.appId
|
|
11
|
+
this.datasource = input.datasource
|
|
12
|
+
this.queryVerb = input.queryVerb
|
|
13
|
+
this.fields = input.fields
|
|
14
|
+
this.parameters = input.parameters
|
|
15
|
+
this.transformer = input.transformer
|
|
16
|
+
this.queryId = input.queryId
|
|
17
|
+
this.noRecursiveQuery = flags.noRecursiveQuery
|
|
18
|
+
this.cachedVariables = []
|
|
19
|
+
// allows the response from a query to be stored throughout this
|
|
20
|
+
// execution so that if it needs to be re-used for another variable
|
|
21
|
+
// it can be
|
|
22
|
+
this.queryResponse = {}
|
|
23
|
+
this.hasRerun = false
|
|
12
24
|
}
|
|
13
|
-
return resp
|
|
14
|
-
}
|
|
15
25
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
async execute() {
|
|
27
|
+
let { datasource, fields, queryVerb, transformer } = this
|
|
28
|
+
// pre-query, make sure datasource variables are added to parameters
|
|
29
|
+
const parameters = await this.addDatasourceVariables()
|
|
30
|
+
const query = threadUtils.enrichQueryFields(fields, parameters)
|
|
31
|
+
const Integration = integrations[datasource.source]
|
|
32
|
+
if (!Integration) {
|
|
33
|
+
throw "Integration type does not exist."
|
|
34
|
+
}
|
|
35
|
+
const integration = new Integration(datasource.config)
|
|
24
36
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
let output = threadUtils.formatResponse(await integration[queryVerb](query))
|
|
38
|
+
let rows = output,
|
|
39
|
+
info = undefined,
|
|
40
|
+
extra = undefined
|
|
41
|
+
if (threadUtils.hasExtraData(output)) {
|
|
42
|
+
rows = output.data
|
|
43
|
+
info = output.info
|
|
44
|
+
extra = output.extra
|
|
45
|
+
}
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
47
|
+
// transform as required
|
|
48
|
+
if (transformer) {
|
|
49
|
+
const runner = new ScriptRunner(transformer, {
|
|
50
|
+
data: rows,
|
|
51
|
+
params: parameters,
|
|
52
|
+
})
|
|
53
|
+
rows = runner.execute()
|
|
54
|
+
}
|
|
41
55
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
// if the request fails we retry once, invalidating the cached value
|
|
57
|
+
if (
|
|
58
|
+
info &&
|
|
59
|
+
info.code >= 400 &&
|
|
60
|
+
this.cachedVariables.length > 0 &&
|
|
61
|
+
!this.hasRerun
|
|
62
|
+
) {
|
|
63
|
+
this.hasRerun = true
|
|
64
|
+
// invalidate the cache value
|
|
65
|
+
await threadUtils.invalidateDynamicVariables(this.cachedVariables)
|
|
66
|
+
return this.execute()
|
|
67
|
+
}
|
|
47
68
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
// needs to an array for next step
|
|
70
|
+
if (!Array.isArray(rows)) {
|
|
71
|
+
rows = [rows]
|
|
72
|
+
}
|
|
52
73
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
74
|
+
// map into JSON if just raw primitive here
|
|
75
|
+
if (rows.find(row => typeof row !== "object")) {
|
|
76
|
+
rows = rows.map(value => ({ value }))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// get all the potential fields in the schema
|
|
80
|
+
let keys = rows.flatMap(Object.keys)
|
|
81
|
+
|
|
82
|
+
if (integration.end) {
|
|
83
|
+
integration.end()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { rows, keys, info, extra }
|
|
56
87
|
}
|
|
57
88
|
|
|
58
|
-
|
|
59
|
-
|
|
89
|
+
async runAnotherQuery(queryId, parameters) {
|
|
90
|
+
const db = new CouchDB(this.appId)
|
|
91
|
+
const query = await db.get(queryId)
|
|
92
|
+
const datasource = await db.get(query.datasourceId)
|
|
93
|
+
return new QueryRunner(
|
|
94
|
+
{
|
|
95
|
+
appId: this.appId,
|
|
96
|
+
datasource,
|
|
97
|
+
queryVerb: query.queryVerb,
|
|
98
|
+
fields: query.fields,
|
|
99
|
+
parameters,
|
|
100
|
+
transformer: query.transformer,
|
|
101
|
+
},
|
|
102
|
+
{ noRecursiveQuery: true }
|
|
103
|
+
).execute()
|
|
104
|
+
}
|
|
60
105
|
|
|
61
|
-
|
|
62
|
-
|
|
106
|
+
async getDynamicVariable(variable) {
|
|
107
|
+
let { parameters } = this
|
|
108
|
+
const queryId = variable.queryId,
|
|
109
|
+
name = variable.name
|
|
110
|
+
let value = await threadUtils.checkCacheForDynamicVariable(queryId, name)
|
|
111
|
+
if (!value) {
|
|
112
|
+
value = this.queryResponse[queryId]
|
|
113
|
+
? this.queryResponse[queryId]
|
|
114
|
+
: await this.runAnotherQuery(queryId, parameters)
|
|
115
|
+
// store incase this query is to be called again
|
|
116
|
+
this.queryResponse[queryId] = value
|
|
117
|
+
await threadUtils.storeDynamicVariable(queryId, name, value)
|
|
118
|
+
} else {
|
|
119
|
+
this.cachedVariables.push({ queryId, name })
|
|
120
|
+
}
|
|
121
|
+
return value
|
|
63
122
|
}
|
|
64
123
|
|
|
65
|
-
|
|
124
|
+
async addDatasourceVariables() {
|
|
125
|
+
let { datasource, parameters, fields } = this
|
|
126
|
+
if (!datasource || !datasource.config) {
|
|
127
|
+
return parameters
|
|
128
|
+
}
|
|
129
|
+
const staticVars = datasource.config.staticVariables || {}
|
|
130
|
+
const dynamicVars = datasource.config.dynamicVariables || []
|
|
131
|
+
for (let [key, value] of Object.entries(staticVars)) {
|
|
132
|
+
if (!parameters[key]) {
|
|
133
|
+
parameters[key] = value
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (!this.noRecursiveQuery) {
|
|
137
|
+
// need to see if this uses any variables
|
|
138
|
+
const stringFields = JSON.stringify(fields)
|
|
139
|
+
const foundVars = dynamicVars.filter(variable => {
|
|
140
|
+
// don't allow a query to use its own dynamic variable (loop)
|
|
141
|
+
if (variable.queryId === this.queryId) {
|
|
142
|
+
return false
|
|
143
|
+
}
|
|
144
|
+
// look for {{ variable }} but allow spaces between handlebars
|
|
145
|
+
const regex = new RegExp(`{{[ ]*${variable.name}[ ]*}}`)
|
|
146
|
+
return regex.test(stringFields)
|
|
147
|
+
})
|
|
148
|
+
const dynamics = foundVars.map(dynVar => this.getDynamicVariable(dynVar))
|
|
149
|
+
const responses = await Promise.all(dynamics)
|
|
150
|
+
for (let i = 0; i < foundVars.length; i++) {
|
|
151
|
+
const variable = foundVars[i]
|
|
152
|
+
parameters[variable.name] = processStringSync(variable.value, {
|
|
153
|
+
data: responses[i].rows,
|
|
154
|
+
info: responses[i].extra,
|
|
155
|
+
})
|
|
156
|
+
// make sure its known that this uses dynamic variables in case it fails
|
|
157
|
+
this.hasDynamicVariables = true
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return parameters
|
|
161
|
+
}
|
|
66
162
|
}
|
|
67
163
|
|
|
68
164
|
module.exports = (input, callback) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
input.queryVerb,
|
|
72
|
-
input.query,
|
|
73
|
-
input.transformer
|
|
74
|
-
)
|
|
165
|
+
const Runner = new QueryRunner(input)
|
|
166
|
+
Runner.execute()
|
|
75
167
|
.then(response => {
|
|
76
168
|
callback(null, response)
|
|
77
169
|
})
|
package/src/threads/utils.js
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
const env = require("../environment")
|
|
2
2
|
const CouchDB = require("../db")
|
|
3
3
|
const { init } = require("@budibase/auth")
|
|
4
|
+
const redis = require("@budibase/auth/redis")
|
|
5
|
+
const { SEPARATOR } = require("@budibase/auth/db")
|
|
6
|
+
const { processStringSync } = require("@budibase/string-templates")
|
|
7
|
+
|
|
8
|
+
const VARIABLE_TTL_SECONDS = 3600
|
|
9
|
+
let client
|
|
10
|
+
|
|
11
|
+
const IS_TRIPLE_BRACE = new RegExp(/^{{3}.*}{3}$/)
|
|
12
|
+
const IS_HANDLEBARS = new RegExp(/^{{2}.*}{2}$/)
|
|
13
|
+
|
|
14
|
+
async function getClient() {
|
|
15
|
+
if (!client) {
|
|
16
|
+
client = await new redis.Client(redis.utils.Databases.QUERY_VARS).init()
|
|
17
|
+
}
|
|
18
|
+
return client
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
process.on("exit", async () => {
|
|
22
|
+
if (client) await client.finish()
|
|
23
|
+
})
|
|
4
24
|
|
|
5
25
|
exports.threadSetup = () => {
|
|
6
26
|
// don't run this if not threading
|
|
@@ -11,3 +31,98 @@ exports.threadSetup = () => {
|
|
|
11
31
|
env.setInThread()
|
|
12
32
|
init(CouchDB)
|
|
13
33
|
}
|
|
34
|
+
|
|
35
|
+
function makeVariableKey(queryId, variable) {
|
|
36
|
+
return `${queryId}${SEPARATOR}${variable}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
exports.checkCacheForDynamicVariable = async (queryId, variable) => {
|
|
40
|
+
const cache = await getClient()
|
|
41
|
+
return cache.get(makeVariableKey(queryId, variable))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
exports.invalidateDynamicVariables = async cachedVars => {
|
|
45
|
+
const cache = await getClient()
|
|
46
|
+
let promises = []
|
|
47
|
+
for (let variable of cachedVars) {
|
|
48
|
+
promises.push(
|
|
49
|
+
cache.delete(makeVariableKey(variable.queryId, variable.name))
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
await Promise.all(promises)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
exports.storeDynamicVariable = async (queryId, variable, value) => {
|
|
56
|
+
const cache = await getClient()
|
|
57
|
+
await cache.store(
|
|
58
|
+
makeVariableKey(queryId, variable),
|
|
59
|
+
value,
|
|
60
|
+
VARIABLE_TTL_SECONDS
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
exports.formatResponse = resp => {
|
|
65
|
+
if (typeof resp === "string") {
|
|
66
|
+
try {
|
|
67
|
+
resp = JSON.parse(resp)
|
|
68
|
+
} catch (err) {
|
|
69
|
+
resp = { response: resp }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return resp
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
exports.hasExtraData = response => {
|
|
76
|
+
return (
|
|
77
|
+
typeof response === "object" &&
|
|
78
|
+
!Array.isArray(response) &&
|
|
79
|
+
response.data != null &&
|
|
80
|
+
response.info != null
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
exports.enrichQueryFields = (fields, parameters = {}) => {
|
|
85
|
+
const enrichedQuery = {}
|
|
86
|
+
|
|
87
|
+
// enrich the fields with dynamic parameters
|
|
88
|
+
for (let key of Object.keys(fields)) {
|
|
89
|
+
if (fields[key] == null) {
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
if (typeof fields[key] === "object") {
|
|
93
|
+
// enrich nested fields object
|
|
94
|
+
enrichedQuery[key] = this.enrichQueryFields(fields[key], parameters)
|
|
95
|
+
} else if (typeof fields[key] === "string") {
|
|
96
|
+
// enrich string value as normal
|
|
97
|
+
let value = fields[key]
|
|
98
|
+
// add triple brace to avoid escaping e.g. '=' in cookie header
|
|
99
|
+
if (IS_HANDLEBARS.test(value) && !IS_TRIPLE_BRACE.test(value)) {
|
|
100
|
+
value = `{${value}}`
|
|
101
|
+
}
|
|
102
|
+
enrichedQuery[key] = processStringSync(value, parameters, {
|
|
103
|
+
noHelpers: true,
|
|
104
|
+
})
|
|
105
|
+
} else {
|
|
106
|
+
enrichedQuery[key] = fields[key]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
enrichedQuery.json ||
|
|
112
|
+
enrichedQuery.customData ||
|
|
113
|
+
enrichedQuery.requestBody
|
|
114
|
+
) {
|
|
115
|
+
try {
|
|
116
|
+
enrichedQuery.json = JSON.parse(
|
|
117
|
+
enrichedQuery.json ||
|
|
118
|
+
enrichedQuery.customData ||
|
|
119
|
+
enrichedQuery.requestBody
|
|
120
|
+
)
|
|
121
|
+
} catch (err) {
|
|
122
|
+
// no json found, ignore
|
|
123
|
+
}
|
|
124
|
+
delete enrichedQuery.customData
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return enrichedQuery
|
|
128
|
+
}
|
|
@@ -2,6 +2,7 @@ const {
|
|
|
2
2
|
ObjectStore,
|
|
3
3
|
makeSureBucketExists,
|
|
4
4
|
upload,
|
|
5
|
+
deleteFiles,
|
|
5
6
|
streamUpload,
|
|
6
7
|
retrieve,
|
|
7
8
|
retrieveToTmp,
|
|
@@ -28,3 +29,4 @@ exports.retrieveToTmp = retrieveToTmp
|
|
|
28
29
|
exports.deleteFolder = deleteFolder
|
|
29
30
|
exports.uploadDirectory = uploadDirectory
|
|
30
31
|
exports.downloadTarball = downloadTarball
|
|
32
|
+
exports.deleteFiles = deleteFiles
|
|
@@ -3,6 +3,10 @@ const { cloneDeep } = require("lodash/fp")
|
|
|
3
3
|
const { FieldTypes, AutoFieldSubTypes } = require("../../constants")
|
|
4
4
|
const { attachmentsRelativeURL } = require("../index")
|
|
5
5
|
const { processFormulas } = require("./utils")
|
|
6
|
+
const { deleteFiles } = require("../../utilities/fileSystem/utilities")
|
|
7
|
+
const { ObjectStoreBuckets } = require("../../constants")
|
|
8
|
+
const { isProdAppID, getDeployedAppID, dbExists } = require("@budibase/auth/db")
|
|
9
|
+
const CouchDB = require("../../db")
|
|
6
10
|
|
|
7
11
|
const BASE_AUTO_ID = 1
|
|
8
12
|
|
|
@@ -81,6 +85,35 @@ const TYPE_TRANSFORM_MAP = {
|
|
|
81
85
|
[FieldTypes.AUTO]: {
|
|
82
86
|
parse: () => undefined,
|
|
83
87
|
},
|
|
88
|
+
[FieldTypes.JSON]: {
|
|
89
|
+
parse: input => {
|
|
90
|
+
try {
|
|
91
|
+
if (input === "") {
|
|
92
|
+
return undefined
|
|
93
|
+
}
|
|
94
|
+
return JSON.parse(input)
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return input
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Given the old state of the row and the new one after an update, this will
|
|
104
|
+
* find the keys that have been removed in the updated row.
|
|
105
|
+
*/
|
|
106
|
+
function getRemovedAttachmentKeys(oldRow, row, attachmentKey) {
|
|
107
|
+
if (!oldRow[attachmentKey]) {
|
|
108
|
+
return []
|
|
109
|
+
}
|
|
110
|
+
const oldKeys = oldRow[attachmentKey].map(attachment => attachment.key)
|
|
111
|
+
// no attachments in new row, all removed
|
|
112
|
+
if (!row[attachmentKey]) {
|
|
113
|
+
return oldKeys
|
|
114
|
+
}
|
|
115
|
+
const newKeys = row[attachmentKey].map(attachment => attachment.key)
|
|
116
|
+
return oldKeys.filter(key => newKeys.indexOf(key) === -1)
|
|
84
117
|
}
|
|
85
118
|
|
|
86
119
|
/**
|
|
@@ -260,3 +293,45 @@ exports.outputProcessing = async (
|
|
|
260
293
|
}
|
|
261
294
|
return wasArray ? enriched : enriched[0]
|
|
262
295
|
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Clean up any attachments that were attached to a row.
|
|
299
|
+
* @param {string} appId The ID of the app from which a row is being deleted.
|
|
300
|
+
* @param {object} table The table from which a row is being removed.
|
|
301
|
+
* @param {any} row optional - the row being removed.
|
|
302
|
+
* @param {any} rows optional - if multiple rows being deleted can do this in bulk.
|
|
303
|
+
* @param {any} oldRow optional - if updating a row this will determine the difference.
|
|
304
|
+
* @return {Promise<void>} When all attachments have been removed this will return.
|
|
305
|
+
*/
|
|
306
|
+
exports.cleanupAttachments = async (appId, table, { row, rows, oldRow }) => {
|
|
307
|
+
if (!isProdAppID(appId)) {
|
|
308
|
+
const prodAppId = getDeployedAppID(appId)
|
|
309
|
+
// if prod exists, then don't allow deleting
|
|
310
|
+
const exists = await dbExists(CouchDB, prodAppId)
|
|
311
|
+
if (exists) {
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
let files = []
|
|
316
|
+
function addFiles(row, key) {
|
|
317
|
+
if (row[key]) {
|
|
318
|
+
files = files.concat(row[key].map(attachment => attachment.key))
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
for (let [key, schema] of Object.entries(table.schema)) {
|
|
322
|
+
if (schema.type !== FieldTypes.ATTACHMENT) {
|
|
323
|
+
continue
|
|
324
|
+
}
|
|
325
|
+
// if updating, need to manage the differences
|
|
326
|
+
if (oldRow && row) {
|
|
327
|
+
files = files.concat(getRemovedAttachmentKeys(oldRow, row, key))
|
|
328
|
+
} else if (row) {
|
|
329
|
+
addFiles(row, key)
|
|
330
|
+
} else if (rows) {
|
|
331
|
+
rows.forEach(row => addFiles(row, key))
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (files.length > 0) {
|
|
335
|
+
return deleteFiles(ObjectStoreBuckets.APPS, files)
|
|
336
|
+
}
|
|
337
|
+
}
|