Handle nulls / empty in views and tables

This commit is contained in:
Rory Powell 2021-10-21 14:15:55 +01:00
parent 65077556b1
commit b88319d201
7 changed files with 103 additions and 27 deletions

View File

@ -28,9 +28,27 @@
$: type = schema?.type ?? "string" $: type = schema?.type ?? "string"
$: customRenderer = customRenderers?.find(x => x.column === schema?.name) $: customRenderer = customRenderers?.find(x => x.column === schema?.name)
$: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer $: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer
/**
* Don't use falsy here as we want to:
* - include empty arrays
* - exclude 0 and booleans
*
* If updated, the corresponding view expression should be updated in 'server/viewBuilder.js'
*/
const isNotSet = value => {
return (
value === undefined ||
value === null ||
value === "" ||
(Array.isArray(value) && value.length === 0)
)
}
</script> </script>
{#if renderer && (customRenderer || (value != null && value !== ""))} {#if !customRenderer && isNotSet(value)}
<svelte:component this={StringRenderer} value={"Not Set"} secondary={true} />
{:else if renderer}
<svelte:component this={renderer} {row} {schema} {value} on:clickrelationship> <svelte:component this={renderer} {row} {schema} {value} on:clickrelationship>
<slot /> <slot />
</svelte:component> </svelte:component>

View File

@ -1,8 +1,11 @@
<script> <script>
export let value export let value
export let secondary
</script> </script>
<div>{typeof value === "object" ? JSON.stringify(value) : value}</div> <div class={secondary ? "secondary" : ""}>
{typeof value === "object" ? JSON.stringify(value) : value}
</div>
<style> <style>
div { div {
@ -10,4 +13,8 @@
text-overflow: ellipsis; text-overflow: ellipsis;
width: 150px; width: 150px;
} }
.secondary {
font-style: italic;
color: var(--grey-5);
}
</style> </style>

View File

@ -42,6 +42,14 @@
name: "Contains", name: "Contains",
key: "CONTAINS", key: "CONTAINS",
}, },
{
name: "Is Set",
key: "SET",
},
{
name: "Is Not Set",
key: "NOT_SET",
},
] ]
const CONJUNCTIONS = [ const CONJUNCTIONS = [
@ -116,6 +124,10 @@
const getOptionLabel = x => x.name const getOptionLabel = x => x.name
const getOptionValue = x => x.key const getOptionValue = x => x.key
const showValue = filter => {
return !(filter.condition === "SET" || filter.condition === "NOT_SET")
}
</script> </script>
<ModalContent title="Filter" confirmText="Save" onConfirm={saveView} size="L"> <ModalContent title="Filter" confirmText="Save" onConfirm={saveView} size="L">
@ -144,30 +156,36 @@
{getOptionLabel} {getOptionLabel}
{getOptionValue} {getOptionValue}
/> />
{#if filter.key && isMultipleChoice(filter.key)} {#if showValue(filter)}
<Select {#if filter.key && isMultipleChoice(filter.key)}
bind:value={filter.value} <Select
options={fieldOptions(filter.key)} bind:value={filter.value}
getOptionLabel={x => x.toString()} options={fieldOptions(filter.key)}
/> getOptionLabel={x => x.toString()}
{:else if filter.key && isDate(filter.key)} />
<DatePicker {:else if filter.key && isDate(filter.key)}
bind:value={filter.value} <DatePicker
placeholder={filter.key || fields[0]} bind:value={filter.value}
/> placeholder={filter.key || fields[0]}
{:else if filter.key && isNumber(filter.key)} />
<Input {:else if filter.key && isNumber(filter.key)}
bind:value={filter.value} <Input
placeholder={filter.key || fields[0]} bind:value={filter.value}
type="number" placeholder={filter.key || fields[0]}
/> type="number"
/>
{:else}
<Input
placeholder={filter.key || fields[0]}
bind:value={filter.value}
/>
{/if}
<Icon hoverable name="Close" on:click={() => removeFilter(idx)} />
{:else} {:else}
<Input <Icon hoverable name="Close" on:click={() => removeFilter(idx)} />
placeholder={filter.key || fields[0]} <!-- empty div to preserve spacing -->
bind:value={filter.value} <div />
/>
{/if} {/if}
<Icon hoverable name="Close" on:click={() => removeFilter(idx)} />
{/each} {/each}
</div> </div>
{:else} {:else}

View File

@ -37,7 +37,11 @@ interface RunConfig {
module External { module External {
const { makeExternalQuery } = require("./utils") const { makeExternalQuery } = require("./utils")
const { DataSourceOperation, FieldTypes, RelationshipTypes } = require("../../../constants") const {
DataSourceOperation,
FieldTypes,
RelationshipTypes,
} = require("../../../constants")
const { breakExternalTableId, isSQL } = require("../../../integrations/utils") const { breakExternalTableId, isSQL } = require("../../../integrations/utils")
const { processObjectSync } = require("@budibase/string-templates") const { processObjectSync } = require("@budibase/string-templates")
const { cloneDeep } = require("lodash/fp") const { cloneDeep } = require("lodash/fp")

View File

@ -10,6 +10,22 @@ const TOKEN_MAP = {
OR: "||", OR: "||",
} }
/**
* Don't use falsy here as we want to:
* - include empty arrays
* - exclude 0 and booleans
*
* If updated, the corresponding rendering condition should be updated in 'bbui/CellRenderer.svelte'
*/
const isNotSetExpression = key => {
return `(
doc["${key}"] === undefined ||
doc["${key}"] === null ||
doc["${key}"] === "" ||
(Array.isArray(doc["${key}"]) && doc["${key}"].length === 0)
)`
}
const GROUP_PROPERTY = { const GROUP_PROPERTY = {
group: { group: {
type: "string", type: "string",
@ -72,6 +88,10 @@ function parseFilterExpression(filters) {
expression.push( expression.push(
`doc["${filter.key}"].${TOKEN_MAP[filter.condition]}("${filter.value}")` `doc["${filter.key}"].${TOKEN_MAP[filter.condition]}("${filter.value}")`
) )
} else if (filter.condition === "NOT_SET") {
expression.push(isNotSetExpression(filter.key))
} else if (filter.condition === "SET") {
expression.push(`!${isNotSetExpression(filter.key)}`)
} else { } else {
const value = const value =
typeof filter.value == "string" ? `"${filter.value}"` : filter.value typeof filter.value == "string" ? `"${filter.value}"` : filter.value

View File

@ -201,7 +201,13 @@ function buildRead(knex: Knex, json: QueryJson, limit: number): KnexQuery {
[tableName]: query, [tableName]: query,
}).select(selectStatement) }).select(selectStatement)
// handle joins // handle joins
return addRelationships(knex, preQuery, selectStatement, tableName, relationships) return addRelationships(
knex,
preQuery,
selectStatement,
tableName,
relationships
)
} }
function buildUpdate( function buildUpdate(

View File

@ -34,7 +34,10 @@ export function generateRowIdField(keyProps: any[] = []) {
} }
export function isRowId(field: any) { export function isRowId(field: any) {
return Array.isArray(field) || (typeof field === "string" && field.match(ROW_ID_REGEX) != null) return (
Array.isArray(field) ||
(typeof field === "string" && field.match(ROW_ID_REGEX) != null)
)
} }
export function convertRowId(field: any) { export function convertRowId(field: any) {