Handle nulls / empty in views and tables

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

View File

@ -28,9 +28,27 @@
$: type = schema?.type ?? "string"
$: customRenderer = customRenderers?.find(x => x.column === schema?.name)
$: 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>
{#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>
<slot />
</svelte:component>

View File

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

View File

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

View File

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

View File

@ -10,6 +10,22 @@ const TOKEN_MAP = {
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 = {
group: {
type: "string",
@ -72,6 +88,10 @@ function parseFilterExpression(filters) {
expression.push(
`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 {
const 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,
}).select(selectStatement)
// handle joins
return addRelationships(knex, preQuery, selectStatement, tableName, relationships)
return addRelationships(
knex,
preQuery,
selectStatement,
tableName,
relationships
)
}
function buildUpdate(

View File

@ -34,7 +34,10 @@ export function generateRowIdField(keyProps: 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) {