@budibase/server 2.4.38 → 2.4.40
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.7c263eae.js → index.07ed2ead.js} +249 -250
- package/builder/index.html +1 -1
- package/dist/api/controllers/datasource.js +1 -2
- package/dist/api/controllers/row/external.js +8 -16
- package/dist/api/controllers/row/index.js +1 -5
- package/dist/api/controllers/row/internal.js +10 -1
- package/dist/api/controllers/row/utils.js +4 -3
- package/dist/api/controllers/table/external.js +12 -16
- package/dist/api/controllers/table/utils.js +1 -15
- package/dist/constants/index.js +1 -2
- package/dist/integrations/googlesheets.js +59 -125
- package/dist/integrations/utils.js +2 -17
- package/dist/package.json +7 -7
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/src/api/controllers/datasource.ts +1 -2
- package/src/api/controllers/row/external.ts +16 -26
- package/src/api/controllers/row/index.ts +2 -7
- package/src/api/controllers/row/internal.ts +7 -0
- package/src/api/controllers/row/utils.ts +4 -3
- package/src/api/controllers/table/external.ts +17 -24
- package/src/api/controllers/table/index.ts +9 -9
- package/src/api/controllers/table/utils.ts +2 -18
- package/src/constants/index.ts +0 -1
- package/src/integrations/googlesheets.ts +71 -143
- package/src/integrations/utils.ts +4 -16
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DatasourceFieldType,
|
|
3
3
|
DatasourcePlus,
|
|
4
|
-
FieldType,
|
|
5
4
|
Integration,
|
|
6
|
-
Operation,
|
|
7
5
|
PaginationJson,
|
|
8
6
|
QueryJson,
|
|
9
7
|
QueryType,
|
|
10
|
-
Row,
|
|
11
8
|
SearchFilters,
|
|
12
9
|
SortJson,
|
|
13
10
|
Table,
|
|
14
|
-
|
|
11
|
+
TableSchema,
|
|
15
12
|
} from "@budibase/types"
|
|
16
13
|
import { OAuth2Client } from "google-auth-library"
|
|
17
|
-
import { buildExternalTableId
|
|
14
|
+
import { buildExternalTableId } from "./utils"
|
|
15
|
+
import { DataSourceOperation, FieldTypes } from "../constants"
|
|
18
16
|
import { GoogleSpreadsheet } from "google-spreadsheet"
|
|
19
17
|
import fetch from "node-fetch"
|
|
20
18
|
import { configs, HTTPError } from "@budibase/backend-core"
|
|
21
19
|
import { dataFilters } from "@budibase/shared-core"
|
|
22
|
-
import { GOOGLE_SHEETS_PRIMARY_KEY } from "../constants"
|
|
23
20
|
|
|
24
21
|
interface GoogleSheetsConfig {
|
|
25
22
|
spreadsheetId: string
|
|
@@ -42,17 +39,6 @@ interface AuthTokenResponse {
|
|
|
42
39
|
access_token: string
|
|
43
40
|
}
|
|
44
41
|
|
|
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
|
-
|
|
56
42
|
const SCHEMA: Integration = {
|
|
57
43
|
plus: true,
|
|
58
44
|
auth: {
|
|
@@ -213,90 +199,73 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
213
199
|
|
|
214
200
|
this.client.useOAuth2Client(oauthClient)
|
|
215
201
|
await this.client.loadInfo()
|
|
216
|
-
} catch (err
|
|
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
|
-
}
|
|
202
|
+
} catch (err) {
|
|
222
203
|
console.error("Error connecting to google sheets", err)
|
|
223
204
|
throw err
|
|
224
205
|
}
|
|
225
206
|
}
|
|
226
207
|
|
|
227
|
-
|
|
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>) {
|
|
208
|
+
async buildSchema(datasourceId: string) {
|
|
248
209
|
await this.connect()
|
|
249
210
|
const sheets = this.client.sheetsByIndex
|
|
250
211
|
const tables: Record<string, Table> = {}
|
|
251
212
|
for (let sheet of sheets) {
|
|
252
213
|
// must fetch rows to determine schema
|
|
253
214
|
await sheet.getRows()
|
|
215
|
+
// build schema
|
|
216
|
+
const schema: TableSchema = {}
|
|
254
217
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
+
|
|
226
|
+
// create tables
|
|
227
|
+
tables[sheet.title] = {
|
|
228
|
+
_id: buildExternalTableId(datasourceId, sheet.title),
|
|
229
|
+
name: sheet.title,
|
|
230
|
+
primary: ["rowNumber"],
|
|
231
|
+
schema,
|
|
232
|
+
}
|
|
261
233
|
}
|
|
262
|
-
|
|
263
|
-
this.tables =
|
|
264
|
-
this.schemaErrors = final.errors
|
|
234
|
+
|
|
235
|
+
this.tables = tables
|
|
265
236
|
}
|
|
266
237
|
|
|
267
238
|
async query(json: QueryJson) {
|
|
268
239
|
const sheet = json.endpoint.entityId
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
case Operation.UPDATE:
|
|
277
|
-
return this.update({
|
|
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({
|
|
278
247
|
// exclude the header row and zero index
|
|
279
248
|
rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
|
|
280
249
|
sheet,
|
|
281
250
|
row: json.body,
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
|
|
251
|
+
}),
|
|
252
|
+
[DataSourceOperation.DELETE]: () =>
|
|
253
|
+
this.delete({
|
|
285
254
|
// exclude the header row and zero index
|
|
286
255
|
rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
|
|
287
256
|
sheet,
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
return this.deleteTable(json?.table?.name)
|
|
295
|
-
default:
|
|
296
|
-
throw new Error(
|
|
297
|
-
`GSheets integration does not support "${json.endpoint.operation}".`
|
|
298
|
-
)
|
|
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),
|
|
299
263
|
}
|
|
264
|
+
|
|
265
|
+
// @ts-ignore
|
|
266
|
+
const internalQueryMethod = handlers[json.endpoint.operation]
|
|
267
|
+
|
|
268
|
+
return await internalQueryMethod()
|
|
300
269
|
}
|
|
301
270
|
|
|
302
271
|
buildRowObject(headers: string[], values: string[], rowNumber: number) {
|
|
@@ -309,70 +278,47 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
309
278
|
}
|
|
310
279
|
|
|
311
280
|
async createTable(name?: string) {
|
|
312
|
-
if (!name) {
|
|
313
|
-
throw new Error("Must provide name for new sheet.")
|
|
314
|
-
}
|
|
315
281
|
try {
|
|
316
282
|
await this.connect()
|
|
317
|
-
return await this.client.addSheet({ title: name, headerValues: [
|
|
283
|
+
return await this.client.addSheet({ title: name, headerValues: ["test"] })
|
|
318
284
|
} catch (err) {
|
|
319
285
|
console.error("Error creating new table in google sheets", err)
|
|
320
286
|
throw err
|
|
321
287
|
}
|
|
322
288
|
}
|
|
323
289
|
|
|
324
|
-
async updateTable(table
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
+
}
|
|
336
304
|
}
|
|
337
|
-
}
|
|
338
|
-
try {
|
|
339
305
|
await sheet.setHeaderRow(headers)
|
|
340
|
-
}
|
|
341
|
-
|
|
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
|
-
}
|
|
306
|
+
} else {
|
|
307
|
+
const updatedHeaderValues = [...sheet.headerValues]
|
|
361
308
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
309
|
+
const newField = Object.keys(table.schema).find(
|
|
310
|
+
key => !sheet.headerValues.includes(key)
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
if (newField) {
|
|
314
|
+
updatedHeaderValues.push(newField)
|
|
367
315
|
}
|
|
368
|
-
}
|
|
369
316
|
|
|
370
|
-
try {
|
|
371
317
|
await sheet.setHeaderRow(updatedHeaderValues)
|
|
372
|
-
} catch (err) {
|
|
373
|
-
console.error("Error updating table in google sheets", err)
|
|
374
|
-
throw err
|
|
375
318
|
}
|
|
319
|
+
} catch (err) {
|
|
320
|
+
console.error("Error updating table in google sheets", err)
|
|
321
|
+
throw err
|
|
376
322
|
}
|
|
377
323
|
}
|
|
378
324
|
|
|
@@ -403,24 +349,6 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
403
349
|
}
|
|
404
350
|
}
|
|
405
351
|
|
|
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
|
-
|
|
424
352
|
async read(query: {
|
|
425
353
|
sheet: string
|
|
426
354
|
filters?: SearchFilters
|
|
@@ -4,7 +4,6 @@ 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(" ")
|
|
8
7
|
|
|
9
8
|
const SQL_NUMBER_TYPE_MAP = {
|
|
10
9
|
integer: FieldTypes.NUMBER,
|
|
@@ -80,10 +79,6 @@ export function isExternalTable(tableId: string) {
|
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
export function buildExternalTableId(datasourceId: string, tableName: string) {
|
|
83
|
-
// encode spaces
|
|
84
|
-
if (tableName.includes(" ")) {
|
|
85
|
-
tableName = encodeURIComponent(tableName)
|
|
86
|
-
}
|
|
87
82
|
return `${datasourceId}${DOUBLE_SEPARATOR}${tableName}`
|
|
88
83
|
}
|
|
89
84
|
|
|
@@ -95,10 +90,6 @@ export function breakExternalTableId(tableId: string | undefined) {
|
|
|
95
90
|
let datasourceId = parts.shift()
|
|
96
91
|
// if they need joined
|
|
97
92
|
let tableName = parts.join(DOUBLE_SEPARATOR)
|
|
98
|
-
// if contains encoded spaces, decode it
|
|
99
|
-
if (tableName.includes(ENCODED_SPACE)) {
|
|
100
|
-
tableName = decodeURIComponent(tableName)
|
|
101
|
-
}
|
|
102
93
|
return { datasourceId, tableName }
|
|
103
94
|
}
|
|
104
95
|
|
|
@@ -209,9 +200,9 @@ export function isIsoDateString(str: string) {
|
|
|
209
200
|
* @param column The column to check, to see if it is a valid relationship.
|
|
210
201
|
* @param tableIds The IDs of the tables which currently exist.
|
|
211
202
|
*/
|
|
212
|
-
|
|
203
|
+
function shouldCopyRelationship(
|
|
213
204
|
column: { type: string; tableId?: string },
|
|
214
|
-
tableIds: string
|
|
205
|
+
tableIds: [string]
|
|
215
206
|
) {
|
|
216
207
|
return (
|
|
217
208
|
column.type === FieldTypes.LINK &&
|
|
@@ -228,7 +219,7 @@ export function shouldCopyRelationship(
|
|
|
228
219
|
* @param column The column to check for options or boolean type.
|
|
229
220
|
* @param fetchedColumn The fetched column to check for the type in the external database.
|
|
230
221
|
*/
|
|
231
|
-
|
|
222
|
+
function shouldCopySpecialColumn(
|
|
232
223
|
column: { type: string },
|
|
233
224
|
fetchedColumn: { type: string } | undefined
|
|
234
225
|
) {
|
|
@@ -266,12 +257,9 @@ function copyExistingPropsOver(
|
|
|
266
257
|
tableIds: [string]
|
|
267
258
|
) {
|
|
268
259
|
if (entities && entities[tableName]) {
|
|
269
|
-
if (entities[tableName]
|
|
260
|
+
if (entities[tableName].primaryDisplay) {
|
|
270
261
|
table.primaryDisplay = entities[tableName].primaryDisplay
|
|
271
262
|
}
|
|
272
|
-
if (entities[tableName]?.created) {
|
|
273
|
-
table.created = entities[tableName]?.created
|
|
274
|
-
}
|
|
275
263
|
const existingTableSchema = entities[tableName].schema
|
|
276
264
|
for (let key in existingTableSchema) {
|
|
277
265
|
if (!existingTableSchema.hasOwnProperty(key)) {
|