Bindings support for views and table row searches

This commit is contained in:
Dean 2024-04-18 17:04:26 +01:00
parent 5614c040ea
commit 6bbdf0e474
6 changed files with 122 additions and 41 deletions

View File

@ -1,7 +1,9 @@
<script> <script>
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { ActionButton, Modal, ModalContent } from "@budibase/bbui" import { ActionButton, Drawer, Button } from "@budibase/bbui"
import FilterDrawer from "components/design/settings/controls/FilterEditor/FilterDrawer.svelte" import FilterDrawer from "components/design/settings/controls/FilterEditor/FilterDrawer.svelte"
import { getUserBindings } from "dataBinding"
import { makePropSafe } from "@budibase/string-templates"
export let schema export let schema
export let filters export let filters
@ -10,7 +12,7 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let modal let drawer
$: tempValue = filters || [] $: tempValue = filters || []
$: schemaFields = Object.entries(schema || {}).map( $: schemaFields = Object.entries(schema || {}).map(
@ -22,37 +24,52 @@
$: text = getText(filters) $: text = getText(filters)
$: selected = tempValue.filter(x => !x.onEmptyFilter)?.length > 0 $: selected = tempValue.filter(x => !x.onEmptyFilter)?.length > 0
$: bindings = [
{
type: "context",
runtimeBinding: `${makePropSafe("now")}`,
readableBinding: `Date`,
category: "Date",
icon: "Date",
display: {
name: "Server date",
},
},
...getUserBindings(true),
]
const getText = filters => { const getText = filters => {
const count = filters?.filter(filter => filter.field)?.length const count = filters?.filter(filter => filter.field)?.length
return count ? `Filter (${count})` : "Filter" return count ? `Filter (${count})` : "Filter"
} }
</script> </script>
<ActionButton icon="Filter" quiet {disabled} on:click={modal.show} {selected}> <ActionButton icon="Filter" quiet {disabled} on:click={drawer.show} {selected}>
{text} {text}
</ActionButton> </ActionButton>
<Modal bind:this={modal}>
<ModalContent
title="Filter"
confirmText="Save"
size="XL"
onConfirm={() => dispatch("change", tempValue)}
>
<div class="wrapper">
<FilterDrawer
allowBindings={false}
{filters}
{schemaFields}
datasource={{ type: "table", tableId }}
on:change={e => (tempValue = e.detail)}
/>
</div>
</ModalContent>
</Modal>
<style> <Drawer
.wrapper :global(.main) { bind:this={drawer}
padding: 0; title="Filtering"
} on:drawerHide
</style> on:drawerShow
forceModal
>
<Button
cta
slot="buttons"
on:click={() => {
dispatch("change", tempValue)
drawer.hide()
}}
>
Save
</Button>
<FilterDrawer
slot="body"
{filters}
{schemaFields}
datasource={{ type: "table", tableId }}
on:change={e => (tempValue = e.detail)}
{bindings}
/>
</Drawer>

View File

@ -304,6 +304,7 @@
OperatorOptions.ContainsAny.value, OperatorOptions.ContainsAny.value,
].includes(filter.operator)} ].includes(filter.operator)}
disabled={filter.noValue} disabled={filter.noValue}
type={filter.valueType}
/> />
{:else} {:else}
<DrawerBindableInput disabled /> <DrawerBindableInput disabled />

View File

@ -1,7 +1,6 @@
<script> <script>
import { Select, Multiselect } from "@budibase/bbui" import { Select, Multiselect } from "@budibase/bbui"
import { fetchData } from "@budibase/frontend-core" import { fetchData } from "@budibase/frontend-core"
import { API } from "api" import { API } from "api"
export let value = null export let value = null
@ -23,12 +22,14 @@
$: component = multiselect ? Multiselect : Select $: component = multiselect ? Multiselect : Select
</script> </script>
<svelte:component <div class="user-control">
this={component} <svelte:component
bind:value this={component}
autocomplete bind:value
{options} autocomplete
getOptionLabel={option => option.email} {options}
getOptionValue={option => option._id} getOptionLabel={option => option.email}
{disabled} getOptionValue={option => option._id}
/> {disabled}
/>
</div>

View File

@ -2,7 +2,7 @@ import stream from "stream"
import archiver from "archiver" import archiver from "archiver"
import { quotas } from "@budibase/pro" import { quotas } from "@budibase/pro"
import { objectStore } from "@budibase/backend-core" import { objectStore, context } from "@budibase/backend-core"
import * as internal from "./internal" import * as internal from "./internal"
import * as external from "./external" import * as external from "./external"
import { isExternalTableID } from "../../../integrations/utils" import { isExternalTableID } from "../../../integrations/utils"
@ -198,8 +198,21 @@ export async function destroy(ctx: UserCtx<DeleteRowRequest>) {
export async function search(ctx: Ctx<SearchRowRequest, SearchRowResponse>) { export async function search(ctx: Ctx<SearchRowRequest, SearchRowResponse>) {
const tableId = utils.getTableId(ctx) const tableId = utils.getTableId(ctx)
// Current user context for bindable search
const { _id, _rev, firstName, lastName, email, status, roleId } = ctx.user
await context.ensureSnippetContext()
const enrichedQuery = await utils.enrichSearchContext(
{ ...ctx.request.body.query },
{
user: { _id, _rev, firstName, lastName, email, status, roleId },
}
)
const searchParams: RowSearchParams = { const searchParams: RowSearchParams = {
...ctx.request.body, ...ctx.request.body,
query: enrichedQuery,
tableId, tableId,
} }

View File

@ -7,6 +7,8 @@ import {
FieldType, FieldType,
RelationshipsJson, RelationshipsJson,
Row, Row,
SearchRowRequest,
SearchRowResponse,
Table, Table,
UserCtx, UserCtx,
} from "@budibase/types" } from "@budibase/types"
@ -22,7 +24,7 @@ import {
getInternalRowId, getInternalRowId,
} from "./basic" } from "./basic"
import sdk from "../../../../sdk" import sdk from "../../../../sdk"
import { processStringSync } from "@budibase/string-templates"
import validateJs from "validate.js" import validateJs from "validate.js"
validateJs.extend(validateJs.validators.datetime, { validateJs.extend(validateJs.validators.datetime, {
@ -187,3 +189,40 @@ export async function sqlOutputProcessing(
export function isUserMetadataTable(tableId: string) { export function isUserMetadataTable(tableId: string) {
return tableId === InternalTables.USER_METADATA return tableId === InternalTables.USER_METADATA
} }
export async function enrichSearchContext(
fields: Record<string, any>,
inputs = {},
helpers = true
): Promise<Record<string, any>> {
const enrichedQuery: Record<string, any> = {}
if (!fields || !inputs) {
return enrichedQuery
}
const parameters = { ...inputs }
// enrich the fields with dynamic parameters
for (let key of Object.keys(fields)) {
if (fields[key] == null) {
continue
}
if (typeof fields[key] === "object") {
// enrich nested fields object
enrichedQuery[key] = await enrichSearchContext(
fields[key],
parameters,
helpers
)
} else if (typeof fields[key] === "string") {
// enrich string value as normal
enrichedQuery[key] = processStringSync(fields[key], parameters, {
noEscaping: true,
noHelpers: !helpers,
escapeNewlines: true,
})
} else {
enrichedQuery[key] = fields[key]
}
}
return enrichedQuery
}

View File

@ -9,7 +9,8 @@ import {
} from "@budibase/types" } from "@budibase/types"
import { dataFilters } from "@budibase/shared-core" import { dataFilters } from "@budibase/shared-core"
import sdk from "../../../sdk" import sdk from "../../../sdk"
import { db } from "@budibase/backend-core" import { db, context } from "@budibase/backend-core"
import { enrichSearchContext, userSearchFromContext } from "./utils"
export async function searchView( export async function searchView(
ctx: UserCtx<SearchViewRowRequest, SearchRowResponse> ctx: UserCtx<SearchViewRowRequest, SearchRowResponse>
@ -56,10 +57,19 @@ export async function searchView(
}) })
} }
// Current user search context.
const { _id, _rev, firstName, lastName, email, status, roleId } = ctx.user
await context.ensureSnippetContext()
const enrichedQuery = await enrichSearchContext(query, {
user: { _id, _rev, firstName, lastName, email, status, roleId },
})
const searchOptions: RequiredKeys<SearchViewRowRequest> & const searchOptions: RequiredKeys<SearchViewRowRequest> &
RequiredKeys<Pick<RowSearchParams, "tableId" | "query" | "fields">> = { RequiredKeys<Pick<RowSearchParams, "tableId" | "query" | "fields">> = {
tableId: view.tableId, tableId: view.tableId,
query, query: enrichedQuery,
fields: viewFields, fields: viewFields,
...getSortOptions(body, view), ...getSortOptions(body, view),
limit: body.limit, limit: body.limit,