@budibase/server 1.0.27-alpha.2 → 1.0.27-alpha.6
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.5522fcd5.css → index.431d69df.css} +1 -1
- package/builder/assets/{index.e9e2c4d7.js → index.f909f0e2.js} +245 -240
- package/builder/index.html +2 -2
- package/coverage/clover.xml +429 -339
- package/coverage/coverage-final.json +22 -22
- 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 +1 -1
- 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 +66 -150
- 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 +1 -1
- package/coverage/lcov-report/api/controllers/row/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/internal.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/utils.js.html +1 -1
- 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 +46 -46
- 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 +30 -30
- package/coverage/lcov-report/threads/index.js.html +11 -11
- package/coverage/lcov-report/threads/query.js.html +358 -91
- package/coverage/lcov-report/threads/utils.js.html +352 -10
- 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 +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
- 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 +732 -576
- package/dist/api/controllers/query/import/sources/curl.js +5 -1
- package/dist/api/controllers/query/index.js +29 -47
- package/dist/api/controllers/query/validation.js +2 -1
- package/dist/api/routes/query.js +3 -1
- package/dist/automations/steps/executeQuery.js +8 -1
- package/dist/integrations/rest.js +2 -2
- package/dist/package.json +5 -4
- package/dist/tests/utilities/TestConfiguration.js +7 -0
- package/dist/threads/query.js +145 -52
- package/dist/threads/utils.js +104 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
- package/src/api/controllers/query/import/sources/curl.ts +5 -1
- package/src/api/controllers/query/index.js +27 -55
- package/src/api/controllers/query/validation.js +2 -1
- 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/rest.ts +5 -2
- package/src/tests/utilities/TestConfiguration.js +10 -0
- package/src/threads/query.js +145 -56
- package/src/threads/utils.js +114 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/server",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "1.0.27-alpha.
|
|
4
|
+
"version": "1.0.27-alpha.6",
|
|
5
5
|
"description": "Budibase Web Server",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
"license": "GPL-3.0",
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"@apidevtools/swagger-parser": "^10.0.3",
|
|
73
|
-
"@budibase/auth": "^1.0.27-alpha.
|
|
74
|
-
"@budibase/client": "^1.0.27-alpha.
|
|
75
|
-
"@budibase/string-templates": "^1.0.27-alpha.
|
|
73
|
+
"@budibase/auth": "^1.0.27-alpha.6",
|
|
74
|
+
"@budibase/client": "^1.0.27-alpha.6",
|
|
75
|
+
"@budibase/string-templates": "^1.0.27-alpha.6",
|
|
76
76
|
"@bull-board/api": "^3.7.0",
|
|
77
77
|
"@bull-board/koa": "^3.7.0",
|
|
78
78
|
"@elastic/elasticsearch": "7.10.0",
|
|
@@ -122,6 +122,7 @@
|
|
|
122
122
|
"pouchdb-replication-stream": "1.2.9",
|
|
123
123
|
"server-destroy": "1.0.1",
|
|
124
124
|
"svelte": "^3.38.2",
|
|
125
|
+
"swagger-parser": "^10.0.3",
|
|
125
126
|
"to-json-schema": "0.2.5",
|
|
126
127
|
"uuid": "3.3.2",
|
|
127
128
|
"validate.js": "0.13.1",
|
|
@@ -164,5 +165,5 @@
|
|
|
164
165
|
"optionalDependencies": {
|
|
165
166
|
"oracledb": "^5.3.0"
|
|
166
167
|
},
|
|
167
|
-
"gitHead": "
|
|
168
|
+
"gitHead": "4ccf3f96877736296736f4e74b68c0d9e99c2e18"
|
|
168
169
|
}
|
|
@@ -17,8 +17,12 @@ const parseBody = (curl: any) => {
|
|
|
17
17
|
if (curl.data) {
|
|
18
18
|
const keys = Object.keys(curl.data)
|
|
19
19
|
if (keys.length) {
|
|
20
|
-
|
|
20
|
+
let key = keys[0]
|
|
21
21
|
try {
|
|
22
|
+
// filter out the dollar syntax used by curl for shell support
|
|
23
|
+
if (key.startsWith("$")) {
|
|
24
|
+
key = key.substring(1)
|
|
25
|
+
}
|
|
22
26
|
return JSON.parse(key)
|
|
23
27
|
} catch (e) {
|
|
24
28
|
// do nothing
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const { processString } = require("@budibase/string-templates")
|
|
2
1
|
const CouchDB = require("../../../db")
|
|
3
2
|
const {
|
|
4
3
|
generateQueryID,
|
|
@@ -90,47 +89,6 @@ exports.save = async function (ctx) {
|
|
|
90
89
|
ctx.message = `Query ${query.name} saved successfully.`
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
async function enrichQueryFields(fields, parameters = {}) {
|
|
94
|
-
const enrichedQuery = {}
|
|
95
|
-
|
|
96
|
-
// enrich the fields with dynamic parameters
|
|
97
|
-
for (let key of Object.keys(fields)) {
|
|
98
|
-
if (fields[key] == null) {
|
|
99
|
-
continue
|
|
100
|
-
}
|
|
101
|
-
if (typeof fields[key] === "object") {
|
|
102
|
-
// enrich nested fields object
|
|
103
|
-
enrichedQuery[key] = await enrichQueryFields(fields[key], parameters)
|
|
104
|
-
} else if (typeof fields[key] === "string") {
|
|
105
|
-
// enrich string value as normal
|
|
106
|
-
enrichedQuery[key] = await processString(fields[key], parameters, {
|
|
107
|
-
noHelpers: true,
|
|
108
|
-
})
|
|
109
|
-
} else {
|
|
110
|
-
enrichedQuery[key] = fields[key]
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
enrichedQuery.json ||
|
|
116
|
-
enrichedQuery.customData ||
|
|
117
|
-
enrichedQuery.requestBody
|
|
118
|
-
) {
|
|
119
|
-
try {
|
|
120
|
-
enrichedQuery.json = JSON.parse(
|
|
121
|
-
enrichedQuery.json ||
|
|
122
|
-
enrichedQuery.customData ||
|
|
123
|
-
enrichedQuery.requestBody
|
|
124
|
-
)
|
|
125
|
-
} catch (err) {
|
|
126
|
-
// no json found, ignore
|
|
127
|
-
}
|
|
128
|
-
delete enrichedQuery.customData
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return enrichedQuery
|
|
132
|
-
}
|
|
133
|
-
|
|
134
92
|
exports.find = async function (ctx) {
|
|
135
93
|
const db = new CouchDB(ctx.appId)
|
|
136
94
|
const query = enrichQueries(await db.get(ctx.params.queryId))
|
|
@@ -146,16 +104,20 @@ exports.preview = async function (ctx) {
|
|
|
146
104
|
const db = new CouchDB(ctx.appId)
|
|
147
105
|
|
|
148
106
|
const datasource = await db.get(ctx.request.body.datasourceId)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
107
|
+
// preview may not have a queryId as it hasn't been saved, but if it does
|
|
108
|
+
// this stops dynamic variables from calling the same query
|
|
109
|
+
const { fields, parameters, queryVerb, transformer, queryId } =
|
|
110
|
+
ctx.request.body
|
|
152
111
|
|
|
153
112
|
try {
|
|
154
113
|
const { rows, keys, info, extra } = await Runner.run({
|
|
114
|
+
appId: ctx.appId,
|
|
155
115
|
datasource,
|
|
156
116
|
queryVerb,
|
|
157
|
-
|
|
117
|
+
fields,
|
|
118
|
+
parameters,
|
|
158
119
|
transformer,
|
|
120
|
+
queryId,
|
|
159
121
|
})
|
|
160
122
|
|
|
161
123
|
ctx.body = {
|
|
@@ -169,31 +131,41 @@ exports.preview = async function (ctx) {
|
|
|
169
131
|
}
|
|
170
132
|
}
|
|
171
133
|
|
|
172
|
-
|
|
134
|
+
async function execute(ctx, opts = { rowsOnly: false }) {
|
|
173
135
|
const db = new CouchDB(ctx.appId)
|
|
174
136
|
|
|
175
137
|
const query = await db.get(ctx.params.queryId)
|
|
176
138
|
const datasource = await db.get(query.datasourceId)
|
|
177
139
|
|
|
178
|
-
const enrichedQuery = await enrichQueryFields(
|
|
179
|
-
query.fields,
|
|
180
|
-
ctx.request.body.parameters
|
|
181
|
-
)
|
|
182
|
-
|
|
183
140
|
// call the relevant CRUD method on the integration class
|
|
184
141
|
try {
|
|
185
|
-
const { rows } = await Runner.run({
|
|
142
|
+
const { rows, extra } = await Runner.run({
|
|
143
|
+
appId: ctx.appId,
|
|
186
144
|
datasource,
|
|
187
145
|
queryVerb: query.queryVerb,
|
|
188
|
-
|
|
146
|
+
fields: query.fields,
|
|
147
|
+
parameters: ctx.request.body.parameters,
|
|
189
148
|
transformer: query.transformer,
|
|
149
|
+
queryId: ctx.params.queryId,
|
|
190
150
|
})
|
|
191
|
-
|
|
151
|
+
if (opts && opts.rowsOnly) {
|
|
152
|
+
ctx.body = rows
|
|
153
|
+
} else {
|
|
154
|
+
ctx.body = { data: rows, ...extra }
|
|
155
|
+
}
|
|
192
156
|
} catch (err) {
|
|
193
157
|
ctx.throw(400, err)
|
|
194
158
|
}
|
|
195
159
|
}
|
|
196
160
|
|
|
161
|
+
exports.executeV1 = async function (ctx) {
|
|
162
|
+
return execute(ctx, { rowsOnly: true })
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
exports.executeV2 = async function (ctx) {
|
|
166
|
+
return execute(ctx, { rowsOnly: false })
|
|
167
|
+
}
|
|
168
|
+
|
|
197
169
|
exports.destroy = async function (ctx) {
|
|
198
170
|
const db = new CouchDB(ctx.appId)
|
|
199
171
|
await db.remove(ctx.params.queryId, ctx.params.revId)
|
|
@@ -36,6 +36,7 @@ exports.generateQueryPreviewValidation = () => {
|
|
|
36
36
|
extra: Joi.object().optional(),
|
|
37
37
|
datasourceId: Joi.string().required(),
|
|
38
38
|
transformer: Joi.string().optional(),
|
|
39
|
-
parameters: Joi.object({}).required().unknown(true)
|
|
39
|
+
parameters: Joi.object({}).required().unknown(true),
|
|
40
|
+
queryId: Joi.string().optional(),
|
|
40
41
|
}))
|
|
41
42
|
}
|
package/src/api/routes/query.js
CHANGED
|
@@ -41,11 +41,18 @@ router
|
|
|
41
41
|
authorized(PermissionTypes.QUERY, PermissionLevels.READ),
|
|
42
42
|
queryController.find
|
|
43
43
|
)
|
|
44
|
+
// DEPRECATED - use new query endpoint for future work
|
|
44
45
|
.post(
|
|
45
46
|
"/api/queries/:queryId",
|
|
46
47
|
paramResource("queryId"),
|
|
47
48
|
authorized(PermissionTypes.QUERY, PermissionLevels.WRITE),
|
|
48
|
-
queryController.
|
|
49
|
+
queryController.executeV1
|
|
50
|
+
)
|
|
51
|
+
.post(
|
|
52
|
+
"/api/v2/queries/:queryId",
|
|
53
|
+
paramResource("queryId"),
|
|
54
|
+
authorized(PermissionTypes.QUERY, PermissionLevels.WRITE),
|
|
55
|
+
queryController.executeV2
|
|
49
56
|
)
|
|
50
57
|
.delete(
|
|
51
58
|
"/api/queries/:queryId/:revId",
|
|
@@ -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",
|
|
@@ -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,166 @@
|
|
|
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
|
-
rows = output.data
|
|
38
|
-
info = output.info
|
|
39
|
-
extra = output.extra
|
|
40
|
-
}
|
|
47
|
+
// transform as required
|
|
48
|
+
if (transformer) {
|
|
49
|
+
const runner = new ScriptRunner(transformer, { data: rows })
|
|
50
|
+
rows = runner.execute()
|
|
51
|
+
}
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
// if the request fails we retry once, invalidating the cached value
|
|
54
|
+
if (
|
|
55
|
+
info &&
|
|
56
|
+
info.code >= 400 &&
|
|
57
|
+
this.cachedVariables.length > 0 &&
|
|
58
|
+
!this.hasRerun
|
|
59
|
+
) {
|
|
60
|
+
this.hasRerun = true
|
|
61
|
+
// invalidate the cache value
|
|
62
|
+
await threadUtils.invalidateDynamicVariables(this.cachedVariables)
|
|
63
|
+
return this.execute()
|
|
64
|
+
}
|
|
47
65
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
// needs to an array for next step
|
|
67
|
+
if (!Array.isArray(rows)) {
|
|
68
|
+
rows = [rows]
|
|
69
|
+
}
|
|
52
70
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
// map into JSON if just raw primitive here
|
|
72
|
+
if (rows.find(row => typeof row !== "object")) {
|
|
73
|
+
rows = rows.map(value => ({ value }))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// get all the potential fields in the schema
|
|
77
|
+
let keys = rows.flatMap(Object.keys)
|
|
78
|
+
|
|
79
|
+
if (integration.end) {
|
|
80
|
+
integration.end()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { rows, keys, info, extra }
|
|
56
84
|
}
|
|
57
85
|
|
|
58
|
-
|
|
59
|
-
|
|
86
|
+
async runAnotherQuery(queryId, parameters) {
|
|
87
|
+
const db = new CouchDB(this.appId)
|
|
88
|
+
const query = await db.get(queryId)
|
|
89
|
+
const datasource = await db.get(query.datasourceId)
|
|
90
|
+
return new QueryRunner(
|
|
91
|
+
{
|
|
92
|
+
appId: this.appId,
|
|
93
|
+
datasource,
|
|
94
|
+
queryVerb: query.queryVerb,
|
|
95
|
+
fields: query.fields,
|
|
96
|
+
parameters,
|
|
97
|
+
transformer: query.transformer,
|
|
98
|
+
},
|
|
99
|
+
{ noRecursiveQuery: true }
|
|
100
|
+
).execute()
|
|
101
|
+
}
|
|
60
102
|
|
|
61
|
-
|
|
62
|
-
|
|
103
|
+
async getDynamicVariable(variable) {
|
|
104
|
+
let { parameters } = this
|
|
105
|
+
const queryId = variable.queryId,
|
|
106
|
+
name = variable.name
|
|
107
|
+
let value = await threadUtils.checkCacheForDynamicVariable(queryId, name)
|
|
108
|
+
if (!value) {
|
|
109
|
+
value = this.queryResponse[queryId]
|
|
110
|
+
? this.queryResponse[queryId]
|
|
111
|
+
: await this.runAnotherQuery(queryId, parameters)
|
|
112
|
+
// store incase this query is to be called again
|
|
113
|
+
this.queryResponse[queryId] = value
|
|
114
|
+
await threadUtils.storeDynamicVariable(queryId, name, value)
|
|
115
|
+
} else {
|
|
116
|
+
this.cachedVariables.push({ queryId, name })
|
|
117
|
+
}
|
|
118
|
+
return value
|
|
63
119
|
}
|
|
64
120
|
|
|
65
|
-
|
|
121
|
+
async addDatasourceVariables() {
|
|
122
|
+
let { datasource, parameters, fields } = this
|
|
123
|
+
if (!datasource || !datasource.config) {
|
|
124
|
+
return parameters
|
|
125
|
+
}
|
|
126
|
+
const staticVars = datasource.config.staticVariables || {}
|
|
127
|
+
const dynamicVars = datasource.config.dynamicVariables || []
|
|
128
|
+
for (let [key, value] of Object.entries(staticVars)) {
|
|
129
|
+
if (!parameters[key]) {
|
|
130
|
+
parameters[key] = value
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!this.noRecursiveQuery) {
|
|
134
|
+
// need to see if this uses any variables
|
|
135
|
+
const stringFields = JSON.stringify(fields)
|
|
136
|
+
const foundVars = dynamicVars.filter(variable => {
|
|
137
|
+
// don't allow a query to use its own dynamic variable (loop)
|
|
138
|
+
if (variable.queryId === this.queryId) {
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
// look for {{ variable }} but allow spaces between handlebars
|
|
142
|
+
const regex = new RegExp(`{{[ ]*${variable.name}[ ]*}}`)
|
|
143
|
+
return regex.test(stringFields)
|
|
144
|
+
})
|
|
145
|
+
const dynamics = foundVars.map(dynVar => this.getDynamicVariable(dynVar))
|
|
146
|
+
const responses = await Promise.all(dynamics)
|
|
147
|
+
for (let i = 0; i < foundVars.length; i++) {
|
|
148
|
+
const variable = foundVars[i]
|
|
149
|
+
parameters[variable.name] = processStringSync(variable.value, {
|
|
150
|
+
data: responses[i].rows,
|
|
151
|
+
info: responses[i].extra,
|
|
152
|
+
})
|
|
153
|
+
// make sure its known that this uses dynamic variables in case it fails
|
|
154
|
+
this.hasDynamicVariables = true
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return parameters
|
|
158
|
+
}
|
|
66
159
|
}
|
|
67
160
|
|
|
68
161
|
module.exports = (input, callback) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
input.queryVerb,
|
|
72
|
-
input.query,
|
|
73
|
-
input.transformer
|
|
74
|
-
)
|
|
162
|
+
const Runner = new QueryRunner(input)
|
|
163
|
+
Runner.execute()
|
|
75
164
|
.then(response => {
|
|
76
165
|
callback(null, response)
|
|
77
166
|
})
|