diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index c16260779e..eceb2420dc 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -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() + }
@@ -84,6 +132,26 @@ options={value.enum} getOptionLabel={(x, idx) => (value.pretty ? value.pretty[idx] : x)} /> + {:else if value.customType === "column"} + 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 || [] } @@ -127,6 +127,7 @@ title={`Value for "${filter.field}"`} value={filter.value} placeholder="Value" + {panel} {bindings} on:change={event => (filter.value = event.detail)} /> diff --git a/packages/server/src/api/controllers/row/index.js b/packages/server/src/api/controllers/row/index.js index a50afa77ea..1d003ebd18 100644 --- a/packages/server/src/api/controllers/row/index.js +++ b/packages/server/src/api/controllers/row/index.js @@ -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) diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index ef77387a40..13f7c487c1 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -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, diff --git a/packages/server/src/automations/steps/queryRows.js b/packages/server/src/automations/steps/queryRows.js index 8b01bd9755..64b757418e 100644 --- a/packages/server/src/automations/steps/queryRows.js +++ b/packages/server/src/automations/steps/queryRows.js @@ -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, + } + } } diff --git a/packages/string-templates/manifest.json b/packages/string-templates/manifest.json index 02bd7f8a64..629bb2ebe3 100644 --- a/packages/string-templates/manifest.json +++ b/packages/string-templates/manifest.json @@ -1090,6 +1090,115 @@ "description": "

Block helper that always renders the inverse block unless a is less than or equal to b.

\n" } }, + "object": { + "extend": { + "args": [ + "objects" + ], + "numArgs": 1, + "description": "

Extend the context with the properties of other objects. A shallow merge is performed to avoid mutating the context.

\n" + }, + "forIn": { + "args": [ + "context", + "options" + ], + "numArgs": 2, + "description": "

Block helper that iterates over the properties of an object, exposing each key and value on the context.

\n" + }, + "forOwn": { + "args": [ + "obj", + "options" + ], + "numArgs": 2, + "description": "

Block helper that iterates over the own properties of an object, exposing each key and value on the context.

\n" + }, + "toPath": { + "args": [ + "prop" + ], + "numArgs": 1, + "description": "

Take arguments and, if they are string or number, convert them to a dot-delineated object property path.

\n" + }, + "get": { + "args": [ + "prop", + "context", + "options" + ], + "numArgs": 3, + "description": "

Use property paths (a.b.c) to get a value or nested value from the context. Works as a regular helper or block helper.

\n" + }, + "getObject": { + "args": [ + "prop", + "context" + ], + "numArgs": 2, + "description": "

Use property paths (a.b.c) to get an object from the context. Differs from the get 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.

\n" + }, + "hasOwn": { + "args": [ + "key", + "context" + ], + "numArgs": 2, + "description": "

Return true if key is an own, enumerable property of the given context object.

\n" + }, + "isObject": { + "args": [ + "value" + ], + "numArgs": 1, + "description": "

Return true if value is an object.

\n" + }, + "JSONparse": { + "args": [ + "string" + ], + "numArgs": 1, + "description": "

Parses the given string using JSON.parse.

\n" + }, + "JSONstringify": { + "args": [ + "obj" + ], + "numArgs": 1, + "description": "

Stringify an object using JSON.stringify.

\n" + }, + "merge": { + "args": [ + "object", + "objects" + ], + "numArgs": 2, + "description": "

Deeply merge the properties of the given objects with the context object.

\n" + }, + "parseJSON": { + "args": [ + "string" + ], + "numArgs": 1, + "description": "

Parses the given string using JSON.parse.

\n" + }, + "pick": { + "args": [ + "properties", + "context", + "options" + ], + "numArgs": 3, + "description": "

Pick properties from the context object.

\n" + }, + "stringify": { + "args": [ + "obj" + ], + "numArgs": 1, + "description": "

Stringify an object using JSON.stringify.

\n" + } + }, "date": { "date": { "args": [ diff --git a/packages/string-templates/scripts/gen-collection-info.js b/packages/string-templates/scripts/gen-collection-info.js index fcd3cb4923..29df10423f 100644 --- a/packages/string-templates/scripts/gen-collection-info.js +++ b/packages/string-templates/scripts/gen-collection-info.js @@ -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 = {