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:
parent
87ee17ffee
commit
b18a082951
|
@ -2,8 +2,16 @@
|
|||
import TableSelector from "./TableSelector.svelte"
|
||||
import RowSelector from "./RowSelector.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 { tables } from "stores/backend"
|
||||
import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
|
||||
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
|
||||
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
||||
|
@ -15,11 +23,17 @@
|
|||
import { database } from "stores/backend"
|
||||
import { debounce } from "lodash"
|
||||
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 webhookModal
|
||||
export let testData
|
||||
export let schemaProperties
|
||||
let drawer
|
||||
let tempFilters = lookForFilters(schemaProperties) || []
|
||||
|
||||
$: stepId = block.stepId
|
||||
$: bindings = getAvailableBindings(
|
||||
block || $automationStore.selectedBlock,
|
||||
|
@ -28,6 +42,11 @@
|
|||
$: instanceId = $database._id
|
||||
|
||||
$: 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) {
|
||||
if (testData) {
|
||||
|
@ -71,6 +90,35 @@
|
|||
}
|
||||
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>
|
||||
|
||||
<div class="fields">
|
||||
|
@ -84,6 +132,26 @@
|
|||
options={value.enum}
|
||||
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"}
|
||||
<Input
|
||||
type="password"
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
<script>
|
||||
import {
|
||||
DatePicker,
|
||||
Icon,
|
||||
Button,
|
||||
Select,
|
||||
Combobox,
|
||||
Input,
|
||||
DrawerContent,
|
||||
Layout,
|
||||
Body,
|
||||
Button,
|
||||
Combobox,
|
||||
DatePicker,
|
||||
DrawerContent,
|
||||
Icon,
|
||||
Input,
|
||||
Layout,
|
||||
Select,
|
||||
} from "@budibase/bbui"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
||||
import { generate } from "shortid"
|
||||
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
|
||||
import { getValidOperatorsForType, OperatorOptions } from "helpers/lucene"
|
||||
|
||||
export let schemaFields
|
||||
export let filters = []
|
||||
export let bindings = []
|
||||
export let panel = BindingPanel
|
||||
|
||||
const BannedTypes = ["link", "attachment", "formula"]
|
||||
|
||||
|
@ -82,9 +84,7 @@
|
|||
|
||||
const getFieldOptions = field => {
|
||||
const schema = schemaFields.find(x => x.name === field)
|
||||
const opt = schema?.constraints?.inclusion || []
|
||||
|
||||
return opt
|
||||
return schema?.constraints?.inclusion || []
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -127,6 +127,7 @@
|
|||
title={`Value for "${filter.field}"`}
|
||||
value={filter.value}
|
||||
placeholder="Value"
|
||||
{panel}
|
||||
{bindings}
|
||||
on:change={event => (filter.value = event.detail)}
|
||||
/>
|
||||
|
|
|
@ -113,6 +113,7 @@ exports.destroy = async function (ctx) {
|
|||
exports.search = async ctx => {
|
||||
const tableId = getTableId(ctx)
|
||||
try {
|
||||
ctx.status = 200
|
||||
ctx.body = await pickApi(tableId).search(ctx)
|
||||
} catch (err) {
|
||||
ctx.throw(400, err)
|
||||
|
|
|
@ -13,6 +13,7 @@ const zapier = require("./steps/zapier")
|
|||
const integromat = require("./steps/integromat")
|
||||
let filter = require("./steps/filter")
|
||||
let delay = require("./steps/delay")
|
||||
let queryRow = require("./steps/queryRows")
|
||||
|
||||
const ACTION_IMPLS = {
|
||||
SEND_EMAIL_SMTP: sendSmtpEmail.run,
|
||||
|
@ -26,6 +27,7 @@ const ACTION_IMPLS = {
|
|||
SERVER_LOG: serverLog.run,
|
||||
DELAY: delay.run,
|
||||
FILTER: filter.run,
|
||||
QUERY_ROWS: queryRow.run,
|
||||
// these used to be lowercase step IDs, maintain for backwards compat
|
||||
discord: discord.run,
|
||||
slack: slack.run,
|
||||
|
@ -44,6 +46,7 @@ const ACTION_DEFINITIONS = {
|
|||
SERVER_LOG: serverLog.definition,
|
||||
DELAY: delay.definition,
|
||||
FILTER: filter.definition,
|
||||
QUERY_ROWS: queryRow.definition,
|
||||
// these used to be lowercase step IDs, maintain for backwards compat
|
||||
discord: discord.definition,
|
||||
slack: slack.definition,
|
||||
|
|
|
@ -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 = {
|
||||
ASCENDING: "ascending",
|
||||
|
@ -12,7 +14,7 @@ const SortOrdersPretty = {
|
|||
|
||||
exports.definition = {
|
||||
description: "Query rows from the database",
|
||||
icon: "ri-search-line",
|
||||
icon: "Search",
|
||||
name: "Query rows",
|
||||
tagline: "Query rows from {{inputs.enriched.table.name}} table",
|
||||
type: "ACTION",
|
||||
|
@ -48,11 +50,11 @@ exports.definition = {
|
|||
title: "Limit",
|
||||
},
|
||||
},
|
||||
required: ["tableId", "filters"],
|
||||
required: ["tableId"],
|
||||
},
|
||||
outputs: {
|
||||
properties: {
|
||||
row: {
|
||||
rows: {
|
||||
type: "array",
|
||||
customType: "rows",
|
||||
description: "The rows that were found",
|
||||
|
@ -67,8 +69,51 @@ exports.definition = {
|
|||
},
|
||||
}
|
||||
|
||||
exports.run = async function ({ inputs, appId }) {
|
||||
console.log(inputs)
|
||||
console.log(appId)
|
||||
// TODO: use the search controller
|
||||
async function getTable(appId, tableId) {
|
||||
const ctx = {
|
||||
params: {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"args": [
|
||||
|
|
|
@ -11,7 +11,15 @@ const marked = require("marked")
|
|||
*/
|
||||
|
||||
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 outputJSON = {}
|
||||
const ADDED_HELPERS = {
|
||||
|
|
Loading…
Reference in New Issue