@budibase/server 1.0.46-alpha.5 → 1.0.46-alpha.6
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.298ba3d3.js → index.00221ff2.js} +234 -234
- package/builder/assets/{index.243ca52c.css → index.dd9e386f.css} +1 -1
- package/builder/index.html +2 -2
- package/coverage/clover.xml +157 -145
- package/coverage/coverage-final.json +4 -4
- package/coverage/lcov-report/api/controllers/analytics.js.html +1 -1
- package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/controllers/application.js.html +1 -1
- package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
- package/coverage/lcov-report/api/controllers/automation.js.html +1 -1
- package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
- package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
- package/coverage/lcov-report/api/controllers/component.js.html +1 -1
- package/coverage/lcov-report/api/controllers/datasource.js.html +44 -8
- package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
- package/coverage/lcov-report/api/controllers/index.html +15 -15
- package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
- package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
- package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
- package/coverage/lcov-report/api/controllers/permission.js.html +1 -1
- package/coverage/lcov-report/api/controllers/query/index.html +15 -15
- package/coverage/lcov-report/api/controllers/query/index.js.html +44 -11
- package/coverage/lcov-report/api/controllers/query/validation.js.html +1 -1
- package/coverage/lcov-report/api/controllers/role.js.html +1 -1
- package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/index.html +5 -5
- package/coverage/lcov-report/api/controllers/row/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/internal.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/utils.js.html +5 -14
- package/coverage/lcov-report/api/controllers/screen.js.html +1 -1
- package/coverage/lcov-report/api/controllers/script.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/external.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/index.html +1 -1
- package/coverage/lcov-report/api/controllers/table/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/internal.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/utils.js.html +1 -1
- package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
- package/coverage/lcov-report/api/controllers/user.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
- package/coverage/lcov-report/api/controllers/webhook.js.html +1 -1
- package/coverage/lcov-report/api/index.html +1 -1
- package/coverage/lcov-report/api/index.js.html +1 -1
- package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
- package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/routes/application.js.html +1 -1
- package/coverage/lcov-report/api/routes/auth.js.html +1 -1
- package/coverage/lcov-report/api/routes/automation.js.html +1 -1
- package/coverage/lcov-report/api/routes/backup.js.html +1 -1
- package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
- package/coverage/lcov-report/api/routes/component.js.html +1 -1
- package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
- package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
- package/coverage/lcov-report/api/routes/dev.js.html +1 -1
- package/coverage/lcov-report/api/routes/index.html +1 -1
- package/coverage/lcov-report/api/routes/index.js.html +1 -1
- package/coverage/lcov-report/api/routes/integration.js.html +1 -1
- package/coverage/lcov-report/api/routes/layout.js.html +1 -1
- package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
- package/coverage/lcov-report/api/routes/permission.js.html +1 -1
- package/coverage/lcov-report/api/routes/query.js.html +1 -1
- package/coverage/lcov-report/api/routes/role.js.html +1 -1
- package/coverage/lcov-report/api/routes/routing.js.html +1 -1
- package/coverage/lcov-report/api/routes/row.js.html +1 -1
- package/coverage/lcov-report/api/routes/screen.js.html +1 -1
- package/coverage/lcov-report/api/routes/script.js.html +1 -1
- package/coverage/lcov-report/api/routes/static.js.html +1 -1
- package/coverage/lcov-report/api/routes/table.js.html +1 -1
- package/coverage/lcov-report/api/routes/templates.js.html +1 -1
- package/coverage/lcov-report/api/routes/user.js.html +1 -1
- package/coverage/lcov-report/api/routes/view.js.html +1 -1
- package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/actions.js.html +1 -1
- package/coverage/lcov-report/automations/automationUtils.js.html +1 -1
- package/coverage/lcov-report/automations/bullboard.js.html +1 -1
- package/coverage/lcov-report/automations/index.html +1 -1
- package/coverage/lcov-report/automations/index.js.html +1 -1
- package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
- package/coverage/lcov-report/automations/steps/createRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
- package/coverage/lcov-report/automations/steps/deleteRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
- package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
- package/coverage/lcov-report/automations/steps/index.html +1 -1
- package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
- package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +1 -1
- package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
- package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
- package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
- package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
- package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
- package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/triggers.js.html +1 -1
- package/coverage/lcov-report/automations/utils.js.html +1 -1
- package/coverage/lcov-report/constants/index.html +5 -5
- package/coverage/lcov-report/constants/index.js.html +16 -4
- package/coverage/lcov-report/constants/layouts.js.html +1 -1
- package/coverage/lcov-report/constants/screens.js.html +1 -1
- package/coverage/lcov-report/db/client.js.html +1 -1
- package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
- package/coverage/lcov-report/db/index.html +1 -1
- package/coverage/lcov-report/db/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
- package/coverage/lcov-report/db/newid.js.html +1 -1
- package/coverage/lcov-report/db/utils.js.html +1 -1
- package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
- package/coverage/lcov-report/events/index.html +1 -1
- package/coverage/lcov-report/events/index.js.html +1 -1
- package/coverage/lcov-report/events/utils.js.html +1 -1
- package/coverage/lcov-report/index.html +26 -26
- package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
- package/coverage/lcov-report/integrations/tests/index.html +1 -1
- package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
- package/coverage/lcov-report/middleware/authorized.js.html +1 -1
- package/coverage/lcov-report/middleware/builder.js.html +1 -1
- package/coverage/lcov-report/middleware/currentapp.js.html +1 -1
- package/coverage/lcov-report/middleware/index.html +1 -1
- package/coverage/lcov-report/middleware/joi-validator.js.html +1 -1
- package/coverage/lcov-report/middleware/resourceId.js.html +1 -1
- package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
- package/coverage/lcov-report/middleware/usageQuota.js.html +1 -1
- package/coverage/lcov-report/middleware/utils.js.html +1 -1
- package/coverage/lcov-report/migrations/usageQuotas/index.html +1 -1
- package/coverage/lcov-report/migrations/usageQuotas/index.js.html +1 -1
- package/coverage/lcov-report/migrations/usageQuotas/syncApps.js.html +1 -1
- package/coverage/lcov-report/migrations/usageQuotas/syncRows.js.html +1 -1
- package/coverage/lcov-report/threads/automation.js.html +1 -1
- package/coverage/lcov-report/threads/index.html +1 -1
- package/coverage/lcov-report/threads/index.js.html +1 -1
- package/coverage/lcov-report/threads/query.js.html +1 -1
- package/coverage/lcov-report/threads/utils.js.html +1 -1
- package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
- package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
- package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
- package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
- package/coverage/lcov-report/utilities/global.js.html +1 -1
- package/coverage/lcov-report/utilities/index.html +1 -1
- package/coverage/lcov-report/utilities/index.js.html +1 -1
- package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +1 -1
- package/coverage/lcov-report/utilities/queue/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
- package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
- package/coverage/lcov-report/utilities/security.js.html +1 -1
- package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/index.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/index.js.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/rows.js.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +1 -1
- package/coverage/lcov-report/utilities/users.js.html +1 -1
- package/coverage/lcov-report/utilities/workerRequests.js.html +1 -1
- package/coverage/lcov.info +253 -226
- package/dist/api/controllers/datasource.js +11 -0
- package/dist/api/controllers/query/index.js +11 -3
- package/dist/api/controllers/row/utils.js +1 -2
- package/dist/constants/index.js +3 -0
- package/dist/definitions/datasource.js +1 -0
- package/dist/integrations/googlesheets.js +340 -0
- package/dist/integrations/index.js +3 -0
- package/dist/integrations/rest.js +5 -5
- package/dist/integrations/s3.js +1 -1
- package/dist/package.json +7 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -5
- package/src/api/controllers/datasource.js +12 -0
- package/src/api/controllers/query/index.js +14 -3
- package/src/api/controllers/row/utils.js +1 -4
- package/src/constants/index.js +4 -0
- package/src/definitions/datasource.ts +3 -0
- package/src/integrations/googlesheets.ts +353 -0
- package/src/integrations/index.ts +3 -0
- package/src/integrations/rest.ts +28 -9
- package/src/integrations/s3.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/server",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "1.0.46-alpha.
|
|
4
|
+
"version": "1.0.46-alpha.6",
|
|
5
5
|
"description": "Budibase Web Server",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
"license": "GPL-3.0",
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"@apidevtools/swagger-parser": "^10.0.3",
|
|
73
|
-
"@budibase/backend-core": "^1.0.46-alpha.
|
|
74
|
-
"@budibase/client": "^1.0.46-alpha.
|
|
75
|
-
"@budibase/string-templates": "^1.0.46-alpha.
|
|
73
|
+
"@budibase/backend-core": "^1.0.46-alpha.6",
|
|
74
|
+
"@budibase/client": "^1.0.46-alpha.6",
|
|
75
|
+
"@budibase/string-templates": "^1.0.46-alpha.6",
|
|
76
76
|
"@bull-board/api": "^3.7.0",
|
|
77
77
|
"@bull-board/koa": "^3.7.0",
|
|
78
78
|
"@elastic/elasticsearch": "7.10.0",
|
|
@@ -92,6 +92,8 @@
|
|
|
92
92
|
"fix-path": "3.0.0",
|
|
93
93
|
"form-data": "^4.0.0",
|
|
94
94
|
"fs-extra": "8.1.0",
|
|
95
|
+
"google-auth-library": "^7.11.0",
|
|
96
|
+
"google-spreadsheet": "^3.2.0",
|
|
95
97
|
"jimp": "0.16.1",
|
|
96
98
|
"joi": "17.2.1",
|
|
97
99
|
"js-yaml": "^4.1.0",
|
|
@@ -139,6 +141,7 @@
|
|
|
139
141
|
"@jest/test-sequencer": "^24.8.0",
|
|
140
142
|
"@types/apidoc": "^0.50.0",
|
|
141
143
|
"@types/bull": "^3.15.1",
|
|
144
|
+
"@types/google-spreadsheet": "^3.1.5",
|
|
142
145
|
"@types/jest": "^26.0.23",
|
|
143
146
|
"@types/koa": "^2.13.3",
|
|
144
147
|
"@types/koa-router": "^7.4.2",
|
|
@@ -165,5 +168,5 @@
|
|
|
165
168
|
"optionalDependencies": {
|
|
166
169
|
"oracledb": "^5.3.0"
|
|
167
170
|
},
|
|
168
|
-
"gitHead": "
|
|
171
|
+
"gitHead": "a3e5c5af7f047b46ba1d7875ad489f357632b267"
|
|
169
172
|
}
|
|
@@ -38,6 +38,13 @@ exports.fetch = async function (ctx) {
|
|
|
38
38
|
)
|
|
39
39
|
).rows.map(row => row.doc)
|
|
40
40
|
|
|
41
|
+
for (let datasource of datasources) {
|
|
42
|
+
if (datasource.config && datasource.config.auth) {
|
|
43
|
+
// strip secrets from response so they don't show in the network request
|
|
44
|
+
delete datasource.config.auth
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
41
48
|
ctx.body = [bbInternalDb, ...datasources]
|
|
42
49
|
}
|
|
43
50
|
|
|
@@ -94,8 +101,13 @@ exports.update = async function (ctx) {
|
|
|
94
101
|
const db = new CouchDB(ctx.appId)
|
|
95
102
|
const datasourceId = ctx.params.datasourceId
|
|
96
103
|
let datasource = await db.get(datasourceId)
|
|
104
|
+
const auth = datasource.config.auth
|
|
97
105
|
await invalidateVariables(datasource, ctx.request.body)
|
|
98
106
|
datasource = { ...datasource, ...ctx.request.body }
|
|
107
|
+
if (auth && !ctx.request.body.auth) {
|
|
108
|
+
// don't strip auth config from DB
|
|
109
|
+
datasource.config.auth = auth
|
|
110
|
+
}
|
|
99
111
|
|
|
100
112
|
const response = await db.put(datasource)
|
|
101
113
|
datasource._rev = response.rev
|
|
@@ -141,6 +141,16 @@ async function execute(ctx, opts = { rowsOnly: false }) {
|
|
|
141
141
|
const query = await db.get(ctx.params.queryId)
|
|
142
142
|
const datasource = await db.get(query.datasourceId)
|
|
143
143
|
|
|
144
|
+
const enrichedParameters = ctx.request.body.parameters || {}
|
|
145
|
+
// make sure parameters are fully enriched with defaults
|
|
146
|
+
if (query && query.parameters) {
|
|
147
|
+
for (let parameter of query.parameters) {
|
|
148
|
+
if (!enrichedParameters[parameter.name]) {
|
|
149
|
+
enrichedParameters[parameter.name] = parameter.default
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
144
154
|
// call the relevant CRUD method on the integration class
|
|
145
155
|
try {
|
|
146
156
|
const { rows, pagination, extra } = await Runner.run({
|
|
@@ -149,7 +159,7 @@ async function execute(ctx, opts = { rowsOnly: false }) {
|
|
|
149
159
|
queryVerb: query.queryVerb,
|
|
150
160
|
fields: query.fields,
|
|
151
161
|
pagination: ctx.request.body.pagination,
|
|
152
|
-
parameters:
|
|
162
|
+
parameters: enrichedParameters,
|
|
153
163
|
transformer: query.transformer,
|
|
154
164
|
queryId: ctx.params.queryId,
|
|
155
165
|
})
|
|
@@ -178,8 +188,9 @@ const removeDynamicVariables = async (db, queryId) => {
|
|
|
178
188
|
|
|
179
189
|
if (dynamicVariables) {
|
|
180
190
|
// delete dynamic variables from the datasource
|
|
181
|
-
|
|
182
|
-
|
|
191
|
+
datasource.config.dynamicVariables = dynamicVariables.filter(
|
|
192
|
+
dv => dv.queryId !== queryId
|
|
193
|
+
)
|
|
183
194
|
await db.put(datasource)
|
|
184
195
|
|
|
185
196
|
// invalidate the deleted variables
|
|
@@ -52,10 +52,7 @@ exports.validate = async ({ appId, tableId, row, table }) => {
|
|
|
52
52
|
const constraints = cloneDeep(table.schema[fieldName].constraints)
|
|
53
53
|
const type = table.schema[fieldName].type
|
|
54
54
|
// special case for options, need to always allow unselected (null)
|
|
55
|
-
if (
|
|
56
|
-
(type === FieldTypes.OPTIONS || type === FieldTypes.ARRAY) &&
|
|
57
|
-
constraints.inclusion
|
|
58
|
-
) {
|
|
55
|
+
if (type === FieldTypes.OPTIONS && constraints.inclusion) {
|
|
59
56
|
constraints.inclusion.push(null)
|
|
60
57
|
}
|
|
61
58
|
let res
|
package/src/constants/index.js
CHANGED
|
@@ -47,6 +47,7 @@ export enum SourceNames {
|
|
|
47
47
|
ARANGODB = "ARANGODB",
|
|
48
48
|
REST = "REST",
|
|
49
49
|
ORACLE = "ORACLE",
|
|
50
|
+
GOOGLE_SHEETS = "GOOGLE_SHEETS",
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export enum IncludeRelationships {
|
|
@@ -86,6 +87,8 @@ export interface ExtraQueryConfig {
|
|
|
86
87
|
export interface Integration {
|
|
87
88
|
docs: string
|
|
88
89
|
plus?: boolean
|
|
90
|
+
auth?: { type: string }
|
|
91
|
+
relationships?: boolean
|
|
89
92
|
description: string
|
|
90
93
|
friendlyName: string
|
|
91
94
|
datasource: {}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DatasourceFieldTypes,
|
|
3
|
+
Integration,
|
|
4
|
+
QueryJson,
|
|
5
|
+
QueryTypes,
|
|
6
|
+
} from "../definitions/datasource"
|
|
7
|
+
import { OAuth2Client } from "google-auth-library"
|
|
8
|
+
import { DatasourcePlus } from "./base/datasourcePlus"
|
|
9
|
+
import { Row, Table, TableSchema } from "../definitions/common"
|
|
10
|
+
import { buildExternalTableId } from "./utils"
|
|
11
|
+
import { DataSourceOperation, FieldTypes } from "../constants"
|
|
12
|
+
import { GoogleSpreadsheet } from "google-spreadsheet"
|
|
13
|
+
import { table } from "console"
|
|
14
|
+
|
|
15
|
+
module GoogleSheetsModule {
|
|
16
|
+
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
|
17
|
+
const { getScopedConfig } = require("@budibase/backend-core/db")
|
|
18
|
+
const { Configs } = require("@budibase/backend-core/constants")
|
|
19
|
+
|
|
20
|
+
interface GoogleSheetsConfig {
|
|
21
|
+
spreadsheetId: string
|
|
22
|
+
auth: OAuthClientConfig
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface OAuthClientConfig {
|
|
26
|
+
appId: string
|
|
27
|
+
accessToken: string
|
|
28
|
+
refreshToken: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const SCHEMA: Integration = {
|
|
32
|
+
plus: true,
|
|
33
|
+
auth: {
|
|
34
|
+
type: "google",
|
|
35
|
+
},
|
|
36
|
+
relationships: false,
|
|
37
|
+
docs: "https://developers.google.com/sheets/api/quickstart/nodejs",
|
|
38
|
+
description:
|
|
39
|
+
"Create and collaborate on online spreadsheets in real-time and from any device. ",
|
|
40
|
+
friendlyName: "Google Sheets",
|
|
41
|
+
datasource: {
|
|
42
|
+
spreadsheetId: {
|
|
43
|
+
type: DatasourceFieldTypes.STRING,
|
|
44
|
+
required: true,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
query: {
|
|
48
|
+
create: {
|
|
49
|
+
type: QueryTypes.FIELDS,
|
|
50
|
+
fields: {
|
|
51
|
+
sheet: {
|
|
52
|
+
type: DatasourceFieldTypes.STRING,
|
|
53
|
+
required: true,
|
|
54
|
+
},
|
|
55
|
+
row: {
|
|
56
|
+
type: QueryTypes.JSON,
|
|
57
|
+
required: true,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
read: {
|
|
62
|
+
type: QueryTypes.FIELDS,
|
|
63
|
+
fields: {
|
|
64
|
+
sheet: {
|
|
65
|
+
type: DatasourceFieldTypes.STRING,
|
|
66
|
+
required: true,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
update: {
|
|
71
|
+
type: QueryTypes.FIELDS,
|
|
72
|
+
fields: {
|
|
73
|
+
sheet: {
|
|
74
|
+
type: DatasourceFieldTypes.STRING,
|
|
75
|
+
required: true,
|
|
76
|
+
},
|
|
77
|
+
rowIndex: {
|
|
78
|
+
type: DatasourceFieldTypes.STRING,
|
|
79
|
+
required: true,
|
|
80
|
+
},
|
|
81
|
+
row: {
|
|
82
|
+
type: QueryTypes.JSON,
|
|
83
|
+
required: true,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
delete: {
|
|
88
|
+
type: QueryTypes.FIELDS,
|
|
89
|
+
fields: {
|
|
90
|
+
sheet: {
|
|
91
|
+
type: DatasourceFieldTypes.STRING,
|
|
92
|
+
required: true,
|
|
93
|
+
},
|
|
94
|
+
rowIndex: {
|
|
95
|
+
type: DatasourceFieldTypes.NUMBER,
|
|
96
|
+
required: true,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
class GoogleSheetsIntegration implements DatasourcePlus {
|
|
104
|
+
private readonly config: GoogleSheetsConfig
|
|
105
|
+
private client: any
|
|
106
|
+
public tables: Record<string, Table> = {}
|
|
107
|
+
public schemaErrors: Record<string, string> = {}
|
|
108
|
+
|
|
109
|
+
constructor(config: GoogleSheetsConfig) {
|
|
110
|
+
this.config = config
|
|
111
|
+
const spreadsheetId = this.cleanSpreadsheetUrl(this.config.spreadsheetId)
|
|
112
|
+
this.client = new GoogleSpreadsheet(spreadsheetId)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Pull the spreadsheet ID out from a valid google sheets URL
|
|
117
|
+
* @param spreadsheetId - the URL or standard spreadsheetId of the google sheet
|
|
118
|
+
* @returns spreadsheet Id of the google sheet
|
|
119
|
+
*/
|
|
120
|
+
cleanSpreadsheetUrl(spreadsheetId: string) {
|
|
121
|
+
if (!spreadsheetId) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"You must set a spreadsheet ID in your configuration to fetch tables."
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
const parts = spreadsheetId.split("/")
|
|
127
|
+
return parts.length > 5 ? parts[5] : spreadsheetId
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async connect() {
|
|
131
|
+
try {
|
|
132
|
+
// Initialise oAuth client
|
|
133
|
+
const db = getGlobalDB()
|
|
134
|
+
const googleConfig = await getScopedConfig(db, {
|
|
135
|
+
type: Configs.GOOGLE,
|
|
136
|
+
})
|
|
137
|
+
const oauthClient = new OAuth2Client({
|
|
138
|
+
clientId: googleConfig.clientID,
|
|
139
|
+
clientSecret: googleConfig.clientSecret,
|
|
140
|
+
})
|
|
141
|
+
oauthClient.credentials.access_token = this.config.auth.accessToken
|
|
142
|
+
oauthClient.credentials.refresh_token = this.config.auth.refreshToken
|
|
143
|
+
this.client.useOAuth2Client(oauthClient)
|
|
144
|
+
await this.client.loadInfo()
|
|
145
|
+
} catch (err) {
|
|
146
|
+
console.error("Error connecting to google sheets", err)
|
|
147
|
+
throw err
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async buildSchema(datasourceId: string) {
|
|
152
|
+
await this.connect()
|
|
153
|
+
const sheets = await this.client.sheetsByIndex
|
|
154
|
+
const tables: Record<string, Table> = {}
|
|
155
|
+
for (let sheet of sheets) {
|
|
156
|
+
// must fetch rows to determine schema
|
|
157
|
+
await sheet.getRows()
|
|
158
|
+
// build schema
|
|
159
|
+
const schema: TableSchema = {}
|
|
160
|
+
|
|
161
|
+
// build schema from headers
|
|
162
|
+
for (let header of sheet.headerValues) {
|
|
163
|
+
schema[header] = {
|
|
164
|
+
name: header,
|
|
165
|
+
type: FieldTypes.STRING,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// create tables
|
|
170
|
+
tables[sheet.title] = {
|
|
171
|
+
_id: buildExternalTableId(datasourceId, sheet.title),
|
|
172
|
+
name: sheet.title,
|
|
173
|
+
primary: ["rowNumber"],
|
|
174
|
+
schema,
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this.tables = tables
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async query(json: QueryJson) {
|
|
182
|
+
const sheet = json.endpoint.entityId
|
|
183
|
+
|
|
184
|
+
const handlers = {
|
|
185
|
+
[DataSourceOperation.CREATE]: () =>
|
|
186
|
+
this.create({ sheet, row: json.body }),
|
|
187
|
+
[DataSourceOperation.READ]: () => this.read({ sheet }),
|
|
188
|
+
[DataSourceOperation.UPDATE]: () =>
|
|
189
|
+
this.update({
|
|
190
|
+
// exclude the header row and zero index
|
|
191
|
+
rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
|
|
192
|
+
sheet,
|
|
193
|
+
row: json.body,
|
|
194
|
+
}),
|
|
195
|
+
[DataSourceOperation.DELETE]: () =>
|
|
196
|
+
this.delete({
|
|
197
|
+
// exclude the header row and zero index
|
|
198
|
+
rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
|
|
199
|
+
sheet,
|
|
200
|
+
}),
|
|
201
|
+
[DataSourceOperation.CREATE_TABLE]: () =>
|
|
202
|
+
this.createTable(json?.table?.name),
|
|
203
|
+
[DataSourceOperation.UPDATE_TABLE]: () => this.updateTable(json.table),
|
|
204
|
+
[DataSourceOperation.DELETE_TABLE]: () =>
|
|
205
|
+
this.deleteTable(json?.table?.name),
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const internalQueryMethod = handlers[json.endpoint.operation]
|
|
209
|
+
|
|
210
|
+
return await internalQueryMethod()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
buildRowObject(headers: string[], values: string[], rowNumber: number) {
|
|
214
|
+
const rowObject: { rowNumber: number; [key: string]: any } = { rowNumber }
|
|
215
|
+
for (let i = 0; i < headers.length; i++) {
|
|
216
|
+
rowObject._id = rowNumber
|
|
217
|
+
rowObject[headers[i]] = values[i]
|
|
218
|
+
}
|
|
219
|
+
return rowObject
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async createTable(name?: string) {
|
|
223
|
+
try {
|
|
224
|
+
await this.connect()
|
|
225
|
+
const sheet = await this.client.addSheet({ title: name })
|
|
226
|
+
return sheet
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.error("Error creating new table in google sheets", err)
|
|
229
|
+
throw err
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async updateTable(table?: any) {
|
|
234
|
+
try {
|
|
235
|
+
await this.connect()
|
|
236
|
+
const sheet = await this.client.sheetsByTitle[table.name]
|
|
237
|
+
await sheet.loadHeaderRow()
|
|
238
|
+
|
|
239
|
+
if (table._rename) {
|
|
240
|
+
const headers = []
|
|
241
|
+
for (let header of sheet.headerValues) {
|
|
242
|
+
if (header === table._rename.old) {
|
|
243
|
+
headers.push(table._rename.updated)
|
|
244
|
+
} else {
|
|
245
|
+
headers.push(header)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
await sheet.setHeaderRow(headers)
|
|
249
|
+
} else {
|
|
250
|
+
let newField = Object.keys(table.schema).find(
|
|
251
|
+
key => !sheet.headerValues.includes(key)
|
|
252
|
+
)
|
|
253
|
+
await sheet.setHeaderRow([...sheet.headerValues, newField])
|
|
254
|
+
}
|
|
255
|
+
} catch (err) {
|
|
256
|
+
console.error("Error updating table in google sheets", err)
|
|
257
|
+
throw err
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async deleteTable(sheet: any) {
|
|
262
|
+
try {
|
|
263
|
+
await this.connect()
|
|
264
|
+
const sheetToDelete = await this.client.sheetsByTitle[sheet]
|
|
265
|
+
return await sheetToDelete.delete()
|
|
266
|
+
} catch (err) {
|
|
267
|
+
console.error("Error deleting table in google sheets", err)
|
|
268
|
+
throw err
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async create(query: { sheet: string; row: any }) {
|
|
273
|
+
try {
|
|
274
|
+
await this.connect()
|
|
275
|
+
const sheet = await this.client.sheetsByTitle[query.sheet]
|
|
276
|
+
const rowToInsert =
|
|
277
|
+
typeof query.row === "string" ? JSON.parse(query.row) : query.row
|
|
278
|
+
const row = await sheet.addRow(rowToInsert)
|
|
279
|
+
return [
|
|
280
|
+
this.buildRowObject(sheet.headerValues, row._rawData, row._rowNumber),
|
|
281
|
+
]
|
|
282
|
+
} catch (err) {
|
|
283
|
+
console.error("Error writing to google sheets", err)
|
|
284
|
+
throw err
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async read(query: { sheet: string }) {
|
|
289
|
+
try {
|
|
290
|
+
await this.connect()
|
|
291
|
+
const sheet = await this.client.sheetsByTitle[query.sheet]
|
|
292
|
+
const rows = await sheet.getRows()
|
|
293
|
+
const headerValues = sheet.headerValues
|
|
294
|
+
const response = []
|
|
295
|
+
for (let row of rows) {
|
|
296
|
+
response.push(
|
|
297
|
+
this.buildRowObject(headerValues, row._rawData, row._rowNumber)
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
return response
|
|
301
|
+
} catch (err) {
|
|
302
|
+
console.error("Error reading from google sheets", err)
|
|
303
|
+
throw err
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async update(query: { sheet: string; rowIndex: number; row: any }) {
|
|
308
|
+
try {
|
|
309
|
+
await this.connect()
|
|
310
|
+
const sheet = await this.client.sheetsByTitle[query.sheet]
|
|
311
|
+
const rows = await sheet.getRows()
|
|
312
|
+
const row = rows[query.rowIndex]
|
|
313
|
+
if (row) {
|
|
314
|
+
const updateValues = query.row
|
|
315
|
+
for (let key in updateValues) {
|
|
316
|
+
row[key] = updateValues[key]
|
|
317
|
+
}
|
|
318
|
+
await row.save()
|
|
319
|
+
return [
|
|
320
|
+
this.buildRowObject(
|
|
321
|
+
sheet.headerValues,
|
|
322
|
+
row._rawData,
|
|
323
|
+
row._rowNumber
|
|
324
|
+
),
|
|
325
|
+
]
|
|
326
|
+
} else {
|
|
327
|
+
throw new Error("Row does not exist.")
|
|
328
|
+
}
|
|
329
|
+
} catch (err) {
|
|
330
|
+
console.error("Error reading from google sheets", err)
|
|
331
|
+
throw err
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async delete(query: { sheet: string; rowIndex: number }) {
|
|
336
|
+
await this.connect()
|
|
337
|
+
const sheet = await this.client.sheetsByTitle[query.sheet]
|
|
338
|
+
const rows = await sheet.getRows()
|
|
339
|
+
const row = rows[query.rowIndex]
|
|
340
|
+
if (row) {
|
|
341
|
+
await row.delete()
|
|
342
|
+
return [{ deleted: query.rowIndex }]
|
|
343
|
+
} else {
|
|
344
|
+
throw new Error("Row does not exist.")
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
module.exports = {
|
|
350
|
+
schema: SCHEMA,
|
|
351
|
+
integration: GoogleSheetsIntegration,
|
|
352
|
+
}
|
|
353
|
+
}
|
|
@@ -9,6 +9,7 @@ const airtable = require("./airtable")
|
|
|
9
9
|
const mysql = require("./mysql")
|
|
10
10
|
const arangodb = require("./arangodb")
|
|
11
11
|
const rest = require("./rest")
|
|
12
|
+
const googlesheets = require("./googlesheets")
|
|
12
13
|
const { SourceNames } = require("../definitions/datasource")
|
|
13
14
|
|
|
14
15
|
const DEFINITIONS = {
|
|
@@ -23,6 +24,7 @@ const DEFINITIONS = {
|
|
|
23
24
|
[SourceNames.MYSQL]: mysql.schema,
|
|
24
25
|
[SourceNames.ARANGODB]: arangodb.schema,
|
|
25
26
|
[SourceNames.REST]: rest.schema,
|
|
27
|
+
[SourceNames.GOOGLE_SHEETS]: googlesheets.schema,
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const INTEGRATIONS = {
|
|
@@ -37,6 +39,7 @@ const INTEGRATIONS = {
|
|
|
37
39
|
[SourceNames.MYSQL]: mysql.integration,
|
|
38
40
|
[SourceNames.ARANGODB]: arangodb.integration,
|
|
39
41
|
[SourceNames.REST]: rest.integration,
|
|
42
|
+
[SourceNames.GOOGLE_SHEETS]: googlesheets.integration,
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
// optionally add oracle integration if the oracle binary can be installed
|
package/src/integrations/rest.ts
CHANGED
|
@@ -43,8 +43,8 @@ const coreFields = {
|
|
|
43
43
|
enum: Object.values(BodyTypes),
|
|
44
44
|
},
|
|
45
45
|
pagination: {
|
|
46
|
-
type: DatasourceFieldTypes.OBJECT
|
|
47
|
-
}
|
|
46
|
+
type: DatasourceFieldTypes.OBJECT,
|
|
47
|
+
},
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
module RestModule {
|
|
@@ -178,12 +178,17 @@ module RestModule {
|
|
|
178
178
|
headers,
|
|
179
179
|
},
|
|
180
180
|
pagination: {
|
|
181
|
-
cursor: nextCursor
|
|
182
|
-
}
|
|
181
|
+
cursor: nextCursor,
|
|
182
|
+
},
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
getUrl(
|
|
186
|
+
getUrl(
|
|
187
|
+
path: string,
|
|
188
|
+
queryString: string,
|
|
189
|
+
pagination: PaginationConfig | null,
|
|
190
|
+
paginationValues: PaginationValues | null
|
|
191
|
+
): string {
|
|
187
192
|
// Add pagination params to query string if required
|
|
188
193
|
if (pagination?.location === "query" && paginationValues) {
|
|
189
194
|
const { pageParam, sizeParam } = pagination
|
|
@@ -217,14 +222,22 @@ module RestModule {
|
|
|
217
222
|
return complete
|
|
218
223
|
}
|
|
219
224
|
|
|
220
|
-
addBody(
|
|
225
|
+
addBody(
|
|
226
|
+
bodyType: string,
|
|
227
|
+
body: string | any,
|
|
228
|
+
input: any,
|
|
229
|
+
pagination: PaginationConfig | null,
|
|
230
|
+
paginationValues: PaginationValues | null
|
|
231
|
+
) {
|
|
221
232
|
if (!input.headers) {
|
|
222
233
|
input.headers = {}
|
|
223
234
|
}
|
|
224
235
|
if (bodyType === BodyTypes.NONE) {
|
|
225
236
|
return input
|
|
226
237
|
}
|
|
227
|
-
let error,
|
|
238
|
+
let error,
|
|
239
|
+
object: any = {},
|
|
240
|
+
string = ""
|
|
228
241
|
try {
|
|
229
242
|
if (body) {
|
|
230
243
|
string = typeof body !== "string" ? JSON.stringify(body) : body
|
|
@@ -333,7 +346,7 @@ module RestModule {
|
|
|
333
346
|
requestBody,
|
|
334
347
|
authConfigId,
|
|
335
348
|
pagination,
|
|
336
|
-
paginationValues
|
|
349
|
+
paginationValues,
|
|
337
350
|
} = query
|
|
338
351
|
const authHeaders = this.getAuthHeaders(authConfigId)
|
|
339
352
|
|
|
@@ -352,7 +365,13 @@ module RestModule {
|
|
|
352
365
|
}
|
|
353
366
|
|
|
354
367
|
let input: any = { method, headers: this.headers }
|
|
355
|
-
input = this.addBody(
|
|
368
|
+
input = this.addBody(
|
|
369
|
+
bodyType,
|
|
370
|
+
requestBody,
|
|
371
|
+
input,
|
|
372
|
+
pagination,
|
|
373
|
+
paginationValues
|
|
374
|
+
)
|
|
356
375
|
|
|
357
376
|
this.startTimeMs = performance.now()
|
|
358
377
|
const url = this.getUrl(path, queryString, pagination, paginationValues)
|