@budibase/server 2.3.18-alpha.14 → 2.3.18-alpha.16

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.
@@ -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: any
114
+ private client: GoogleSpreadsheet
115
115
  public tables: Record<string, Table> = {}
116
116
  public schemaErrors: Record<string, string> = {}
117
117
 
@@ -172,7 +172,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
172
172
  async connect() {
173
173
  try {
174
174
  // Initialise oAuth client
175
- let googleConfig = await configs.getGoogleConfig()
175
+ let googleConfig = await configs.getGoogleDatasourceConfig()
176
176
  if (!googleConfig) {
177
177
  throw new HTTPError("Google config not found", 400)
178
178
  }
@@ -203,7 +203,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
203
203
 
204
204
  async buildSchema(datasourceId: string) {
205
205
  await this.connect()
206
- const sheets = await this.client.sheetsByIndex
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 = await this.client.sheetsByTitle[table.name]
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
- let newField = Object.keys(table.schema).find(
303
+ const updatedHeaderValues = [...sheet.headerValues]
304
+
305
+ const newField = Object.keys(table.schema).find(
304
306
  key => !sheet.headerValues.includes(key)
305
307
  )
306
- await sheet.setHeaderRow([...sheet.headerValues, newField])
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 = await this.client.sheetsByTitle[sheet]
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 = await this.client.sheetsByTitle[query.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 = await this.client.sheetsByTitle[query.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 = await this.client.sheetsByTitle[query.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 = await this.client.sheetsByTitle[query.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
+ })
package/src/startup.ts CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  generateApiKey,
6
6
  getChecklist,
7
7
  } from "./utilities/workerRequests"
8
- import { installation, tenancy, logging } from "@budibase/backend-core"
8
+ import { installation, tenancy, logging, events } from "@budibase/backend-core"
9
9
  import fs from "fs"
10
10
  import { watch } from "./watch"
11
11
  import * as automations from "./automations"
@@ -124,6 +124,9 @@ export async function startup(app?: any, server?: any) {
124
124
  // get the references to the queue promises, don't await as
125
125
  // they will never end, unless the processing stops
126
126
  let queuePromises = []
127
+ // configure events to use the pro audit log write
128
+ // can't integrate directly into backend-core due to cyclic issues
129
+ queuePromises.push(events.processors.init(pro.sdk.auditLogs.write))
127
130
  queuePromises.push(automations.init())
128
131
  queuePromises.push(initPro())
129
132
  if (app) {
@@ -1,16 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.removeKeyNumbering = void 0;
4
- const QUERY_START_REGEX = /\d[0-9]*:/g;
5
- function removeKeyNumbering(key) {
6
- if (typeof key === "string" && key.match(QUERY_START_REGEX) != null) {
7
- const parts = key.split(":");
8
- // remove the number
9
- parts.shift();
10
- return parts.join(":");
11
- }
12
- else {
13
- return key;
14
- }
15
- }
16
- exports.removeKeyNumbering = removeKeyNumbering;
@@ -1,12 +0,0 @@
1
- const QUERY_START_REGEX = /\d[0-9]*:/g
2
-
3
- export function removeKeyNumbering(key: any): string {
4
- if (typeof key === "string" && key.match(QUERY_START_REGEX) != null) {
5
- const parts = key.split(":")
6
- // remove the number
7
- parts.shift()
8
- return parts.join(":")
9
- } else {
10
- return key
11
- }
12
- }