@budibase/server 2.3.18-alpha.15 → 2.3.18-alpha.17
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 +3 -0
- package/builder/assets/{index.b0911233.js → index.57df7f7b.js} +420 -416
- package/builder/assets/{index.ca370ee3.css → index.dc0472d8.css} +2 -2
- package/builder/index.html +2 -2
- package/dist/integrations/googlesheets.js +18 -11
- package/dist/package.json +6 -6
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/integrations/googlesheets.ts +18 -11
- package/src/integrations/tests/googlesheets.spec.ts +122 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/server",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "2.3.18-alpha.
|
|
4
|
+
"version": "2.3.18-alpha.17",
|
|
5
5
|
"description": "Budibase Web Server",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
"license": "GPL-3.0",
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@apidevtools/swagger-parser": "10.0.3",
|
|
46
|
-
"@budibase/backend-core": "2.3.18-alpha.
|
|
47
|
-
"@budibase/client": "2.3.18-alpha.
|
|
48
|
-
"@budibase/pro": "2.3.18-alpha.
|
|
49
|
-
"@budibase/string-templates": "2.3.18-alpha.
|
|
50
|
-
"@budibase/types": "2.3.18-alpha.
|
|
46
|
+
"@budibase/backend-core": "2.3.18-alpha.17",
|
|
47
|
+
"@budibase/client": "2.3.18-alpha.17",
|
|
48
|
+
"@budibase/pro": "2.3.18-alpha.16",
|
|
49
|
+
"@budibase/string-templates": "2.3.18-alpha.17",
|
|
50
|
+
"@budibase/types": "2.3.18-alpha.17",
|
|
51
51
|
"@bull-board/api": "3.7.0",
|
|
52
52
|
"@bull-board/koa": "3.9.4",
|
|
53
53
|
"@elastic/elasticsearch": "7.10.0",
|
|
@@ -174,5 +174,5 @@
|
|
|
174
174
|
"optionalDependencies": {
|
|
175
175
|
"oracledb": "5.3.0"
|
|
176
176
|
},
|
|
177
|
-
"gitHead": "
|
|
177
|
+
"gitHead": "f703ebb73409290aa949c240c51b95c7ce3826c8"
|
|
178
178
|
}
|
|
@@ -11,8 +11,8 @@ import { OAuth2Client } from "google-auth-library"
|
|
|
11
11
|
import { buildExternalTableId } from "./utils"
|
|
12
12
|
import { DataSourceOperation, FieldTypes } from "../constants"
|
|
13
13
|
import { GoogleSpreadsheet } from "google-spreadsheet"
|
|
14
|
+
import fetch from "node-fetch"
|
|
14
15
|
import { configs, HTTPError } from "@budibase/backend-core"
|
|
15
|
-
const fetch = require("node-fetch")
|
|
16
16
|
|
|
17
17
|
interface GoogleSheetsConfig {
|
|
18
18
|
spreadsheetId: string
|
|
@@ -111,7 +111,7 @@ const SCHEMA: Integration = {
|
|
|
111
111
|
|
|
112
112
|
class GoogleSheetsIntegration implements DatasourcePlus {
|
|
113
113
|
private readonly config: GoogleSheetsConfig
|
|
114
|
-
private client:
|
|
114
|
+
private client: GoogleSpreadsheet
|
|
115
115
|
public tables: Record<string, Table> = {}
|
|
116
116
|
public schemaErrors: Record<string, string> = {}
|
|
117
117
|
|
|
@@ -203,7 +203,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
203
203
|
|
|
204
204
|
async buildSchema(datasourceId: string) {
|
|
205
205
|
await this.connect()
|
|
206
|
-
const sheets =
|
|
206
|
+
const sheets = this.client.sheetsByIndex
|
|
207
207
|
const tables: Record<string, Table> = {}
|
|
208
208
|
for (let sheet of sheets) {
|
|
209
209
|
// must fetch rows to determine schema
|
|
@@ -286,7 +286,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
286
286
|
async updateTable(table?: any) {
|
|
287
287
|
try {
|
|
288
288
|
await this.connect()
|
|
289
|
-
const sheet =
|
|
289
|
+
const sheet = this.client.sheetsByTitle[table.name]
|
|
290
290
|
await sheet.loadHeaderRow()
|
|
291
291
|
|
|
292
292
|
if (table._rename) {
|
|
@@ -300,10 +300,17 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
300
300
|
}
|
|
301
301
|
await sheet.setHeaderRow(headers)
|
|
302
302
|
} else {
|
|
303
|
-
|
|
303
|
+
const updatedHeaderValues = [...sheet.headerValues]
|
|
304
|
+
|
|
305
|
+
const newField = Object.keys(table.schema).find(
|
|
304
306
|
key => !sheet.headerValues.includes(key)
|
|
305
307
|
)
|
|
306
|
-
|
|
308
|
+
|
|
309
|
+
if (newField) {
|
|
310
|
+
updatedHeaderValues.push(newField)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
await sheet.setHeaderRow(updatedHeaderValues)
|
|
307
314
|
}
|
|
308
315
|
} catch (err) {
|
|
309
316
|
console.error("Error updating table in google sheets", err)
|
|
@@ -314,7 +321,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
314
321
|
async deleteTable(sheet: any) {
|
|
315
322
|
try {
|
|
316
323
|
await this.connect()
|
|
317
|
-
const sheetToDelete =
|
|
324
|
+
const sheetToDelete = this.client.sheetsByTitle[sheet]
|
|
318
325
|
return await sheetToDelete.delete()
|
|
319
326
|
} catch (err) {
|
|
320
327
|
console.error("Error deleting table in google sheets", err)
|
|
@@ -325,7 +332,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
325
332
|
async create(query: { sheet: string; row: any }) {
|
|
326
333
|
try {
|
|
327
334
|
await this.connect()
|
|
328
|
-
const sheet =
|
|
335
|
+
const sheet = this.client.sheetsByTitle[query.sheet]
|
|
329
336
|
const rowToInsert =
|
|
330
337
|
typeof query.row === "string" ? JSON.parse(query.row) : query.row
|
|
331
338
|
const row = await sheet.addRow(rowToInsert)
|
|
@@ -341,7 +348,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
341
348
|
async read(query: { sheet: string }) {
|
|
342
349
|
try {
|
|
343
350
|
await this.connect()
|
|
344
|
-
const sheet =
|
|
351
|
+
const sheet = this.client.sheetsByTitle[query.sheet]
|
|
345
352
|
const rows = await sheet.getRows()
|
|
346
353
|
const headerValues = sheet.headerValues
|
|
347
354
|
const response = []
|
|
@@ -360,7 +367,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
360
367
|
async update(query: { sheet: string; rowIndex: number; row: any }) {
|
|
361
368
|
try {
|
|
362
369
|
await this.connect()
|
|
363
|
-
const sheet =
|
|
370
|
+
const sheet = this.client.sheetsByTitle[query.sheet]
|
|
364
371
|
const rows = await sheet.getRows()
|
|
365
372
|
const row = rows[query.rowIndex]
|
|
366
373
|
if (row) {
|
|
@@ -384,7 +391,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
384
391
|
|
|
385
392
|
async delete(query: { sheet: string; rowIndex: number }) {
|
|
386
393
|
await this.connect()
|
|
387
|
-
const sheet =
|
|
394
|
+
const sheet = this.client.sheetsByTitle[query.sheet]
|
|
388
395
|
const rows = await sheet.getRows()
|
|
389
396
|
const row = rows[query.rowIndex]
|
|
390
397
|
if (row) {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { GoogleSpreadsheetWorksheet } from "google-spreadsheet"
|
|
2
|
+
|
|
3
|
+
jest.mock("google-auth-library")
|
|
4
|
+
const { OAuth2Client } = require("google-auth-library")
|
|
5
|
+
|
|
6
|
+
const setCredentialsMock = jest.fn()
|
|
7
|
+
const getAccessTokenMock = jest.fn()
|
|
8
|
+
|
|
9
|
+
OAuth2Client.mockImplementation(() => {
|
|
10
|
+
return {
|
|
11
|
+
setCredentials: setCredentialsMock,
|
|
12
|
+
getAccessToken: getAccessTokenMock,
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
jest.mock("google-spreadsheet")
|
|
17
|
+
const { GoogleSpreadsheet } = require("google-spreadsheet")
|
|
18
|
+
|
|
19
|
+
const sheetsByTitle: { [title: string]: GoogleSpreadsheetWorksheet } = {}
|
|
20
|
+
|
|
21
|
+
GoogleSpreadsheet.mockImplementation(() => {
|
|
22
|
+
return {
|
|
23
|
+
useOAuth2Client: jest.fn(),
|
|
24
|
+
loadInfo: jest.fn(),
|
|
25
|
+
sheetsByTitle,
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
import { structures } from "@budibase/backend-core/tests"
|
|
30
|
+
import TestConfiguration from "../../tests/utilities/TestConfiguration"
|
|
31
|
+
import GoogleSheetsIntegration from "../googlesheets"
|
|
32
|
+
import { FieldType, Table, TableSchema } from "../../../../types/src/documents"
|
|
33
|
+
|
|
34
|
+
describe("Google Sheets Integration", () => {
|
|
35
|
+
let integration: any,
|
|
36
|
+
config = new TestConfiguration()
|
|
37
|
+
|
|
38
|
+
beforeEach(async () => {
|
|
39
|
+
integration = new GoogleSheetsIntegration.integration({
|
|
40
|
+
spreadsheetId: "randomId",
|
|
41
|
+
auth: {
|
|
42
|
+
appId: "appId",
|
|
43
|
+
accessToken: "accessToken",
|
|
44
|
+
refreshToken: "refreshToken",
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
await config.init()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
function createBasicTable(name: string, columns: string[]): Table {
|
|
51
|
+
return {
|
|
52
|
+
name,
|
|
53
|
+
schema: {
|
|
54
|
+
...columns.reduce((p, c) => {
|
|
55
|
+
p[c] = {
|
|
56
|
+
name: c,
|
|
57
|
+
type: FieldType.STRING,
|
|
58
|
+
constraints: {
|
|
59
|
+
type: "string",
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
return p
|
|
63
|
+
}, {} as TableSchema),
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createSheet({
|
|
69
|
+
headerValues,
|
|
70
|
+
}: {
|
|
71
|
+
headerValues: string[]
|
|
72
|
+
}): GoogleSpreadsheetWorksheet {
|
|
73
|
+
return {
|
|
74
|
+
// to ignore the unmapped fields
|
|
75
|
+
...({} as any),
|
|
76
|
+
loadHeaderRow: jest.fn(),
|
|
77
|
+
headerValues,
|
|
78
|
+
setHeaderRow: jest.fn(),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe("update table", () => {
|
|
83
|
+
test("adding a new field will be adding a new header row", async () => {
|
|
84
|
+
await config.doInContext(structures.uuid(), async () => {
|
|
85
|
+
const tableColumns = ["name", "description", "new field"]
|
|
86
|
+
const table = createBasicTable(structures.uuid(), tableColumns)
|
|
87
|
+
|
|
88
|
+
const sheet = createSheet({ headerValues: ["name", "description"] })
|
|
89
|
+
sheetsByTitle[table.name] = sheet
|
|
90
|
+
await integration.updateTable(table)
|
|
91
|
+
|
|
92
|
+
expect(sheet.loadHeaderRow).toBeCalledTimes(1)
|
|
93
|
+
expect(sheet.setHeaderRow).toBeCalledTimes(1)
|
|
94
|
+
expect(sheet.setHeaderRow).toBeCalledWith(tableColumns)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test("removing an existing field will not remove the data from the spreadsheet", async () => {
|
|
99
|
+
await config.doInContext(structures.uuid(), async () => {
|
|
100
|
+
const tableColumns = ["name"]
|
|
101
|
+
const table = createBasicTable(structures.uuid(), tableColumns)
|
|
102
|
+
|
|
103
|
+
const sheet = createSheet({
|
|
104
|
+
headerValues: ["name", "description", "location"],
|
|
105
|
+
})
|
|
106
|
+
sheetsByTitle[table.name] = sheet
|
|
107
|
+
await integration.updateTable(table)
|
|
108
|
+
|
|
109
|
+
expect(sheet.loadHeaderRow).toBeCalledTimes(1)
|
|
110
|
+
expect(sheet.setHeaderRow).toBeCalledTimes(1)
|
|
111
|
+
expect(sheet.setHeaderRow).toBeCalledWith([
|
|
112
|
+
"name",
|
|
113
|
+
"description",
|
|
114
|
+
"location",
|
|
115
|
+
])
|
|
116
|
+
|
|
117
|
+
// No undefineds are sent
|
|
118
|
+
expect((sheet.setHeaderRow as any).mock.calls[0][0]).toHaveLength(3)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
})
|