Adding in backend implementation of the query step, as well as some front-end work to support the filter drawer within automations.

This commit is contained in:
mike12345567 2021-09-15 19:02:44 +01:00
parent 87ee17ffee
commit b18a082951
7 changed files with 257 additions and 22 deletions

View File

@ -2,8 +2,16 @@
import TableSelector from "./TableSelector.svelte" import TableSelector from "./TableSelector.svelte"
import RowSelector from "./RowSelector.svelte" import RowSelector from "./RowSelector.svelte"
import SchemaSetup from "./SchemaSetup.svelte" import SchemaSetup from "./SchemaSetup.svelte"
import { Button, Input, Select, Label } from "@budibase/bbui" import {
Button,
Input,
Select,
Label,
ActionButton,
Drawer,
} from "@budibase/bbui"
import { automationStore } from "builderStore" import { automationStore } from "builderStore"
import { tables } from "stores/backend"
import WebhookDisplay from "../Shared/WebhookDisplay.svelte" import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte" import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte" import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
@ -15,11 +23,17 @@
import { database } from "stores/backend" import { database } from "stores/backend"
import { debounce } from "lodash" import { debounce } from "lodash"
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte" import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
import FilterDrawer from "components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte"
// need the client lucene builder to convert to the structure API expects
import { buildLuceneQuery } from "../../../../../client/src/utils/lucene"
export let block export let block
export let webhookModal export let webhookModal
export let testData export let testData
export let schemaProperties export let schemaProperties
let drawer
let tempFilters = lookForFilters(schemaProperties) || []
$: stepId = block.stepId $: stepId = block.stepId
$: bindings = getAvailableBindings( $: bindings = getAvailableBindings(
block || $automationStore.selectedBlock, block || $automationStore.selectedBlock,
@ -28,6 +42,11 @@
$: instanceId = $database._id $: instanceId = $database._id
$: inputData = testData ? testData : block.inputs $: inputData = testData ? testData : block.inputs
$: tableId = inputData ? inputData.tableId : null
$: table = tableId
? $tables.list.find(table => table._id === inputData.tableId)
: { schema: {} }
$: schemaFields = table ? Object.values(table.schema) : []
const onChange = debounce(async function (e, key) { const onChange = debounce(async function (e, key) {
if (testData) { if (testData) {
@ -71,6 +90,35 @@
} }
return bindings return bindings
} }
function lookForFilters(properties) {
console.log("testing")
if (!properties) {
return []
}
let filters
const inputs = testData ? testData : block.inputs
for (let [key, field] of properties) {
// need to look for the builder definition (keyed separately, see saveFilters)
const defKey = `${key}-def`
if (field.customType === "filters" && inputs?.[defKey]) {
filters = inputs[defKey]
break
}
}
return filters || []
}
function saveFilters(key) {
const filters = buildLuceneQuery(tempFilters)
const defKey = `${key}-def`
inputData[key] = filters
inputData[defKey] = tempFilters
onChange({ detail: filters }, key)
// need to store the builder definition in the automation
onChange({ detail: tempFilters }, defKey)
drawer.hide()
}
</script> </script>
<div class="fields"> <div class="fields">
@ -84,6 +132,26 @@
options={value.enum} options={value.enum}
getOptionLabel={(x, idx) => (value.pretty ? value.pretty[idx] : x)} getOptionLabel={(x, idx) => (value.pretty ? value.pretty[idx] : x)}
/> />
{:else if value.customType === "column"}
<Select
on:change={e => onChange(e, key)}
value={inputData[key]}
options={Object.keys(table.schema)}
/>
{:else if value.customType === "filters"}
<ActionButton on:click={drawer.show}>Define filters</ActionButton>
<Drawer bind:this={drawer} title="Filtering">
<Button cta slot="buttons" on:click={() => saveFilters(key)}
>Save</Button
>
<FilterDrawer
slot="body"
bind:filters={tempFilters}
{bindings}
{schemaFields}
panel={AutomationBindingPanel}
/>
</Drawer>
{:else if value.customType === "password"} {:else if value.customType === "password"}
<Input <Input
type="password" type="password"

View File

@ -1,22 +1,24 @@
<script> <script>
import { import {
DatePicker,
Icon,
Button,
Select,
Combobox,
Input,
DrawerContent,
Layout,
Body, Body,
Button,
Combobox,
DatePicker,
DrawerContent,
Icon,
Input,
Layout,
Select,
} from "@budibase/bbui" } from "@budibase/bbui"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
import { generate } from "shortid" import { generate } from "shortid"
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene" import { getValidOperatorsForType, OperatorOptions } from "helpers/lucene"
export let schemaFields export let schemaFields
export let filters = [] export let filters = []
export let bindings = [] export let bindings = []
export let panel = BindingPanel
const BannedTypes = ["link", "attachment", "formula"] const BannedTypes = ["link", "attachment", "formula"]
@ -82,9 +84,7 @@
const getFieldOptions = field => { const getFieldOptions = field => {
const schema = schemaFields.find(x => x.name === field) const schema = schemaFields.find(x => x.name === field)
const opt = schema?.constraints?.inclusion || [] return schema?.constraints?.inclusion || []
return opt
} }
</script> </script>
@ -127,6 +127,7 @@
title={`Value for "${filter.field}"`} title={`Value for "${filter.field}"`}
value={filter.value} value={filter.value}
placeholder="Value" placeholder="Value"
{panel}
{bindings} {bindings}
on:change={event => (filter.value = event.detail)} on:change={event => (filter.value = event.detail)}
/> />

View File

@ -113,6 +113,7 @@ exports.destroy = async function (ctx) {
exports.search = async ctx => { exports.search = async ctx => {
const tableId = getTableId(ctx) const tableId = getTableId(ctx)
try { try {
ctx.status = 200
ctx.body = await pickApi(tableId).search(ctx) ctx.body = await pickApi(tableId).search(ctx)
} catch (err) { } catch (err) {
ctx.throw(400, err) ctx.throw(400, err)

View File

@ -13,6 +13,7 @@ const zapier = require("./steps/zapier")
const integromat = require("./steps/integromat") const integromat = require("./steps/integromat")
let filter = require("./steps/filter") let filter = require("./steps/filter")
let delay = require("./steps/delay") let delay = require("./steps/delay")
let queryRow = require("./steps/queryRows")
const ACTION_IMPLS = { const ACTION_IMPLS = {
SEND_EMAIL_SMTP: sendSmtpEmail.run, SEND_EMAIL_SMTP: sendSmtpEmail.run,
@ -26,6 +27,7 @@ const ACTION_IMPLS = {
SERVER_LOG: serverLog.run, SERVER_LOG: serverLog.run,
DELAY: delay.run, DELAY: delay.run,
FILTER: filter.run, FILTER: filter.run,
QUERY_ROWS: queryRow.run,
// these used to be lowercase step IDs, maintain for backwards compat // these used to be lowercase step IDs, maintain for backwards compat
discord: discord.run, discord: discord.run,
slack: slack.run, slack: slack.run,
@ -44,6 +46,7 @@ const ACTION_DEFINITIONS = {
SERVER_LOG: serverLog.definition, SERVER_LOG: serverLog.definition,
DELAY: delay.definition, DELAY: delay.definition,
FILTER: filter.definition, FILTER: filter.definition,
QUERY_ROWS: queryRow.definition,
// these used to be lowercase step IDs, maintain for backwards compat // these used to be lowercase step IDs, maintain for backwards compat
discord: discord.definition, discord: discord.definition,
slack: slack.definition, slack: slack.definition,

View File

@ -1,4 +1,6 @@
//const rowController = require("../../api/controllers/row") const rowController = require("../../api/controllers/row")
const tableController = require("../../api/controllers/table")
const { FieldTypes } = require("../../constants")
const SortOrders = { const SortOrders = {
ASCENDING: "ascending", ASCENDING: "ascending",
@ -12,7 +14,7 @@ const SortOrdersPretty = {
exports.definition = { exports.definition = {
description: "Query rows from the database", description: "Query rows from the database",
icon: "ri-search-line", icon: "Search",
name: "Query rows", name: "Query rows",
tagline: "Query rows from {{inputs.enriched.table.name}} table", tagline: "Query rows from {{inputs.enriched.table.name}} table",
type: "ACTION", type: "ACTION",
@ -48,11 +50,11 @@ exports.definition = {
title: "Limit", title: "Limit",
}, },
}, },
required: ["tableId", "filters"], required: ["tableId"],
}, },
outputs: { outputs: {
properties: { properties: {
row: { rows: {
type: "array", type: "array",
customType: "rows", customType: "rows",
description: "The rows that were found", description: "The rows that were found",
@ -67,8 +69,51 @@ exports.definition = {
}, },
} }
exports.run = async function ({ inputs, appId }) { async function getTable(appId, tableId) {
console.log(inputs) const ctx = {
console.log(appId) params: {
// TODO: use the search controller id: tableId,
},
appId,
}
await tableController.find(ctx)
return ctx.body
}
exports.run = async function ({ inputs, appId }) {
const { tableId, filters, sortColumn, sortOrder, limit } = inputs
const table = await getTable(appId, tableId)
let sortType = FieldTypes.STRING
if (table && table.schema && sortColumn) {
const fieldType = table.schema[sortColumn].type
sortType =
fieldType === FieldTypes.NUMBER ? FieldTypes.NUMBER : FieldTypes.STRING
}
const ctx = {
params: {
tableId,
},
request: {
body: {
sortOrder,
sortType,
sort: sortColumn,
query: filters || {},
limit,
},
},
appId,
}
try {
await rowController.search(ctx)
return {
rows: ctx.body ? ctx.body.rows : [],
success: ctx.status === 200,
}
} catch (err) {
return {
success: false,
response: err,
}
}
} }

View File

@ -1090,6 +1090,115 @@
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is less than or equal to <code>b</code></strong>.</p>\n" "description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is less than or equal to <code>b</code></strong>.</p>\n"
} }
}, },
"object": {
"extend": {
"args": [
"objects"
],
"numArgs": 1,
"description": "<p>Extend the context with the properties of other objects. A shallow merge is performed to avoid mutating the context.</p>\n"
},
"forIn": {
"args": [
"context",
"options"
],
"numArgs": 2,
"description": "<p>Block helper that iterates over the properties of an object, exposing each key and value on the context.</p>\n"
},
"forOwn": {
"args": [
"obj",
"options"
],
"numArgs": 2,
"description": "<p>Block helper that iterates over the <strong>own</strong> properties of an object, exposing each key and value on the context.</p>\n"
},
"toPath": {
"args": [
"prop"
],
"numArgs": 1,
"description": "<p>Take arguments and, if they are string or number, convert them to a dot-delineated object property path.</p>\n"
},
"get": {
"args": [
"prop",
"context",
"options"
],
"numArgs": 3,
"description": "<p>Use property paths (<code>a.b.c</code>) to get a value or nested value from the context. Works as a regular helper or block helper.</p>\n"
},
"getObject": {
"args": [
"prop",
"context"
],
"numArgs": 2,
"description": "<p>Use property paths (<code>a.b.c</code>) to get an object from the context. Differs from the <code>get</code> helper in that this helper will return the actual object, including the given property key. Also, this helper does not work as a block helper.</p>\n"
},
"hasOwn": {
"args": [
"key",
"context"
],
"numArgs": 2,
"description": "<p>Return true if <code>key</code> is an own, enumerable property of the given <code>context</code> object.</p>\n"
},
"isObject": {
"args": [
"value"
],
"numArgs": 1,
"description": "<p>Return true if <code>value</code> is an object.</p>\n"
},
"JSONparse": {
"args": [
"string"
],
"numArgs": 1,
"description": "<p>Parses the given string using <code>JSON.parse</code>.</p>\n"
},
"JSONstringify": {
"args": [
"obj"
],
"numArgs": 1,
"description": "<p>Stringify an object using <code>JSON.stringify</code>.</p>\n"
},
"merge": {
"args": [
"object",
"objects"
],
"numArgs": 2,
"description": "<p>Deeply merge the properties of the given <code>objects</code> with the context object.</p>\n"
},
"parseJSON": {
"args": [
"string"
],
"numArgs": 1,
"description": "<p>Parses the given string using <code>JSON.parse</code>.</p>\n"
},
"pick": {
"args": [
"properties",
"context",
"options"
],
"numArgs": 3,
"description": "<p>Pick properties from the context object.</p>\n"
},
"stringify": {
"args": [
"obj"
],
"numArgs": 1,
"description": "<p>Stringify an object using <code>JSON.stringify</code>.</p>\n"
}
},
"date": { "date": {
"date": { "date": {
"args": [ "args": [

View File

@ -11,7 +11,15 @@ const marked = require("marked")
*/ */
const DIRECTORY = fs.existsSync("node_modules") ? "." : ".." const DIRECTORY = fs.existsSync("node_modules") ? "." : ".."
const COLLECTIONS = ["math", "array", "number", "url", "string", "comparison"] const COLLECTIONS = [
"math",
"array",
"number",
"url",
"string",
"comparison",
"object",
]
const FILENAME = `${DIRECTORY}/manifest.json` const FILENAME = `${DIRECTORY}/manifest.json`
const outputJSON = {} const outputJSON = {}
const ADDED_HELPERS = { const ADDED_HELPERS = {