@budibase/server 2.5.9 → 2.5.10-alpha.1

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 (198) hide show
  1. package/builder/assets/index.24635afb.js +1794 -0
  2. package/builder/assets/index.4eae16b2.css +6 -0
  3. package/builder/index.html +2 -2
  4. package/dist/api/controllers/application.js +3 -4
  5. package/dist/api/controllers/automation.js +13 -7
  6. package/dist/api/controllers/datasource.js +1 -1
  7. package/dist/api/controllers/dev.js +1 -1
  8. package/dist/api/controllers/ops.js +40 -0
  9. package/dist/api/controllers/plugin/index.js +6 -37
  10. package/dist/api/controllers/query/index.js +2 -2
  11. package/dist/api/controllers/row/ExternalRequest.js +21 -14
  12. package/dist/api/controllers/row/internal.js +5 -2
  13. package/dist/api/controllers/row/utils.js +2 -2
  14. package/dist/api/controllers/table/index.js +2 -2
  15. package/dist/api/controllers/table/utils.js +9 -3
  16. package/dist/api/controllers/user.js +1 -83
  17. package/dist/api/controllers/view/exporters.js +3 -1
  18. package/dist/api/index.js +1 -2
  19. package/dist/api/routes/index.js +2 -2
  20. package/dist/api/routes/{cloud.js → ops.js} +19 -6
  21. package/dist/api/routes/user.js +0 -1
  22. package/dist/app.js +4 -13
  23. package/dist/automations/actions.js +32 -6
  24. package/dist/automations/index.js +3 -2
  25. package/dist/automations/steps/bash.js +6 -6
  26. package/dist/automations/steps/createRow.js +11 -11
  27. package/dist/automations/steps/delay.js +3 -3
  28. package/dist/automations/steps/deleteRow.js +8 -8
  29. package/dist/automations/steps/discord.js +8 -8
  30. package/dist/automations/steps/executeQuery.js +9 -9
  31. package/dist/automations/steps/executeScript.js +6 -6
  32. package/dist/automations/steps/filter.js +6 -6
  33. package/dist/automations/steps/integromat.js +10 -10
  34. package/dist/automations/steps/loop.js +9 -9
  35. package/dist/automations/steps/outgoingWebhook.js +10 -10
  36. package/dist/automations/steps/queryRows.js +14 -14
  37. package/dist/automations/steps/sendSmtpEmail.js +9 -9
  38. package/dist/automations/steps/serverLog.js +4 -4
  39. package/dist/automations/steps/slack.js +6 -6
  40. package/dist/automations/steps/updateRow.js +11 -11
  41. package/dist/automations/steps/zapier.js +9 -9
  42. package/dist/automations/triggerInfo/app.js +5 -5
  43. package/dist/automations/triggerInfo/cron.js +4 -4
  44. package/dist/automations/triggerInfo/rowDeleted.js +5 -5
  45. package/dist/automations/triggerInfo/rowSaved.js +7 -7
  46. package/dist/automations/triggerInfo/rowUpdated.js +7 -7
  47. package/dist/automations/triggerInfo/webhook.js +6 -6
  48. package/dist/db/utils.js +3 -2
  49. package/dist/environment.js +0 -1
  50. package/dist/events/docUpdates/index.js +17 -0
  51. package/dist/events/docUpdates/processors.js +18 -0
  52. package/dist/events/docUpdates/syncUsers.js +49 -0
  53. package/dist/events/index.js +3 -0
  54. package/dist/integrations/base/sqlTable.js +9 -2
  55. package/dist/integrations/index.js +3 -3
  56. package/dist/integrations/microsoftSqlServer.js +5 -2
  57. package/dist/integrations/mysql.js +5 -3
  58. package/dist/integrations/postgres.js +7 -5
  59. package/dist/integrations/redis.js +7 -0
  60. package/dist/integrations/rest.js +4 -0
  61. package/dist/migrations/functions/syncQuotas.js +2 -0
  62. package/dist/migrations/functions/usageQuotas/syncApps.js +1 -2
  63. package/dist/migrations/functions/usageQuotas/syncRows.js +1 -2
  64. package/dist/migrations/functions/usageQuotas/syncUsers.js +21 -0
  65. package/dist/sdk/app/applications/sync.js +117 -23
  66. package/dist/sdk/app/backups/exports.js +14 -38
  67. package/dist/sdk/index.js +2 -0
  68. package/dist/sdk/plugins/index.js +27 -0
  69. package/dist/sdk/plugins/plugins.js +53 -0
  70. package/dist/sdk/users/utils.js +21 -4
  71. package/dist/startup.js +31 -28
  72. package/dist/threads/automation.js +16 -5
  73. package/dist/tsconfig.build.tsbuildinfo +1 -1
  74. package/dist/utilities/csv.js +33 -0
  75. package/dist/utilities/fileSystem/plugin.js +33 -23
  76. package/dist/utilities/global.js +17 -12
  77. package/dist/utilities/rowProcessor/utils.js +4 -5
  78. package/dist/utilities/schema.js +5 -1
  79. package/dist/watch.js +2 -2
  80. package/dist/websockets/client.js +14 -0
  81. package/dist/websockets/grid.js +60 -0
  82. package/dist/websockets/index.js +17 -0
  83. package/dist/websockets/websocket.js +78 -0
  84. package/package.json +16 -16
  85. package/scripts/dev/manage.js +2 -0
  86. package/scripts/integrations/mssql/data/entrypoint.sh +1 -0
  87. package/scripts/integrations/mssql/data/setup.sql +17 -17
  88. package/scripts/integrations/mysql/init.sql +1 -1
  89. package/scripts/integrations/postgres/init.sql +1 -0
  90. package/src/api/controllers/application.ts +4 -4
  91. package/src/api/controllers/automation.ts +12 -6
  92. package/src/api/controllers/datasource.ts +15 -5
  93. package/src/api/controllers/dev.ts +2 -2
  94. package/src/api/controllers/ops.ts +32 -0
  95. package/src/api/controllers/plugin/index.ts +8 -45
  96. package/src/api/controllers/query/index.ts +2 -2
  97. package/src/api/controllers/row/ExternalRequest.ts +21 -12
  98. package/src/api/controllers/row/internal.ts +13 -11
  99. package/src/api/controllers/row/utils.ts +4 -4
  100. package/src/api/controllers/table/index.ts +2 -2
  101. package/src/api/controllers/table/utils.ts +10 -3
  102. package/src/api/controllers/user.ts +10 -96
  103. package/src/api/controllers/view/exporters.ts +3 -1
  104. package/src/api/index.ts +2 -4
  105. package/src/api/routes/index.ts +2 -2
  106. package/src/api/routes/ops.ts +30 -0
  107. package/src/api/routes/tests/automation.spec.js +7 -4
  108. package/src/api/routes/tests/user.spec.js +48 -37
  109. package/src/api/routes/user.ts +0 -5
  110. package/src/app.ts +4 -15
  111. package/src/automations/actions.ts +56 -24
  112. package/src/automations/index.ts +1 -1
  113. package/src/automations/steps/bash.ts +10 -7
  114. package/src/automations/steps/createRow.ts +15 -12
  115. package/src/automations/steps/delay.ts +6 -4
  116. package/src/automations/steps/deleteRow.ts +12 -9
  117. package/src/automations/steps/discord.ts +10 -8
  118. package/src/automations/steps/executeQuery.ts +13 -10
  119. package/src/automations/steps/executeScript.ts +10 -7
  120. package/src/automations/steps/filter.ts +8 -6
  121. package/src/automations/steps/integromat.ts +12 -10
  122. package/src/automations/steps/loop.ts +16 -10
  123. package/src/automations/steps/outgoingWebhook.ts +14 -11
  124. package/src/automations/steps/queryRows.ts +18 -15
  125. package/src/automations/steps/sendSmtpEmail.ts +11 -9
  126. package/src/automations/steps/serverLog.ts +6 -4
  127. package/src/automations/steps/slack.ts +8 -6
  128. package/src/automations/steps/updateRow.ts +15 -12
  129. package/src/automations/steps/zapier.ts +11 -9
  130. package/src/automations/tests/utilities/index.ts +2 -2
  131. package/src/automations/triggerInfo/app.ts +8 -5
  132. package/src/automations/triggerInfo/cron.ts +7 -4
  133. package/src/automations/triggerInfo/rowDeleted.ts +8 -5
  134. package/src/automations/triggerInfo/rowSaved.ts +10 -7
  135. package/src/automations/triggerInfo/rowUpdated.ts +10 -7
  136. package/src/automations/triggerInfo/webhook.ts +9 -6
  137. package/src/db/utils.ts +1 -0
  138. package/src/environment.ts +0 -1
  139. package/src/events/docUpdates/index.ts +1 -0
  140. package/src/events/docUpdates/processors.ts +14 -0
  141. package/src/events/docUpdates/syncUsers.ts +35 -0
  142. package/src/events/index.ts +1 -0
  143. package/src/integration-test/postgres.spec.ts +3 -1
  144. package/src/integrations/base/sqlTable.ts +9 -2
  145. package/src/integrations/index.ts +3 -3
  146. package/src/integrations/microsoftSqlServer.ts +5 -2
  147. package/src/integrations/mysql.ts +5 -3
  148. package/src/integrations/postgres.ts +7 -5
  149. package/src/integrations/redis.ts +8 -0
  150. package/src/integrations/rest.ts +3 -0
  151. package/src/migrations/functions/syncQuotas.ts +2 -0
  152. package/src/migrations/functions/usageQuotas/syncApps.ts +2 -3
  153. package/src/migrations/functions/usageQuotas/syncRows.ts +2 -3
  154. package/src/migrations/functions/usageQuotas/syncUsers.ts +9 -0
  155. package/src/migrations/functions/usageQuotas/tests/syncRows.spec.ts +2 -2
  156. package/src/migrations/functions/usageQuotas/tests/syncUsers.spec.ts +26 -0
  157. package/src/migrations/index.ts +1 -0
  158. package/src/sdk/app/applications/sync.ts +129 -22
  159. package/src/sdk/app/applications/tests/sync.spec.ts +137 -0
  160. package/src/sdk/app/backups/exports.ts +17 -41
  161. package/src/sdk/index.ts +2 -0
  162. package/src/sdk/plugins/index.ts +5 -0
  163. package/src/sdk/plugins/plugins.ts +41 -0
  164. package/src/sdk/users/tests/utils.spec.ts +1 -32
  165. package/src/sdk/users/utils.ts +23 -5
  166. package/src/startup.ts +36 -34
  167. package/src/tests/jestEnv.ts +0 -1
  168. package/src/tests/jestSetup.ts +0 -1
  169. package/src/tests/utilities/TestConfiguration.ts +28 -0
  170. package/src/tests/utilities/structures.ts +25 -17
  171. package/src/threads/automation.ts +18 -6
  172. package/src/utilities/csv.ts +22 -0
  173. package/src/utilities/fileSystem/plugin.ts +13 -4
  174. package/src/utilities/global.ts +21 -16
  175. package/src/utilities/rowProcessor/utils.ts +9 -10
  176. package/src/utilities/schema.ts +8 -0
  177. package/src/utilities/tests/csv.spec.ts +33 -0
  178. package/src/watch.ts +2 -2
  179. package/src/websockets/client.ts +11 -0
  180. package/src/websockets/grid.ts +55 -0
  181. package/src/websockets/index.ts +14 -0
  182. package/src/websockets/websocket.ts +83 -0
  183. package/tsconfig.build.json +3 -5
  184. package/tsconfig.json +2 -1
  185. package/builder/assets/index.0b358332.js +0 -1817
  186. package/builder/assets/index.7f9a008b.css +0 -6
  187. package/dist/api/controllers/cloud.js +0 -130
  188. package/dist/elasticApm.js +0 -14
  189. package/dist/package.json +0 -180
  190. package/dist/websocket.js +0 -22
  191. package/scripts/likeCypress.ts +0 -35
  192. package/src/api/controllers/cloud.ts +0 -119
  193. package/src/api/routes/cloud.ts +0 -18
  194. package/src/api/routes/tests/cloud.spec.ts +0 -54
  195. package/src/elasticApm.ts +0 -10
  196. package/src/migrations/functions/tests/syncQuotas.spec.js +0 -26
  197. package/src/tests/logging.ts +0 -34
  198. package/src/websocket.ts +0 -26
@@ -0,0 +1,41 @@
1
+ import { FileType, Plugin, PluginSource, PluginType } from "@budibase/types"
2
+ import {
3
+ db as dbCore,
4
+ objectStore,
5
+ plugins as pluginCore,
6
+ tenancy,
7
+ } from "@budibase/backend-core"
8
+ import { fileUpload } from "../../api/controllers/plugin/file"
9
+ import env from "../../environment"
10
+ import { clientAppSocket } from "../../websockets"
11
+ import { sdk as pro } from "@budibase/pro"
12
+
13
+ export async function fetch(type?: PluginType) {
14
+ const db = tenancy.getGlobalDB()
15
+ const response = await db.allDocs(
16
+ dbCore.getPluginParams(null, {
17
+ include_docs: true,
18
+ })
19
+ )
20
+ let plugins = response.rows.map((row: any) => row.doc) as Plugin[]
21
+ plugins = objectStore.enrichPluginURLs(plugins)
22
+ if (type) {
23
+ return plugins.filter((plugin: Plugin) => plugin.schema?.type === type)
24
+ } else {
25
+ return plugins
26
+ }
27
+ }
28
+
29
+ export async function processUploaded(plugin: FileType, source?: PluginSource) {
30
+ const { metadata, directory } = await fileUpload(plugin)
31
+ pluginCore.validate(metadata?.schema)
32
+
33
+ // Only allow components in cloud
34
+ if (!env.SELF_HOSTED && metadata?.schema?.type !== PluginType.COMPONENT) {
35
+ throw new Error("Only component plugins are supported outside of self-host")
36
+ }
37
+
38
+ const doc = await pro.plugins.storePlugin(metadata, directory, source)
39
+ clientAppSocket.emit("plugin-update", { name: doc.name, hash: doc.hash })
40
+ return doc
41
+ }
@@ -121,38 +121,7 @@ describe("syncGlobalUsers", () => {
121
121
  await syncGlobalUsers()
122
122
 
123
123
  const metadata = await rawUserMetadata()
124
- expect(metadata).toHaveLength(1)
125
- })
126
- })
127
- })
128
-
129
- it("app users are removed when app is removed from user group", async () => {
130
- await config.doInTenant(async () => {
131
- const group = await proSdk.groups.save(structures.userGroups.userGroup())
132
- const user1 = await config.createUser({ admin: false, builder: false })
133
- const user2 = await config.createUser({ admin: false, builder: false })
134
- await proSdk.groups.updateGroupApps(group.id, {
135
- appsToAdd: [
136
- { appId: config.prodAppId!, roleId: roles.BUILTIN_ROLE_IDS.BASIC },
137
- ],
138
- })
139
- await proSdk.groups.addUsers(group.id, [user1._id, user2._id])
140
-
141
- await config.doInContext(config.appId, async () => {
142
- await syncGlobalUsers()
143
- expect(await rawUserMetadata()).toHaveLength(3)
144
-
145
- await proSdk.groups.removeUsers(group.id, [user1._id])
146
- await syncGlobalUsers()
147
-
148
- const metadata = await rawUserMetadata()
149
- expect(metadata).toHaveLength(2)
150
-
151
- expect(metadata).not.toContainEqual(
152
- expect.objectContaining({
153
- _id: db.generateUserMetadataID(user1._id),
154
- })
155
- )
124
+ expect(metadata).toHaveLength(0)
156
125
  })
157
126
  })
158
127
  })
@@ -1,12 +1,13 @@
1
1
  import { getGlobalUsers } from "../../utilities/global"
2
2
  import { context, roles as rolesCore } from "@budibase/backend-core"
3
3
  import {
4
+ getGlobalIDFromUserMetadataID,
4
5
  generateUserMetadataID,
5
6
  getUserMetadataParams,
6
7
  InternalTables,
7
8
  } from "../../db/utils"
8
9
  import { isEqual } from "lodash"
9
- import { ContextUser, UserMetadata } from "@budibase/types"
10
+ import { ContextUser, UserMetadata, User } from "@budibase/types"
10
11
 
11
12
  export function combineMetadataAndUser(
12
13
  user: ContextUser,
@@ -37,6 +38,10 @@ export function combineMetadataAndUser(
37
38
  if (found) {
38
39
  newDoc._rev = found._rev
39
40
  }
41
+ // clear fields that shouldn't be in metadata
42
+ delete newDoc.password
43
+ delete newDoc.forceResetPassword
44
+ delete newDoc.roles
40
45
  if (found == null || !isEqual(newDoc, found)) {
41
46
  return {
42
47
  ...found,
@@ -60,10 +65,9 @@ export async function rawUserMetadata() {
60
65
  export async function syncGlobalUsers() {
61
66
  // sync user metadata
62
67
  const db = context.getAppDB()
63
- const [users, metadata] = await Promise.all([
64
- getGlobalUsers(),
65
- rawUserMetadata(),
66
- ])
68
+ const resp = await Promise.all([getGlobalUsers(), rawUserMetadata()])
69
+ const users = resp[0] as User[]
70
+ const metadata = resp[1] as UserMetadata[]
67
71
  const toWrite = []
68
72
  for (let user of users) {
69
73
  const combined = combineMetadataAndUser(user, metadata)
@@ -71,5 +75,19 @@ export async function syncGlobalUsers() {
71
75
  toWrite.push(combined)
72
76
  }
73
77
  }
78
+ let foundEmails: string[] = []
79
+ for (let data of metadata) {
80
+ if (!data._id) {
81
+ continue
82
+ }
83
+ const alreadyExisting = data.email && foundEmails.indexOf(data.email) !== -1
84
+ const globalId = getGlobalIDFromUserMetadataID(data._id)
85
+ if (!users.find(user => user._id === globalId) || alreadyExisting) {
86
+ toWrite.push({ ...data, _deleted: true })
87
+ }
88
+ if (data.email) {
89
+ foundEmails.push(data.email)
90
+ }
91
+ }
74
92
  await db.bulkDocs(toWrite)
75
93
  }
package/src/startup.ts CHANGED
@@ -10,19 +10,16 @@ import fs from "fs"
10
10
  import { watch } from "./watch"
11
11
  import * as automations from "./automations"
12
12
  import * as fileSystem from "./utilities/fileSystem"
13
- import eventEmitter from "./events"
13
+ import { default as eventEmitter, init as eventInit } from "./events"
14
14
  import * as migrations from "./migrations"
15
15
  import * as bullboard from "./automations/bullboard"
16
16
  import * as pro from "@budibase/pro"
17
17
  import * as api from "./api"
18
18
  import sdk from "./sdk"
19
- const pino = require("koa-pino-logger")
20
19
 
21
20
  let STARTUP_RAN = false
22
21
 
23
22
  async function initRoutes(app: any) {
24
- app.use(pino(logging.pinoSettings()))
25
-
26
23
  if (!env.isTest()) {
27
24
  const plugin = await bullboard.init()
28
25
  app.use(plugin)
@@ -48,8 +45,10 @@ async function initPro() {
48
45
  }
49
46
 
50
47
  function shutdown(server?: any) {
51
- server.close()
52
- server.destroy()
48
+ if (server) {
49
+ server.close()
50
+ server.destroy()
51
+ }
53
52
  }
54
53
 
55
54
  export async function startup(app?: any, server?: any) {
@@ -64,6 +63,7 @@ export async function startup(app?: any, server?: any) {
64
63
  eventEmitter.emitPort(env.PORT)
65
64
  fileSystem.init()
66
65
  await redis.init()
66
+ eventInit()
67
67
 
68
68
  // run migrations on startup if not done via http
69
69
  // not recommended in a clustered environment
@@ -72,11 +72,39 @@ export async function startup(app?: any, server?: any) {
72
72
  await migrations.migrate()
73
73
  } catch (e) {
74
74
  logging.logAlert("Error performing migrations. Exiting.", e)
75
- shutdown()
75
+ shutdown(server)
76
76
  }
77
77
  }
78
78
 
79
+ // monitor plugin directory if required
80
+ if (
81
+ env.SELF_HOSTED &&
82
+ !env.MULTI_TENANCY &&
83
+ env.PLUGINS_DIR &&
84
+ fs.existsSync(env.PLUGINS_DIR)
85
+ ) {
86
+ watch()
87
+ }
88
+
89
+ // check for version updates
90
+ await installation.checkInstallVersion()
91
+
92
+ // get the references to the queue promises, don't await as
93
+ // they will never end, unless the processing stops
94
+ let queuePromises = []
95
+ // configure events to use the pro audit log write
96
+ // can't integrate directly into backend-core due to cyclic issues
97
+ queuePromises.push(events.processors.init(pro.sdk.auditLogs.write))
98
+ queuePromises.push(automations.init())
99
+ queuePromises.push(initPro())
100
+ if (app) {
101
+ // bring routes online as final step once everything ready
102
+ await initRoutes(app)
103
+ }
104
+
79
105
  // check and create admin user if required
106
+ // this must be run after the api has been initialised due to
107
+ // the app user sync
80
108
  if (
81
109
  env.SELF_HOSTED &&
82
110
  !env.MULTI_TENANCY &&
@@ -103,34 +131,8 @@ export async function startup(app?: any, server?: any) {
103
131
  )
104
132
  } catch (e) {
105
133
  logging.logAlert("Error creating initial admin user. Exiting.", e)
106
- shutdown()
134
+ shutdown(server)
107
135
  }
108
136
  }
109
137
  }
110
-
111
- // monitor plugin directory if required
112
- if (
113
- env.SELF_HOSTED &&
114
- !env.MULTI_TENANCY &&
115
- env.PLUGINS_DIR &&
116
- fs.existsSync(env.PLUGINS_DIR)
117
- ) {
118
- watch()
119
- }
120
-
121
- // check for version updates
122
- await installation.checkInstallVersion()
123
-
124
- // get the references to the queue promises, don't await as
125
- // they will never end, unless the processing stops
126
- let queuePromises = []
127
- // configure events to use the pro audit log write
128
- // can't integrate directly into backend-core due to cyclic issues
129
- queuePromises.push(events.processors.init(pro.sdk.auditLogs.write))
130
- queuePromises.push(automations.init())
131
- queuePromises.push(initPro())
132
- if (app) {
133
- // bring routes online as final step once everything ready
134
- await initRoutes(app)
135
- }
136
138
  }
@@ -6,7 +6,6 @@ process.env.MULTI_TENANCY = "1"
6
6
  // @ts-ignore
7
7
  process.env.BUDIBASE_DIR = tmpdir("budibase-unittests")
8
8
  process.env.LOG_LEVEL = process.env.LOG_LEVEL || "error"
9
- process.env.ENABLE_4XX_HTTP_LOGGING = "0"
10
9
  process.env.MOCK_REDIS = "1"
11
10
  process.env.PLATFORM_URL = "http://localhost:10000"
12
11
  process.env.REDIS_PASSWORD = "budibase"
@@ -1,4 +1,3 @@
1
- import "./logging"
2
1
  import env from "../environment"
3
2
  import { env as coreEnv, timers } from "@budibase/backend-core"
4
3
  import { testContainerUtils } from "@budibase/backend-core/tests"
@@ -49,6 +49,7 @@ import {
49
49
  SearchFilters,
50
50
  UserRoles,
51
51
  } from "@budibase/types"
52
+ import { BUILTIN_ROLE_IDS } from "@budibase/backend-core/src/security/roles"
52
53
 
53
54
  type DefaultUserValues = {
54
55
  globalUserId: string
@@ -306,6 +307,33 @@ class TestConfiguration {
306
307
  }
307
308
  }
308
309
 
310
+ async createGroup(roleId: string = BUILTIN_ROLE_IDS.BASIC) {
311
+ return context.doInTenant(this.tenantId!, async () => {
312
+ const baseGroup = structures.userGroups.userGroup()
313
+ baseGroup.roles = {
314
+ [this.prodAppId]: roleId,
315
+ }
316
+ const { id, rev } = await pro.sdk.groups.save(baseGroup)
317
+ return {
318
+ _id: id,
319
+ _rev: rev,
320
+ ...baseGroup,
321
+ }
322
+ })
323
+ }
324
+
325
+ async addUserToGroup(groupId: string, userId: string) {
326
+ return context.doInTenant(this.tenantId!, async () => {
327
+ await pro.sdk.groups.addUsers(groupId, [userId])
328
+ })
329
+ }
330
+
331
+ async removeUserFromGroup(groupId: string, userId: string) {
332
+ return context.doInTenant(this.tenantId!, async () => {
333
+ await pro.sdk.groups.removeUsers(groupId, [userId])
334
+ })
335
+ }
336
+
309
337
  async login({ roleId, userId, builder, prodApp = false }: any = {}) {
310
338
  const appId = prodApp ? this.prodAppId : this.appId
311
339
  return context.doInAppContext(appId, async () => {
@@ -1,18 +1,22 @@
1
- import { permissions, roles } from "@budibase/backend-core"
1
+ import { permissions, roles, utils } from "@budibase/backend-core"
2
2
  import { createHomeScreen } from "../../constants/screens"
3
3
  import { EMPTY_LAYOUT } from "../../constants/layouts"
4
4
  import { cloneDeep } from "lodash/fp"
5
- import { ACTION_DEFINITIONS, TRIGGER_DEFINITIONS } from "../../automations"
5
+ import {
6
+ BUILTIN_ACTION_DEFINITIONS,
7
+ TRIGGER_DEFINITIONS,
8
+ } from "../../automations"
6
9
  import {
7
10
  Automation,
8
11
  AutomationActionStepId,
12
+ AutomationStep,
13
+ AutomationStepType,
14
+ AutomationTrigger,
9
15
  AutomationTriggerStepId,
10
16
  Datasource,
11
17
  SourceName,
12
18
  } from "@budibase/types"
13
19
 
14
- const { v4: uuidv4 } = require("uuid")
15
-
16
20
  export function basicTable() {
17
21
  return {
18
22
  name: "TestTable",
@@ -71,19 +75,19 @@ export function view(tableId: string) {
71
75
  }
72
76
 
73
77
  export function automationStep(
74
- actionDefinition = ACTION_DEFINITIONS.CREATE_ROW
75
- ) {
78
+ actionDefinition = BUILTIN_ACTION_DEFINITIONS.CREATE_ROW
79
+ ): AutomationStep {
76
80
  return {
77
- id: uuidv4(),
81
+ id: utils.newid(),
78
82
  ...actionDefinition,
79
83
  }
80
84
  }
81
85
 
82
86
  export function automationTrigger(
83
87
  triggerDefinition = TRIGGER_DEFINITIONS.ROW_SAVED
84
- ) {
88
+ ): AutomationTrigger {
85
89
  return {
86
- id: uuidv4(),
90
+ id: utils.newid(),
87
91
  ...triggerDefinition,
88
92
  }
89
93
  }
@@ -106,7 +110,7 @@ export function newAutomation({ steps, trigger }: any = {}) {
106
110
  return automation
107
111
  }
108
112
 
109
- export function basicAutomation(appId?: string) {
113
+ export function basicAutomation(appId?: string): Automation {
110
114
  return {
111
115
  name: "My Automation",
112
116
  screenId: "kasdkfldsafkl",
@@ -119,18 +123,22 @@ export function basicAutomation(appId?: string) {
119
123
  tagline: "test",
120
124
  icon: "test",
121
125
  description: "test",
122
- type: "trigger",
126
+ type: AutomationStepType.TRIGGER,
123
127
  id: "test",
124
128
  inputs: {},
125
129
  schema: {
126
- inputs: {},
127
- outputs: {},
130
+ inputs: {
131
+ properties: {},
132
+ },
133
+ outputs: {
134
+ properties: {},
135
+ },
128
136
  },
129
137
  },
130
138
  steps: [],
131
139
  },
132
140
  type: "automation",
133
- appId,
141
+ appId: appId!,
134
142
  }
135
143
  }
136
144
 
@@ -154,7 +162,7 @@ export function loopAutomation(tableId: string, loopOpts?: any): Automation {
154
162
  inputs: {
155
163
  tableId,
156
164
  },
157
- schema: ACTION_DEFINITIONS.QUERY_ROWS.schema,
165
+ schema: BUILTIN_ACTION_DEFINITIONS.QUERY_ROWS.schema,
158
166
  },
159
167
  {
160
168
  id: "c",
@@ -163,7 +171,7 @@ export function loopAutomation(tableId: string, loopOpts?: any): Automation {
163
171
  internal: true,
164
172
  inputs: loopOpts,
165
173
  blockToLoop: "d",
166
- schema: ACTION_DEFINITIONS.LOOP.schema,
174
+ schema: BUILTIN_ACTION_DEFINITIONS.LOOP.schema,
167
175
  },
168
176
  {
169
177
  id: "d",
@@ -173,7 +181,7 @@ export function loopAutomation(tableId: string, loopOpts?: any): Automation {
173
181
  inputs: {
174
182
  text: "log statement",
175
183
  },
176
- schema: ACTION_DEFINITIONS.SERVER_LOG.schema,
184
+ schema: BUILTIN_ACTION_DEFINITIONS.SERVER_LOG.schema,
177
185
  },
178
186
  ],
179
187
  trigger: {
@@ -27,15 +27,15 @@ import { processObject } from "@budibase/string-templates"
27
27
  import { cloneDeep } from "lodash/fp"
28
28
  import * as sdkUtils from "../sdk/utils"
29
29
  import env from "../environment"
30
- const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId
31
- const LOOP_STEP_ID = actions.ACTION_DEFINITIONS.LOOP.stepId
30
+ const FILTER_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.FILTER.stepId
31
+ const LOOP_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.LOOP.stepId
32
32
  const CRON_STEP_ID = triggerDefs.CRON.stepId
33
33
  const STOPPED_STATUS = { success: true, status: AutomationStatus.STOPPED }
34
34
 
35
35
  function getLoopIterations(loopStep: LoopStep, input: LoopInput) {
36
36
  const binding = automationUtils.typecastForLooping(loopStep, input)
37
- if (!loopStep || !binding) {
38
- return 1
37
+ if (!binding) {
38
+ return 0
39
39
  }
40
40
  if (Array.isArray(binding)) {
41
41
  return binding.length
@@ -43,7 +43,7 @@ function getLoopIterations(loopStep: LoopStep, input: LoopInput) {
43
43
  if (typeof binding === "string") {
44
44
  return automationUtils.stringSplit(binding).length
45
45
  }
46
- return 1
46
+ return 0
47
47
  }
48
48
 
49
49
  /**
@@ -423,13 +423,25 @@ class Orchestrator {
423
423
  }
424
424
  }
425
425
 
426
+ if (loopStep && iterations === 0) {
427
+ loopStep = undefined
428
+ this.executionOutput.steps.splice(loopStepNumber + 1, 0, {
429
+ id: step.id,
430
+ stepId: step.stepId,
431
+ outputs: { status: AutomationStatus.NO_ITERATIONS, success: true },
432
+ inputs: {},
433
+ })
434
+
435
+ this._context.steps.splice(loopStepNumber, 1)
436
+ iterations = 1
437
+ }
438
+
426
439
  // Delete the step after the loop step as it's irrelevant, since information is included
427
440
  // in the loop step
428
441
  if (wasLoopStep && !loopStep) {
429
442
  this._context.steps.splice(loopStepNumber + 1, 1)
430
443
  wasLoopStep = false
431
444
  }
432
-
433
445
  if (loopSteps && loopSteps.length) {
434
446
  let tempOutput = {
435
447
  success: true,
@@ -0,0 +1,22 @@
1
+ import csv from "csvtojson"
2
+
3
+ export async function jsonFromCsvString(csvString: string) {
4
+ const castedWithEmptyValues = await csv({ ignoreEmpty: true }).fromString(
5
+ csvString
6
+ )
7
+
8
+ // By default the csvtojson library casts empty values as empty strings. This is causing issues on conversion.
9
+ // ignoreEmpty will remove the key completly if empty, so creating this empty object will ensure we return the values with the keys but empty values
10
+ const result = await csv({ ignoreEmpty: false }).fromString(csvString)
11
+ result.forEach((r, i) => {
12
+ for (const [key] of Object.entries(r).filter(
13
+ ([key, value]) => value === ""
14
+ )) {
15
+ if (castedWithEmptyValues[i][key] === undefined) {
16
+ r[key] = null
17
+ }
18
+ }
19
+ })
20
+
21
+ return result
22
+ }
@@ -5,6 +5,7 @@ import { join } from "path"
5
5
  import { objectStore } from "@budibase/backend-core"
6
6
 
7
7
  const DATASOURCE_PATH = join(budibaseTempDir(), "datasource")
8
+ const AUTOMATION_PATH = join(budibaseTempDir(), "automation")
8
9
 
9
10
  export const getPluginMetadata = async (path: string) => {
10
11
  let metadata: any = {}
@@ -33,12 +34,12 @@ export const getPluginMetadata = async (path: string) => {
33
34
  return { metadata, directory: path }
34
35
  }
35
36
 
36
- export const getDatasourcePlugin = async (plugin: Plugin) => {
37
+ async function getPluginImpl(path: string, plugin: Plugin) {
37
38
  const hash = plugin.schema?.hash
38
- if (!fs.existsSync(DATASOURCE_PATH)) {
39
- fs.mkdirSync(DATASOURCE_PATH)
39
+ if (!fs.existsSync(path)) {
40
+ fs.mkdirSync(path)
40
41
  }
41
- const filename = join(DATASOURCE_PATH, plugin.name)
42
+ const filename = join(path, plugin.name)
42
43
  const metadataName = `${filename}.bbmetadata`
43
44
  if (fs.existsSync(filename)) {
44
45
  const currentHash = fs.readFileSync(metadataName, "utf8")
@@ -62,3 +63,11 @@ export const getDatasourcePlugin = async (plugin: Plugin) => {
62
63
 
63
64
  return require(filename)
64
65
  }
66
+
67
+ export const getDatasourcePlugin = async (plugin: Plugin) => {
68
+ return getPluginImpl(DATASOURCE_PATH, plugin)
69
+ }
70
+
71
+ export const getAutomationPlugin = async (plugin: Plugin) => {
72
+ return getPluginImpl(AUTOMATION_PATH, plugin)
73
+ }
@@ -9,6 +9,7 @@ import {
9
9
  import env from "../environment"
10
10
  import { groups } from "@budibase/pro"
11
11
  import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types"
12
+ import { global } from "yargs"
12
13
 
13
14
  export function updateAppRole(
14
15
  user: ContextUser,
@@ -16,7 +17,7 @@ export function updateAppRole(
16
17
  ) {
17
18
  appId = appId || context.getAppId()
18
19
 
19
- if (!user || !user.roles) {
20
+ if (!user || (!user.roles && !user.userGroups)) {
20
21
  return user
21
22
  }
22
23
  // if in an multi-tenancy environment make sure roles are never updated
@@ -27,7 +28,7 @@ export function updateAppRole(
27
28
  return user
28
29
  }
29
30
  // always use the deployed app
30
- if (appId) {
31
+ if (appId && user.roles) {
31
32
  user.roleId = user.roles[dbCore.getProdAppID(appId)]
32
33
  }
33
34
  // if a role wasn't found then either set as admin (builder) or public (everyone else)
@@ -60,7 +61,7 @@ async function checkGroupRoles(
60
61
  return user
61
62
  }
62
63
 
63
- async function processUser(
64
+ export async function processUser(
64
65
  user: ContextUser,
65
66
  opts: { appId?: string; groups?: UserGroup[] } = {}
66
67
  ) {
@@ -94,16 +95,15 @@ export async function getGlobalUser(userId: string) {
94
95
  return processUser(user, { appId })
95
96
  }
96
97
 
97
- export async function getGlobalUsers(users?: ContextUser[]) {
98
+ export async function getGlobalUsers(
99
+ userIds?: string[],
100
+ opts?: { noProcessing?: boolean }
101
+ ) {
98
102
  const appId = context.getAppId()
99
103
  const db = tenancy.getGlobalDB()
100
- const allGroups = await groups.fetch()
101
104
  let globalUsers
102
- if (users) {
103
- const globalIds = users.map(user =>
104
- getGlobalIDFromUserMetadataID(user._id!)
105
- )
106
- globalUsers = (await db.allDocs(getMultiIDParams(globalIds))).rows.map(
105
+ if (userIds) {
106
+ globalUsers = (await db.allDocs(getMultiIDParams(userIds))).rows.map(
107
107
  row => row.doc
108
108
  )
109
109
  } else {
@@ -126,15 +126,20 @@ export async function getGlobalUsers(users?: ContextUser[]) {
126
126
  return globalUsers
127
127
  }
128
128
 
129
- // pass in the groups, meaning we don't actually need to retrieve them for
130
- // each user individually
131
- return Promise.all(
132
- globalUsers.map(user => processUser(user, { groups: allGroups }))
133
- )
129
+ if (opts?.noProcessing) {
130
+ return globalUsers
131
+ } else {
132
+ // pass in the groups, meaning we don't actually need to retrieve them for
133
+ // each user individually
134
+ const allGroups = await groups.fetch()
135
+ return Promise.all(
136
+ globalUsers.map(user => processUser(user, { groups: allGroups }))
137
+ )
138
+ }
134
139
  }
135
140
 
136
141
  export async function getGlobalUsersFromMetadata(users: ContextUser[]) {
137
- const globalUsers = await getGlobalUsers(users)
142
+ const globalUsers = await getGlobalUsers(users.map(user => user._id!))
138
143
  return users.map(user => {
139
144
  const globalUser = globalUsers.find(
140
145
  globalUser => globalUser && user._id?.includes(globalUser._id)
@@ -1,11 +1,11 @@
1
1
  import {
2
- FieldTypes,
3
- FormulaTypes,
4
2
  AutoFieldDefaultNames,
5
3
  AutoFieldSubTypes,
4
+ FieldTypes,
5
+ FormulaTypes,
6
6
  } from "../../constants"
7
7
  import { processStringSync } from "@budibase/string-templates"
8
- import { FieldSchema, Table, Row } from "@budibase/types"
8
+ import { FieldSchema, FieldType, Row, Table } from "@budibase/types"
9
9
 
10
10
  /**
11
11
  * If the subtype has been lost for any reason this works out what
@@ -50,6 +50,7 @@ export function processFormulas(
50
50
  const isStatic = schema.formulaType === FormulaTypes.STATIC
51
51
  if (
52
52
  schema.type !== FieldTypes.FORMULA ||
53
+ schema.formula == null ||
53
54
  (dynamic && isStatic) ||
54
55
  (!dynamic && !isStatic)
55
56
  ) {
@@ -57,13 +58,11 @@ export function processFormulas(
57
58
  }
58
59
  // iterate through rows and process formula
59
60
  for (let i = 0; i < rowArray.length; i++) {
60
- if (schema.formula) {
61
- let row = rowArray[i]
62
- let context = contextRows ? contextRows[i] : row
63
- rowArray[i] = {
64
- ...row,
65
- [column]: processStringSync(schema.formula, context),
66
- }
61
+ let row = rowArray[i]
62
+ let context = contextRows ? contextRows[i] : row
63
+ rowArray[i] = {
64
+ ...row,
65
+ [column]: processStringSync(schema.formula, context),
67
66
  }
68
67
  }
69
68
  }