Bindings support for views and table row searches
This commit is contained in:
parent
5614c040ea
commit
6bbdf0e474
|
@ -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>
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue