Make block searching on dates useful by using a range of the whole day
This commit is contained in:
parent
ed8ab95ae7
commit
303bfd5be4
|
@ -4,6 +4,7 @@
|
||||||
import BlockComponent from "components/BlockComponent.svelte"
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
import { Heading } from "@budibase/bbui"
|
import { Heading } from "@budibase/bbui"
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
import { enrichSearchColumns, enrichFilter } from "utils/blocks.js"
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let dataSource
|
export let dataSource
|
||||||
|
@ -33,14 +34,6 @@
|
||||||
const { fetchDatasourceSchema, styleable } = getContext("sdk")
|
const { fetchDatasourceSchema, styleable } = getContext("sdk")
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const schemaComponentMap = {
|
|
||||||
string: "stringfield",
|
|
||||||
options: "optionsfield",
|
|
||||||
number: "numberfield",
|
|
||||||
datetime: "datetimefield",
|
|
||||||
boolean: "booleanfield",
|
|
||||||
formula: "stringfield",
|
|
||||||
}
|
|
||||||
|
|
||||||
let formId
|
let formId
|
||||||
let dataProviderId
|
let dataProviderId
|
||||||
|
@ -68,39 +61,6 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// Enrich the default filter with the specified search fields
|
|
||||||
const enrichFilter = (filter, columns, formId) => {
|
|
||||||
let enrichedFilter = [...(filter || [])]
|
|
||||||
columns?.forEach(column => {
|
|
||||||
const safePath = column.name.split(".").map(safe).join(".")
|
|
||||||
enrichedFilter.push({
|
|
||||||
field: column.name,
|
|
||||||
operator: column.type === "string" ? "string" : "equal",
|
|
||||||
type: column.type,
|
|
||||||
valueType: "Binding",
|
|
||||||
value: `{{ ${safe(formId)}.${safePath} }}`,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return enrichedFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine data types for search fields and only use those that are valid
|
|
||||||
const enrichSearchColumns = (searchColumns, schema) => {
|
|
||||||
let enrichedColumns = []
|
|
||||||
searchColumns?.forEach(column => {
|
|
||||||
const schemaType = schema?.[column]?.type
|
|
||||||
const componentType = schemaComponentMap[schemaType]
|
|
||||||
if (componentType) {
|
|
||||||
enrichedColumns.push({
|
|
||||||
name: column,
|
|
||||||
componentType,
|
|
||||||
type: schemaType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return enrichedColumns.slice(0, 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builds a full details page URL for the card title
|
// Builds a full details page URL for the card title
|
||||||
const buildFullCardUrl = (link, url, repeaterId, linkColumn) => {
|
const buildFullCardUrl = (link, url, repeaterId, linkColumn) => {
|
||||||
if (!link || !url || !repeaterId) {
|
if (!link || !url || !repeaterId) {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import BlockComponent from "components/BlockComponent.svelte"
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
import { Heading } from "@budibase/bbui"
|
import { Heading } from "@budibase/bbui"
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
import { enrichSearchColumns, enrichFilter } from "utils/blocks.js"
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let dataSource
|
export let dataSource
|
||||||
|
@ -31,14 +32,6 @@
|
||||||
const { fetchDatasourceSchema, styleable } = getContext("sdk")
|
const { fetchDatasourceSchema, styleable } = getContext("sdk")
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const schemaComponentMap = {
|
|
||||||
string: "stringfield",
|
|
||||||
options: "optionsfield",
|
|
||||||
number: "numberfield",
|
|
||||||
datetime: "datetimefield",
|
|
||||||
boolean: "booleanfield",
|
|
||||||
formula: "stringfield",
|
|
||||||
}
|
|
||||||
|
|
||||||
let formId
|
let formId
|
||||||
let dataProviderId
|
let dataProviderId
|
||||||
|
@ -58,40 +51,6 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// Enrich the default filter with the specified search fields
|
|
||||||
const enrichFilter = (filter, columns, formId) => {
|
|
||||||
let enrichedFilter = [...(filter || [])]
|
|
||||||
columns?.forEach(column => {
|
|
||||||
const safePath = column.name.split(".").map(safe).join(".")
|
|
||||||
const stringType = column.type === "string" || column.type === "formula"
|
|
||||||
enrichedFilter.push({
|
|
||||||
field: column.name,
|
|
||||||
type: column.type,
|
|
||||||
operator: stringType ? "string" : "equal",
|
|
||||||
valueType: "Binding",
|
|
||||||
value: `{{ ${safe(formId)}.${safePath} }}`,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return enrichedFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine data types for search fields and only use those that are valid
|
|
||||||
const enrichSearchColumns = (searchColumns, schema) => {
|
|
||||||
let enrichedColumns = []
|
|
||||||
searchColumns?.forEach(column => {
|
|
||||||
const schemaType = schema?.[column]?.type
|
|
||||||
const componentType = schemaComponentMap[schemaType]
|
|
||||||
if (componentType) {
|
|
||||||
enrichedColumns.push({
|
|
||||||
name: column,
|
|
||||||
componentType,
|
|
||||||
type: schemaType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return enrichedColumns.slice(0, 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the datasource schema so we can determine column types
|
// Load the datasource schema so we can determine column types
|
||||||
const fetchSchema = async dataSource => {
|
const fetchSchema = async dataSource => {
|
||||||
if (dataSource) {
|
if (dataSource) {
|
||||||
|
@ -109,7 +68,7 @@
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="form"
|
type="form"
|
||||||
bind:id={formId}
|
bind:id={formId}
|
||||||
props={{ dataSource, disableValidation: true }}
|
props={{ dataSource, disableValidation: true, editAutoColumns: true }}
|
||||||
>
|
>
|
||||||
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||||
<div class="header" class:mobile={$context.device.mobile}>
|
<div class="header" class:mobile={$context.device.mobile}>
|
||||||
|
|
|
@ -13,6 +13,10 @@
|
||||||
// for fields rendered in things like search blocks.
|
// for fields rendered in things like search blocks.
|
||||||
export let disableValidation = false
|
export let disableValidation = false
|
||||||
|
|
||||||
|
// Not exposed as a builder setting. Used internally to allow searching on
|
||||||
|
// auto columns.
|
||||||
|
export let editAutoColumns = false
|
||||||
|
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
const { API, fetchDatasourceSchema } = getContext("sdk")
|
const { API, fetchDatasourceSchema } = getContext("sdk")
|
||||||
|
|
||||||
|
@ -107,6 +111,7 @@
|
||||||
{table}
|
{table}
|
||||||
{initialValues}
|
{initialValues}
|
||||||
{disableValidation}
|
{disableValidation}
|
||||||
|
{editAutoColumns}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</InnerForm>
|
</InnerForm>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let schema
|
export let schema
|
||||||
export let table
|
export let table
|
||||||
export let disableValidation = false
|
export let disableValidation = false
|
||||||
|
export let editAutoColumns = false
|
||||||
|
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const { styleable, Provider, ActionTypes } = getContext("sdk")
|
const { styleable, Provider, ActionTypes } = getContext("sdk")
|
||||||
|
@ -183,7 +184,8 @@
|
||||||
fieldId,
|
fieldId,
|
||||||
value: initialValue,
|
value: initialValue,
|
||||||
error: initialError,
|
error: initialError,
|
||||||
disabled: disabled || fieldDisabled || isAutoColumn,
|
disabled:
|
||||||
|
disabled || fieldDisabled || (isAutoColumn && !editAutoColumns),
|
||||||
defaultValue,
|
defaultValue,
|
||||||
validator,
|
validator,
|
||||||
lastUpdate: Date.now(),
|
lastUpdate: Date.now(),
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
|
||||||
|
// Map of data types to component types for search fields inside blocks
|
||||||
|
const schemaComponentMap = {
|
||||||
|
string: "stringfield",
|
||||||
|
options: "optionsfield",
|
||||||
|
number: "numberfield",
|
||||||
|
datetime: "datetimefield",
|
||||||
|
boolean: "booleanfield",
|
||||||
|
formula: "stringfield",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine data types for search fields and only use those that are valid
|
||||||
|
* @param searchColumns the search columns to use
|
||||||
|
* @param schema the data source schema
|
||||||
|
*/
|
||||||
|
export const enrichSearchColumns = (searchColumns, schema) => {
|
||||||
|
let enrichedColumns = []
|
||||||
|
searchColumns?.forEach(column => {
|
||||||
|
const schemaType = schema?.[column]?.type
|
||||||
|
const componentType = schemaComponentMap[schemaType]
|
||||||
|
if (componentType) {
|
||||||
|
enrichedColumns.push({
|
||||||
|
name: column,
|
||||||
|
componentType,
|
||||||
|
type: schemaType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return enrichedColumns.slice(0, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enriches a normal datasource filter with bindings representing the additional
|
||||||
|
* search fields configured as part of a searchable block. These bindings are
|
||||||
|
* fields inside a form used as part of the block.
|
||||||
|
* @param filter the normal data provider filter
|
||||||
|
* @param columns the enriched search column structure
|
||||||
|
* @param formId the ID of the form containing the search fields
|
||||||
|
*/
|
||||||
|
export const enrichFilter = (filter, columns, formId) => {
|
||||||
|
let enrichedFilter = [...(filter || [])]
|
||||||
|
columns?.forEach(column => {
|
||||||
|
const safePath = column.name.split(".").map(safe).join(".")
|
||||||
|
const stringType = column.type === "string" || column.type === "formula"
|
||||||
|
const dateType = column.type === "datetime"
|
||||||
|
const binding = `${safe(formId)}.${safePath}`
|
||||||
|
|
||||||
|
// For dates, use a range of the entire day selected
|
||||||
|
if (dateType) {
|
||||||
|
enrichedFilter.push({
|
||||||
|
field: column.name,
|
||||||
|
type: column.type,
|
||||||
|
operator: "rangeLow",
|
||||||
|
valueType: "Binding",
|
||||||
|
value: `{{ ${binding} }}`,
|
||||||
|
})
|
||||||
|
const format = "YYYY-MM-DDTHH:mm:ss.SSSZ"
|
||||||
|
enrichedFilter.push({
|
||||||
|
field: column.name,
|
||||||
|
type: column.type,
|
||||||
|
operator: "rangeHigh",
|
||||||
|
valueType: "Binding",
|
||||||
|
value: `{{ date (add (date ${binding} "x") 86399999) "${format}" }}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other fields, do an exact match
|
||||||
|
else {
|
||||||
|
enrichedFilter.push({
|
||||||
|
field: column.name,
|
||||||
|
type: column.type,
|
||||||
|
operator: stringType ? "string" : "equal",
|
||||||
|
valueType: "Binding",
|
||||||
|
value: `{{ ${binding} }}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return enrichedFilter
|
||||||
|
}
|
|
@ -99,8 +99,16 @@ export const buildLuceneQuery = filter => {
|
||||||
filter.forEach(expression => {
|
filter.forEach(expression => {
|
||||||
let { operator, field, type, value, externalType } = expression
|
let { operator, field, type, value, externalType } = expression
|
||||||
// Parse all values into correct types
|
// Parse all values into correct types
|
||||||
if (type === "datetime" && value) {
|
if (type === "datetime") {
|
||||||
value = new Date(value).toISOString()
|
// Ensure date value is a valid date and parse into correct format
|
||||||
|
if (!value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
value = new Date(value).toISOString()
|
||||||
|
} catch (error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (type === "number" && !Array.isArray(value)) {
|
if (type === "number" && !Array.isArray(value)) {
|
||||||
if (operator === "oneOf") {
|
if (operator === "oneOf") {
|
||||||
|
|
Loading…
Reference in New Issue