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
ed71f6bf75
commit
96f0f9b4fd
|
@ -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"
|
||||||
|
|
|
@ -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)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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": [
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
Loading…
Reference in New Issue