@budibase/server 2.4.40 → 2.4.42-alpha.0

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 (75) hide show
  1. package/__mocks__/node-fetch.ts +6 -1
  2. package/builder/assets/index.3cb1022d.css +6 -0
  3. package/builder/assets/{index.07ed2ead.js → index.4b6c8c0e.js} +384 -383
  4. package/builder/index.html +7 -7
  5. package/dist/api/controllers/application.js +28 -24
  6. package/dist/api/controllers/datasource.js +2 -1
  7. package/dist/api/controllers/public/metrics.js +113 -0
  8. package/dist/api/controllers/row/external.js +16 -8
  9. package/dist/api/controllers/row/index.js +11 -1
  10. package/dist/api/controllers/row/internal.js +1 -10
  11. package/dist/api/controllers/row/utils.js +7 -7
  12. package/dist/api/controllers/static/index.js +84 -24
  13. package/dist/api/controllers/static/templates/BudibaseApp.svelte +33 -10
  14. package/dist/api/controllers/table/external.js +16 -12
  15. package/dist/api/controllers/table/utils.js +15 -1
  16. package/dist/api/routes/public/index.js +8 -0
  17. package/dist/api/routes/public/metrics.js +30 -0
  18. package/dist/app.js +1 -0
  19. package/dist/constants/index.js +2 -1
  20. package/dist/integrations/googlesheets.js +125 -59
  21. package/dist/integrations/redis.js +1 -1
  22. package/dist/integrations/utils.js +17 -2
  23. package/dist/package.json +13 -12
  24. package/dist/sdk/users/utils.js +2 -1
  25. package/dist/tsconfig.build.tsbuildinfo +1 -1
  26. package/dist/utilities/global.js +17 -7
  27. package/jest.config.ts +1 -0
  28. package/package.json +14 -13
  29. package/scripts/test.sh +3 -3
  30. package/specs/openapi.json +39 -0
  31. package/specs/openapi.yaml +169 -0
  32. package/specs/resources/application.ts +11 -0
  33. package/specs/resources/index.ts +2 -0
  34. package/specs/resources/metrics.ts +81 -0
  35. package/src/api/controllers/application.ts +20 -21
  36. package/src/api/controllers/datasource.ts +2 -1
  37. package/src/api/controllers/public/metrics.ts +251 -0
  38. package/src/api/controllers/row/external.ts +26 -16
  39. package/src/api/controllers/row/index.ts +12 -2
  40. package/src/api/controllers/row/internal.ts +0 -7
  41. package/src/api/controllers/row/utils.ts +7 -7
  42. package/src/api/controllers/static/index.ts +69 -26
  43. package/src/api/controllers/static/templates/BudibaseApp.svelte +33 -10
  44. package/src/api/controllers/table/external.ts +24 -17
  45. package/src/api/controllers/table/index.ts +9 -9
  46. package/src/api/controllers/table/utils.ts +18 -2
  47. package/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap +48 -48
  48. package/src/api/routes/public/index.ts +10 -1
  49. package/src/api/routes/public/metrics.ts +28 -0
  50. package/src/api/routes/public/tests/metrics.spec.js +34 -0
  51. package/src/api/routes/tests/__snapshots__/datasource.spec.ts.snap +22 -22
  52. package/src/api/routes/tests/__snapshots__/view.spec.js.snap +5 -5
  53. package/src/api/routes/tests/appSync.spec.ts +31 -0
  54. package/src/api/routes/tests/internalSearch.spec.js +8 -7
  55. package/src/app.ts +2 -1
  56. package/src/automations/automationUtils.ts +1 -1
  57. package/src/automations/tests/automation.spec.ts +99 -0
  58. package/src/constants/index.ts +1 -0
  59. package/src/definitions/openapi.ts +15 -0
  60. package/src/integration-test/postgres.spec.ts +46 -52
  61. package/src/integrations/googlesheets.ts +143 -71
  62. package/src/integrations/redis.ts +1 -1
  63. package/src/integrations/tests/googlesheets.spec.ts +13 -13
  64. package/src/integrations/tests/redis.spec.ts +9 -5
  65. package/src/integrations/utils.ts +16 -4
  66. package/src/middleware/currentapp.ts +2 -2
  67. package/src/sdk/users/utils.ts +4 -1
  68. package/src/tests/jestEnv.ts +1 -0
  69. package/src/tests/jestSetup.ts +5 -1
  70. package/src/tests/utilities/TestConfiguration.ts +13 -0
  71. package/src/tests/utilities/structures.ts +13 -1
  72. package/src/utilities/global.ts +21 -9
  73. package/builder/assets/favicon.e7fc7733.png +0 -0
  74. package/builder/assets/index.b0e3aca6.css +0 -6
  75. package/src/automations/tests/automation.spec.js +0 -84
@@ -1,22 +1,25 @@
1
1
  import {
2
2
  DatasourceFieldType,
3
3
  DatasourcePlus,
4
+ FieldType,
4
5
  Integration,
6
+ Operation,
5
7
  PaginationJson,
6
8
  QueryJson,
7
9
  QueryType,
10
+ Row,
8
11
  SearchFilters,
9
12
  SortJson,
10
13
  Table,
11
- TableSchema,
14
+ TableRequest,
12
15
  } from "@budibase/types"
13
16
  import { OAuth2Client } from "google-auth-library"
14
- import { buildExternalTableId } from "./utils"
15
- import { DataSourceOperation, FieldTypes } from "../constants"
17
+ import { buildExternalTableId, finaliseExternalTables } from "./utils"
16
18
  import { GoogleSpreadsheet } from "google-spreadsheet"
17
19
  import fetch from "node-fetch"
18
20
  import { configs, HTTPError } from "@budibase/backend-core"
19
21
  import { dataFilters } from "@budibase/shared-core"
22
+ import { GOOGLE_SHEETS_PRIMARY_KEY } from "../constants"
20
23
 
21
24
  interface GoogleSheetsConfig {
22
25
  spreadsheetId: string
@@ -39,6 +42,17 @@ interface AuthTokenResponse {
39
42
  access_token: string
40
43
  }
41
44
 
45
+ const ALLOWED_TYPES = [
46
+ FieldType.STRING,
47
+ FieldType.FORMULA,
48
+ FieldType.NUMBER,
49
+ FieldType.LONGFORM,
50
+ FieldType.DATETIME,
51
+ FieldType.OPTIONS,
52
+ FieldType.BOOLEAN,
53
+ FieldType.BARCODEQR,
54
+ ]
55
+
42
56
  const SCHEMA: Integration = {
43
57
  plus: true,
44
58
  auth: {
@@ -199,73 +213,90 @@ class GoogleSheetsIntegration implements DatasourcePlus {
199
213
 
200
214
  this.client.useOAuth2Client(oauthClient)
201
215
  await this.client.loadInfo()
202
- } catch (err) {
216
+ } catch (err: any) {
217
+ // this happens for xlsx imports
218
+ if (err.message?.includes("operation is not supported")) {
219
+ err.message =
220
+ "This operation is not supported - XLSX sheets must be converted."
221
+ }
203
222
  console.error("Error connecting to google sheets", err)
204
223
  throw err
205
224
  }
206
225
  }
207
226
 
208
- async buildSchema(datasourceId: string) {
227
+ getTableSchema(title: string, headerValues: string[], id?: string) {
228
+ // base table
229
+ const table: Table = {
230
+ name: title,
231
+ primary: [GOOGLE_SHEETS_PRIMARY_KEY],
232
+ schema: {},
233
+ }
234
+ if (id) {
235
+ table._id = id
236
+ }
237
+ // build schema from headers
238
+ for (let header of headerValues) {
239
+ table.schema[header] = {
240
+ name: header,
241
+ type: FieldType.STRING,
242
+ }
243
+ }
244
+ return table
245
+ }
246
+
247
+ async buildSchema(datasourceId: string, entities: Record<string, Table>) {
209
248
  await this.connect()
210
249
  const sheets = this.client.sheetsByIndex
211
250
  const tables: Record<string, Table> = {}
212
251
  for (let sheet of sheets) {
213
252
  // must fetch rows to determine schema
214
253
  await sheet.getRows()
215
- // build schema
216
- const schema: TableSchema = {}
217
-
218
- // build schema from headers
219
- for (let header of sheet.headerValues) {
220
- schema[header] = {
221
- name: header,
222
- type: FieldTypes.STRING,
223
- }
224
- }
225
254
 
226
- // create tables
227
- tables[sheet.title] = {
228
- _id: buildExternalTableId(datasourceId, sheet.title),
229
- name: sheet.title,
230
- primary: ["rowNumber"],
231
- schema,
232
- }
255
+ const id = buildExternalTableId(datasourceId, sheet.title)
256
+ tables[sheet.title] = this.getTableSchema(
257
+ sheet.title,
258
+ sheet.headerValues,
259
+ id
260
+ )
233
261
  }
234
-
235
- this.tables = tables
262
+ const final = finaliseExternalTables(tables, entities)
263
+ this.tables = final.tables
264
+ this.schemaErrors = final.errors
236
265
  }
237
266
 
238
267
  async query(json: QueryJson) {
239
268
  const sheet = json.endpoint.entityId
240
-
241
- const handlers = {
242
- [DataSourceOperation.CREATE]: () =>
243
- this.create({ sheet, row: json.body }),
244
- [DataSourceOperation.READ]: () => this.read({ ...json, sheet }),
245
- [DataSourceOperation.UPDATE]: () =>
246
- this.update({
269
+ switch (json.endpoint.operation) {
270
+ case Operation.CREATE:
271
+ return this.create({ sheet, row: json.body as Row })
272
+ case Operation.BULK_CREATE:
273
+ return this.createBulk({ sheet, rows: json.body as Row[] })
274
+ case Operation.READ:
275
+ return this.read({ ...json, sheet })
276
+ case Operation.UPDATE:
277
+ return this.update({
247
278
  // exclude the header row and zero index
248
279
  rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
249
280
  sheet,
250
281
  row: json.body,
251
- }),
252
- [DataSourceOperation.DELETE]: () =>
253
- this.delete({
282
+ })
283
+ case Operation.DELETE:
284
+ return this.delete({
254
285
  // exclude the header row and zero index
255
286
  rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
256
287
  sheet,
257
- }),
258
- [DataSourceOperation.CREATE_TABLE]: () =>
259
- this.createTable(json?.table?.name),
260
- [DataSourceOperation.UPDATE_TABLE]: () => this.updateTable(json.table),
261
- [DataSourceOperation.DELETE_TABLE]: () =>
262
- this.deleteTable(json?.table?.name),
288
+ })
289
+ case Operation.CREATE_TABLE:
290
+ return this.createTable(json?.table?.name)
291
+ case Operation.UPDATE_TABLE:
292
+ return this.updateTable(json.table!)
293
+ case Operation.DELETE_TABLE:
294
+ return this.deleteTable(json?.table?.name)
295
+ default:
296
+ throw new Error(
297
+ `GSheets integration does not support "${json.endpoint.operation}".`
298
+ )
263
299
  }
264
-
265
- // @ts-ignore
266
- const internalQueryMethod = handlers[json.endpoint.operation]
267
-
268
- return await internalQueryMethod()
269
300
  }
270
301
 
271
302
  buildRowObject(headers: string[], values: string[], rowNumber: number) {
@@ -278,47 +309,70 @@ class GoogleSheetsIntegration implements DatasourcePlus {
278
309
  }
279
310
 
280
311
  async createTable(name?: string) {
312
+ if (!name) {
313
+ throw new Error("Must provide name for new sheet.")
314
+ }
281
315
  try {
282
316
  await this.connect()
283
- return await this.client.addSheet({ title: name, headerValues: ["test"] })
317
+ return await this.client.addSheet({ title: name, headerValues: [name] })
284
318
  } catch (err) {
285
319
  console.error("Error creating new table in google sheets", err)
286
320
  throw err
287
321
  }
288
322
  }
289
323
 
290
- async updateTable(table?: any) {
291
- try {
292
- await this.connect()
293
- const sheet = this.client.sheetsByTitle[table.name]
294
- await sheet.loadHeaderRow()
295
-
296
- if (table._rename) {
297
- const headers = []
298
- for (let header of sheet.headerValues) {
299
- if (header === table._rename.old) {
300
- headers.push(table._rename.updated)
301
- } else {
302
- headers.push(header)
303
- }
324
+ async updateTable(table: TableRequest) {
325
+ await this.connect()
326
+ const sheet = this.client.sheetsByTitle[table.name]
327
+ await sheet.loadHeaderRow()
328
+
329
+ if (table._rename) {
330
+ const headers = []
331
+ for (let header of sheet.headerValues) {
332
+ if (header === table._rename.old) {
333
+ headers.push(table._rename.updated)
334
+ } else {
335
+ headers.push(header)
304
336
  }
337
+ }
338
+ try {
305
339
  await sheet.setHeaderRow(headers)
306
- } else {
307
- const updatedHeaderValues = [...sheet.headerValues]
308
-
309
- const newField = Object.keys(table.schema).find(
310
- key => !sheet.headerValues.includes(key)
311
- )
340
+ } catch (err) {
341
+ console.error("Error updating column name in google sheets", err)
342
+ throw err
343
+ }
344
+ } else {
345
+ const updatedHeaderValues = [...sheet.headerValues]
346
+
347
+ // add new column - doesn't currently exist
348
+ for (let [key, column] of Object.entries(table.schema)) {
349
+ if (!ALLOWED_TYPES.includes(column.type)) {
350
+ throw new Error(
351
+ `Column type: ${column.type} not allowed for GSheets integration.`
352
+ )
353
+ }
354
+ if (
355
+ !sheet.headerValues.includes(key) &&
356
+ column.type !== FieldType.FORMULA
357
+ ) {
358
+ updatedHeaderValues.push(key)
359
+ }
360
+ }
312
361
 
313
- if (newField) {
314
- updatedHeaderValues.push(newField)
362
+ // clear out deleted columns
363
+ for (let key of sheet.headerValues) {
364
+ if (!Object.keys(table.schema).includes(key)) {
365
+ const idx = updatedHeaderValues.indexOf(key)
366
+ updatedHeaderValues.splice(idx, 1)
315
367
  }
368
+ }
316
369
 
370
+ try {
317
371
  await sheet.setHeaderRow(updatedHeaderValues)
372
+ } catch (err) {
373
+ console.error("Error updating table in google sheets", err)
374
+ throw err
318
375
  }
319
- } catch (err) {
320
- console.error("Error updating table in google sheets", err)
321
- throw err
322
376
  }
323
377
  }
324
378
 
@@ -349,6 +403,24 @@ class GoogleSheetsIntegration implements DatasourcePlus {
349
403
  }
350
404
  }
351
405
 
406
+ async createBulk(query: { sheet: string; rows: any[] }) {
407
+ try {
408
+ await this.connect()
409
+ const sheet = this.client.sheetsByTitle[query.sheet]
410
+ let rowsToInsert = []
411
+ for (let row of query.rows) {
412
+ rowsToInsert.push(typeof row === "string" ? JSON.parse(row) : row)
413
+ }
414
+ const rows = await sheet.addRows(rowsToInsert)
415
+ return rows.map(row =>
416
+ this.buildRowObject(sheet.headerValues, row._rawData, row._rowNumber)
417
+ )
418
+ } catch (err) {
419
+ console.error("Error bulk writing to google sheets", err)
420
+ throw err
421
+ }
422
+ }
423
+
352
424
  async read(query: {
353
425
  sheet: string
354
426
  filters?: SearchFilters
@@ -92,7 +92,7 @@ class RedisIntegration {
92
92
  }
93
93
 
94
94
  async disconnect() {
95
- return this.client.disconnect()
95
+ return this.client.quit()
96
96
  }
97
97
 
98
98
  async redisContext(query: Function) {
@@ -39,6 +39,10 @@ describe("Google Sheets Integration", () => {
39
39
  config.setGoogleAuth("test")
40
40
  })
41
41
 
42
+ afterAll(async () => {
43
+ await config.end()
44
+ })
45
+
42
46
  beforeEach(async () => {
43
47
  integration = new GoogleSheetsIntegration.integration({
44
48
  spreadsheetId: "randomId",
@@ -99,8 +103,8 @@ describe("Google Sheets Integration", () => {
99
103
  })
100
104
  })
101
105
 
102
- test("removing an existing field will not remove the data from the spreadsheet", async () => {
103
- await config.doInContext(structures.uuid(), async () => {
106
+ test("removing an existing field will remove the header from the google sheet", async () => {
107
+ const sheet = await config.doInContext(structures.uuid(), async () => {
104
108
  const tableColumns = ["name"]
105
109
  const table = createBasicTable(structures.uuid(), tableColumns)
106
110
 
@@ -109,18 +113,14 @@ describe("Google Sheets Integration", () => {
109
113
  })
110
114
  sheetsByTitle[table.name] = sheet
111
115
  await integration.updateTable(table)
112
-
113
- expect(sheet.loadHeaderRow).toBeCalledTimes(1)
114
- expect(sheet.setHeaderRow).toBeCalledTimes(1)
115
- expect(sheet.setHeaderRow).toBeCalledWith([
116
- "name",
117
- "description",
118
- "location",
119
- ])
120
-
121
- // No undefineds are sent
122
- expect((sheet.setHeaderRow as any).mock.calls[0][0]).toHaveLength(3)
116
+ return sheet
123
117
  })
118
+ expect(sheet.loadHeaderRow).toBeCalledTimes(1)
119
+ expect(sheet.setHeaderRow).toBeCalledTimes(1)
120
+ expect(sheet.setHeaderRow).toBeCalledWith(["name"])
121
+
122
+ // No undefined are sent
123
+ expect((sheet.setHeaderRow as any).mock.calls[0][0]).toHaveLength(1)
124
124
  })
125
125
  })
126
126
  })
@@ -3,17 +3,17 @@ import { default as RedisIntegration } from "../redis"
3
3
 
4
4
  class TestConfiguration {
5
5
  integration: any
6
- redis: any
7
6
 
8
7
  constructor(config: any = {}) {
9
8
  this.integration = new RedisIntegration.integration(config)
10
- this.redis = new Redis({
9
+ // have to kill the basic integration before replacing it
10
+ this.integration.client.quit()
11
+ this.integration.client = new Redis({
11
12
  data: {
12
13
  test: "test",
13
14
  result: "1",
14
15
  },
15
16
  })
16
- this.integration.client = this.redis
17
17
  }
18
18
  }
19
19
 
@@ -24,13 +24,17 @@ describe("Redis Integration", () => {
24
24
  config = new TestConfiguration()
25
25
  })
26
26
 
27
+ afterAll(() => {
28
+ config.integration.disconnect()
29
+ })
30
+
27
31
  it("calls the create method with the correct params", async () => {
28
32
  const body = {
29
33
  key: "key",
30
34
  value: "value",
31
35
  }
32
36
  await config.integration.create(body)
33
- expect(await config.redis.get("key")).toEqual("value")
37
+ expect(await config.integration.client.get("key")).toEqual("value")
34
38
  })
35
39
 
36
40
  it("calls the read method with the correct params", async () => {
@@ -46,7 +50,7 @@ describe("Redis Integration", () => {
46
50
  key: "test",
47
51
  }
48
52
  await config.integration.delete(body)
49
- expect(await config.redis.get(body.key)).toEqual(null)
53
+ expect(await config.integration.client.get(body.key)).toEqual(null)
50
54
  })
51
55
 
52
56
  it("calls the pipeline method with the correct params", async () => {
@@ -4,6 +4,7 @@ import { FieldTypes, BuildSchemaErrors, InvalidColumns } from "../constants"
4
4
 
5
5
  const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}`
6
6
  const ROW_ID_REGEX = /^\[.*]$/g
7
+ const ENCODED_SPACE = encodeURIComponent(" ")
7
8
 
8
9
  const SQL_NUMBER_TYPE_MAP = {
9
10
  integer: FieldTypes.NUMBER,
@@ -79,6 +80,10 @@ export function isExternalTable(tableId: string) {
79
80
  }
80
81
 
81
82
  export function buildExternalTableId(datasourceId: string, tableName: string) {
83
+ // encode spaces
84
+ if (tableName.includes(" ")) {
85
+ tableName = encodeURIComponent(tableName)
86
+ }
82
87
  return `${datasourceId}${DOUBLE_SEPARATOR}${tableName}`
83
88
  }
84
89
 
@@ -90,6 +95,10 @@ export function breakExternalTableId(tableId: string | undefined) {
90
95
  let datasourceId = parts.shift()
91
96
  // if they need joined
92
97
  let tableName = parts.join(DOUBLE_SEPARATOR)
98
+ // if contains encoded spaces, decode it
99
+ if (tableName.includes(ENCODED_SPACE)) {
100
+ tableName = decodeURIComponent(tableName)
101
+ }
93
102
  return { datasourceId, tableName }
94
103
  }
95
104
 
@@ -200,9 +209,9 @@ export function isIsoDateString(str: string) {
200
209
  * @param column The column to check, to see if it is a valid relationship.
201
210
  * @param tableIds The IDs of the tables which currently exist.
202
211
  */
203
- function shouldCopyRelationship(
212
+ export function shouldCopyRelationship(
204
213
  column: { type: string; tableId?: string },
205
- tableIds: [string]
214
+ tableIds: string[]
206
215
  ) {
207
216
  return (
208
217
  column.type === FieldTypes.LINK &&
@@ -219,7 +228,7 @@ function shouldCopyRelationship(
219
228
  * @param column The column to check for options or boolean type.
220
229
  * @param fetchedColumn The fetched column to check for the type in the external database.
221
230
  */
222
- function shouldCopySpecialColumn(
231
+ export function shouldCopySpecialColumn(
223
232
  column: { type: string },
224
233
  fetchedColumn: { type: string } | undefined
225
234
  ) {
@@ -257,9 +266,12 @@ function copyExistingPropsOver(
257
266
  tableIds: [string]
258
267
  ) {
259
268
  if (entities && entities[tableName]) {
260
- if (entities[tableName].primaryDisplay) {
269
+ if (entities[tableName]?.primaryDisplay) {
261
270
  table.primaryDisplay = entities[tableName].primaryDisplay
262
271
  }
272
+ if (entities[tableName]?.created) {
273
+ table.created = entities[tableName]?.created
274
+ }
263
275
  const existingTableSchema = entities[tableName].schema
264
276
  for (let key in existingTableSchema) {
265
277
  if (!existingTableSchema.hasOwnProperty(key)) {
@@ -10,9 +10,9 @@ import { generateUserMetadataID, isDevAppID } from "../db/utils"
10
10
  import { getCachedSelf } from "../utilities/global"
11
11
  import env from "../environment"
12
12
  import { isWebhookEndpoint } from "./utils"
13
- import { BBContext } from "@budibase/types"
13
+ import { UserCtx } from "@budibase/types"
14
14
 
15
- export default async (ctx: BBContext, next: any) => {
15
+ export default async (ctx: UserCtx, next: any) => {
16
16
  // try to get the appID from the request
17
17
  let requestAppId = await utils.getAppIdFromCtx(ctx)
18
18
  // get app cookie if it exists
@@ -9,7 +9,10 @@ import { isEqual } from "lodash"
9
9
 
10
10
  export function combineMetadataAndUser(user: any, metadata: any) {
11
11
  // skip users with no access
12
- if (user.roleId === rolesCore.BUILTIN_ROLE_IDS.PUBLIC) {
12
+ if (
13
+ user.roleId == null ||
14
+ user.roleId === rolesCore.BUILTIN_ROLE_IDS.PUBLIC
15
+ ) {
13
16
  return null
14
17
  }
15
18
  delete user._rev
@@ -9,3 +9,4 @@ process.env.LOG_LEVEL = process.env.LOG_LEVEL || "error"
9
9
  process.env.ENABLE_4XX_HTTP_LOGGING = "0"
10
10
  process.env.MOCK_REDIS = "1"
11
11
  process.env.PLATFORM_URL = "http://localhost:10000"
12
+ process.env.REDIS_PASSWORD = "budibase"
@@ -1,6 +1,6 @@
1
1
  import "./logging"
2
2
  import env from "../environment"
3
- import { env as coreEnv } from "@budibase/backend-core"
3
+ import { env as coreEnv, timers } from "@budibase/backend-core"
4
4
  import { testContainerUtils } from "@budibase/backend-core/tests"
5
5
 
6
6
  if (!process.env.DEBUG) {
@@ -17,3 +17,7 @@ if (!process.env.CI) {
17
17
  }
18
18
 
19
19
  testContainerUtils.setupEnv(env, coreEnv)
20
+
21
+ afterAll(() => {
22
+ timers.cleanup()
23
+ })
@@ -46,6 +46,7 @@ import {
46
46
  Row,
47
47
  SourceName,
48
48
  Table,
49
+ SearchFilters,
49
50
  } from "@budibase/types"
50
51
 
51
52
  type DefaultUserValues = {
@@ -164,6 +165,8 @@ class TestConfiguration {
164
165
  }
165
166
  if (this.server) {
166
167
  this.server.close()
168
+ } else {
169
+ require("../../app").default.close()
167
170
  }
168
171
  if (this.allApps) {
169
172
  cleanup(this.allApps.map(app => app.appId))
@@ -568,6 +571,16 @@ class TestConfiguration {
568
571
  return this._req(null, { tableId }, controllers.row.fetch)
569
572
  }
570
573
 
574
+ async searchRows(tableId: string, searchParams: SearchFilters = {}) {
575
+ if (!tableId && this.table) {
576
+ tableId = this.table._id
577
+ }
578
+ const body = {
579
+ query: searchParams,
580
+ }
581
+ return this._req(body, { tableId }, controllers.row.search)
582
+ }
583
+
571
584
  // ROLE
572
585
 
573
586
  async createRole(config?: any) {
@@ -106,7 +106,7 @@ export function newAutomation({ steps, trigger }: any = {}) {
106
106
  return automation
107
107
  }
108
108
 
109
- export function basicAutomation() {
109
+ export function basicAutomation(appId?: string) {
110
110
  return {
111
111
  name: "My Automation",
112
112
  screenId: "kasdkfldsafkl",
@@ -114,11 +114,23 @@ export function basicAutomation() {
114
114
  uiTree: {},
115
115
  definition: {
116
116
  trigger: {
117
+ stepId: AutomationTriggerStepId.APP,
118
+ name: "test",
119
+ tagline: "test",
120
+ icon: "test",
121
+ description: "test",
122
+ type: "trigger",
123
+ id: "test",
117
124
  inputs: {},
125
+ schema: {
126
+ inputs: {},
127
+ outputs: {},
128
+ },
118
129
  },
119
130
  steps: [],
120
131
  },
121
132
  type: "automation",
133
+ appId,
122
134
  }
123
135
  }
124
136
 
@@ -8,7 +8,7 @@ import {
8
8
  } from "@budibase/backend-core"
9
9
  import env from "../environment"
10
10
  import { groups } from "@budibase/pro"
11
- import { BBContext, ContextUser, User } from "@budibase/types"
11
+ import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types"
12
12
 
13
13
  export function updateAppRole(
14
14
  user: ContextUser,
@@ -43,33 +43,40 @@ export function updateAppRole(
43
43
 
44
44
  async function checkGroupRoles(
45
45
  user: ContextUser,
46
- { appId }: { appId?: string } = {}
46
+ opts: { appId?: string; groups?: UserGroup[] } = {}
47
47
  ) {
48
48
  if (user.roleId && user.roleId !== roles.BUILTIN_ROLE_IDS.PUBLIC) {
49
49
  return user
50
50
  }
51
- if (appId) {
52
- user.roleId = await groups.getGroupRoleId(user as User, appId)
51
+ if (opts.appId) {
52
+ user.roleId = await groups.getGroupRoleId(user as User, opts.appId, {
53
+ groups: opts.groups,
54
+ })
55
+ }
56
+ // final fallback, simply couldn't find a role - user must be public
57
+ if (!user.roleId) {
58
+ user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
53
59
  }
54
60
  return user
55
61
  }
56
62
 
57
63
  async function processUser(
58
64
  user: ContextUser,
59
- { appId }: { appId?: string } = {}
65
+ opts: { appId?: string; groups?: UserGroup[] } = {}
60
66
  ) {
61
67
  if (user) {
62
68
  delete user.password
63
69
  }
64
- user = await updateAppRole(user, { appId })
70
+ const appId = opts.appId || context.getAppId()
71
+ user = updateAppRole(user, { appId })
65
72
  if (!user.roleId && user?.userGroups?.length) {
66
- user = await checkGroupRoles(user, { appId })
73
+ user = await checkGroupRoles(user, { appId, groups: opts?.groups })
67
74
  }
68
75
 
69
76
  return user
70
77
  }
71
78
 
72
- export async function getCachedSelf(ctx: BBContext, appId: string) {
79
+ export async function getCachedSelf(ctx: UserCtx, appId: string) {
73
80
  // this has to be tenant aware, can't depend on the context to find it out
74
81
  // running some middlewares before the tenancy causes context to break
75
82
  const user = await cache.user.getUser(ctx.user?._id!)
@@ -90,6 +97,7 @@ export async function getGlobalUser(userId: string) {
90
97
  export async function getGlobalUsers(users?: ContextUser[]) {
91
98
  const appId = context.getAppId()
92
99
  const db = tenancy.getGlobalDB()
100
+ const allGroups = await groups.fetch()
93
101
  let globalUsers
94
102
  if (users) {
95
103
  const globalIds = users.map(user =>
@@ -118,7 +126,11 @@ export async function getGlobalUsers(users?: ContextUser[]) {
118
126
  return globalUsers
119
127
  }
120
128
 
121
- return globalUsers.map(user => updateAppRole(user))
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
+ )
122
134
  }
123
135
 
124
136
  export async function getGlobalUsersFromMetadata(users: ContextUser[]) {
Binary file