Feat: Add collection fields as "extra" query config for MongoDB

This commit is contained in:
Christo 2021-07-08 14:38:49 +02:00
parent aa2691a452
commit 4096f4857c
5 changed files with 191 additions and 11 deletions

View File

@ -0,0 +1,48 @@
<script>
import { Select, Label, Input } from "@budibase/bbui"
/**
* This component takes the query object and populates the 'extra' property
* when a datasource has specified a configuration for these fields in SCHEMA.extra
*/
export let populateExtraQuery
export let config
export let query
$: extraFields = Object.keys(config).map(key => ({
...config[key],
key,
}))
$: extraQueryFields = query.fields.extra || {}
</script>
{#each extraFields as { key, displayName, type }}
<div class="config-field">
<Label>{displayName}</Label>
{#if type === "string"}
<Input
on:change={() => populateExtraQuery(extraQueryFields)}
bind:value={extraQueryFields[key]}
/>
{/if}
{#if type === "list"}
<Select
on:change={() => populateExtraQuery(extraQueryFields)}
bind:value={extraQueryFields[key]}
options={config[key].data[query.queryVerb]}
getOptionLabel={current => current}
/>
{/if}
</div>
{/each}
<style>
.config-field {
display: grid;
grid-template-columns: 20% 1fr;
grid-gap: var(--spacing-l);
align-items: center;
}
</style>

View File

@ -15,6 +15,7 @@
} from "@budibase/bbui" } from "@budibase/bbui"
import { notifications, Divider } from "@budibase/bbui" import { notifications, Divider } from "@budibase/bbui"
import api from "builderStore/api" import api from "builderStore/api"
import ExtraQueryConfig from "./ExtraQueryConfig.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte" import IntegrationQueryEditor from "components/integration/index.svelte"
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte" import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte"
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte" import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
@ -60,6 +61,14 @@
fields = fields fields = fields
} }
function resetDependentFields() {
if (query.fields.extra) query.fields.extra = {}
}
function populateExtraQuery(extraQueryFields) {
query.fields.extra = extraQueryFields
}
async function previewQuery() { async function previewQuery() {
try { try {
const response = await api.post(`/api/queries/preview`, { const response = await api.post(`/api/queries/preview`, {
@ -127,11 +136,19 @@
<Label>Function</Label> <Label>Function</Label>
<Select <Select
bind:value={query.queryVerb} bind:value={query.queryVerb}
on:change={resetDependentFields}
options={Object.keys(queryConfig)} options={Object.keys(queryConfig)}
getOptionLabel={verb => getOptionLabel={verb =>
queryConfig[verb]?.displayName || capitalise(verb)} queryConfig[verb]?.displayName || capitalise(verb)}
/> />
</div> </div>
{#if integrationInfo?.extra && query.queryVerb}
<ExtraQueryConfig
{query}
{populateExtraQuery}
config={integrationInfo.extra}
/>
{/if}
<ParameterBuilder bind:parameters={query.parameters} bindable={false} /> <ParameterBuilder bind:parameters={query.parameters} bindable={false} />
{/if} {/if}
</div> </div>

View File

@ -30,6 +30,7 @@ function generateQueryValidation() {
default: Joi.string().allow(""), default: Joi.string().allow(""),
})), })),
queryVerb: Joi.string().allow().required(), queryVerb: Joi.string().allow().required(),
extra: Joi.object().optional(),
schema: Joi.object({}).required().unknown(true) schema: Joi.object({}).required().unknown(true)
})) }))
} }
@ -39,6 +40,7 @@ function generateQueryPreviewValidation() {
return joiValidator.body(Joi.object({ return joiValidator.body(Joi.object({
fields: Joi.object().required(), fields: Joi.object().required(),
queryVerb: Joi.string().allow().required(), queryVerb: Joi.string().allow().required(),
extra: Joi.object().optional(),
datasourceId: Joi.string().required(), datasourceId: Joi.string().required(),
parameters: Joi.object({}).required().unknown(true) parameters: Joi.object({}).required().unknown(true)
})) }))

View File

@ -49,6 +49,15 @@ export interface QueryDefinition {
urlDisplay?: boolean urlDisplay?: boolean
} }
export interface ExtraQueryConfig {
[key: string]: {
displayName: string,
type: string,
required: boolean
data?: object
}
}
export interface Integration { export interface Integration {
docs: string docs: string
plus?: boolean plus?: boolean
@ -58,6 +67,7 @@ export interface Integration {
query: { query: {
[key: string]: QueryDefinition [key: string]: QueryDefinition
} }
extra?: ExtraQueryConfig
} }
export interface SearchFilters { export interface SearchFilters {

View File

@ -10,7 +10,7 @@ module MongoDBModule {
interface MongoDBConfig { interface MongoDBConfig {
connectionString: string connectionString: string
db: string db: string
collection: string // collection: string
} }
const SCHEMA: Integration = { const SCHEMA: Integration = {
@ -28,10 +28,6 @@ module MongoDBModule {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldTypes.STRING,
required: true, required: true,
}, },
collection: {
type: DatasourceFieldTypes.STRING,
required: true,
},
}, },
query: { query: {
create: { create: {
@ -40,7 +36,31 @@ module MongoDBModule {
read: { read: {
type: QueryTypes.JSON, type: QueryTypes.JSON,
}, },
update: {
type: QueryTypes.JSON,
},
delete: {
type: QueryTypes.JSON,
}
}, },
extra: {
collection: {
displayName: "Collection",
type: DatasourceFieldTypes.STRING,
required: true,
},
actionTypes: {
displayName: "Action Types",
type: DatasourceFieldTypes.LIST,
required: true,
data: {
read: ['find', 'findOne', 'findOneAndUpdate', "count", "distinct"],
create: ['insertOne', 'insertMany'],
update: ['updateOne', 'updateMany'],
delete: ['deleteOne', 'deleteMany']
}
}
}
} }
class MongoIntegration { class MongoIntegration {
@ -56,12 +76,25 @@ module MongoDBModule {
return this.client.connect() return this.client.connect()
} }
async create(query: { json: object }) { async create(query: { json: object, extra: { [key: string]: string } }) {
try { try {
await this.connect() await this.connect()
const db = this.client.db(this.config.db) const db = this.client.db(this.config.db)
const collection = db.collection(this.config.collection) const collection = db.collection(query.extra.collection)
return collection.insertOne(query.json)
// For mongodb we add an extra actionType to specify
// which method we want to call on the collection
switch(query.extra.actionTypes) {
case 'insertOne': {
return collection.insertOne(query.json)
}
case 'insertMany': {
return collection.insertOne(query.json).toArray()
}
default: {
throw new Error(`actionType ${query.extra.actionTypes} does not exist on DB for create`)
}
}
} catch (err) { } catch (err) {
console.error("Error writing to mongodb", err) console.error("Error writing to mongodb", err)
throw err throw err
@ -70,12 +103,32 @@ module MongoDBModule {
} }
} }
async read(query: { json: object }) { async read(query: { json: object, extra: { [key: string]: string } }) {
try { try {
await this.connect() await this.connect()
const db = this.client.db(this.config.db) const db = this.client.db(this.config.db)
const collection = db.collection(this.config.collection) const collection = db.collection(query.extra.collection)
return collection.find(query.json).toArray()
switch(query.extra.actionTypes) {
case 'find': {
return collection.find(query.json).toArray()
}
case 'findOne': {
return collection.findOne(query.json)
}
case 'findOneAndUpdate': {
return collection.findOneAndUpdate(query.json)
}
case 'count': {
return collection.countDocuments(query.json)
}
case 'distinct': {
return collection.distinct(query.json)
}
default: {
throw new Error(`actionType ${query.extra.actionTypes} does not exist on DB for read`)
}
}
} catch (err) { } catch (err) {
console.error("Error querying mongodb", err) console.error("Error querying mongodb", err)
throw err throw err
@ -83,6 +136,56 @@ module MongoDBModule {
await this.client.close() await this.client.close()
} }
} }
async update(query: { json: object, extra: { [key: string]: string } }) {
try {
await this.connect()
const db = this.client.db(this.config.db)
const collection = db.collection(query.extra.collection)
switch(query.extra.actionTypes) {
case 'updateOne': {
return collection.updateOne(query.json)
}
case 'updateMany': {
return collection.updateMany(query.json).toArray()
}
default: {
throw new Error(`actionType ${query.extra.actionTypes} does not exist on DB for update`)
}
}
} catch (err) {
console.error("Error writing to mongodb", err)
throw err
} finally {
await this.client.close()
}
}
async delete(query: { json: object, extra: { [key: string]: string } }) {
try {
await this.connect()
const db = this.client.db(this.config.db)
const collection = db.collection(query.extra.collection)
switch(query.extra.actionTypes) {
case 'deleteOne': {
return collection.deleteOne(query.json)
}
case 'deleteMany': {
return collection.deleteMany(query.json).toArray()
}
default: {
throw new Error(`actionType ${query.extra.actionTypes} does not exist on DB for delete`)
}
}
} catch (err) {
console.error("Error writing to mongodb", err)
throw err
} finally {
await this.client.close()
}
}
} }
module.exports = { module.exports = {