@budibase/server 2.6.16-alpha.4 → 2.6.16-alpha.5

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.
Files changed (49) hide show
  1. package/builder/assets/{index.8bf0a95b.js → index.b9eeb2a8.js} +202 -202
  2. package/builder/index.html +1 -1
  3. package/dist/api/controllers/datasource.js +70 -39
  4. package/dist/api/controllers/integration.js +2 -2
  5. package/dist/api/routes/datasource.js +1 -0
  6. package/dist/db/dynamoClient.js +1 -1
  7. package/dist/integrations/airtable.js +28 -2
  8. package/dist/integrations/arangodb.js +19 -3
  9. package/dist/integrations/couchdb.js +16 -1
  10. package/dist/integrations/dynamodb.js +16 -0
  11. package/dist/integrations/elasticsearch.js +15 -0
  12. package/dist/integrations/firebase.js +15 -0
  13. package/dist/integrations/googlesheets.js +16 -0
  14. package/dist/integrations/index.js +5 -2
  15. package/dist/integrations/microsoftSqlServer.js +16 -0
  16. package/dist/integrations/mongodb.js +16 -0
  17. package/dist/integrations/mysql.js +21 -5
  18. package/dist/integrations/oracle.js +29 -0
  19. package/dist/integrations/postgres.js +26 -7
  20. package/dist/integrations/redis.js +20 -1
  21. package/dist/integrations/s3.js +23 -4
  22. package/dist/integrations/snowflake.js +15 -0
  23. package/dist/migrations/functions/backfill/app/queries.js +1 -2
  24. package/dist/sdk/app/datasources/datasources.js +10 -3
  25. package/dist/tsconfig.build.tsbuildinfo +1 -1
  26. package/package.json +10 -9
  27. package/src/api/controllers/datasource.ts +88 -49
  28. package/src/api/controllers/integration.ts +3 -3
  29. package/src/api/routes/datasource.ts +5 -0
  30. package/src/db/dynamoClient.ts +1 -1
  31. package/src/integration-test/postgres.spec.ts +0 -1
  32. package/src/integrations/airtable.ts +31 -4
  33. package/src/integrations/arangodb.ts +18 -2
  34. package/src/integrations/couchdb.ts +18 -4
  35. package/src/integrations/dynamodb.ts +34 -5
  36. package/src/integrations/elasticsearch.ts +16 -1
  37. package/src/integrations/firebase.ts +15 -0
  38. package/src/integrations/googlesheets.ts +16 -0
  39. package/src/integrations/index.ts +12 -7
  40. package/src/integrations/microsoftSqlServer.ts +16 -0
  41. package/src/integrations/mongodb.ts +16 -0
  42. package/src/integrations/mysql.ts +27 -16
  43. package/src/integrations/oracle.ts +28 -6
  44. package/src/integrations/postgres.ts +21 -3
  45. package/src/integrations/redis.ts +26 -3
  46. package/src/integrations/s3.ts +19 -3
  47. package/src/integrations/snowflake.ts +20 -1
  48. package/src/migrations/functions/backfill/app/queries.ts +1 -1
  49. package/src/sdk/app/datasources/datasources.ts +7 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/server",
3
3
  "email": "hi@budibase.com",
4
- "version": "2.6.16-alpha.4",
4
+ "version": "2.6.16-alpha.5",
5
5
  "description": "Budibase Web Server",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -45,12 +45,12 @@
45
45
  "license": "GPL-3.0",
46
46
  "dependencies": {
47
47
  "@apidevtools/swagger-parser": "10.0.3",
48
- "@budibase/backend-core": "2.6.16-alpha.4",
49
- "@budibase/client": "2.6.16-alpha.4",
50
- "@budibase/pro": "2.6.16-alpha.4",
51
- "@budibase/shared-core": "2.6.16-alpha.4",
52
- "@budibase/string-templates": "2.6.16-alpha.4",
53
- "@budibase/types": "2.6.16-alpha.4",
48
+ "@budibase/backend-core": "2.6.16-alpha.5",
49
+ "@budibase/client": "2.6.16-alpha.5",
50
+ "@budibase/pro": "2.6.16-alpha.5",
51
+ "@budibase/shared-core": "2.6.16-alpha.5",
52
+ "@budibase/string-templates": "2.6.16-alpha.5",
53
+ "@budibase/types": "2.6.16-alpha.5",
54
54
  "@bull-board/api": "3.7.0",
55
55
  "@bull-board/koa": "3.9.4",
56
56
  "@elastic/elasticsearch": "7.10.0",
@@ -99,7 +99,7 @@
99
99
  "mysql2": "2.3.3",
100
100
  "node-fetch": "2.6.7",
101
101
  "open": "8.4.0",
102
- "pg": "8.5.1",
102
+ "pg": "8.10.0",
103
103
  "posthog-node": "1.3.0",
104
104
  "pouchdb": "7.3.0",
105
105
  "pouchdb-adapter-memory": "7.2.2",
@@ -141,6 +141,7 @@
141
141
  "@types/node": "14.18.20",
142
142
  "@types/node-fetch": "2.6.1",
143
143
  "@types/oracledb": "5.2.2",
144
+ "@types/pg": "8.6.6",
144
145
  "@types/pouchdb": "6.4.0",
145
146
  "@types/redis": "4.0.11",
146
147
  "@types/server-destroy": "1.0.1",
@@ -176,5 +177,5 @@
176
177
  "optionalDependencies": {
177
178
  "oracledb": "5.3.0"
178
179
  },
179
- "gitHead": "7ef56bb0effd46a525ca978773976e71b9a90465"
180
+ "gitHead": "bbb516f26ae966668b5fa65418d81fbccf3dce37"
180
181
  }
@@ -18,11 +18,71 @@ import {
18
18
  Row,
19
19
  CreateDatasourceResponse,
20
20
  UpdateDatasourceResponse,
21
- UpdateDatasourceRequest,
22
21
  CreateDatasourceRequest,
22
+ VerifyDatasourceRequest,
23
+ VerifyDatasourceResponse,
24
+ IntegrationBase,
25
+ DatasourcePlus,
23
26
  } from "@budibase/types"
24
27
  import sdk from "../../sdk"
25
28
 
29
+ function getErrorTables(errors: any, errorType: string) {
30
+ return Object.entries(errors)
31
+ .filter(entry => entry[1] === errorType)
32
+ .map(([name]) => name)
33
+ }
34
+
35
+ function updateError(error: any, newError: any, tables: string[]) {
36
+ if (!error) {
37
+ error = ""
38
+ }
39
+ if (error.length > 0) {
40
+ error += "\n"
41
+ }
42
+ error += `${newError} ${tables.join(", ")}`
43
+ return error
44
+ }
45
+
46
+ async function getConnector(
47
+ datasource: Datasource
48
+ ): Promise<IntegrationBase | DatasourcePlus> {
49
+ const Connector = await getIntegration(datasource.source)
50
+ // can't enrich if it doesn't have an ID yet
51
+ if (datasource._id) {
52
+ datasource = await sdk.datasources.enrich(datasource)
53
+ }
54
+ // Connect to the DB and build the schema
55
+ return new Connector(datasource.config)
56
+ }
57
+
58
+ async function buildSchemaHelper(datasource: Datasource) {
59
+ const connector = (await getConnector(datasource)) as DatasourcePlus
60
+ await connector.buildSchema(datasource._id!, datasource.entities!)
61
+
62
+ const errors = connector.schemaErrors
63
+ let error = null
64
+ if (errors && Object.keys(errors).length > 0) {
65
+ const noKey = getErrorTables(errors, BuildSchemaErrors.NO_KEY)
66
+ const invalidCol = getErrorTables(errors, BuildSchemaErrors.INVALID_COLUMN)
67
+ if (noKey.length) {
68
+ error = updateError(
69
+ error,
70
+ "No primary key constraint found for the following:",
71
+ noKey
72
+ )
73
+ }
74
+ if (invalidCol.length) {
75
+ const invalidCols = Object.values(InvalidColumns).join(", ")
76
+ error = updateError(
77
+ error,
78
+ `Cannot use columns ${invalidCols} found in following:`,
79
+ invalidCol
80
+ )
81
+ }
82
+ }
83
+ return { tables: connector.tables, error }
84
+ }
85
+
26
86
  export async function fetch(ctx: UserCtx) {
27
87
  // Get internal tables
28
88
  const db = context.getAppDB()
@@ -66,6 +126,33 @@ export async function fetch(ctx: UserCtx) {
66
126
  ctx.body = [bbInternalDb, ...datasources]
67
127
  }
68
128
 
129
+ export async function verify(
130
+ ctx: UserCtx<VerifyDatasourceRequest, VerifyDatasourceResponse>
131
+ ) {
132
+ const { datasource } = ctx.request.body
133
+ let existingDatasource: undefined | Datasource
134
+ if (datasource._id) {
135
+ existingDatasource = await sdk.datasources.get(datasource._id)
136
+ }
137
+ let enrichedDatasource = datasource
138
+ if (existingDatasource) {
139
+ enrichedDatasource = sdk.datasources.mergeConfigs(
140
+ datasource,
141
+ existingDatasource
142
+ )
143
+ }
144
+ const connector = await getConnector(enrichedDatasource)
145
+ if (!connector.testConnection) {
146
+ ctx.throw(400, "Connection information verification not supported")
147
+ }
148
+ const response = await connector.testConnection()
149
+
150
+ ctx.body = {
151
+ connected: response.connected,
152
+ error: response.error,
153
+ }
154
+ }
155
+
69
156
  export async function buildSchemaFromDb(ctx: UserCtx) {
70
157
  const db = context.getAppDB()
71
158
  const datasource = await sdk.datasources.get(ctx.params.datasourceId)
@@ -311,51 +398,3 @@ export async function query(ctx: UserCtx) {
311
398
  ctx.throw(400, err)
312
399
  }
313
400
  }
314
-
315
- function getErrorTables(errors: any, errorType: string) {
316
- return Object.entries(errors)
317
- .filter(entry => entry[1] === errorType)
318
- .map(([name]) => name)
319
- }
320
-
321
- function updateError(error: any, newError: any, tables: string[]) {
322
- if (!error) {
323
- error = ""
324
- }
325
- if (error.length > 0) {
326
- error += "\n"
327
- }
328
- error += `${newError} ${tables.join(", ")}`
329
- return error
330
- }
331
-
332
- async function buildSchemaHelper(datasource: Datasource) {
333
- const Connector = await getIntegration(datasource.source)
334
- datasource = await sdk.datasources.enrich(datasource)
335
- // Connect to the DB and build the schema
336
- const connector = new Connector(datasource.config)
337
- await connector.buildSchema(datasource._id, datasource.entities)
338
-
339
- const errors = connector.schemaErrors
340
- let error = null
341
- if (errors && Object.keys(errors).length > 0) {
342
- const noKey = getErrorTables(errors, BuildSchemaErrors.NO_KEY)
343
- const invalidCol = getErrorTables(errors, BuildSchemaErrors.INVALID_COLUMN)
344
- if (noKey.length) {
345
- error = updateError(
346
- error,
347
- "No primary key constraint found for the following:",
348
- noKey
349
- )
350
- }
351
- if (invalidCol.length) {
352
- const invalidCols = Object.values(InvalidColumns).join(", ")
353
- error = updateError(
354
- error,
355
- `Cannot use columns ${invalidCols} found in following:`,
356
- invalidCol
357
- )
358
- }
359
- }
360
- return { tables: connector.tables, error }
361
- }
@@ -1,4 +1,4 @@
1
- import { getDefinitions } from "../../integrations"
1
+ import { getDefinition, getDefinitions } from "../../integrations"
2
2
  import { BBContext } from "@budibase/types"
3
3
 
4
4
  export async function fetch(ctx: BBContext) {
@@ -7,7 +7,7 @@ export async function fetch(ctx: BBContext) {
7
7
  }
8
8
 
9
9
  export async function find(ctx: BBContext) {
10
- const defs = await getDefinitions()
10
+ const def = await getDefinition(ctx.params.type)
11
+ ctx.body = def
11
12
  ctx.status = 200
12
- ctx.body = defs[ctx.params.type]
13
13
  }
@@ -15,6 +15,11 @@ router
15
15
  authorized(permissions.BUILDER),
16
16
  datasourceController.fetch
17
17
  )
18
+ .post(
19
+ "/api/datasources/verify",
20
+ authorized(permissions.BUILDER),
21
+ datasourceController.verify
22
+ )
18
23
  .get(
19
24
  "/api/datasources/:datasourceId",
20
25
  authorized(
@@ -140,7 +140,7 @@ export function init(endpoint: string) {
140
140
  docClient = new AWS.DynamoDB.DocumentClient(docClientParams)
141
141
  }
142
142
 
143
- if (!env.isProd()) {
143
+ if (!env.isProd() && !env.isJest()) {
144
144
  env._set("AWS_ACCESS_KEY_ID", "KEY_ID")
145
145
  env._set("AWS_SECRET_ACCESS_KEY", "SECRET_KEY")
146
146
  init("http://localhost:8333")
@@ -19,7 +19,6 @@ import _ from "lodash"
19
19
  import { generator } from "@budibase/backend-core/tests"
20
20
  import { utils } from "@budibase/backend-core"
21
21
  import { GenericContainer } from "testcontainers"
22
- import { generateRowIdField } from "../integrations/utils"
23
22
 
24
23
  const config = setup.getConfig()!
25
24
 
@@ -1,11 +1,13 @@
1
1
  import {
2
- Integration,
2
+ ConnectionInfo,
3
+ DatasourceFeature,
3
4
  DatasourceFieldType,
4
- QueryType,
5
+ Integration,
5
6
  IntegrationBase,
7
+ QueryType,
6
8
  } from "@budibase/types"
7
9
 
8
- const Airtable = require("airtable")
10
+ import Airtable from "airtable"
9
11
 
10
12
  interface AirtableConfig {
11
13
  apiKey: string
@@ -18,6 +20,7 @@ const SCHEMA: Integration = {
18
20
  "Airtable is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet.",
19
21
  friendlyName: "Airtable",
20
22
  type: "Spreadsheet",
23
+ features: [DatasourceFeature.CONNECTION_CHECKING],
21
24
  datasource: {
22
25
  apiKey: {
23
26
  type: DatasourceFieldType.PASSWORD,
@@ -81,13 +84,37 @@ const SCHEMA: Integration = {
81
84
 
82
85
  class AirtableIntegration implements IntegrationBase {
83
86
  private config: AirtableConfig
84
- private client: any
87
+ private client
85
88
 
86
89
  constructor(config: AirtableConfig) {
87
90
  this.config = config
88
91
  this.client = new Airtable(config).base(config.base)
89
92
  }
90
93
 
94
+ async testConnection(): Promise<ConnectionInfo> {
95
+ const mockTable = Date.now().toString()
96
+ try {
97
+ await this.client.makeRequest({
98
+ path: `/${mockTable}`,
99
+ })
100
+
101
+ return { connected: true }
102
+ } catch (e: any) {
103
+ if (
104
+ e.message ===
105
+ `Could not find table ${mockTable} in application ${this.config.base}`
106
+ ) {
107
+ // The request managed to check the application, so the credentials are valid
108
+ return { connected: true }
109
+ }
110
+
111
+ return {
112
+ connected: false,
113
+ error: e.message as string,
114
+ }
115
+ }
116
+ }
117
+
91
118
  async create(query: { table: any; json: any }) {
92
119
  const { table, json } = query
93
120
 
@@ -3,9 +3,11 @@ import {
3
3
  DatasourceFieldType,
4
4
  QueryType,
5
5
  IntegrationBase,
6
+ DatasourceFeature,
7
+ ConnectionInfo,
6
8
  } from "@budibase/types"
7
9
 
8
- const { Database, aql } = require("arangojs")
10
+ import { Database, aql } from "arangojs"
9
11
 
10
12
  interface ArangodbConfig {
11
13
  url: string
@@ -21,6 +23,7 @@ const SCHEMA: Integration = {
21
23
  type: "Non-relational",
22
24
  description:
23
25
  "ArangoDB is a scalable open-source multi-model database natively supporting graph, document and search. All supported data models & access patterns can be combined in queries allowing for maximal flexibility. ",
26
+ features: [DatasourceFeature.CONNECTION_CHECKING],
24
27
  datasource: {
25
28
  url: {
26
29
  type: DatasourceFieldType.STRING,
@@ -58,7 +61,7 @@ const SCHEMA: Integration = {
58
61
 
59
62
  class ArangoDBIntegration implements IntegrationBase {
60
63
  private config: ArangodbConfig
61
- private client: any
64
+ private client
62
65
 
63
66
  constructor(config: ArangodbConfig) {
64
67
  const newConfig = {
@@ -74,6 +77,19 @@ class ArangoDBIntegration implements IntegrationBase {
74
77
  this.client = new Database(newConfig)
75
78
  }
76
79
 
80
+ async testConnection() {
81
+ const response: ConnectionInfo = {
82
+ connected: false,
83
+ }
84
+ try {
85
+ await this.client.get()
86
+ response.connected = true
87
+ } catch (e: any) {
88
+ response.error = e.message as string
89
+ }
90
+ return response
91
+ }
92
+
77
93
  async read(query: { sql: any }) {
78
94
  try {
79
95
  const result = await this.client.query(query.sql)
@@ -1,4 +1,6 @@
1
1
  import {
2
+ ConnectionInfo,
3
+ DatasourceFeature,
2
4
  DatasourceFieldType,
3
5
  Document,
4
6
  Integration,
@@ -18,6 +20,7 @@ const SCHEMA: Integration = {
18
20
  type: "Non-relational",
19
21
  description:
20
22
  "Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.",
23
+ features: [DatasourceFeature.CONNECTION_CHECKING],
21
24
  datasource: {
22
25
  url: {
23
26
  type: DatasourceFieldType.STRING,
@@ -61,21 +64,32 @@ const SCHEMA: Integration = {
61
64
  }
62
65
 
63
66
  class CouchDBIntegration implements IntegrationBase {
64
- private config: CouchDBConfig
65
- private readonly client: any
67
+ private readonly client: dbCore.DatabaseImpl
66
68
 
67
69
  constructor(config: CouchDBConfig) {
68
- this.config = config
69
70
  this.client = dbCore.DatabaseWithConnection(config.database, config.url)
70
71
  }
71
72
 
73
+ async testConnection() {
74
+ const response: ConnectionInfo = {
75
+ connected: false,
76
+ }
77
+ try {
78
+ const result = await this.query("exists", "validation error", {})
79
+ response.connected = result === true
80
+ } catch (e: any) {
81
+ response.error = e.message as string
82
+ }
83
+ return response
84
+ }
85
+
72
86
  async query(
73
87
  command: string,
74
88
  errorMsg: string,
75
89
  query: { json?: object; id?: string }
76
90
  ) {
77
91
  try {
78
- return await this.client[command](query.id || query.json)
92
+ return await (this.client as any)[command](query.id || query.json)
79
93
  } catch (err) {
80
94
  console.error(errorMsg, err)
81
95
  throw err
@@ -3,10 +3,13 @@ import {
3
3
  DatasourceFieldType,
4
4
  QueryType,
5
5
  IntegrationBase,
6
+ DatasourceFeature,
7
+ ConnectionInfo,
6
8
  } from "@budibase/types"
7
9
 
8
10
  import AWS from "aws-sdk"
9
11
  import { AWS_REGION } from "../db/dynamoClient"
12
+ import { DocumentClient } from "aws-sdk/clients/dynamodb"
10
13
 
11
14
  interface DynamoDBConfig {
12
15
  region: string
@@ -22,6 +25,7 @@ const SCHEMA: Integration = {
22
25
  "Amazon DynamoDB is a key-value and document database that delivers single-digit millisecond performance at any scale.",
23
26
  friendlyName: "DynamoDB",
24
27
  type: "Non-relational",
28
+ features: [DatasourceFeature.CONNECTION_CHECKING],
25
29
  datasource: {
26
30
  region: {
27
31
  type: DatasourceFieldType.STRING,
@@ -128,7 +132,7 @@ const SCHEMA: Integration = {
128
132
 
129
133
  class DynamoDBIntegration implements IntegrationBase {
130
134
  private config: DynamoDBConfig
131
- private client: any
135
+ private client
132
136
 
133
137
  constructor(config: DynamoDBConfig) {
134
138
  this.config = config
@@ -148,7 +152,23 @@ class DynamoDBIntegration implements IntegrationBase {
148
152
  this.client = new AWS.DynamoDB.DocumentClient(this.config)
149
153
  }
150
154
 
151
- async create(query: { table: string; json: object }) {
155
+ async testConnection() {
156
+ const response: ConnectionInfo = {
157
+ connected: false,
158
+ }
159
+ try {
160
+ const scanRes = await new AWS.DynamoDB(this.config).listTables().promise()
161
+ response.connected = !!scanRes.$response
162
+ } catch (e: any) {
163
+ response.error = e.message as string
164
+ }
165
+ return response
166
+ }
167
+
168
+ async create(query: {
169
+ table: string
170
+ json: Omit<DocumentClient.PutItemInput, "TableName">
171
+ }) {
152
172
  const params = {
153
173
  TableName: query.table,
154
174
  ...query.json,
@@ -189,7 +209,10 @@ class DynamoDBIntegration implements IntegrationBase {
189
209
  return new AWS.DynamoDB(this.config).describeTable(params).promise()
190
210
  }
191
211
 
192
- async get(query: { table: string; json: object }) {
212
+ async get(query: {
213
+ table: string
214
+ json: Omit<DocumentClient.GetItemInput, "TableName">
215
+ }) {
193
216
  const params = {
194
217
  TableName: query.table,
195
218
  ...query.json,
@@ -197,7 +220,10 @@ class DynamoDBIntegration implements IntegrationBase {
197
220
  return this.client.get(params).promise()
198
221
  }
199
222
 
200
- async update(query: { table: string; json: object }) {
223
+ async update(query: {
224
+ table: string
225
+ json: Omit<DocumentClient.UpdateItemInput, "TableName">
226
+ }) {
201
227
  const params = {
202
228
  TableName: query.table,
203
229
  ...query.json,
@@ -205,7 +231,10 @@ class DynamoDBIntegration implements IntegrationBase {
205
231
  return this.client.update(params).promise()
206
232
  }
207
233
 
208
- async delete(query: { table: string; json: object }) {
234
+ async delete(query: {
235
+ table: string
236
+ json: Omit<DocumentClient.DeleteItemInput, "TableName">
237
+ }) {
209
238
  const params = {
210
239
  TableName: query.table,
211
240
  ...query.json,
@@ -3,6 +3,8 @@ import {
3
3
  DatasourceFieldType,
4
4
  QueryType,
5
5
  IntegrationBase,
6
+ DatasourceFeature,
7
+ ConnectionInfo,
6
8
  } from "@budibase/types"
7
9
 
8
10
  import { Client, ClientOptions } from "@elastic/elasticsearch"
@@ -20,6 +22,7 @@ const SCHEMA: Integration = {
20
22
  "Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents.",
21
23
  friendlyName: "ElasticSearch",
22
24
  type: "Non-relational",
25
+ features: [DatasourceFeature.CONNECTION_CHECKING],
23
26
  datasource: {
24
27
  url: {
25
28
  type: DatasourceFieldType.STRING,
@@ -95,7 +98,7 @@ const SCHEMA: Integration = {
95
98
 
96
99
  class ElasticSearchIntegration implements IntegrationBase {
97
100
  private config: ElasticsearchConfig
98
- private client: any
101
+ private client
99
102
 
100
103
  constructor(config: ElasticsearchConfig) {
101
104
  this.config = config
@@ -114,6 +117,18 @@ class ElasticSearchIntegration implements IntegrationBase {
114
117
  this.client = new Client(clientConfig)
115
118
  }
116
119
 
120
+ async testConnection(): Promise<ConnectionInfo> {
121
+ try {
122
+ await this.client.info()
123
+ return { connected: true }
124
+ } catch (e: any) {
125
+ return {
126
+ connected: false,
127
+ error: e.message as string,
128
+ }
129
+ }
130
+ }
131
+
117
132
  async create(query: { index: string; json: object }) {
118
133
  const { index, json } = query
119
134
 
@@ -3,6 +3,8 @@ import {
3
3
  Integration,
4
4
  QueryType,
5
5
  IntegrationBase,
6
+ DatasourceFeature,
7
+ ConnectionInfo,
6
8
  } from "@budibase/types"
7
9
  import { Firestore, WhereFilterOp } from "@google-cloud/firestore"
8
10
 
@@ -18,6 +20,7 @@ const SCHEMA: Integration = {
18
20
  type: "Non-relational",
19
21
  description:
20
22
  "Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud.",
23
+ features: [DatasourceFeature.CONNECTION_CHECKING],
21
24
  datasource: {
22
25
  email: {
23
26
  type: DatasourceFieldType.STRING,
@@ -99,6 +102,18 @@ class FirebaseIntegration implements IntegrationBase {
99
102
  })
100
103
  }
101
104
 
105
+ async testConnection(): Promise<ConnectionInfo> {
106
+ try {
107
+ await this.client.listCollections()
108
+ return { connected: true }
109
+ } catch (e: any) {
110
+ return {
111
+ connected: false,
112
+ error: e.message as string,
113
+ }
114
+ }
115
+ }
116
+
102
117
  async create(query: { json: object; extra: { [key: string]: string } }) {
103
118
  try {
104
119
  const documentReference = this.client
@@ -1,4 +1,6 @@
1
1
  import {
2
+ ConnectionInfo,
3
+ DatasourceFeature,
2
4
  DatasourceFieldType,
3
5
  DatasourcePlus,
4
6
  FieldType,
@@ -64,6 +66,7 @@ const SCHEMA: Integration = {
64
66
  "Create and collaborate on online spreadsheets in real-time and from any device. ",
65
67
  friendlyName: "Google Sheets",
66
68
  type: "Spreadsheet",
69
+ features: [DatasourceFeature.CONNECTION_CHECKING],
67
70
  datasource: {
68
71
  spreadsheetId: {
69
72
  display: "Google Sheet URL",
@@ -139,6 +142,19 @@ class GoogleSheetsIntegration implements DatasourcePlus {
139
142
  this.client = new GoogleSpreadsheet(spreadsheetId)
140
143
  }
141
144
 
145
+ async testConnection(): Promise<ConnectionInfo> {
146
+ try {
147
+ await this.connect()
148
+ await this.client.loadInfo()
149
+ return { connected: true }
150
+ } catch (e: any) {
151
+ return {
152
+ connected: false,
153
+ error: e.message as string,
154
+ }
155
+ }
156
+ }
157
+
142
158
  getBindingIdentifier() {
143
159
  return ""
144
160
  }
@@ -20,7 +20,7 @@ import env from "../environment"
20
20
  import { cloneDeep } from "lodash"
21
21
  import sdk from "../sdk"
22
22
 
23
- const DEFINITIONS: { [key: string]: Integration } = {
23
+ const DEFINITIONS: Record<SourceName, Integration | undefined> = {
24
24
  [SourceName.POSTGRES]: postgres.schema,
25
25
  [SourceName.DYNAMODB]: dynamodb.schema,
26
26
  [SourceName.MONGODB]: mongodb.schema,
@@ -36,9 +36,10 @@ const DEFINITIONS: { [key: string]: Integration } = {
36
36
  [SourceName.GOOGLE_SHEETS]: googlesheets.schema,
37
37
  [SourceName.REDIS]: redis.schema,
38
38
  [SourceName.SNOWFLAKE]: snowflake.schema,
39
+ [SourceName.ORACLE]: undefined,
39
40
  }
40
41
 
41
- const INTEGRATIONS: { [key: string]: any } = {
42
+ const INTEGRATIONS: Record<SourceName, any> = {
42
43
  [SourceName.POSTGRES]: postgres.integration,
43
44
  [SourceName.DYNAMODB]: dynamodb.integration,
44
45
  [SourceName.MONGODB]: mongodb.integration,
@@ -55,6 +56,7 @@ const INTEGRATIONS: { [key: string]: any } = {
55
56
  [SourceName.REDIS]: redis.integration,
56
57
  [SourceName.FIRESTORE]: firebase.integration,
57
58
  [SourceName.SNOWFLAKE]: snowflake.integration,
59
+ [SourceName.ORACLE]: undefined,
58
60
  }
59
61
 
60
62
  // optionally add oracle integration if the oracle binary can be installed
@@ -67,10 +69,13 @@ if (
67
69
  INTEGRATIONS[SourceName.ORACLE] = oracle.integration
68
70
  }
69
71
 
70
- export async function getDefinition(source: SourceName): Promise<Integration> {
72
+ export async function getDefinition(
73
+ source: SourceName
74
+ ): Promise<Integration | undefined> {
71
75
  // check if its integrated, faster
72
- if (DEFINITIONS[source]) {
73
- return DEFINITIONS[source]
76
+ const definition = DEFINITIONS[source]
77
+ if (definition) {
78
+ return definition
74
79
  }
75
80
  const allDefinitions = await getDefinitions()
76
81
  return allDefinitions[source]
@@ -98,7 +103,7 @@ export async function getDefinitions() {
98
103
  }
99
104
  }
100
105
 
101
- export async function getIntegration(integration: string) {
106
+ export async function getIntegration(integration: SourceName) {
102
107
  if (INTEGRATIONS[integration]) {
103
108
  return INTEGRATIONS[integration]
104
109
  }
@@ -107,7 +112,7 @@ export async function getIntegration(integration: string) {
107
112
  for (let plugin of plugins) {
108
113
  if (plugin.name === integration) {
109
114
  // need to use commonJS require due to its dynamic runtime nature
110
- const retrieved: any = await getDatasourcePlugin(plugin)
115
+ const retrieved = await getDatasourcePlugin(plugin)
111
116
  if (retrieved.integration) {
112
117
  return retrieved.integration
113
118
  } else {