@budibase/server 2.4.40 → 2.4.41
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/builder/assets/{index.07ed2ead.js → index.cf8e1455.js} +277 -276
- package/builder/index.html +1 -1
- package/dist/api/controllers/datasource.js +2 -1
- package/dist/api/controllers/row/external.js +1 -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 +3 -4
- package/dist/api/controllers/table/external.js +16 -12
- package/dist/api/controllers/table/utils.js +15 -1
- package/dist/constants/index.js +2 -1
- package/dist/integrations/googlesheets.js +125 -59
- package/dist/integrations/utils.js +17 -2
- package/dist/package.json +7 -7
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/src/api/controllers/datasource.ts +2 -1
- package/src/api/controllers/row/external.ts +12 -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 +3 -4
- 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/constants/index.ts +1 -0
- package/src/integrations/googlesheets.ts +143 -71
- package/src/integrations/utils.ts +16 -4
|
@@ -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
|
|
@@ -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)) {
|