@budibase/server 2.4.41 → 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 (58) hide show
  1. package/__mocks__/node-fetch.ts +6 -1
  2. package/builder/assets/index.3cb1022d.css +6 -0
  3. package/builder/assets/{index.cf8e1455.js → index.4b6c8c0e.js} +384 -384
  4. package/builder/index.html +7 -7
  5. package/dist/api/controllers/application.js +28 -24
  6. package/dist/api/controllers/public/metrics.js +113 -0
  7. package/dist/api/controllers/row/external.js +15 -0
  8. package/dist/api/controllers/row/utils.js +4 -3
  9. package/dist/api/controllers/static/index.js +84 -24
  10. package/dist/api/controllers/static/templates/BudibaseApp.svelte +33 -10
  11. package/dist/api/routes/public/index.js +8 -0
  12. package/dist/api/routes/public/metrics.js +30 -0
  13. package/dist/app.js +1 -0
  14. package/dist/integrations/redis.js +1 -1
  15. package/dist/package.json +12 -11
  16. package/dist/sdk/users/utils.js +2 -1
  17. package/dist/tsconfig.build.tsbuildinfo +1 -1
  18. package/dist/utilities/global.js +17 -7
  19. package/jest.config.ts +1 -0
  20. package/package.json +13 -12
  21. package/scripts/test.sh +3 -3
  22. package/specs/openapi.json +39 -0
  23. package/specs/openapi.yaml +169 -0
  24. package/specs/resources/application.ts +11 -0
  25. package/specs/resources/index.ts +2 -0
  26. package/specs/resources/metrics.ts +81 -0
  27. package/src/api/controllers/application.ts +20 -21
  28. package/src/api/controllers/public/metrics.ts +251 -0
  29. package/src/api/controllers/row/external.ts +14 -0
  30. package/src/api/controllers/row/utils.ts +4 -3
  31. package/src/api/controllers/static/index.ts +69 -26
  32. package/src/api/controllers/static/templates/BudibaseApp.svelte +33 -10
  33. package/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap +48 -48
  34. package/src/api/routes/public/index.ts +10 -1
  35. package/src/api/routes/public/metrics.ts +28 -0
  36. package/src/api/routes/public/tests/metrics.spec.js +34 -0
  37. package/src/api/routes/tests/__snapshots__/datasource.spec.ts.snap +22 -22
  38. package/src/api/routes/tests/__snapshots__/view.spec.js.snap +5 -5
  39. package/src/api/routes/tests/appSync.spec.ts +31 -0
  40. package/src/api/routes/tests/internalSearch.spec.js +8 -7
  41. package/src/app.ts +2 -1
  42. package/src/automations/automationUtils.ts +1 -1
  43. package/src/automations/tests/automation.spec.ts +99 -0
  44. package/src/definitions/openapi.ts +15 -0
  45. package/src/integration-test/postgres.spec.ts +46 -52
  46. package/src/integrations/redis.ts +1 -1
  47. package/src/integrations/tests/googlesheets.spec.ts +13 -13
  48. package/src/integrations/tests/redis.spec.ts +9 -5
  49. package/src/middleware/currentapp.ts +2 -2
  50. package/src/sdk/users/utils.ts +4 -1
  51. package/src/tests/jestEnv.ts +1 -0
  52. package/src/tests/jestSetup.ts +5 -1
  53. package/src/tests/utilities/TestConfiguration.ts +13 -0
  54. package/src/tests/utilities/structures.ts +13 -1
  55. package/src/utilities/global.ts +21 -9
  56. package/builder/assets/favicon.e7fc7733.png +0 -0
  57. package/builder/assets/index.b0e3aca6.css +0 -6
  58. package/src/automations/tests/automation.spec.js +0 -84
@@ -1,3 +1,6 @@
1
+ import fetch from "node-fetch"
2
+ // @ts-ignore
3
+ fetch.mockSearch()
1
4
  import {
2
5
  generateMakeRequest,
3
6
  MakeRequestResponse,
@@ -16,6 +19,7 @@ import _ from "lodash"
16
19
  import { generator } from "@budibase/backend-core/tests"
17
20
  import { utils } from "@budibase/backend-core"
18
21
  import { GenericContainer } from "testcontainers"
22
+ import { generateRowIdField } from "../integrations/utils"
19
23
 
20
24
  const config = setup.getConfig()!
21
25
 
@@ -80,16 +84,10 @@ describe("row api - postgres", () => {
80
84
  name: "id",
81
85
  type: FieldType.AUTO,
82
86
  autocolumn: true,
83
- constraints: {
84
- presence: true,
85
- },
86
87
  },
87
88
  title: {
88
89
  name: "title",
89
90
  type: FieldType.STRING,
90
- constraints: {
91
- presence: true,
92
- },
93
91
  },
94
92
  },
95
93
  sourceId: postgresDatasource._id,
@@ -121,16 +119,10 @@ describe("row api - postgres", () => {
121
119
  name: "id",
122
120
  type: FieldType.AUTO,
123
121
  autocolumn: true,
124
- constraints: {
125
- presence: true,
126
- },
127
122
  },
128
123
  name: {
129
124
  name: "name",
130
125
  type: FieldType.STRING,
131
- constraints: {
132
- presence: true,
133
- },
134
126
  },
135
127
  description: {
136
128
  name: "description",
@@ -144,7 +136,6 @@ describe("row api - postgres", () => {
144
136
  type: FieldType.LINK,
145
137
  constraints: {
146
138
  type: "array",
147
- presence: false,
148
139
  },
149
140
  fieldName: oneToManyRelationshipInfo.fieldName,
150
141
  name: "oneToManyRelation",
@@ -156,7 +147,6 @@ describe("row api - postgres", () => {
156
147
  type: FieldType.LINK,
157
148
  constraints: {
158
149
  type: "array",
159
- presence: false,
160
150
  },
161
151
  fieldName: manyToOneRelationshipInfo.fieldName,
162
152
  name: "manyToOneRelation",
@@ -168,7 +158,6 @@ describe("row api - postgres", () => {
168
158
  type: FieldType.LINK,
169
159
  constraints: {
170
160
  type: "array",
171
- presence: false,
172
161
  },
173
162
  fieldName: manyToManyRelationshipInfo.fieldName,
174
163
  name: "manyToManyRelation",
@@ -309,9 +298,6 @@ describe("row api - postgres", () => {
309
298
  id: {
310
299
  name: "id",
311
300
  type: FieldType.AUTO,
312
- constraints: {
313
- presence: true,
314
- },
315
301
  },
316
302
  },
317
303
  sourceId: postgresDatasource._id,
@@ -921,47 +907,55 @@ describe("row api - postgres", () => {
921
907
  foreignRows,
922
908
  x => x.relationshipType
923
909
  )
924
- expect(res.body).toEqual({
925
- ...rowData,
926
- [`fk_${oneToManyRelationshipInfo.table.name}_${oneToManyRelationshipInfo.fieldName}`]:
927
- foreignRowsByType[RelationshipTypes.ONE_TO_MANY][0].row.id,
928
- [oneToManyRelationshipInfo.fieldName]: [
929
- {
930
- ...foreignRowsByType[RelationshipTypes.ONE_TO_MANY][0].row,
931
- _id: expect.any(String),
932
- _rev: expect.any(String),
933
- },
934
- ],
935
- [manyToOneRelationshipInfo.fieldName]: [
936
- {
937
- ...foreignRowsByType[RelationshipTypes.MANY_TO_ONE][0].row,
938
- [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
939
- row.id,
940
- },
941
- {
942
- ...foreignRowsByType[RelationshipTypes.MANY_TO_ONE][1].row,
943
- [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
944
- row.id,
945
- },
910
+ const m2mFieldName = manyToManyRelationshipInfo.fieldName,
911
+ o2mFieldName = oneToManyRelationshipInfo.fieldName,
912
+ m2oFieldName = manyToOneRelationshipInfo.fieldName
913
+ const m2mRow1 = res.body[m2mFieldName].find(
914
+ (row: Row) => row.id === 1
915
+ )
916
+ const m2mRow2 = res.body[m2mFieldName].find(
917
+ (row: Row) => row.id === 2
918
+ )
919
+ expect(m2mRow1).toEqual({
920
+ ...foreignRowsByType[RelationshipTypes.MANY_TO_MANY][0].row,
921
+ [m2mFieldName]: [
946
922
  {
947
- ...foreignRowsByType[RelationshipTypes.MANY_TO_ONE][2].row,
948
- [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
949
- row.id,
923
+ _id: row._id,
950
924
  },
951
925
  ],
952
- [manyToManyRelationshipInfo.fieldName]: [
953
- {
954
- ...foreignRowsByType[RelationshipTypes.MANY_TO_MANY][0].row,
955
- },
926
+ })
927
+ expect(m2mRow2).toEqual({
928
+ ...foreignRowsByType[RelationshipTypes.MANY_TO_MANY][1].row,
929
+ [m2mFieldName]: [
956
930
  {
957
- ...foreignRowsByType[RelationshipTypes.MANY_TO_MANY][1].row,
931
+ _id: row._id,
958
932
  },
959
933
  ],
960
- id: row.id,
961
- tableId: row.tableId,
962
- _id: expect.any(String),
963
- _rev: expect.any(String),
964
934
  })
935
+ expect(res.body[m2oFieldName]).toEqual([
936
+ {
937
+ ...foreignRowsByType[RelationshipTypes.MANY_TO_ONE][0].row,
938
+ [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
939
+ row.id,
940
+ },
941
+ {
942
+ ...foreignRowsByType[RelationshipTypes.MANY_TO_ONE][1].row,
943
+ [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
944
+ row.id,
945
+ },
946
+ {
947
+ ...foreignRowsByType[RelationshipTypes.MANY_TO_ONE][2].row,
948
+ [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
949
+ row.id,
950
+ },
951
+ ])
952
+ expect(res.body[o2mFieldName]).toEqual([
953
+ {
954
+ ...foreignRowsByType[RelationshipTypes.ONE_TO_MANY][0].row,
955
+ _id: expect.any(String),
956
+ _rev: expect.any(String),
957
+ },
958
+ ])
965
959
  })
966
960
  })
967
961
  })
@@ -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 () => {
@@ -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