Add type hierarchy for importers
This commit is contained in:
parent
c421499a46
commit
3923fd6831
|
@ -68,6 +68,7 @@
|
|||
"author": "Budibase",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "^10.0.3",
|
||||
"@budibase/auth": "^0.9.185-alpha.21",
|
||||
"@budibase/client": "^0.9.185-alpha.21",
|
||||
"@budibase/string-templates": "^0.9.185-alpha.21",
|
||||
|
@ -77,7 +78,6 @@
|
|||
"@koa/router": "8.0.0",
|
||||
"@sendgrid/mail": "7.1.1",
|
||||
"@sentry/node": "^6.0.0",
|
||||
"@types/swagger-schema-official": "^2.0.22",
|
||||
"airtable": "0.10.1",
|
||||
"arangojs": "7.2.0",
|
||||
"aws-sdk": "^2.767.0",
|
||||
|
@ -109,6 +109,7 @@
|
|||
"mysql2": "^2.3.1",
|
||||
"node-fetch": "2.6.0",
|
||||
"open": "7.3.0",
|
||||
"openapi-types": "^9.3.1",
|
||||
"pg": "8.5.1",
|
||||
"pino-pretty": "4.0.0",
|
||||
"posthog-node": "^1.1.4",
|
||||
|
|
|
@ -1,334 +0,0 @@
|
|||
import CouchDB from "../../../db"
|
||||
import { queryValidation } from "./validation"
|
||||
import { generateQueryID } from "../../../db/utils"
|
||||
import { Spec as Swagger2, Operation } from "swagger-schema-official"
|
||||
const curlconverter = require("curlconverter")
|
||||
import { URL } from "url"
|
||||
|
||||
// {
|
||||
// "_id": "query_datasource_d62738f2d72a466997ffbf46f4952404_e7258ad382cd4c37961b81730633ff2d",
|
||||
// "_rev": "1-e702a18eaa96c7cb4be1b402c34eaa59",
|
||||
// "datasourceId": "datasource_d62738f2d72a466997ffbf46f4952404",
|
||||
// "parameters": [
|
||||
// {
|
||||
// "name": "paramtest",
|
||||
// "default": "defaultValue"
|
||||
// }
|
||||
// ],
|
||||
// "fields": {
|
||||
// "headers": {
|
||||
// "headertest": "test"
|
||||
// },
|
||||
// "queryString": "query=test",
|
||||
// "path": "/path/test"
|
||||
// },
|
||||
// "queryVerb": "read",
|
||||
// "transformer": "return data.test",
|
||||
// "schema": {},
|
||||
// "name": "name",
|
||||
// "readable": true
|
||||
// }
|
||||
|
||||
// return joiValidator.body(Joi.object({
|
||||
// _id: Joi.string(),
|
||||
// _rev: Joi.string(),
|
||||
// name: Joi.string().required(),
|
||||
// fields: Joi.object().required(),
|
||||
// datasourceId: Joi.string().required(),
|
||||
// readable: Joi.boolean(),
|
||||
// parameters: Joi.array().items(Joi.object({
|
||||
// name: Joi.string(),
|
||||
// default: Joi.string().allow(""),
|
||||
// })),
|
||||
// queryVerb: Joi.string().allow().required(),
|
||||
// extra: Joi.object().optional(),
|
||||
// schema: Joi.object({}).required().unknown(true),
|
||||
// transformer: Joi.string().optional(),
|
||||
// }))
|
||||
|
||||
interface Parameter {
|
||||
name: string
|
||||
default: string
|
||||
}
|
||||
|
||||
interface Query {
|
||||
_id?: string
|
||||
datasourceId: string
|
||||
name: string
|
||||
parameters: Parameter[]
|
||||
fields: {
|
||||
headers: any
|
||||
queryString: string
|
||||
path: string
|
||||
}
|
||||
transformer: string | null
|
||||
schema: any
|
||||
readable: boolean
|
||||
queryVerb: string
|
||||
}
|
||||
|
||||
enum Strategy {
|
||||
SWAGGER2,
|
||||
OPENAPI3,
|
||||
CURL,
|
||||
}
|
||||
|
||||
enum MethodToVerb {
|
||||
get = "read",
|
||||
post = "create",
|
||||
put = "update",
|
||||
patch = "patch",
|
||||
delete = "delete",
|
||||
}
|
||||
|
||||
interface ImportResult {
|
||||
errorQueries: Query[]
|
||||
}
|
||||
|
||||
interface DatasourceInfo {
|
||||
url: string
|
||||
name: string
|
||||
defaultHeaders: any[]
|
||||
}
|
||||
|
||||
const parseImportStrategy = (data: string): Strategy => {
|
||||
try {
|
||||
const json = JSON.parse(data)
|
||||
if (json.swagger === "2.0") {
|
||||
return Strategy.CURL
|
||||
} else if (json.openapi?.includes("3.0")) {
|
||||
return Strategy.OPENAPI3
|
||||
}
|
||||
} catch (jsonError) {
|
||||
try {
|
||||
parseCurl(data)
|
||||
return Strategy.CURL
|
||||
} catch (curlError) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`The import data could not be processed`)
|
||||
}
|
||||
|
||||
const processPath = (path: string): string => {
|
||||
if (path?.startsWith("/")) {
|
||||
return path.substring(1)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// SWAGGER
|
||||
|
||||
const parseSwagger2Info = (swagger2: Swagger2): DatasourceInfo => {
|
||||
const scheme = swagger2.schemes?.includes("https") ? "https" : "http"
|
||||
const basePath = swagger2.basePath || ""
|
||||
const host = swagger2.host || "<host>"
|
||||
const url = `${scheme}://${host}${basePath}`
|
||||
const name = swagger2.info.title || "Swagger Import"
|
||||
|
||||
return {
|
||||
url: url,
|
||||
name: name,
|
||||
defaultHeaders: [],
|
||||
}
|
||||
}
|
||||
|
||||
const parseSwagger2Queries = (
|
||||
datasourceId: string,
|
||||
swagger2: Swagger2
|
||||
): Query[] => {
|
||||
const queries = []
|
||||
|
||||
for (let [pathName, path] of Object.entries(swagger2.paths)) {
|
||||
for (let [methodName, op] of Object.entries(path)) {
|
||||
let operation = op as Operation
|
||||
|
||||
const name = operation.operationId || pathName
|
||||
const queryString = ""
|
||||
const headers = {}
|
||||
const parameters: Parameter[] = []
|
||||
|
||||
const query = constructQuery(
|
||||
datasourceId,
|
||||
name,
|
||||
methodName,
|
||||
pathName,
|
||||
queryString,
|
||||
headers,
|
||||
parameters
|
||||
)
|
||||
queries.push(query)
|
||||
}
|
||||
}
|
||||
|
||||
return queries
|
||||
}
|
||||
|
||||
// OPEN API
|
||||
|
||||
const parseOpenAPI3Info = (data: any): DatasourceInfo => {
|
||||
return {
|
||||
url: "http://localhost:3000",
|
||||
name: "swagger",
|
||||
defaultHeaders: [],
|
||||
}
|
||||
}
|
||||
|
||||
const parseOpenAPI3Queries = (datasourceId: string, data: string): Query[] => {
|
||||
return []
|
||||
}
|
||||
|
||||
// CURL
|
||||
|
||||
const parseCurl = (data: string): any => {
|
||||
const curlJson = curlconverter.toJsonString(data)
|
||||
return JSON.parse(curlJson)
|
||||
}
|
||||
|
||||
const parseCurlDatasourceInfo = (data: any): DatasourceInfo => {
|
||||
const curl = parseCurl(data)
|
||||
|
||||
const url = new URL(curl.url)
|
||||
|
||||
return {
|
||||
url: url.origin,
|
||||
name: url.hostname,
|
||||
defaultHeaders: [],
|
||||
}
|
||||
}
|
||||
|
||||
const parseCurlQueries = (datasourceId: string, data: string): Query[] => {
|
||||
const curl = parseCurl(data)
|
||||
|
||||
const url = new URL(curl.url)
|
||||
const name = url.pathname
|
||||
const path = url.pathname
|
||||
const method = curl.method
|
||||
const queryString = url.search
|
||||
const headers = curl.headers
|
||||
|
||||
const query = constructQuery(
|
||||
datasourceId,
|
||||
name,
|
||||
method,
|
||||
path,
|
||||
queryString,
|
||||
headers
|
||||
)
|
||||
return [query]
|
||||
}
|
||||
|
||||
const verbFromMethod = (method: string) => {
|
||||
const verb = (<any>MethodToVerb)[method]
|
||||
if (!verb) {
|
||||
throw new Error(`Unsupported method: ${method}`)
|
||||
}
|
||||
return verb
|
||||
}
|
||||
|
||||
const constructQuery = (
|
||||
datasourceId: string,
|
||||
name: string,
|
||||
method: string,
|
||||
path: string,
|
||||
queryString: string,
|
||||
headers: any = {},
|
||||
parameters: Parameter[] = []
|
||||
): Query => {
|
||||
const readable = true
|
||||
const queryVerb = verbFromMethod(method)
|
||||
const transformer = "return data"
|
||||
const schema = {}
|
||||
path = processPath(path)
|
||||
|
||||
const query: Query = {
|
||||
datasourceId,
|
||||
name,
|
||||
parameters,
|
||||
fields: {
|
||||
headers,
|
||||
queryString,
|
||||
path,
|
||||
},
|
||||
transformer,
|
||||
schema,
|
||||
readable,
|
||||
queryVerb,
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
export const getDatasourceInfo = (data: string): DatasourceInfo => {
|
||||
const strategy = parseImportStrategy(data)
|
||||
|
||||
let info: DatasourceInfo
|
||||
switch (strategy) {
|
||||
case Strategy.SWAGGER2:
|
||||
info = parseSwagger2Info(JSON.parse(data))
|
||||
break
|
||||
case Strategy.OPENAPI3:
|
||||
info = parseOpenAPI3Info(JSON.parse(data))
|
||||
break
|
||||
case Strategy.CURL:
|
||||
info = parseCurlDatasourceInfo(data)
|
||||
break
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
export const importQueries = async (
|
||||
appId: string,
|
||||
datasourceId: string,
|
||||
data: string
|
||||
): Promise<ImportResult> => {
|
||||
const strategy = parseImportStrategy(data)
|
||||
|
||||
// constuct the queries
|
||||
let queries: Query[]
|
||||
switch (strategy) {
|
||||
case Strategy.SWAGGER2:
|
||||
queries = parseSwagger2Queries(datasourceId, JSON.parse(data))
|
||||
break
|
||||
case Strategy.OPENAPI3:
|
||||
queries = parseOpenAPI3Queries(datasourceId, JSON.parse(data))
|
||||
break
|
||||
case Strategy.CURL:
|
||||
queries = parseCurlQueries(datasourceId, data)
|
||||
break
|
||||
}
|
||||
|
||||
// validate queries
|
||||
const errorQueries = []
|
||||
const schema = queryValidation()
|
||||
queries = queries
|
||||
.filter(query => {
|
||||
const validation = schema.validate(query)
|
||||
if (validation.error) {
|
||||
errorQueries.push(query)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
.map(query => {
|
||||
query._id = generateQueryID(query.datasourceId)
|
||||
return query
|
||||
})
|
||||
|
||||
// persist queries
|
||||
const db = new CouchDB(appId)
|
||||
for (const query of queries) {
|
||||
try {
|
||||
await db.put(query)
|
||||
} catch (error) {
|
||||
errorQueries.push(query)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
errorQueries,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import CouchDB from "../../../../db"
|
||||
import { queryValidation } from "../validation"
|
||||
import { generateQueryID } from "../../../../db/utils"
|
||||
import { Query, ImportInfo, ImportSource } from "./sources/base"
|
||||
import { OpenAPI2 } from "./sources/openapi2"
|
||||
import { OpenAPI3 } from "./sources/openapi3"
|
||||
import { Curl } from "./sources/curl"
|
||||
|
||||
interface ImportResult {
|
||||
errorQueries: Query[]
|
||||
}
|
||||
|
||||
export class RestImporter {
|
||||
data: string
|
||||
sources: ImportSource[]
|
||||
source!: ImportSource
|
||||
|
||||
constructor(data: string) {
|
||||
this.data = data
|
||||
this.sources = [new OpenAPI2(), new OpenAPI3(), new Curl()]
|
||||
}
|
||||
|
||||
init = async () => {
|
||||
for (let source of this.sources) {
|
||||
if (await source.isSupported(this.data)){
|
||||
this.source = source
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getInfo = async (): Promise<ImportInfo> => {
|
||||
return this.source.getInfo()
|
||||
}
|
||||
|
||||
importQueries = async (
|
||||
appId: string,
|
||||
datasourceId: string,
|
||||
): Promise<ImportResult> => {
|
||||
|
||||
// constuct the queries
|
||||
let queries = await this.source.getQueries(datasourceId)
|
||||
|
||||
// validate queries
|
||||
const errorQueries = []
|
||||
const schema = queryValidation()
|
||||
queries = queries
|
||||
.filter(query => {
|
||||
const validation = schema.validate(query)
|
||||
if (validation.error) {
|
||||
errorQueries.push(query)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
.map(query => {
|
||||
query._id = generateQueryID(query.datasourceId)
|
||||
return query
|
||||
})
|
||||
|
||||
// persist queries
|
||||
const db = new CouchDB(appId)
|
||||
for (const query of queries) {
|
||||
try {
|
||||
await db.put(query)
|
||||
} catch (error) {
|
||||
errorQueries.push(query)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
errorQueries,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
export interface ImportInfo {
|
||||
url: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface QueryParameter {
|
||||
name: string
|
||||
default: string
|
||||
}
|
||||
|
||||
export interface Query {
|
||||
_id?: string
|
||||
datasourceId: string
|
||||
name: string
|
||||
parameters: QueryParameter[]
|
||||
fields: {
|
||||
headers: object
|
||||
queryString: string | null
|
||||
path: string
|
||||
requestBody?: object
|
||||
}
|
||||
transformer: string | null
|
||||
schema: any
|
||||
readable: boolean
|
||||
queryVerb: string
|
||||
}
|
||||
|
||||
enum MethodToVerb {
|
||||
get = "read",
|
||||
post = "create",
|
||||
put = "update",
|
||||
patch = "patch",
|
||||
delete = "delete",
|
||||
}
|
||||
|
||||
export abstract class ImportSource {
|
||||
|
||||
abstract isSupported(data: string): Promise<boolean>
|
||||
abstract getInfo(): Promise<ImportInfo>
|
||||
abstract getQueries(datasourceId: string): Promise<Query[]>
|
||||
|
||||
constructQuery = (
|
||||
datasourceId: string,
|
||||
name: string,
|
||||
method: string,
|
||||
path: string,
|
||||
queryString: string,
|
||||
headers: object = {},
|
||||
parameters: QueryParameter[] = [],
|
||||
requestBody: object | undefined = undefined,
|
||||
): Query => {
|
||||
const readable = true
|
||||
const queryVerb = this.verbFromMethod(method)
|
||||
const transformer = "return data"
|
||||
const schema = {}
|
||||
path = this.processPath(path)
|
||||
|
||||
const query: Query = {
|
||||
datasourceId,
|
||||
name,
|
||||
parameters,
|
||||
fields: {
|
||||
headers,
|
||||
queryString,
|
||||
path,
|
||||
requestBody
|
||||
},
|
||||
transformer,
|
||||
schema,
|
||||
readable,
|
||||
queryVerb,
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
verbFromMethod = (method: string) => {
|
||||
const verb = (<any>MethodToVerb)[method]
|
||||
if (!verb) {
|
||||
throw new Error(`Unsupported method: ${method}`)
|
||||
}
|
||||
return verb
|
||||
}
|
||||
|
||||
processPath = (path: string): string => {
|
||||
if (path?.startsWith("/")) {
|
||||
return path.substring(1)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
import { ImportSource } from "."
|
||||
import SwaggerParser from "@apidevtools/swagger-parser";
|
||||
import { OpenAPI } from "openapi-types";
|
||||
|
||||
export abstract class OpenAPISource extends ImportSource {
|
||||
|
||||
parseData = async (data: string): Promise<OpenAPI.Document> => {
|
||||
const json = JSON.parse(data)
|
||||
return SwaggerParser.validate(json, {})
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
import { ImportSource, ImportInfo, Query } from "./base"
|
||||
import { URL } from 'url'
|
||||
const curlconverter = require("curlconverter")
|
||||
|
||||
const parseCurl = (data: string): any => {
|
||||
const curlJson = curlconverter.toJsonString(data)
|
||||
return JSON.parse(curlJson)
|
||||
}
|
||||
|
||||
/**
|
||||
* Curl
|
||||
* https://curl.se/docs/manpage.html
|
||||
*/
|
||||
export class Curl extends ImportSource {
|
||||
curl: any
|
||||
|
||||
isSupported = async (data: string): Promise<boolean> => {
|
||||
try {
|
||||
const curl = parseCurl(data)
|
||||
this.curl = curl
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
getInfo = async (): Promise<ImportInfo> => {
|
||||
const url = new URL(this.curl.url)
|
||||
return {
|
||||
url: url.origin,
|
||||
name: url.hostname,
|
||||
}
|
||||
}
|
||||
|
||||
getQueries = async (datasourceId: string): Promise<Query[]> => {
|
||||
const url = new URL(this.curl.url)
|
||||
const name = url.pathname
|
||||
const path = url.pathname
|
||||
const method = this.curl.method
|
||||
const queryString = url.search
|
||||
const headers = this.curl.headers
|
||||
|
||||
const query = this.constructQuery(
|
||||
datasourceId,
|
||||
name,
|
||||
method,
|
||||
path,
|
||||
queryString,
|
||||
headers
|
||||
)
|
||||
|
||||
return [query]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import { ImportInfo, QueryParameter, Query } from "./base"
|
||||
import { OpenAPIV2 } from "openapi-types"
|
||||
import { OpenAPISource } from "./base/openapi";
|
||||
|
||||
const isBodyParameter = (param: OpenAPIV2.Parameter): param is OpenAPIV2.InBodyParameterObject => {
|
||||
return param.in === "body"
|
||||
}
|
||||
|
||||
const isParameter = (param: OpenAPIV2.Parameter | OpenAPIV2.ReferenceObject): param is OpenAPIV2.Parameter => {
|
||||
// we can guarantee this is not a reference object
|
||||
// due to the deferencing done by the parser library
|
||||
return true
|
||||
}
|
||||
|
||||
const isOpenAPI2 = (document: any): document is OpenAPIV2.Document => {
|
||||
if (document.swagger === "2.0") {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenAPI Version 2.0 - aka "Swagger"
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md
|
||||
*/
|
||||
export class OpenAPI2 extends OpenAPISource {
|
||||
document!: OpenAPIV2.Document
|
||||
|
||||
isSupported = async (data: string): Promise<boolean> => {
|
||||
try {
|
||||
const document: any = await this.parseData(data)
|
||||
if (isOpenAPI2(document)) {
|
||||
this.document = document
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
getInfo = async (): Promise<ImportInfo> => {
|
||||
const scheme = this.document.schemes?.includes("https") ? "https" : "http"
|
||||
const basePath = this.document.basePath || ""
|
||||
const host = this.document.host || "<host>"
|
||||
const url = `${scheme}://${host}${basePath}`
|
||||
const name = this.document.info.title || "Swagger Import"
|
||||
|
||||
return {
|
||||
url: url,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
getQueries = async (datasourceId: string): Promise<Query[]> => {
|
||||
const queries = []
|
||||
|
||||
let pathName: string
|
||||
let path: OpenAPIV2.PathItemObject
|
||||
|
||||
for ([pathName, path] of Object.entries(this.document.paths)) {
|
||||
for (let [methodName, op] of Object.entries(path)) {
|
||||
let operation = op as OpenAPIV2.OperationObject
|
||||
|
||||
const name = operation.operationId || pathName
|
||||
const queryString = ""
|
||||
const headers = {}
|
||||
let requestBody = undefined
|
||||
const parameters: QueryParameter[] = []
|
||||
|
||||
if (operation.parameters) {
|
||||
for (let param of operation.parameters) {
|
||||
if (isParameter(param)) {
|
||||
if (isBodyParameter(param)) {
|
||||
requestBody = {}
|
||||
} else {
|
||||
parameters.push({
|
||||
name: param.name,
|
||||
default: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const query = this.constructQuery(
|
||||
datasourceId,
|
||||
name,
|
||||
methodName,
|
||||
pathName,
|
||||
queryString,
|
||||
headers,
|
||||
parameters,
|
||||
requestBody
|
||||
)
|
||||
queries.push(query)
|
||||
}
|
||||
}
|
||||
|
||||
return queries
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import { ImportInfo, Query } from "./base"
|
||||
import { OpenAPISource } from "./base/openapi"
|
||||
import { OpenAPIV3 } from "openapi-types"
|
||||
|
||||
const isOpenAPI3 = (document: any): document is OpenAPIV3.Document => {
|
||||
return document.openapi === "3.0.0"
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenAPI Version 3.0.0
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md
|
||||
*/
|
||||
export class OpenAPI3 extends OpenAPISource {
|
||||
document!: OpenAPIV3.Document
|
||||
|
||||
isSupported = async (data: string): Promise<boolean> => {
|
||||
try {
|
||||
const document: any = await this.parseData(data)
|
||||
if (isOpenAPI3(document)) {
|
||||
this.document = document
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
getInfo = async (): Promise<ImportInfo> => {
|
||||
return {
|
||||
url: "http://localhost:3000",
|
||||
name: "swagger",
|
||||
}
|
||||
}
|
||||
|
||||
getQueries = async (datasourceId: string): Promise<Query[]> => {
|
||||
return []
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// const Airtable = require("airtable")
|
||||
// const AirtableIntegration = require("../airtable")
|
||||
|
||||
jest.mock("airtable")
|
||||
|
||||
// class TestConfiguration {
|
||||
// constructor(config = {}) {
|
||||
// this.integration = new AirtableIntegration.integration(config)
|
||||
// this.client = {
|
||||
// create: jest.fn(),
|
||||
// select: jest.fn(),
|
||||
// update: jest.fn(),
|
||||
// destroy: jest.fn(),
|
||||
// }
|
||||
// this.integration.client = () => this.client
|
||||
// }
|
||||
// }
|
||||
|
||||
describe("Airtable Integration", () => {
|
||||
let config
|
||||
|
||||
beforeEach(() => {
|
||||
config = new TestConfiguration()
|
||||
})
|
||||
|
||||
it("calls the create method with the correct params", async () => {
|
||||
const response = await config.integration.create({
|
||||
table: "test",
|
||||
json: {}
|
||||
})
|
||||
expect(config.client.create).toHaveBeenCalledWith([
|
||||
{
|
||||
fields: {}
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it("calls the read method with the correct params", async () => {
|
||||
const response = await config.integration.read({
|
||||
table: "test",
|
||||
view: "Grid view"
|
||||
})
|
||||
expect(config.client.select).toHaveBeenCalledWith({
|
||||
maxRecords: 10, view: "Grid view"
|
||||
})
|
||||
})
|
||||
|
||||
it("calls the update method with the correct params", async () => {
|
||||
const response = await config.integration.update({
|
||||
table: "test",
|
||||
id: "123",
|
||||
json: {
|
||||
name: "test"
|
||||
}
|
||||
})
|
||||
expect(config.client.update).toHaveBeenCalledWith([
|
||||
{
|
||||
id: "123",
|
||||
fields: { name: "test" }
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it("calls the delete method with the correct params", async () => {
|
||||
const ids = [1,2,3,4]
|
||||
const response = await config.integration.delete({
|
||||
ids
|
||||
})
|
||||
expect(config.client.destroy).toHaveBeenCalledWith(ids)
|
||||
})
|
||||
})
|
|
@ -4,8 +4,8 @@ const { generateQueryID, getQueryParams } = require("../../../db/utils")
|
|||
const { BaseQueryVerbs } = require("../../../constants")
|
||||
const env = require("../../../environment")
|
||||
const { Thread, ThreadType } = require("../../../threads")
|
||||
const { importQueries, getDatasourceInfo } = require("./import")
|
||||
const { save: saveDatasource } = require("../datasource")
|
||||
const { RestImporter } = require("./import")
|
||||
|
||||
const Runner = new Thread(ThreadType.QUERY, { timeoutMs: 10000 })
|
||||
|
||||
|
@ -37,16 +37,19 @@ exports.import = async ctx => {
|
|||
const body = ctx.request.body
|
||||
const data = body.data
|
||||
|
||||
const importer = new RestImporter(data)
|
||||
await importer.init()
|
||||
|
||||
let datasourceId
|
||||
if (!body.datasourceId) {
|
||||
// construct new datasource
|
||||
const info = getDatasourceInfo(data)
|
||||
const info = await importer.getInfo()
|
||||
let datasource = {
|
||||
type: "datasource",
|
||||
source: "REST",
|
||||
config: {
|
||||
url: info.url,
|
||||
defaultHeaders: info.defaultHeaders,
|
||||
defaultHeaders: [],
|
||||
},
|
||||
name: info.name,
|
||||
}
|
||||
|
@ -60,7 +63,7 @@ exports.import = async ctx => {
|
|||
datasourceId = body.datasourceId
|
||||
}
|
||||
|
||||
const importResult = await importQueries(ctx.appId, datasourceId, data)
|
||||
const importResult = await importer.importQueries(ctx.appId, datasourceId)
|
||||
|
||||
ctx.body = {
|
||||
...importResult,
|
||||
|
|
|
@ -7,6 +7,38 @@
|
|||
resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.1.tgz#7e2cb3fcfb5c8b12d7275afafbb6ec44913551b4"
|
||||
integrity sha512-uVgekyBXnOVkxp+CUssjN/gefARtudZC8duEn1vm0lBQFwGRZFlDEzU1QC+aIRWCrD1Z8OgRpmBYlSZ7QS003w==
|
||||
|
||||
"@apidevtools/json-schema-ref-parser@^9.0.6":
|
||||
version "9.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b"
|
||||
integrity sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==
|
||||
dependencies:
|
||||
"@jsdevtools/ono" "^7.1.3"
|
||||
"@types/json-schema" "^7.0.6"
|
||||
call-me-maybe "^1.0.1"
|
||||
js-yaml "^4.1.0"
|
||||
|
||||
"@apidevtools/openapi-schemas@^2.0.4":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz#9fa08017fb59d80538812f03fc7cac5992caaa17"
|
||||
integrity sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==
|
||||
|
||||
"@apidevtools/swagger-methods@^3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267"
|
||||
integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==
|
||||
|
||||
"@apidevtools/swagger-parser@^10.0.3":
|
||||
version "10.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz#32057ae99487872c4dd96b314a1ab4b95d89eaf5"
|
||||
integrity sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==
|
||||
dependencies:
|
||||
"@apidevtools/json-schema-ref-parser" "^9.0.6"
|
||||
"@apidevtools/openapi-schemas" "^2.0.4"
|
||||
"@apidevtools/swagger-methods" "^3.0.2"
|
||||
"@jsdevtools/ono" "^7.1.3"
|
||||
call-me-maybe "^1.0.1"
|
||||
z-schema "^5.0.1"
|
||||
|
||||
"@azure/abort-controller@^1.0.0":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.4.tgz#fd3c4d46c8ed67aace42498c8e2270960250eafd"
|
||||
|
@ -1826,6 +1858,11 @@
|
|||
"@babel/runtime" "^7.7.2"
|
||||
regenerator-runtime "^0.13.3"
|
||||
|
||||
"@jsdevtools/ono@^7.1.3":
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796"
|
||||
integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==
|
||||
|
||||
"@koa/router@8.0.0":
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@koa/router/-/router-8.0.0.tgz#fd4ffa6f03d8293a04c023cb4a22b612401fbe70"
|
||||
|
@ -2393,6 +2430,11 @@
|
|||
jest-diff "^26.0.0"
|
||||
pretty-format "^26.0.0"
|
||||
|
||||
"@types/json-schema@^7.0.6":
|
||||
version "7.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
|
||||
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
|
||||
|
||||
"@types/keygrip@*":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
|
||||
|
@ -2500,11 +2542,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
||||
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
||||
|
||||
"@types/swagger-schema-official@^2.0.22":
|
||||
version "2.0.22"
|
||||
resolved "https://registry.yarnpkg.com/@types/swagger-schema-official/-/swagger-schema-official-2.0.22.tgz#f7e06168e6994574dfd86928ac04b196870ab043"
|
||||
integrity sha512-7yQiX6MWSFSvc/1wW5smJMZTZ4fHOd+hqLr3qr/HONDxHEa2bnYAsOcGBOEqFIjd4yetwMOdEDdeW+udRAQnHA==
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "20.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129"
|
||||
|
@ -2865,6 +2902,11 @@ argparse@^1.0.10, argparse@^1.0.7:
|
|||
dependencies:
|
||||
sprintf-js "~1.0.2"
|
||||
|
||||
argparse@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||
|
||||
args@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/args/-/args-5.0.1.tgz#4bf298df90a4799a09521362c579278cc2fdd761"
|
||||
|
@ -3535,6 +3577,11 @@ call-bind@^1.0.0, call-bind@^1.0.2:
|
|||
function-bind "^1.1.1"
|
||||
get-intrinsic "^1.0.2"
|
||||
|
||||
call-me-maybe@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
|
||||
integrity sha1-JtII6onje1y95gJQoV8DHBak1ms=
|
||||
|
||||
callsites@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||
|
@ -3787,7 +3834,7 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
|
|||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^2.19.0, commander@^2.5.0, commander@^2.8.1:
|
||||
commander@^2.19.0, commander@^2.5.0, commander@^2.7.1, commander@^2.8.1:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
@ -7418,6 +7465,13 @@ js-yaml@^3.13.1:
|
|||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
|
||||
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
jsbi@^3.1.1:
|
||||
version "3.2.5"
|
||||
resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.2.5.tgz#b37bb90e0e5c2814c1c2a1bcd8c729888a2e37d6"
|
||||
|
@ -8096,6 +8150,11 @@ lodash.flatten@^4.4.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
||||
|
||||
lodash.get@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
||||
|
||||
lodash.includes@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||
|
@ -8913,6 +8972,11 @@ open@7.3.0:
|
|||
is-docker "^2.0.0"
|
||||
is-wsl "^2.1.1"
|
||||
|
||||
openapi-types@^9.3.1:
|
||||
version "9.3.1"
|
||||
resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-9.3.1.tgz#617ae6db3efd3e3f68e849f65ced58801d01d3cf"
|
||||
integrity sha512-/Yvsd2D7miYB4HLJ3hOOS0+vnowQpaT75FsHzr/y5M9P4q9bwa7RcbW2YdH6KZBn8ceLbKGnHxMZ1CHliGHUFw==
|
||||
|
||||
opencollective-postinstall@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
|
||||
|
@ -11887,6 +11951,11 @@ validate.js@0.13.1:
|
|||
resolved "https://registry.yarnpkg.com/validate.js/-/validate.js-0.13.1.tgz#b58bfac04a0f600a340f62e5227e70d95971e92a"
|
||||
integrity sha512-PnFM3xiZ+kYmLyTiMgTYmU7ZHkjBZz2/+F0DaALc/uUtVzdCt1wAosvYJ5hFQi/hz8O4zb52FQhHZRC+uVkJ+g==
|
||||
|
||||
validator@^13.7.0:
|
||||
version "13.7.0"
|
||||
resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857"
|
||||
integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==
|
||||
|
||||
vary@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
|
@ -12320,6 +12389,17 @@ yn@3.1.1:
|
|||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
||||
|
||||
z-schema@^5.0.1:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-5.0.2.tgz#f410394b2c9fcb9edaf6a7511491c0bb4e89a504"
|
||||
integrity sha512-40TH47ukMHq5HrzkeVE40Ad7eIDKaRV2b+Qpi2prLc9X9eFJFzV7tMe5aH12e6avaSS/u5l653EQOv+J9PirPw==
|
||||
dependencies:
|
||||
lodash.get "^4.4.2"
|
||||
lodash.isequal "^4.5.0"
|
||||
validator "^13.7.0"
|
||||
optionalDependencies:
|
||||
commander "^2.7.1"
|
||||
|
||||
zlib@1.0.5, zlib@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0"
|
||||
|
|
Loading…
Reference in New Issue