@budibase/server 1.0.26 → 1.0.27-alpha.11

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 (258) hide show
  1. package/__mocks__/node-fetch.ts +21 -0
  2. package/builder/assets/bb-spaceship.8f6e81e1.svg +227 -0
  3. package/builder/assets/{index.4fb3f887.css → index.0dce895b.css} +1 -1
  4. package/builder/assets/index.b98dd7ca.js +1348 -0
  5. package/builder/assets/{vendor.470042a4.js → vendor.f9faf6ab.js} +88 -88
  6. package/builder/index.html +3 -3
  7. package/coverage/clover.xml +1364 -1121
  8. package/coverage/coverage-final.json +37 -36
  9. package/coverage/lcov-report/api/controllers/analytics.js.html +1 -1
  10. package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
  11. package/coverage/lcov-report/api/controllers/application.js.html +59 -59
  12. package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
  13. package/coverage/lcov-report/api/controllers/automation.js.html +22 -10
  14. package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
  15. package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
  16. package/coverage/lcov-report/api/controllers/component.js.html +4 -4
  17. package/coverage/lcov-report/api/controllers/datasource.js.html +170 -68
  18. package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
  19. package/coverage/lcov-report/api/controllers/hosting.js.html +1 -1
  20. package/coverage/lcov-report/api/controllers/index.html +39 -54
  21. package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
  22. package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
  23. package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
  24. package/coverage/lcov-report/api/controllers/permission.js.html +1 -1
  25. package/coverage/lcov-report/api/controllers/query/index.html +126 -0
  26. package/coverage/lcov-report/api/controllers/{query.js.html → query/index.js.html} +225 -114
  27. package/coverage/lcov-report/api/controllers/query/validation.js.html +206 -0
  28. package/coverage/lcov-report/api/controllers/role.js.html +1 -1
  29. package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
  30. package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
  31. package/coverage/lcov-report/api/controllers/row/index.html +22 -22
  32. package/coverage/lcov-report/api/controllers/row/index.js.html +1 -1
  33. package/coverage/lcov-report/api/controllers/row/internal.js.html +38 -11
  34. package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +7 -4
  35. package/coverage/lcov-report/api/controllers/row/utils.js.html +29 -20
  36. package/coverage/lcov-report/api/controllers/screen.js.html +1 -1
  37. package/coverage/lcov-report/api/controllers/script.js.html +1 -1
  38. package/coverage/lcov-report/api/controllers/table/external.js.html +1 -1
  39. package/coverage/lcov-report/api/controllers/table/index.html +1 -1
  40. package/coverage/lcov-report/api/controllers/table/index.js.html +1 -1
  41. package/coverage/lcov-report/api/controllers/table/internal.js.html +1 -1
  42. package/coverage/lcov-report/api/controllers/table/utils.js.html +1 -1
  43. package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
  44. package/coverage/lcov-report/api/controllers/user.js.html +108 -9
  45. package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
  46. package/coverage/lcov-report/api/controllers/view/index.html +1 -1
  47. package/coverage/lcov-report/api/controllers/view/index.js.html +1 -1
  48. package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
  49. package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
  50. package/coverage/lcov-report/api/controllers/webhook.js.html +18 -6
  51. package/coverage/lcov-report/api/index.html +1 -1
  52. package/coverage/lcov-report/api/index.js.html +5 -5
  53. package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
  54. package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
  55. package/coverage/lcov-report/api/routes/application.js.html +1 -1
  56. package/coverage/lcov-report/api/routes/auth.js.html +1 -1
  57. package/coverage/lcov-report/api/routes/automation.js.html +1 -1
  58. package/coverage/lcov-report/api/routes/backup.js.html +1 -1
  59. package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
  60. package/coverage/lcov-report/api/routes/component.js.html +1 -1
  61. package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
  62. package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
  63. package/coverage/lcov-report/api/routes/dev.js.html +1 -1
  64. package/coverage/lcov-report/api/routes/hosting.js.html +1 -1
  65. package/coverage/lcov-report/api/routes/index.html +9 -9
  66. package/coverage/lcov-report/api/routes/index.js.html +1 -1
  67. package/coverage/lcov-report/api/routes/integration.js.html +1 -1
  68. package/coverage/lcov-report/api/routes/layout.js.html +1 -1
  69. package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
  70. package/coverage/lcov-report/api/routes/permission.js.html +1 -1
  71. package/coverage/lcov-report/api/routes/query.js.html +19 -85
  72. package/coverage/lcov-report/api/routes/role.js.html +1 -1
  73. package/coverage/lcov-report/api/routes/routing.js.html +1 -1
  74. package/coverage/lcov-report/api/routes/row.js.html +1 -1
  75. package/coverage/lcov-report/api/routes/screen.js.html +1 -1
  76. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  77. package/coverage/lcov-report/api/routes/static.js.html +1 -1
  78. package/coverage/lcov-report/api/routes/table.js.html +1 -1
  79. package/coverage/lcov-report/api/routes/templates.js.html +1 -1
  80. package/coverage/lcov-report/api/routes/user.js.html +32 -2
  81. package/coverage/lcov-report/api/routes/view.js.html +1 -1
  82. package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
  83. package/coverage/lcov-report/automations/actions.js.html +1 -1
  84. package/coverage/lcov-report/automations/automationUtils.js.html +1 -1
  85. package/coverage/lcov-report/automations/bullboard.js.html +1 -1
  86. package/coverage/lcov-report/automations/index.html +20 -20
  87. package/coverage/lcov-report/automations/index.js.html +1 -1
  88. package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
  89. package/coverage/lcov-report/automations/steps/createRow.js.html +1 -1
  90. package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
  91. package/coverage/lcov-report/automations/steps/deleteRow.js.html +1 -1
  92. package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
  93. package/coverage/lcov-report/automations/steps/executeQuery.js.html +31 -7
  94. package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
  95. package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
  96. package/coverage/lcov-report/automations/steps/index.html +11 -11
  97. package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
  98. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +5 -8
  99. package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
  100. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
  101. package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
  102. package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
  103. package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
  104. package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
  105. package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
  106. package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
  107. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
  108. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  109. package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
  110. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
  111. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
  112. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
  113. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
  114. package/coverage/lcov-report/automations/triggers.js.html +1 -1
  115. package/coverage/lcov-report/automations/utils.js.html +48 -15
  116. package/coverage/lcov-report/constants/index.html +1 -1
  117. package/coverage/lcov-report/constants/index.js.html +22 -22
  118. package/coverage/lcov-report/constants/layouts.js.html +1 -1
  119. package/coverage/lcov-report/constants/screens.js.html +1 -1
  120. package/coverage/lcov-report/db/client.js.html +18 -18
  121. package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
  122. package/coverage/lcov-report/db/index.html +15 -15
  123. package/coverage/lcov-report/db/index.js.html +3 -3
  124. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
  125. package/coverage/lcov-report/db/linkedRows/index.html +1 -1
  126. package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
  127. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
  128. package/coverage/lcov-report/db/newid.js.html +4 -4
  129. package/coverage/lcov-report/db/utils.js.html +107 -80
  130. package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
  131. package/coverage/lcov-report/events/index.html +1 -1
  132. package/coverage/lcov-report/events/index.js.html +1 -1
  133. package/coverage/lcov-report/events/utils.js.html +1 -1
  134. package/coverage/lcov-report/index.html +99 -84
  135. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  136. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  137. package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
  138. package/coverage/lcov-report/middleware/authorized.js.html +10 -10
  139. package/coverage/lcov-report/middleware/builder.js.html +8 -8
  140. package/coverage/lcov-report/middleware/currentapp.js.html +28 -28
  141. package/coverage/lcov-report/middleware/index.html +1 -1
  142. package/coverage/lcov-report/middleware/joi-validator.js.html +11 -11
  143. package/coverage/lcov-report/middleware/resourceId.js.html +18 -18
  144. package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
  145. package/coverage/lcov-report/middleware/usageQuota.js.html +1 -1
  146. package/coverage/lcov-report/middleware/utils.js.html +2 -2
  147. package/coverage/lcov-report/threads/automation.js.html +1 -1
  148. package/coverage/lcov-report/threads/index.html +31 -31
  149. package/coverage/lcov-report/threads/index.js.html +12 -12
  150. package/coverage/lcov-report/threads/query.js.html +396 -69
  151. package/coverage/lcov-report/threads/utils.js.html +358 -13
  152. package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
  153. package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
  154. package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
  155. package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
  156. package/coverage/lcov-report/utilities/global.js.html +13 -13
  157. package/coverage/lcov-report/utilities/index.html +19 -19
  158. package/coverage/lcov-report/utilities/index.js.html +69 -36
  159. package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +2 -2
  160. package/coverage/lcov-report/utilities/queue/index.html +1 -1
  161. package/coverage/lcov-report/utilities/routing/index.html +1 -1
  162. package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
  163. package/coverage/lcov-report/utilities/rowProcessor/index.html +19 -19
  164. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +235 -10
  165. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
  166. package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
  167. package/coverage/lcov-report/utilities/security.js.html +2 -2
  168. package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
  169. package/coverage/lcov-report/utilities/users.js.html +1 -1
  170. package/coverage/lcov-report/utilities/workerRequests.js.html +19 -19
  171. package/coverage/lcov.info +2294 -1874
  172. package/dist/api/controllers/automation.js +7 -5
  173. package/dist/api/controllers/datasource.js +29 -0
  174. package/dist/api/controllers/query/import/index.js +79 -0
  175. package/dist/api/controllers/query/import/sources/base/index.js +64 -0
  176. package/dist/api/controllers/query/import/sources/base/openapi.js +42 -0
  177. package/dist/api/controllers/query/import/sources/curl.js +96 -0
  178. package/dist/api/controllers/query/import/sources/openapi2.js +142 -0
  179. package/dist/api/controllers/query/import/sources/tests/openapi2/data/crud/crud.json +253 -0
  180. package/dist/api/controllers/query/import/sources/tests/openapi2/data/petstore/petstore.json +1054 -0
  181. package/dist/api/controllers/{query.js → query/index.js} +86 -52
  182. package/dist/api/controllers/query/validation.js +38 -0
  183. package/dist/api/controllers/row/internal.js +14 -6
  184. package/dist/api/controllers/row/internalSearch.js +2 -1
  185. package/dist/api/controllers/row/utils.js +13 -6
  186. package/dist/api/controllers/user.js +37 -1
  187. package/dist/api/controllers/webhook.js +4 -0
  188. package/dist/api/routes/query.js +5 -33
  189. package/dist/api/routes/user.js +3 -1
  190. package/dist/automations/steps/executeQuery.js +8 -1
  191. package/dist/automations/steps/outgoingWebhook.js +2 -3
  192. package/dist/automations/utils.js +10 -0
  193. package/dist/db/utils.js +8 -0
  194. package/dist/definitions/datasource.js +6 -1
  195. package/dist/integrations/base/sqlTable.js +1 -1
  196. package/dist/integrations/rest.js +207 -117
  197. package/dist/package.json +11 -4
  198. package/dist/tests/utilities/TestConfiguration.js +47 -0
  199. package/dist/threads/query.js +148 -40
  200. package/dist/threads/utils.js +105 -0
  201. package/dist/tsconfig.tsbuildinfo +1 -1
  202. package/dist/utilities/fileSystem/utilities.js +2 -1
  203. package/dist/utilities/index.js +10 -0
  204. package/dist/utilities/rowProcessor/index.js +76 -0
  205. package/package.json +12 -5
  206. package/src/api/controllers/automation.js +8 -4
  207. package/src/api/controllers/datasource.js +34 -0
  208. package/src/api/controllers/query/import/index.ts +85 -0
  209. package/src/api/controllers/query/import/sources/base/index.ts +87 -0
  210. package/src/api/controllers/query/import/sources/base/openapi.ts +24 -0
  211. package/src/api/controllers/query/import/sources/curl.ts +102 -0
  212. package/src/api/controllers/query/import/sources/openapi2.ts +163 -0
  213. package/src/api/controllers/query/import/sources/tests/curl/curl.spec.js +103 -0
  214. package/src/api/controllers/query/import/sources/tests/curl/data/delete.txt +1 -0
  215. package/src/api/controllers/query/import/sources/tests/curl/data/empty-body.txt +2 -0
  216. package/src/api/controllers/query/import/sources/tests/curl/data/get.txt +1 -0
  217. package/src/api/controllers/query/import/sources/tests/curl/data/headers.txt +3 -0
  218. package/src/api/controllers/query/import/sources/tests/curl/data/patch.txt +2 -0
  219. package/src/api/controllers/query/import/sources/tests/curl/data/path.txt +1 -0
  220. package/src/api/controllers/query/import/sources/tests/curl/data/post.txt +2 -0
  221. package/src/api/controllers/query/import/sources/tests/curl/data/put.txt +2 -0
  222. package/src/api/controllers/query/import/sources/tests/curl/data/query.txt +1 -0
  223. package/src/api/controllers/query/import/sources/tests/openapi2/data/crud/crud.json +253 -0
  224. package/src/api/controllers/query/import/sources/tests/openapi2/data/crud/crud.yaml +160 -0
  225. package/src/api/controllers/query/import/sources/tests/openapi2/data/petstore/petstore.json +1054 -0
  226. package/src/api/controllers/query/import/sources/tests/openapi2/data/petstore/petstore.yaml +711 -0
  227. package/src/api/controllers/query/import/sources/tests/openapi2/openapi2.spec.js +238 -0
  228. package/src/api/controllers/query/import/tests/index.spec.js +109 -0
  229. package/src/api/controllers/query/index.js +195 -0
  230. package/src/api/controllers/query/validation.js +42 -0
  231. package/src/api/controllers/row/internal.js +14 -5
  232. package/src/api/controllers/row/internalSearch.js +2 -1
  233. package/src/api/controllers/row/utils.js +11 -8
  234. package/src/api/controllers/user.js +33 -0
  235. package/src/api/controllers/webhook.js +4 -0
  236. package/src/api/routes/query.js +13 -35
  237. package/src/api/routes/tests/datasource.spec.js +45 -0
  238. package/src/api/routes/tests/query.spec.js +69 -0
  239. package/src/api/routes/user.js +10 -0
  240. package/src/automations/steps/executeQuery.js +9 -1
  241. package/src/automations/steps/outgoingWebhook.js +2 -3
  242. package/src/automations/utils.js +11 -0
  243. package/src/db/utils.js +9 -0
  244. package/src/definitions/common.ts +2 -16
  245. package/src/definitions/datasource.ts +83 -1
  246. package/src/integrations/base/IntegrationBase.ts +4 -4
  247. package/src/integrations/base/sqlTable.ts +1 -1
  248. package/src/integrations/oracle.ts +1 -1
  249. package/src/integrations/rest.ts +230 -137
  250. package/src/integrations/tests/rest.spec.js +167 -19
  251. package/src/tests/utilities/TestConfiguration.js +57 -0
  252. package/src/threads/query.js +149 -40
  253. package/src/threads/utils.js +115 -0
  254. package/src/utilities/fileSystem/utilities.js +2 -0
  255. package/src/utilities/index.js +11 -0
  256. package/src/utilities/rowProcessor/index.js +75 -0
  257. package/builder/assets/index.8adef83f.js +0 -1316
  258. package/src/api/controllers/query.js +0 -158
@@ -1,6 +1,9 @@
1
1
  jest.mock("node-fetch", () =>
2
2
  jest.fn(() => ({
3
3
  headers: {
4
+ raw: () => {
5
+ return { "content-type": ["application/json"] }
6
+ },
4
7
  get: () => ["application/json"]
5
8
  },
6
9
  json: jest.fn(),
@@ -9,6 +12,12 @@ jest.mock("node-fetch", () =>
9
12
  )
10
13
  const fetch = require("node-fetch")
11
14
  const RestIntegration = require("../rest")
15
+ const { AuthType } = require("../rest")
16
+
17
+ const HEADERS = {
18
+ "Accept": "application/json",
19
+ "Content-Type": "application/json"
20
+ }
12
21
 
13
22
  class TestConfiguration {
14
23
  constructor(config = {}) {
@@ -24,26 +33,24 @@ describe("REST Integration", () => {
24
33
  config = new TestConfiguration({
25
34
  url: BASE_URL,
26
35
  })
36
+ jest.clearAllMocks()
27
37
  })
28
38
 
29
39
  it("calls the create method with the correct params", async () => {
30
40
  const query = {
31
41
  path: "api",
32
42
  queryString: "test=1",
33
- headers: {
34
- Accept: "application/json",
35
- },
36
- json: {
43
+ headers: HEADERS,
44
+ bodyType: "json",
45
+ requestBody: JSON.stringify({
37
46
  name: "test",
38
- },
47
+ }),
39
48
  }
40
49
  const response = await config.integration.create(query)
41
50
  expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
42
51
  method: "POST",
43
52
  body: '{"name":"test"}',
44
- headers: {
45
- Accept: "application/json",
46
- },
53
+ headers: HEADERS,
47
54
  })
48
55
  })
49
56
 
@@ -60,6 +67,7 @@ describe("REST Integration", () => {
60
67
  headers: {
61
68
  Accept: "text/html",
62
69
  },
70
+ method: "GET",
63
71
  })
64
72
  })
65
73
 
@@ -70,17 +78,16 @@ describe("REST Integration", () => {
70
78
  headers: {
71
79
  Accept: "application/json",
72
80
  },
73
- json: {
81
+ bodyType: "json",
82
+ requestBody: JSON.stringify({
74
83
  name: "test",
75
- },
84
+ }),
76
85
  }
77
86
  const response = await config.integration.update(query)
78
87
  expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
79
- method: "POST",
88
+ method: "PUT",
80
89
  body: '{"name":"test"}',
81
- headers: {
82
- Accept: "application/json",
83
- },
90
+ headers: HEADERS,
84
91
  })
85
92
  })
86
93
 
@@ -91,16 +98,157 @@ describe("REST Integration", () => {
91
98
  headers: {
92
99
  Accept: "application/json",
93
100
  },
94
- json: {
101
+ bodyType: "json",
102
+ requestBody: JSON.stringify({
95
103
  name: "test",
96
- },
104
+ }),
97
105
  }
98
106
  const response = await config.integration.delete(query)
99
107
  expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
100
108
  method: "DELETE",
101
- headers: {
102
- Accept: "application/json",
103
- },
109
+ headers: HEADERS,
110
+ body: '{"name":"test"}',
111
+ })
112
+ })
113
+
114
+ describe("request body", () => {
115
+ const input = { a: 1, b: 2 }
116
+
117
+ it("should allow no body", () => {
118
+ const output = config.integration.addBody("none", null, {})
119
+ expect(output.body).toBeUndefined()
120
+ expect(Object.keys(output.headers).length).toEqual(0)
121
+ })
122
+
123
+ it("should allow text body", () => {
124
+ const output = config.integration.addBody("text", "hello world", {})
125
+ expect(output.body).toEqual("hello world")
126
+ // gets added by fetch
127
+ expect(Object.keys(output.headers).length).toEqual(0)
128
+ })
129
+
130
+ it("should allow form data", () => {
131
+ const FormData = require("form-data")
132
+ const output = config.integration.addBody("form", input, {})
133
+ expect(output.body instanceof FormData).toEqual(true)
134
+ expect(output.body._valueLength).toEqual(2)
135
+ // gets added by fetch
136
+ expect(Object.keys(output.headers).length).toEqual(0)
137
+ })
138
+
139
+ it("should allow encoded form data", () => {
140
+ const { URLSearchParams } = require("url")
141
+ const output = config.integration.addBody("encoded", input, {})
142
+ expect(output.body instanceof URLSearchParams).toEqual(true)
143
+ expect(output.body.toString()).toEqual("a=1&b=2")
144
+ // gets added by fetch
145
+ expect(Object.keys(output.headers).length).toEqual(0)
146
+ })
147
+
148
+ it("should allow JSON", () => {
149
+ const output = config.integration.addBody("json", input, {})
150
+ expect(output.body).toEqual(JSON.stringify(input))
151
+ expect(output.headers["Content-Type"]).toEqual("application/json")
152
+ })
153
+
154
+ it("should allow XML", () => {
155
+ const output = config.integration.addBody("xml", input, {})
156
+ expect(output.body.includes("<a>1</a>")).toEqual(true)
157
+ expect(output.body.includes("<b>2</b>")).toEqual(true)
158
+ expect(output.headers["Content-Type"]).toEqual("application/xml")
159
+ })
160
+ })
161
+
162
+ describe("response", () => {
163
+ function buildInput(json, text, header) {
164
+ return {
165
+ status: 200,
166
+ json: json ? async () => json : undefined,
167
+ text: text ? async () => text : undefined,
168
+ headers: { get: key => key === "content-length" ? 100 : header, raw: () => ({ "content-type": header }) }
169
+ }
170
+ }
171
+
172
+ it("should be able to parse JSON response", async () => {
173
+ const input = buildInput({a: 1}, null, "application/json")
174
+ const output = await config.integration.parseResponse(input)
175
+ expect(output.data).toEqual({a: 1})
176
+ expect(output.info.code).toEqual(200)
177
+ expect(output.info.size).toEqual("100B")
178
+ expect(output.extra.raw).toEqual(JSON.stringify({a: 1}))
179
+ expect(output.extra.headers["content-type"]).toEqual("application/json")
180
+ })
181
+
182
+ it("should be able to parse text response", async () => {
183
+ const text = "hello world"
184
+ const input = buildInput(null, text, "text/plain")
185
+ const output = await config.integration.parseResponse(input)
186
+ expect(output.data).toEqual(text)
187
+ expect(output.extra.raw).toEqual(text)
188
+ expect(output.extra.headers["content-type"]).toEqual("text/plain")
189
+ })
190
+
191
+ it("should be able to parse XML response", async () => {
192
+ const text = "<root><a>1</a><b>2</b></root>"
193
+ const input = buildInput(null, text, "application/xml")
194
+ const output = await config.integration.parseResponse(input)
195
+ expect(output.data).toEqual({a: "1", b: "2"})
196
+ expect(output.extra.raw).toEqual(text)
197
+ expect(output.extra.headers["content-type"]).toEqual("application/xml")
198
+ })
199
+ })
200
+
201
+ describe("authentication", () => {
202
+ const basicAuth = {
203
+ _id: "c59c14bd1898a43baa08da68959b24686",
204
+ name: "basic-1",
205
+ type : AuthType.BASIC,
206
+ config : {
207
+ username: "user",
208
+ password: "password"
209
+ }
210
+ }
211
+
212
+ const bearerAuth = {
213
+ _id: "0d91d732f34e4befabeff50b392a8ff3",
214
+ name: "bearer-1",
215
+ type : AuthType.BEARER,
216
+ config : {
217
+ "token": "mytoken"
218
+ }
219
+ }
220
+
221
+ beforeEach(() => {
222
+ config = new TestConfiguration({
223
+ url: BASE_URL,
224
+ authConfigs : [basicAuth, bearerAuth]
225
+ })
226
+ })
227
+
228
+ it("adds basic auth", async () => {
229
+ const query = {
230
+ authConfigId: "c59c14bd1898a43baa08da68959b24686"
231
+ }
232
+ await config.integration.read(query)
233
+ expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/?`, {
234
+ method: "GET",
235
+ headers: {
236
+ Authorization: "Basic dXNlcjpwYXNzd29yZA=="
237
+ },
238
+ })
239
+ })
240
+
241
+ it("adds bearer auth", async () => {
242
+ const query = {
243
+ authConfigId: "0d91d732f34e4befabeff50b392a8ff3"
244
+ }
245
+ await config.integration.read(query)
246
+ expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/?`, {
247
+ method: "GET",
248
+ headers: {
249
+ Authorization: "Bearer mytoken"
250
+ },
251
+ })
104
252
  })
105
253
  })
106
254
  })
@@ -316,6 +316,63 @@ 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
+
329
+ async restDatasource(cfg) {
330
+ return this.createDatasource({
331
+ datasource: {
332
+ ...basicDatasource().datasource,
333
+ source: "REST",
334
+ config: cfg || {},
335
+ },
336
+ })
337
+ }
338
+
339
+ async dynamicVariableDatasource() {
340
+ let datasource = await this.restDatasource()
341
+ const basedOnQuery = await this.createQuery({
342
+ ...basicQuery(datasource._id),
343
+ fields: {
344
+ path: "www.google.com",
345
+ },
346
+ })
347
+ datasource = await this.updateDatasource({
348
+ ...datasource,
349
+ config: {
350
+ dynamicVariables: [
351
+ {
352
+ queryId: basedOnQuery._id,
353
+ name: "variable3",
354
+ value: "{{ data.0.[value] }}",
355
+ },
356
+ ],
357
+ },
358
+ })
359
+ return { datasource, query: basedOnQuery }
360
+ }
361
+
362
+ async previewQuery(request, config, datasource, fields) {
363
+ return request
364
+ .post(`/api/queries/preview`)
365
+ .send({
366
+ datasourceId: datasource._id,
367
+ parameters: {},
368
+ fields,
369
+ queryVerb: "read",
370
+ })
371
+ .set(config.defaultHeaders())
372
+ .expect("Content-Type", /json/)
373
+ .expect(200)
374
+ }
375
+
319
376
  async createQuery(config = null) {
320
377
  if (!this.datasource && !config) {
321
378
  throw "No data source created for query."
@@ -1,60 +1,169 @@
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
- async function runAndTransform(datasource, queryVerb, query, transformer) {
17
- const Integration = integrations[datasource.source]
18
- if (!Integration) {
19
- throw "Integration type does not exist."
20
- }
21
- const integration = new Integration(datasource.config)
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)
22
36
 
23
- let rows = formatResponse(await integration[queryVerb](query))
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
+ }
24
46
 
25
- // transform as required
26
- if (transformer) {
27
- const runner = new ScriptRunner(transformer, { data: rows })
28
- rows = runner.execute()
29
- }
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
+ }
30
55
 
31
- // needs to an array for next step
32
- if (!Array.isArray(rows)) {
33
- rows = [rows]
34
- }
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
+ }
68
+
69
+ // needs to an array for next step
70
+ if (!Array.isArray(rows)) {
71
+ rows = [rows]
72
+ }
35
73
 
36
- // map into JSON if just raw primitive here
37
- if (rows.find(row => typeof row !== "object")) {
38
- rows = rows.map(value => ({ value }))
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 }
39
87
  }
40
88
 
41
- // get all the potential fields in the schema
42
- let keys = rows.flatMap(Object.keys)
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
+ }
43
105
 
44
- if (integration.end) {
45
- integration.end()
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
46
122
  }
47
123
 
48
- return { rows, keys }
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
+ }
49
162
  }
50
163
 
51
164
  module.exports = (input, callback) => {
52
- runAndTransform(
53
- input.datasource,
54
- input.queryVerb,
55
- input.query,
56
- input.transformer
57
- )
165
+ const Runner = new QueryRunner(input)
166
+ Runner.execute()
58
167
  .then(response => {
59
168
  callback(null, response)
60
169
  })
@@ -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
@@ -150,3 +150,14 @@ exports.doesDatabaseExist = async dbName => {
150
150
  return false
151
151
  }
152
152
  }
153
+
154
+ exports.formatBytes = bytes => {
155
+ const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
156
+ const byteIncrements = 1024
157
+ let unit = 0
158
+ let size = parseInt(bytes, 10) || 0
159
+ while (size >= byteIncrements && ++unit) {
160
+ size /= byteIncrements
161
+ }
162
+ return `${size.toFixed(size < 10 && unit > 0 ? 1 : 0)}${units[unit]}`
163
+ }