@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.
Files changed (192) hide show
  1. package/__mocks__/node-fetch.ts +18 -0
  2. package/builder/assets/{index.5522fcd5.css → index.431d69df.css} +1 -1
  3. package/builder/assets/{index.e9e2c4d7.js → index.f909f0e2.js} +245 -240
  4. package/builder/index.html +2 -2
  5. package/coverage/clover.xml +429 -339
  6. package/coverage/coverage-final.json +22 -22
  7. package/coverage/lcov-report/api/controllers/analytics.js.html +1 -1
  8. package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
  9. package/coverage/lcov-report/api/controllers/application.js.html +54 -54
  10. package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
  11. package/coverage/lcov-report/api/controllers/automation.js.html +1 -1
  12. package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
  13. package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
  14. package/coverage/lcov-report/api/controllers/component.js.html +1 -1
  15. package/coverage/lcov-report/api/controllers/datasource.js.html +49 -49
  16. package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
  17. package/coverage/lcov-report/api/controllers/hosting.js.html +1 -1
  18. package/coverage/lcov-report/api/controllers/index.html +20 -20
  19. package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
  20. package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
  21. package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
  22. package/coverage/lcov-report/api/controllers/permission.js.html +1 -1
  23. package/coverage/lcov-report/api/controllers/query/index.html +19 -19
  24. package/coverage/lcov-report/api/controllers/query/index.js.html +66 -150
  25. package/coverage/lcov-report/api/controllers/query/validation.js.html +6 -3
  26. package/coverage/lcov-report/api/controllers/role.js.html +1 -1
  27. package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
  28. package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
  29. package/coverage/lcov-report/api/controllers/row/index.html +1 -1
  30. package/coverage/lcov-report/api/controllers/row/index.js.html +1 -1
  31. package/coverage/lcov-report/api/controllers/row/internal.js.html +1 -1
  32. package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +1 -1
  33. package/coverage/lcov-report/api/controllers/row/utils.js.html +1 -1
  34. package/coverage/lcov-report/api/controllers/screen.js.html +1 -1
  35. package/coverage/lcov-report/api/controllers/script.js.html +1 -1
  36. package/coverage/lcov-report/api/controllers/table/external.js.html +1 -1
  37. package/coverage/lcov-report/api/controllers/table/index.html +1 -1
  38. package/coverage/lcov-report/api/controllers/table/index.js.html +1 -1
  39. package/coverage/lcov-report/api/controllers/table/internal.js.html +1 -1
  40. package/coverage/lcov-report/api/controllers/table/utils.js.html +1 -1
  41. package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
  42. package/coverage/lcov-report/api/controllers/user.js.html +1 -1
  43. package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
  44. package/coverage/lcov-report/api/controllers/view/index.html +1 -1
  45. package/coverage/lcov-report/api/controllers/view/index.js.html +1 -1
  46. package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
  47. package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
  48. package/coverage/lcov-report/api/controllers/webhook.js.html +1 -1
  49. package/coverage/lcov-report/api/index.html +1 -1
  50. package/coverage/lcov-report/api/index.js.html +5 -5
  51. package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
  52. package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
  53. package/coverage/lcov-report/api/routes/application.js.html +1 -1
  54. package/coverage/lcov-report/api/routes/auth.js.html +1 -1
  55. package/coverage/lcov-report/api/routes/automation.js.html +1 -1
  56. package/coverage/lcov-report/api/routes/backup.js.html +1 -1
  57. package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
  58. package/coverage/lcov-report/api/routes/component.js.html +1 -1
  59. package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
  60. package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
  61. package/coverage/lcov-report/api/routes/dev.js.html +1 -1
  62. package/coverage/lcov-report/api/routes/hosting.js.html +1 -1
  63. package/coverage/lcov-report/api/routes/index.html +1 -1
  64. package/coverage/lcov-report/api/routes/index.js.html +1 -1
  65. package/coverage/lcov-report/api/routes/integration.js.html +1 -1
  66. package/coverage/lcov-report/api/routes/layout.js.html +1 -1
  67. package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
  68. package/coverage/lcov-report/api/routes/permission.js.html +1 -1
  69. package/coverage/lcov-report/api/routes/query.js.html +24 -3
  70. package/coverage/lcov-report/api/routes/role.js.html +1 -1
  71. package/coverage/lcov-report/api/routes/routing.js.html +1 -1
  72. package/coverage/lcov-report/api/routes/row.js.html +1 -1
  73. package/coverage/lcov-report/api/routes/screen.js.html +1 -1
  74. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  75. package/coverage/lcov-report/api/routes/static.js.html +1 -1
  76. package/coverage/lcov-report/api/routes/table.js.html +1 -1
  77. package/coverage/lcov-report/api/routes/templates.js.html +1 -1
  78. package/coverage/lcov-report/api/routes/user.js.html +1 -1
  79. package/coverage/lcov-report/api/routes/view.js.html +1 -1
  80. package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
  81. package/coverage/lcov-report/automations/actions.js.html +1 -1
  82. package/coverage/lcov-report/automations/automationUtils.js.html +1 -1
  83. package/coverage/lcov-report/automations/bullboard.js.html +1 -1
  84. package/coverage/lcov-report/automations/index.html +1 -1
  85. package/coverage/lcov-report/automations/index.js.html +1 -1
  86. package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
  87. package/coverage/lcov-report/automations/steps/createRow.js.html +1 -1
  88. package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
  89. package/coverage/lcov-report/automations/steps/deleteRow.js.html +1 -1
  90. package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
  91. package/coverage/lcov-report/automations/steps/executeQuery.js.html +31 -7
  92. package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
  93. package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
  94. package/coverage/lcov-report/automations/steps/index.html +11 -11
  95. package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
  96. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +1 -1
  97. package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
  98. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
  99. package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
  100. package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
  101. package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
  102. package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
  103. package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
  104. package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
  105. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
  106. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  107. package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
  108. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
  109. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
  110. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
  111. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
  112. package/coverage/lcov-report/automations/triggers.js.html +1 -1
  113. package/coverage/lcov-report/automations/utils.js.html +5 -5
  114. package/coverage/lcov-report/constants/index.html +1 -1
  115. package/coverage/lcov-report/constants/index.js.html +1 -1
  116. package/coverage/lcov-report/constants/layouts.js.html +1 -1
  117. package/coverage/lcov-report/constants/screens.js.html +1 -1
  118. package/coverage/lcov-report/db/client.js.html +1 -1
  119. package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
  120. package/coverage/lcov-report/db/index.html +1 -1
  121. package/coverage/lcov-report/db/index.js.html +1 -1
  122. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
  123. package/coverage/lcov-report/db/linkedRows/index.html +1 -1
  124. package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
  125. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
  126. package/coverage/lcov-report/db/newid.js.html +2 -2
  127. package/coverage/lcov-report/db/utils.js.html +18 -18
  128. package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
  129. package/coverage/lcov-report/events/index.html +1 -1
  130. package/coverage/lcov-report/events/index.js.html +1 -1
  131. package/coverage/lcov-report/events/utils.js.html +1 -1
  132. package/coverage/lcov-report/index.html +46 -46
  133. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  134. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  135. package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
  136. package/coverage/lcov-report/middleware/authorized.js.html +10 -10
  137. package/coverage/lcov-report/middleware/builder.js.html +8 -8
  138. package/coverage/lcov-report/middleware/currentapp.js.html +28 -28
  139. package/coverage/lcov-report/middleware/index.html +1 -1
  140. package/coverage/lcov-report/middleware/joi-validator.js.html +9 -9
  141. package/coverage/lcov-report/middleware/resourceId.js.html +17 -17
  142. package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
  143. package/coverage/lcov-report/middleware/usageQuota.js.html +1 -1
  144. package/coverage/lcov-report/middleware/utils.js.html +2 -2
  145. package/coverage/lcov-report/threads/automation.js.html +1 -1
  146. package/coverage/lcov-report/threads/index.html +30 -30
  147. package/coverage/lcov-report/threads/index.js.html +11 -11
  148. package/coverage/lcov-report/threads/query.js.html +358 -91
  149. package/coverage/lcov-report/threads/utils.js.html +352 -10
  150. package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
  151. package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
  152. package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
  153. package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
  154. package/coverage/lcov-report/utilities/global.js.html +13 -13
  155. package/coverage/lcov-report/utilities/index.html +1 -1
  156. package/coverage/lcov-report/utilities/index.js.html +9 -9
  157. package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +2 -2
  158. package/coverage/lcov-report/utilities/queue/index.html +1 -1
  159. package/coverage/lcov-report/utilities/routing/index.html +1 -1
  160. package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
  161. package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
  162. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
  163. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
  164. package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
  165. package/coverage/lcov-report/utilities/security.js.html +1 -1
  166. package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
  167. package/coverage/lcov-report/utilities/users.js.html +1 -1
  168. package/coverage/lcov-report/utilities/workerRequests.js.html +1 -1
  169. package/coverage/lcov.info +732 -576
  170. package/dist/api/controllers/query/import/sources/curl.js +5 -1
  171. package/dist/api/controllers/query/index.js +29 -47
  172. package/dist/api/controllers/query/validation.js +2 -1
  173. package/dist/api/routes/query.js +3 -1
  174. package/dist/automations/steps/executeQuery.js +8 -1
  175. package/dist/integrations/rest.js +2 -2
  176. package/dist/package.json +5 -4
  177. package/dist/tests/utilities/TestConfiguration.js +7 -0
  178. package/dist/threads/query.js +145 -52
  179. package/dist/threads/utils.js +104 -0
  180. package/dist/tsconfig.tsbuildinfo +1 -1
  181. package/package.json +6 -5
  182. package/src/api/controllers/query/import/sources/curl.ts +5 -1
  183. package/src/api/controllers/query/index.js +27 -55
  184. package/src/api/controllers/query/validation.js +2 -1
  185. package/src/api/routes/query.js +8 -1
  186. package/src/api/routes/tests/query.spec.js +88 -0
  187. package/src/automations/steps/executeQuery.js +9 -1
  188. package/src/definitions/datasource.ts +10 -0
  189. package/src/integrations/rest.ts +5 -2
  190. package/src/tests/utilities/TestConfiguration.js +10 -0
  191. package/src/threads/query.js +145 -56
  192. 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.2",
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.2",
74
- "@budibase/client": "^1.0.27-alpha.2",
75
- "@budibase/string-templates": "^1.0.27-alpha.2",
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": "0c5de8cf6518f05e4b497857c23e551d293049c1"
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
- const key = keys[0]
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
- const { fields, parameters, queryVerb, transformer } = ctx.request.body
151
- const enrichedQuery = await enrichQueryFields(fields, parameters)
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
- query: enrichedQuery,
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
- exports.execute = async function (ctx) {
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
- query: enrichedQuery,
146
+ fields: query.fields,
147
+ parameters: ctx.request.body.parameters,
189
148
  transformer: query.transformer,
149
+ queryId: ctx.params.queryId,
190
150
  })
191
- ctx.body = rows
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
  }
@@ -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.execute
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: ctx.body,
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 {
@@ -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 { parseStringPromise: xmlParser, Builder: XmlBuilder } = require("xml2js")
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 = (new XmlBuilder()).buildObject(object)
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."
@@ -1,77 +1,166 @@
1
- require("./utils").threadSetup()
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
- function formatResponse(resp) {
6
- if (typeof resp === "string") {
7
- try {
8
- resp = JSON.parse(resp)
9
- } catch (err) {
10
- resp = { response: resp }
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
- function hasExtraData(response) {
17
- return (
18
- typeof response === "object" &&
19
- !Array.isArray(response) &&
20
- response.data != null &&
21
- response.info != null
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
- async function runAndTransform(datasource, queryVerb, query, transformer) {
26
- const Integration = integrations[datasource.source]
27
- if (!Integration) {
28
- throw "Integration type does not exist."
29
- }
30
- const integration = new Integration(datasource.config)
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
- let output = formatResponse(await integration[queryVerb](query))
33
- let rows = output,
34
- info = undefined,
35
- extra = undefined
36
- if (hasExtraData(output)) {
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
- // transform as required
43
- if (transformer) {
44
- const runner = new ScriptRunner(transformer, { data: rows })
45
- rows = runner.execute()
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
- // needs to an array for next step
49
- if (!Array.isArray(rows)) {
50
- rows = [rows]
51
- }
66
+ // needs to an array for next step
67
+ if (!Array.isArray(rows)) {
68
+ rows = [rows]
69
+ }
52
70
 
53
- // map into JSON if just raw primitive here
54
- if (rows.find(row => typeof row !== "object")) {
55
- rows = rows.map(value => ({ value }))
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
- // get all the potential fields in the schema
59
- let keys = rows.flatMap(Object.keys)
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
- if (integration.end) {
62
- integration.end()
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
- return { rows, keys, info, extra }
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
- runAndTransform(
70
- input.datasource,
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
  })