@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.
- package/__mocks__/node-fetch.ts +6 -1
- package/builder/assets/index.3cb1022d.css +6 -0
- package/builder/assets/{index.07ed2ead.js → index.4b6c8c0e.js} +384 -383
- package/builder/index.html +7 -7
- package/dist/api/controllers/application.js +28 -24
- package/dist/api/controllers/datasource.js +2 -1
- package/dist/api/controllers/public/metrics.js +113 -0
- package/dist/api/controllers/row/external.js +16 -8
- package/dist/api/controllers/row/index.js +11 -1
- package/dist/api/controllers/row/internal.js +1 -10
- package/dist/api/controllers/row/utils.js +7 -7
- package/dist/api/controllers/static/index.js +84 -24
- package/dist/api/controllers/static/templates/BudibaseApp.svelte +33 -10
- package/dist/api/controllers/table/external.js +16 -12
- package/dist/api/controllers/table/utils.js +15 -1
- package/dist/api/routes/public/index.js +8 -0
- package/dist/api/routes/public/metrics.js +30 -0
- package/dist/app.js +1 -0
- package/dist/constants/index.js +2 -1
- package/dist/integrations/googlesheets.js +125 -59
- package/dist/integrations/redis.js +1 -1
- package/dist/integrations/utils.js +17 -2
- package/dist/package.json +13 -12
- package/dist/sdk/users/utils.js +2 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utilities/global.js +17 -7
- package/jest.config.ts +1 -0
- package/package.json +14 -13
- package/scripts/test.sh +3 -3
- package/specs/openapi.json +39 -0
- package/specs/openapi.yaml +169 -0
- package/specs/resources/application.ts +11 -0
- package/specs/resources/index.ts +2 -0
- package/specs/resources/metrics.ts +81 -0
- package/src/api/controllers/application.ts +20 -21
- package/src/api/controllers/datasource.ts +2 -1
- package/src/api/controllers/public/metrics.ts +251 -0
- package/src/api/controllers/row/external.ts +26 -16
- package/src/api/controllers/row/index.ts +12 -2
- package/src/api/controllers/row/internal.ts +0 -7
- package/src/api/controllers/row/utils.ts +7 -7
- package/src/api/controllers/static/index.ts +69 -26
- package/src/api/controllers/static/templates/BudibaseApp.svelte +33 -10
- package/src/api/controllers/table/external.ts +24 -17
- package/src/api/controllers/table/index.ts +9 -9
- package/src/api/controllers/table/utils.ts +18 -2
- package/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap +48 -48
- package/src/api/routes/public/index.ts +10 -1
- package/src/api/routes/public/metrics.ts +28 -0
- package/src/api/routes/public/tests/metrics.spec.js +34 -0
- package/src/api/routes/tests/__snapshots__/datasource.spec.ts.snap +22 -22
- package/src/api/routes/tests/__snapshots__/view.spec.js.snap +5 -5
- package/src/api/routes/tests/appSync.spec.ts +31 -0
- package/src/api/routes/tests/internalSearch.spec.js +8 -7
- package/src/app.ts +2 -1
- package/src/automations/automationUtils.ts +1 -1
- package/src/automations/tests/automation.spec.ts +99 -0
- package/src/constants/index.ts +1 -0
- package/src/definitions/openapi.ts +15 -0
- package/src/integration-test/postgres.spec.ts +46 -52
- package/src/integrations/googlesheets.ts +143 -71
- package/src/integrations/redis.ts +1 -1
- package/src/integrations/tests/googlesheets.spec.ts +13 -13
- package/src/integrations/tests/redis.spec.ts +9 -5
- package/src/integrations/utils.ts +16 -4
- package/src/middleware/currentapp.ts +2 -2
- package/src/sdk/users/utils.ts +4 -1
- package/src/tests/jestEnv.ts +1 -0
- package/src/tests/jestSetup.ts +5 -1
- package/src/tests/utilities/TestConfiguration.ts +13 -0
- package/src/tests/utilities/structures.ts +13 -1
- package/src/utilities/global.ts +21 -9
- package/builder/assets/favicon.e7fc7733.png +0 -0
- package/builder/assets/index.b0e3aca6.css +0 -6
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
227
|
-
tables[sheet.title] =
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
259
|
-
this.createTable(json?.table?.name)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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: [
|
|
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
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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
|
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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: [
|
|
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]
|
|
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 {
|
|
13
|
+
import { UserCtx } from "@budibase/types"
|
|
14
14
|
|
|
15
|
-
export default async (ctx:
|
|
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
|
package/src/sdk/users/utils.ts
CHANGED
|
@@ -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 (
|
|
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
|
package/src/tests/jestEnv.ts
CHANGED
package/src/tests/jestSetup.ts
CHANGED
|
@@ -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
|
|
package/src/utilities/global.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
65
|
+
opts: { appId?: string; groups?: UserGroup[] } = {}
|
|
60
66
|
) {
|
|
61
67
|
if (user) {
|
|
62
68
|
delete user.password
|
|
63
69
|
}
|
|
64
|
-
|
|
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:
|
|
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
|
-
|
|
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
|