PR feedback

This commit is contained in:
Dean 2024-09-16 12:46:21 +01:00
parent 08ca33563d
commit e0d2c70611
8 changed files with 127 additions and 101 deletions

View File

@ -206,9 +206,15 @@ export class ComponentStore extends BudiStore {
setting?.type?.startsWith("filter") setting?.type?.startsWith("filter")
) )
for (let setting of filterableTypes || []) { for (let setting of filterableTypes || []) {
enrichedComponent[setting.key] = utils.processSearchFilters( const isLegacy = Array.isArray(enrichedComponent[setting.key])
enrichedComponent[setting.key]
) if (isLegacy) {
const processedSetting = utils.processSearchFilters(
enrichedComponent[setting.key]
)
enrichedComponent[setting.key] = processedSetting
migrated = true
}
} }
return migrated return migrated
} }
@ -575,9 +581,7 @@ export class ComponentStore extends BudiStore {
const patchResult = patchFn(component, screen) const patchResult = patchFn(component, screen)
// Post processing // Post processing
const migrated = null const migrated = this.migrateSettings(component)
this.migrateSettings(component)
// Returning an explicit false signifies that we should skip this // Returning an explicit false signifies that we should skip this
// update. If we migrated something, ensure we never skip. // update. If we migrated something, ensure we never skip.

View File

@ -19,9 +19,7 @@
let queryExtensions = {} let queryExtensions = {}
let localFilters let localFilters
$: if (filter) { $: localFilters = filter ? Helpers.cloneDeep(filter) : null
localFilters = Helpers.cloneDeep(filter)
}
$: defaultQuery = QueryUtils.buildQuery(localFilters) $: defaultQuery = QueryUtils.buildQuery(localFilters)
@ -131,7 +129,7 @@
} }
const extendQuery = (defaultQuery, extensions) => { const extendQuery = (defaultQuery, extensions) => {
return { const extended = {
[LogicalOperator.AND]: { [LogicalOperator.AND]: {
conditions: [ conditions: [
...(defaultQuery ? [defaultQuery] : []), ...(defaultQuery ? [defaultQuery] : []),
@ -140,6 +138,11 @@
}, },
onEmptyFilter: EmptyFilterOption.RETURN_NONE, onEmptyFilter: EmptyFilterOption.RETURN_NONE,
} }
// If there are no conditions applied at all, clear the request.
return extended[LogicalOperator.AND]?.conditions?.length > 0
? extended
: null
} }
const setUpAutoRefresh = autoRefresh => { const setUpAutoRefresh = autoRefresh => {

View File

@ -58,8 +58,13 @@
return { value: entry, label: Helpers.capitalise(entry) } return { value: entry, label: Helpers.capitalise(entry) }
}) })
const onEmptyLabelling = {
[OnEmptyFilter.RETURN_ALL]: "All rows",
[OnEmptyFilter.RETURN_NONE]: "No rows",
}
const onEmptyOptions = Object.values(OnEmptyFilter).map(entry => { const onEmptyOptions = Object.values(OnEmptyFilter).map(entry => {
return { value: entry, label: Helpers.capitalise(entry) } return { value: entry, label: onEmptyLabelling[entry] }
}) })
const context = getContext("context") const context = getContext("context")
@ -151,7 +156,7 @@
const getGroupPrefix = groupIdx => { const getGroupPrefix = groupIdx => {
if (groupIdx == 0) { if (groupIdx == 0) {
return "Where" return "When"
} }
const operatorMapping = { const operatorMapping = {
[FilterOperator.ANY]: "or", [FilterOperator.ANY]: "or",
@ -191,16 +196,17 @@
if (targetFilter) { if (targetFilter) {
if (deleteFilter) { if (deleteFilter) {
targetGroup.filters.splice(filterIdx, 1) targetGroup.filters.splice(filterIdx, 1)
// Clear the group entirely if no valid filters remain
if (targetGroup.filters.length === 0) {
editable.groups.splice(groupIdx, 1)
}
} else if (filter) { } else if (filter) {
targetGroup.filters[filterIdx] = filter targetGroup.filters[filterIdx] = filter
} }
} else if (targetGroup) { } else if (targetGroup) {
if (deleteGroup) { if (deleteGroup) {
if (editable.groups.length > 1) { editable.groups.splice(groupIdx, 1)
editable.groups.splice(groupIdx, 1)
} else {
editable = {}
}
} else if (addFilter) { } else if (addFilter) {
targetGroup.filters.push({ targetGroup.filters.push({
valueType: FilterValueType.VALUE, valueType: FilterValueType.VALUE,
@ -239,6 +245,9 @@
} }
} }
// Set the request to null if the groups are emptied
editable = editable.groups.length ? editable : null
dispatch("change", editable) dispatch("change", editable)
} }
</script> </script>
@ -294,7 +303,7 @@
placeholder={false} placeholder={false}
/> />
</span> </span>
<span>of the following filters are met:</span> <span>of the following filters are matched:</span>
</div> </div>
<div class="group-actions"> <div class="group-actions">
<Icon <Icon
@ -386,49 +395,51 @@
{/if} {/if}
<div class="filters-footer"> <div class="filters-footer">
{#if behaviourFilters && editableFilters?.groups?.length} <Layout noPadding>
<div class="empty-filter"> {#if behaviourFilters && editableFilters?.groups?.length}
<span>Return</span> <div class="empty-filter">
<span class="empty-filter-picker"> <span>Return</span>
<Select <span class="empty-filter-picker">
value={editableFilters?.onEmptyFilter} <Select
options={onEmptyOptions} value={editableFilters?.onEmptyFilter}
getOptionLabel={opt => opt.label} options={onEmptyOptions}
getOptionValue={opt => opt.value} getOptionLabel={opt => opt.label}
on:change={e => { getOptionValue={opt => opt.value}
handleFilterChange({ on:change={e => {
onEmptyFilter: e.detail, handleFilterChange({
}) onEmptyFilter: e.detail,
}} })
placeholder={false} }}
placeholder={false}
/>
</span>
<span>when all filters are empty</span>
</div>
{/if}
<div class="add-group">
<Button
icon="AddCircle"
size="M"
secondary
on:click={() => {
handleFilterChange({
addGroup: true,
})
}}
>
Add filter group
</Button>
<a
href="https://docs.budibase.com/docs/searchfilter-data"
target="_blank"
>
<Icon
name="HelpOutline"
color="var(--spectrum-global-color-gray-600)"
/> />
</span> </a>
<span>when all filters are empty</span>
</div> </div>
{/if} </Layout>
<div class="add-group">
<Button
icon="AddCircle"
size="M"
secondary
on:click={() => {
handleFilterChange({
addGroup: true,
})
}}
>
Add filter group
</Button>
<a
href="https://docs.budibase.com/docs/searchfilter-data"
target="_blank"
>
<Icon
name="HelpOutline"
color="var(--spectrum-global-color-gray-600)"
/>
</a>
</div>
</div> </div>
{:else} {:else}
<Body size="S">None of the table column can be used for filtering.</Body> <Body size="S">None of the table column can be used for filtering.</Body>
@ -446,7 +457,7 @@
.empty-filter, .empty-filter,
.group-options { .group-options {
display: flex; display: flex;
gap: var(--spacing-xl); gap: var(--spacing-m);
align-items: center; align-items: center;
} }
@ -461,14 +472,13 @@
} }
.empty-filter-picker { .empty-filter-picker {
width: 80px; width: 92px;
} }
.filter-groups { .filter-groups {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-xl); gap: var(--spacing-xl);
/* overflow-x: scroll; */
} }
.group { .group {
@ -489,7 +499,7 @@
.filter { .filter {
display: grid; display: grid;
gap: var(--spacing-l); gap: var(--spacing-l);
grid-template-columns: minmax(150px, 1fr) 170px 200px 40px 40px; grid-template-columns: minmax(150px, 1fr) 170px minmax(200px, 1fr) 40px 40px;
} }
.filters-footer { .filters-footer {

View File

@ -20,12 +20,9 @@
export let bindings = [] export let bindings = []
export let allowBindings = false export let allowBindings = false
export let schemaFields export let schemaFields
// Export these to another field type
export let panel export let panel
export let toReadable export let toReadable
export let toRuntime export let toRuntime
// Only required if you're in the builder, which we aint.
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const { OperatorOptions, FilterValueType } = Constants const { OperatorOptions, FilterValueType } = Constants
@ -48,17 +45,21 @@
return schemaFields.find(field => field.name === filter.field) return schemaFields.find(field => field.name === filter.field)
} }
const drawerOnChange = e => {
drawerValue = e.detail
}
const onChange = e => { const onChange = e => {
fieldValue = e.detail fieldValue = e.detail
dispatch("change", { dispatch("change", {
value: toRuntime(bindings, fieldValue), value: toRuntime ? toRuntime(bindings, fieldValue) : fieldValue,
}) })
} }
const onConfirm = () => { const onConfirmBinding = () => {
dispatch("change", { dispatch("change", {
value: fieldValue, value: toRuntime ? toRuntime(bindings, drawerValue) : drawerValue,
valueType: fieldValue ? FilterValueType.BINDING : FilterValueType.VALUE, valueType: drawerValue ? FilterValueType.BINDING : FilterValueType.VALUE,
}) })
} }
@ -139,7 +140,7 @@
cta cta
slot="buttons" slot="buttons"
on:click={() => { on:click={() => {
onConfirm() onConfirmBinding()
bindingDrawer.hide() bindingDrawer.hide()
}} }}
> >
@ -153,7 +154,7 @@
allowJS allowJS
allowHelpers allowHelpers
allowHBS allowHBS
on:change={onChange} on:change={drawerOnChange}
{bindings} {bindings}
/> />
</Drawer> </Drawer>

View File

@ -1,14 +1,6 @@
import DataFetch from "./DataFetch.js" import DataFetch from "./DataFetch.js"
export default class CustomFetch extends DataFetch { export default class CustomFetch extends DataFetch {
determineFeatureFlags() {
return {
supportsSearch: false,
supportsSort: false,
supportsPagination: false,
}
}
// Gets the correct Budibase type for a JS value // Gets the correct Budibase type for a JS value
getType(value) { getType(value) {
if (value == null) { if (value == null) {

View File

@ -183,7 +183,7 @@ export default class DataFetch {
} }
// Build the query // Build the query
let query = this.options.query || null let query = this.options.query
if (!query && this.features.supportsSearch) { if (!query && this.features.supportsSearch) {
query = buildQuery(filter || defaultQuery) query = buildQuery(filter || defaultQuery)

View File

@ -307,11 +307,11 @@ export class ColumnSplitter {
} }
/** /**
* Builds a JSON query from the filter structure generated in the builder * Builds a JSON query from the filter a SearchFilter definition
* @param filter the builder filter structure * @param filter the builder filter structure
*/ */
const builderFilter = (expression: SearchFilter) => { const buildCondition = (expression: SearchFilter) => {
// Filter body // Filter body
let query: SearchFilters = { let query: SearchFilters = {
string: {}, string: {},
@ -378,13 +378,15 @@ const builderFilter = (expression: SearchFilter) => {
value = `${value}`?.toLowerCase() === "true" value = `${value}`?.toLowerCase() === "true"
} }
if ( if (
["contains", "notContains", "containsAny"].includes(operator) && ["contains", "notContains", "containsAny"].includes(
operator.toLocaleString()
) &&
type === "array" && type === "array" &&
typeof value === "string" typeof value === "string"
) { ) {
value = value.split(",") value = value.split(",")
} }
if (operator.startsWith("range") && query.range) { if (operator.toLocaleString().startsWith("range") && query.range) {
const minint = const minint =
SqlNumberTypeRangeMap[externalType as keyof typeof SqlNumberTypeRangeMap] SqlNumberTypeRangeMap[externalType as keyof typeof SqlNumberTypeRangeMap]
?.min || Number.MIN_SAFE_INTEGER ?.min || Number.MIN_SAFE_INTEGER
@ -497,13 +499,15 @@ export const buildQueryLegacy = (
value = `${value}`?.toLowerCase() === "true" value = `${value}`?.toLowerCase() === "true"
} }
if ( if (
["contains", "notContains", "containsAny"].includes(operator) && ["contains", "notContains", "containsAny"].includes(
operator.toLocaleString()
) &&
type === "array" && type === "array" &&
typeof value === "string" typeof value === "string"
) { ) {
value = value.split(",") value = value.split(",")
} }
if (operator.startsWith("range") && query.range) { if (operator.toLocaleString().startsWith("range") && query.range) {
const minint = const minint =
SqlNumberTypeRangeMap[ SqlNumberTypeRangeMap[
externalType as keyof typeof SqlNumberTypeRangeMap externalType as keyof typeof SqlNumberTypeRangeMap
@ -555,28 +559,40 @@ export const buildQueryLegacy = (
return query return query
} }
/**
* Converts a **SearchFilterGroup** filter definition into a grouped
* search query of type **SearchFilters**
*
* Legacy support remains for the old **SearchFilter[]** format.
* These will be migrated to an appropriate **SearchFilters** object, if encountered
*
* @param filter
*
* @returns {SearchFilters}
*/
export const buildQuery = ( export const buildQuery = (
filter?: SearchFilterGroup | SearchFilter[] filter?: SearchFilterGroup | SearchFilter[]
): SearchFilters | undefined => { ): SearchFilters | undefined => {
if (!filter) { const parsedFilter: SearchFilterGroup | undefined =
return processSearchFilters(filter)
}
const parsedFilter = processSearchFilters(filter)
if (!parsedFilter) { if (!parsedFilter) {
return return
} }
const operatorMap = { const operatorMap: { [key in FilterGroupLogicalOperator]: LogicalOperator } =
[FilterGroupLogicalOperator.ALL]: LogicalOperator.AND, {
[FilterGroupLogicalOperator.ANY]: LogicalOperator.OR, [FilterGroupLogicalOperator.ALL]: LogicalOperator.AND,
} [FilterGroupLogicalOperator.ANY]: LogicalOperator.OR,
}
const globalOnEmpty = parsedFilter.onEmptyFilter const globalOnEmpty = parsedFilter.onEmptyFilter
? parsedFilter.onEmptyFilter ? parsedFilter.onEmptyFilter
: null : null
const globalOperator = operatorMap[parsedFilter.logicalOperator]
const globalOperator: LogicalOperator =
operatorMap[parsedFilter.logicalOperator as FilterGroupLogicalOperator]
const coreRequest: SearchFilters = { const coreRequest: SearchFilters = {
...(globalOnEmpty ? { onEmptyFilter: globalOnEmpty } : {}), ...(globalOnEmpty ? { onEmptyFilter: globalOnEmpty } : {}),
@ -585,7 +601,7 @@ export const buildQuery = (
return { return {
[operatorMap[group.logicalOperator]]: { [operatorMap[group.logicalOperator]]: {
conditions: group.filters conditions: group.filters
?.map(x => builderFilter(x)) ?.map(x => buildCondition(x))
.filter(filter => filter), .filter(filter => filter),
}, },
} }

View File

@ -97,8 +97,8 @@ export function trimOtherProps(object: any, allowedProps: string[]) {
* @param {SearchFilter[] | SearchFilterGroup} filters * @param {SearchFilter[] | SearchFilterGroup} filters
*/ */
export const processSearchFilters = ( export const processSearchFilters = (
filters: SearchFilter[] | SearchFilterGroup filters: SearchFilter[] | SearchFilterGroup | undefined
) => { ): SearchFilterGroup | undefined => {
if (!filters) { if (!filters) {
return return
} }
@ -182,7 +182,7 @@ export const processSearchFilters = (
return migratedSetting return migratedSetting
} else if (!filters?.groups) { } else if (!filters?.groups) {
return null return
} }
return filters return filters
} }