diff --git a/packages/bbui/src/Table/CellRenderer.svelte b/packages/bbui/src/Table/CellRenderer.svelte index 9a53fd0169..844501947f 100644 --- a/packages/bbui/src/Table/CellRenderer.svelte +++ b/packages/bbui/src/Table/CellRenderer.svelte @@ -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) + ) + } -{#if renderer && (customRenderer || (value != null && value !== ""))} +{#if !customRenderer && isNotSet(value)} + +{:else if renderer} diff --git a/packages/bbui/src/Table/StringRenderer.svelte b/packages/bbui/src/Table/StringRenderer.svelte index 6fd731f5ec..7219a1a8f1 100644 --- a/packages/bbui/src/Table/StringRenderer.svelte +++ b/packages/bbui/src/Table/StringRenderer.svelte @@ -1,8 +1,11 @@ -
{typeof value === "object" ? JSON.stringify(value) : value}
+
+ {typeof value === "object" ? JSON.stringify(value) : value} +
diff --git a/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte b/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte index 9c6f4956b0..1b2e92ced2 100644 --- a/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte @@ -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") + } @@ -144,30 +156,36 @@ {getOptionLabel} {getOptionValue} /> - {#if filter.key && isMultipleChoice(filter.key)} - + {#if showValue(filter)} + {#if filter.key && isMultipleChoice(filter.key)} + + {:else} + + {/if} + removeFilter(idx)} /> {:else} - + removeFilter(idx)} /> + +
{/if} - removeFilter(idx)} /> {/each}
{:else} diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index 1129a9eda2..c58364af07 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -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") diff --git a/packages/server/src/api/controllers/view/viewBuilder.js b/packages/server/src/api/controllers/view/viewBuilder.js index 068c6ab7b1..9cbd7b83d4 100644 --- a/packages/server/src/api/controllers/view/viewBuilder.js +++ b/packages/server/src/api/controllers/view/viewBuilder.js @@ -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 diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index 5a67f05830..316e20e352 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -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( diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts index 45addef839..07e4e950f8 100644 --- a/packages/server/src/integrations/utils.ts +++ b/packages/server/src/integrations/utils.ts @@ -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) {