Google sheets integration CRUD E2E
This commit is contained in:
parent
7d024f285d
commit
f9b2a3c5e1
|
@ -0,0 +1,184 @@
|
|||
<script>
|
||||
export let width = "100"
|
||||
export let height = "100"
|
||||
</script>
|
||||
|
||||
<svg
|
||||
{width}
|
||||
{height}
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 50 80"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
||||
id="path-1"
|
||||
/>
|
||||
<path
|
||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
||||
id="path-3"
|
||||
/>
|
||||
<path
|
||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
||||
id="path-5"
|
||||
/>
|
||||
<linearGradient
|
||||
x1="50.0053945%"
|
||||
y1="8.58610612%"
|
||||
x2="50.0053945%"
|
||||
y2="100.013939%"
|
||||
id="linearGradient-7"
|
||||
>
|
||||
<stop stop-color="#263238" stop-opacity="0.2" offset="0%" />
|
||||
<stop stop-color="#263238" stop-opacity="0.02" offset="100%" />
|
||||
</linearGradient>
|
||||
<path
|
||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
||||
id="path-8"
|
||||
/>
|
||||
<path
|
||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
||||
id="path-10"
|
||||
/>
|
||||
<path
|
||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
||||
id="path-12"
|
||||
/>
|
||||
<path
|
||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
||||
id="path-14"
|
||||
/>
|
||||
<radialGradient
|
||||
cx="3.16804688%"
|
||||
cy="2.71744318%"
|
||||
fx="3.16804688%"
|
||||
fy="2.71744318%"
|
||||
r="161.248516%"
|
||||
gradientTransform="translate(0.031680,0.027174),scale(1.000000,0.727273),translate(-0.031680,-0.027174)"
|
||||
id="radialGradient-16"
|
||||
>
|
||||
<stop stop-color="#FFFFFF" stop-opacity="0.1" offset="0%" />
|
||||
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g
|
||||
id="Consumer-Apps-Sheets-Large-VD-R8-"
|
||||
transform="translate(-451.000000, -451.000000)"
|
||||
>
|
||||
<g id="Hero" transform="translate(0.000000, 63.000000)">
|
||||
<g id="Personal" transform="translate(277.000000, 299.000000)">
|
||||
<g id="Sheets-icon" transform="translate(174.833333, 89.958333)">
|
||||
<g id="Group">
|
||||
<g id="Clipped">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1" />
|
||||
</mask>
|
||||
<g id="SVGID_1_" />
|
||||
<path
|
||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L36.9791667,10.3541667 L29.5833333,0 Z"
|
||||
id="Path"
|
||||
fill="#0F9D58"
|
||||
fill-rule="nonzero"
|
||||
mask="url(#mask-2)"
|
||||
/>
|
||||
</g>
|
||||
<g id="Clipped">
|
||||
<mask id="mask-4" fill="white">
|
||||
<use xlink:href="#path-3" />
|
||||
</mask>
|
||||
<g id="SVGID_1_" />
|
||||
<path
|
||||
d="M11.8333333,31.8020833 L11.8333333,53.25 L35.5,53.25 L35.5,31.8020833 L11.8333333,31.8020833 Z M22.1875,50.2916667 L14.7916667,50.2916667 L14.7916667,46.59375 L22.1875,46.59375 L22.1875,50.2916667 Z M22.1875,44.375 L14.7916667,44.375 L14.7916667,40.6770833 L22.1875,40.6770833 L22.1875,44.375 Z M22.1875,38.4583333 L14.7916667,38.4583333 L14.7916667,34.7604167 L22.1875,34.7604167 L22.1875,38.4583333 Z M32.5416667,50.2916667 L25.1458333,50.2916667 L25.1458333,46.59375 L32.5416667,46.59375 L32.5416667,50.2916667 Z M32.5416667,44.375 L25.1458333,44.375 L25.1458333,40.6770833 L32.5416667,40.6770833 L32.5416667,44.375 Z M32.5416667,38.4583333 L25.1458333,38.4583333 L25.1458333,34.7604167 L32.5416667,34.7604167 L32.5416667,38.4583333 Z"
|
||||
id="Shape"
|
||||
fill="#F1F1F1"
|
||||
fill-rule="nonzero"
|
||||
mask="url(#mask-4)"
|
||||
/>
|
||||
</g>
|
||||
<g id="Clipped">
|
||||
<mask id="mask-6" fill="white">
|
||||
<use xlink:href="#path-5" />
|
||||
</mask>
|
||||
<g id="SVGID_1_" />
|
||||
<polygon
|
||||
id="Path"
|
||||
fill="url(#linearGradient-7)"
|
||||
fill-rule="nonzero"
|
||||
mask="url(#mask-6)"
|
||||
points="30.8813021 16.4520313 47.3333333 32.9003646 47.3333333 17.75"
|
||||
/>
|
||||
</g>
|
||||
<g id="Clipped">
|
||||
<mask id="mask-9" fill="white">
|
||||
<use xlink:href="#path-8" />
|
||||
</mask>
|
||||
<g id="SVGID_1_" />
|
||||
<g id="Group" mask="url(#mask-9)">
|
||||
<g transform="translate(26.625000, -2.958333)">
|
||||
<path
|
||||
d="M2.95833333,2.95833333 L2.95833333,16.2708333 C2.95833333,18.7225521 4.94411458,20.7083333 7.39583333,20.7083333 L20.7083333,20.7083333 L2.95833333,2.95833333 Z"
|
||||
id="Path"
|
||||
fill="#87CEAC"
|
||||
fill-rule="nonzero"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Clipped">
|
||||
<mask id="mask-11" fill="white">
|
||||
<use xlink:href="#path-10" />
|
||||
</mask>
|
||||
<g id="SVGID_1_" />
|
||||
<path
|
||||
d="M4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,4.80729167 C0,2.36666667 1.996875,0.369791667 4.4375,0.369791667 L29.5833333,0.369791667 L29.5833333,0 L4.4375,0 Z"
|
||||
id="Path"
|
||||
fill-opacity="0.2"
|
||||
fill="#FFFFFF"
|
||||
fill-rule="nonzero"
|
||||
mask="url(#mask-11)"
|
||||
/>
|
||||
</g>
|
||||
<g id="Clipped">
|
||||
<mask id="mask-13" fill="white">
|
||||
<use xlink:href="#path-12" />
|
||||
</mask>
|
||||
<g id="SVGID_1_" />
|
||||
<path
|
||||
d="M42.8958333,64.7135417 L4.4375,64.7135417 C1.996875,64.7135417 0,62.7166667 0,60.2760417 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,60.2760417 C47.3333333,62.7166667 45.3364583,64.7135417 42.8958333,64.7135417 Z"
|
||||
id="Path"
|
||||
fill-opacity="0.2"
|
||||
fill="#263238"
|
||||
fill-rule="nonzero"
|
||||
mask="url(#mask-13)"
|
||||
/>
|
||||
</g>
|
||||
<g id="Clipped">
|
||||
<mask id="mask-15" fill="white">
|
||||
<use xlink:href="#path-14" />
|
||||
</mask>
|
||||
<g id="SVGID_1_" />
|
||||
<path
|
||||
d="M34.0208333,17.75 C31.5691146,17.75 29.5833333,15.7642188 29.5833333,13.3125 L29.5833333,13.6822917 C29.5833333,16.1340104 31.5691146,18.1197917 34.0208333,18.1197917 L47.3333333,18.1197917 L47.3333333,17.75 L34.0208333,17.75 Z"
|
||||
id="Path"
|
||||
fill-opacity="0.1"
|
||||
fill="#263238"
|
||||
fill-rule="nonzero"
|
||||
mask="url(#mask-15)"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
||||
id="Path"
|
||||
fill="url(#radialGradient-16)"
|
||||
fill-rule="nonzero"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
|
@ -11,6 +11,7 @@ import ArangoDB from "./ArangoDB.svelte"
|
|||
import Rest from "./Rest.svelte"
|
||||
import Budibase from "./Budibase.svelte"
|
||||
import Oracle from "./Oracle.svelte"
|
||||
import GoogleSheets from "./GoogleSheets.svelte"
|
||||
|
||||
export default {
|
||||
BUDIBASE: Budibase,
|
||||
|
@ -26,4 +27,5 @@ export default {
|
|||
ARANGODB: ArangoDB,
|
||||
REST: Rest,
|
||||
ORACLE: Oracle,
|
||||
GOOGLE_SHEETS: GoogleSheets,
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ export const IntegrationNames = {
|
|||
AIRTABLE: "Airtable",
|
||||
ARANGODB: "ArangoDB",
|
||||
ORACLE: "Oracle",
|
||||
GOOGLE_SHEETS: "Google Sheets",
|
||||
}
|
||||
|
||||
// fields on the user table that cannot be edited
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
"download": "8.0.0",
|
||||
"fix-path": "3.0.0",
|
||||
"fs-extra": "8.1.0",
|
||||
"google-spreadsheet": "^3.2.0",
|
||||
"jimp": "0.16.1",
|
||||
"joi": "17.2.1",
|
||||
"jsonschema": "1.4.0",
|
||||
|
|
|
@ -47,6 +47,7 @@ export enum SourceNames {
|
|||
ARANGODB = "ARANGODB",
|
||||
REST = "REST",
|
||||
ORACLE = "ORACLE",
|
||||
GOOGLE_SHEETS = "GOOGLE_SHEETS",
|
||||
}
|
||||
|
||||
export enum IncludeRelationships {
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
import {
|
||||
DatasourceFieldTypes,
|
||||
Integration,
|
||||
QueryTypes,
|
||||
} from "../definitions/datasource"
|
||||
import { IntegrationBase } from "./base/IntegrationBase"
|
||||
import { GoogleSpreadsheet } from "google-spreadsheet"
|
||||
|
||||
module GoogleSheetsModule {
|
||||
interface GoogleSheetsConfig {
|
||||
spreadsheetId: string
|
||||
clientEmail: string
|
||||
privateKey: string
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
docs: "https://developers.google.com/sheets/api/quickstart/nodejs",
|
||||
description:
|
||||
"Create and collaborate on online spreadsheets in real-time and from any device. ",
|
||||
friendlyName: "Google Sheets",
|
||||
datasource: {
|
||||
spreadsheetId: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
clientEmail: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
required: true,
|
||||
},
|
||||
privateKey: {
|
||||
type: DatasourceFieldTypes.LONGFORM,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
type: QueryTypes.FIELDS,
|
||||
fields: {
|
||||
sheet: {
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
row: {
|
||||
type: QueryTypes.JSON,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
read: {
|
||||
type: QueryTypes.FIELDS,
|
||||
fields: {
|
||||
sheet: {
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
type: QueryTypes.FIELDS,
|
||||
fields: {
|
||||
sheet: {
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
rowIndex: {
|
||||
type: "number",
|
||||
required: true,
|
||||
},
|
||||
row: {
|
||||
type: QueryTypes.JSON,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
type: QueryTypes.FIELDS,
|
||||
fields: {
|
||||
sheet: {
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
rowIndex: {
|
||||
type: "number",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class GoogleSheetsIntegration implements IntegrationBase {
|
||||
private readonly config: GoogleSheetsConfig
|
||||
private client: any
|
||||
|
||||
constructor(config: GoogleSheetsConfig) {
|
||||
this.config = config
|
||||
this.client = new GoogleSpreadsheet(this.config.spreadsheetId)
|
||||
}
|
||||
|
||||
async connect() {
|
||||
try {
|
||||
await this.client.useServiceAccountAuth({
|
||||
// env var values are copied from service account credentials generated by google
|
||||
// see "Authentication" section in docs for more info
|
||||
client_email: this.config.clientEmail,
|
||||
private_key: this.config.privateKey,
|
||||
})
|
||||
await this.client.loadInfo()
|
||||
} catch (err) {
|
||||
console.error("Error connecting to google sheets", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
buildRowObject(headers: string[], values: string[]) {
|
||||
const rowObject = {}
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
rowObject[headers[i]] = values[i]
|
||||
}
|
||||
return rowObject
|
||||
}
|
||||
|
||||
async create(query: { sheet: string; row: string }) {
|
||||
try {
|
||||
await this.connect()
|
||||
const sheet = await this.client.sheetsByTitle[query.sheet]
|
||||
const rowToInsert = JSON.parse(query.row)
|
||||
const row = await sheet.addRow(rowToInsert)
|
||||
return [this.buildRowObject(sheet.headerValues, row._rawData)]
|
||||
} catch (err) {
|
||||
console.error("Error writing to google sheets", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async read(query: { sheet: string }) {
|
||||
try {
|
||||
await this.connect()
|
||||
const sheet = await this.client.sheetsByTitle[query.sheet]
|
||||
const rows = await sheet.getRows()
|
||||
const headerValues = sheet.headerValues
|
||||
const response = []
|
||||
for (let row of rows) {
|
||||
response.push(this.buildRowObject(headerValues, row._rawData))
|
||||
}
|
||||
return response
|
||||
} catch (err) {
|
||||
console.error("Error reading from google sheets", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async update(query: { sheet: string; rowIndex: number; row: string }) {
|
||||
try {
|
||||
await this.connect()
|
||||
const sheet = await this.client.sheetsByTitle[query.sheet]
|
||||
const rows = await sheet.getRows()
|
||||
const row = rows[query.rowIndex]
|
||||
if (row) {
|
||||
const updateValues = JSON.parse(query.row)
|
||||
for (let key in updateValues) {
|
||||
row[key] = updateValues[key]
|
||||
}
|
||||
await row.save()
|
||||
return [this.buildRowObject(sheet.headerValues, row._rawData)]
|
||||
} else {
|
||||
throw new Error("Row does not exist.")
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error reading from google sheets", err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async delete(query: { sheet: string; rowIndex: number }) {
|
||||
await this.connect()
|
||||
const sheet = await this.client.sheetsByTitle[query.sheet]
|
||||
const rows = await sheet.getRows()
|
||||
const row = rows[query.rowIndex]
|
||||
if (row) {
|
||||
await row.delete()
|
||||
return [{ deleted: query.rowIndex }]
|
||||
} else {
|
||||
throw new Error("Row does not exist.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
schema: SCHEMA,
|
||||
integration: GoogleSheetsIntegration,
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ const airtable = require("./airtable")
|
|||
const mysql = require("./mysql")
|
||||
const arangodb = require("./arangodb")
|
||||
const rest = require("./rest")
|
||||
const googlesheets = require("./googlesheets")
|
||||
const { SourceNames } = require("../definitions/datasource")
|
||||
|
||||
const DEFINITIONS = {
|
||||
|
@ -23,6 +24,7 @@ const DEFINITIONS = {
|
|||
[SourceNames.MYSQL]: mysql.schema,
|
||||
[SourceNames.ARANGODB]: arangodb.schema,
|
||||
[SourceNames.REST]: rest.schema,
|
||||
[SourceNames.GOOGLE_SHEETS]: googlesheets.schema,
|
||||
}
|
||||
|
||||
const INTEGRATIONS = {
|
||||
|
@ -37,6 +39,7 @@ const INTEGRATIONS = {
|
|||
[SourceNames.MYSQL]: mysql.integration,
|
||||
[SourceNames.ARANGODB]: arangodb.integration,
|
||||
[SourceNames.REST]: rest.integration,
|
||||
[SourceNames.GOOGLE_SHEETS]: googlesheets.integration,
|
||||
}
|
||||
|
||||
// optionally add oracle integration if the oracle binary can be installed
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue