Merge branch 'fix-oracle-parse' of github.com:Budibase/budibase into fix-oracle-parse
This commit is contained in:
commit
a30caa399b
|
@ -40,6 +40,15 @@
|
|||
{
|
||||
label: "Colors",
|
||||
colors: [
|
||||
"red-100",
|
||||
"orange-100",
|
||||
"yellow-100",
|
||||
"green-100",
|
||||
"seafoam-100",
|
||||
"blue-100",
|
||||
"indigo-100",
|
||||
"magenta-100",
|
||||
|
||||
"red-400",
|
||||
"orange-400",
|
||||
"yellow-400",
|
||||
|
@ -108,12 +117,17 @@
|
|||
|
||||
const getCheckColor = value => {
|
||||
// Use dynamic color for theme grays
|
||||
if (value?.includes("gray")) {
|
||||
if (value?.includes("-gray-")) {
|
||||
return /^.*(gray-(50|75|100|200|300|400|500))\)$/.test(value)
|
||||
? "var(--spectrum-global-color-gray-900)"
|
||||
: "var(--spectrum-global-color-gray-50)"
|
||||
}
|
||||
|
||||
// Use contrasating check for the dim colours
|
||||
if (value?.includes("-100")) {
|
||||
return "var(--spectrum-global-color-gray-900)"
|
||||
}
|
||||
|
||||
// Use black check for static white
|
||||
if (value?.includes("static-black")) {
|
||||
return "var(--spectrum-global-color-static-gray-50)"
|
||||
|
|
|
@ -62,7 +62,9 @@
|
|||
return placeholder || "Choose an option"
|
||||
}
|
||||
|
||||
return getFieldAttribute(getOptionLabel, value, options)
|
||||
return (
|
||||
getFieldAttribute(getOptionLabel, value, options) || "Choose an option"
|
||||
)
|
||||
}
|
||||
|
||||
const selectOption = value => {
|
||||
|
|
|
@ -111,17 +111,38 @@ a {
|
|||
/* Custom theme additions */
|
||||
.spectrum--darkest {
|
||||
--drop-shadow: rgba(0, 0, 0, 0.6);
|
||||
--spectrum-global-color-blue-100: rgb(30, 36, 50);
|
||||
--spectrum-global-color-red-100: #570000;
|
||||
--spectrum-global-color-orange-100: #481801;
|
||||
--spectrum-global-color-yellow-100: #352400;
|
||||
--spectrum-global-color-green-100: #002f07;
|
||||
--spectrum-global-color-seafoam-100: #122b2a;
|
||||
--spectrum-global-color-blue-100: #002651;
|
||||
--spectrum-global-color-indigo-100: #1a1d61;
|
||||
--spectrum-global-color-magenta-100: #530329;
|
||||
--translucent-grey: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.spectrum--dark {
|
||||
--drop-shadow: rgba(0, 0, 0, 0.3);
|
||||
--spectrum-global-color-blue-100: rgb(42, 47, 57);
|
||||
}
|
||||
.spectrum--light {
|
||||
--drop-shadow: rgba(0, 0, 0, 0.075);
|
||||
--spectrum-global-color-blue-100: rgb(240, 245, 255);
|
||||
--spectrum-global-color-red-100: #7b0000;
|
||||
--spectrum-global-color-orange-100: #662500;
|
||||
--spectrum-global-color-yellow-100: #4c3600;
|
||||
--spectrum-global-color-green-100: #00450a;
|
||||
--spectrum-global-color-seafoam-100: #12413f;
|
||||
--spectrum-global-color-blue-100: #003877;
|
||||
--spectrum-global-color-indigo-100: #282c8c;
|
||||
--spectrum-global-color-magenta-100: #76003a;
|
||||
--translucent-grey: rgba(255, 255, 255, 0.065);
|
||||
}
|
||||
.spectrum--light,
|
||||
.spectrum--lightest {
|
||||
--drop-shadow: rgba(0, 0, 0, 0.05);
|
||||
--spectrum-global-color-blue-100: rgb(240, 244, 255);
|
||||
--drop-shadow: rgba(0, 0, 0, 0.075);
|
||||
--spectrum-global-color-red-100: #ffddd6;
|
||||
--spectrum-global-color-orange-100: #ffdfad;
|
||||
--spectrum-global-color-yellow-100: #fbf198;
|
||||
--spectrum-global-color-green-100: #cef8e0;
|
||||
--spectrum-global-color-seafoam-100: #cef7f3;
|
||||
--spectrum-global-color-blue-100: #e0f2ff;
|
||||
--spectrum-global-color-indigo-100: #edeeff;
|
||||
--spectrum-global-color-magenta-100: #ffeaf1;
|
||||
--translucent-grey: rgba(0, 0, 0, 0.085);
|
||||
}
|
||||
|
|
|
@ -682,7 +682,7 @@
|
|||
{errors}
|
||||
/>
|
||||
{:else if editableColumn.type === FORMULA_TYPE}
|
||||
{#if !table.sql}
|
||||
{#if !externalTable}
|
||||
<div class="split-label">
|
||||
<div class="label-length">
|
||||
<Label size="M">Formula Type</Label>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
export let bindings = []
|
||||
export let value = ""
|
||||
export let allowHBS = true
|
||||
export let allowJS = false
|
||||
export let allowHelpers = true
|
||||
export let autofocusEditor = false
|
||||
|
@ -31,6 +32,7 @@
|
|||
context={{ ...$previewStore.selectedComponentContext, ...context }}
|
||||
snippets={$snippets}
|
||||
{value}
|
||||
{allowHBS}
|
||||
{allowJS}
|
||||
{allowHelpers}
|
||||
{autofocusEditor}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
export let placeholder
|
||||
export let label
|
||||
export let disabled = false
|
||||
export let allowHBS = true
|
||||
export let allowJS = true
|
||||
export let allowHelpers = true
|
||||
export let updateOnChange = true
|
||||
|
@ -100,6 +101,7 @@
|
|||
value={readableValue}
|
||||
on:change={event => (tempValue = event.detail)}
|
||||
{bindings}
|
||||
{allowHBS}
|
||||
{allowJS}
|
||||
{allowHelpers}
|
||||
{context}
|
||||
|
|
|
@ -30,6 +30,7 @@ import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte
|
|||
import FormStepConfiguration from "./controls/FormStepConfiguration.svelte"
|
||||
import FormStepControls from "./controls/FormStepControls.svelte"
|
||||
import PaywalledSetting from "./controls/PaywalledSetting.svelte"
|
||||
import TableConditionEditor from "./controls/TableConditionEditor.svelte"
|
||||
|
||||
const componentMap = {
|
||||
text: DrawerBindableInput,
|
||||
|
@ -61,6 +62,7 @@ const componentMap = {
|
|||
columns: ColumnEditor,
|
||||
"columns/basic": BasicColumnEditor,
|
||||
"columns/grid": GridColumnEditor,
|
||||
tableConditions: TableConditionEditor,
|
||||
"field/sortable": SortableFieldSelect,
|
||||
"field/string": FormFieldSelect,
|
||||
"field/number": FormFieldSelect,
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
export let listItemKey
|
||||
export let draggable = true
|
||||
export let focus
|
||||
export let bindings = []
|
||||
|
||||
let zoneType = generate()
|
||||
|
||||
|
@ -126,6 +127,7 @@
|
|||
anchor={anchors[draggableItem.id]}
|
||||
item={draggableItem.item}
|
||||
{...listTypeProps}
|
||||
{bindings}
|
||||
on:change={onItemChanged}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
export let componentBindings
|
||||
export let bindings
|
||||
export let parseSettings
|
||||
export let disabled
|
||||
|
||||
const draggable = getContext("draggable")
|
||||
const dispatch = createEventDispatcher()
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
import { createEventDispatcher } from "svelte"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import { FIELDS } from "constants/backend"
|
||||
import { Constants } from "@budibase/frontend-core"
|
||||
import { FieldType } from "@budibase/types"
|
||||
|
||||
export let item
|
||||
export let anchor
|
||||
export let bindings
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
@ -28,19 +31,30 @@
|
|||
}
|
||||
|
||||
const parseSettings = settings => {
|
||||
return settings
|
||||
let columnSettings = settings
|
||||
.filter(setting => setting.key !== "field")
|
||||
.map(setting => {
|
||||
return { ...setting, nested: true }
|
||||
})
|
||||
|
||||
// Filter out conditions for invalid types.
|
||||
// Allow formulas as we have all the data already loaded in the table.
|
||||
if (
|
||||
Constants.BannedSearchTypes.includes(item.columnType) &&
|
||||
item.columnType !== FieldType.FORMULA
|
||||
) {
|
||||
return columnSettings.filter(x => x.key !== "conditions")
|
||||
}
|
||||
return columnSettings
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-left">
|
||||
<EditComponentPopover
|
||||
{anchor}
|
||||
componentInstance={item}
|
||||
{bindings}
|
||||
{anchor}
|
||||
{parseSettings}
|
||||
on:change
|
||||
>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export let value
|
||||
export let componentInstance
|
||||
export let bindings
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let primaryDisplayColumnAnchor
|
||||
|
@ -63,6 +64,7 @@
|
|||
items={columns.sortable}
|
||||
listItemKey={"_id"}
|
||||
listType={FieldSetting}
|
||||
{bindings}
|
||||
/>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -68,6 +68,7 @@ const toGridFormat = draggableListColumns => {
|
|||
field: entry.field,
|
||||
active: entry.active,
|
||||
width: entry.width,
|
||||
conditions: entry.conditions,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -83,6 +84,7 @@ const toDraggableListFormat = (gridFormatColumns, createComponent, schema) => {
|
|||
label: column.label,
|
||||
columnType: schema[column.field].type,
|
||||
width: column.width,
|
||||
conditions: column.conditions,
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
@ -106,12 +108,12 @@ const getColumns = ({
|
|||
createComponent,
|
||||
schema
|
||||
)
|
||||
const primary = draggableList.find(
|
||||
entry => entry.field === primaryDisplayColumnName
|
||||
)
|
||||
const sortable = draggableList.filter(
|
||||
entry => entry.field !== primaryDisplayColumnName
|
||||
)
|
||||
const primary = draggableList
|
||||
.filter(entry => entry.field === primaryDisplayColumnName)
|
||||
.map(instance => ({ ...instance, schema }))[0]
|
||||
const sortable = draggableList
|
||||
.filter(entry => entry.field !== primaryDisplayColumnName)
|
||||
.map(instance => ({ ...instance, schema }))
|
||||
|
||||
return {
|
||||
primary,
|
||||
|
|
|
@ -72,6 +72,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "three",
|
||||
label: "three label",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
},
|
||||
{
|
||||
_id: "two",
|
||||
|
@ -81,6 +83,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "two",
|
||||
label: "two label",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
},
|
||||
{
|
||||
_id: "one",
|
||||
|
@ -90,6 +94,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "one",
|
||||
label: "one",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
},
|
||||
])
|
||||
|
||||
|
@ -101,6 +107,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "four",
|
||||
label: "four",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -126,6 +134,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "two",
|
||||
label: "two",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
},
|
||||
{
|
||||
_id: "three",
|
||||
|
@ -135,6 +145,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "three",
|
||||
label: "three",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
},
|
||||
{
|
||||
_id: "one",
|
||||
|
@ -144,6 +156,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "one",
|
||||
label: "one",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
},
|
||||
])
|
||||
|
||||
|
@ -155,6 +169,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "four",
|
||||
label: "four",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -188,6 +204,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "three",
|
||||
label: "three label",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
},
|
||||
{
|
||||
_id: "two",
|
||||
|
@ -197,6 +215,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "two",
|
||||
label: "two",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
},
|
||||
{
|
||||
_id: "one",
|
||||
|
@ -206,6 +226,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "one",
|
||||
label: "one",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
},
|
||||
])
|
||||
|
||||
|
@ -217,6 +239,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "four",
|
||||
label: "four",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -247,6 +271,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "three",
|
||||
label: "three label",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
},
|
||||
{
|
||||
_id: "two",
|
||||
|
@ -256,6 +282,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "two",
|
||||
label: "two",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
},
|
||||
{
|
||||
_id: "one",
|
||||
|
@ -265,6 +293,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "one",
|
||||
label: "one",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
},
|
||||
])
|
||||
|
||||
|
@ -276,6 +306,8 @@ describe("getColumns", () => {
|
|||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "four",
|
||||
label: "four",
|
||||
conditions: undefined,
|
||||
schema: ctx.schema,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
<script>
|
||||
import {
|
||||
ActionButton,
|
||||
Drawer,
|
||||
Button,
|
||||
DrawerContent,
|
||||
Layout,
|
||||
Select,
|
||||
Icon,
|
||||
DatePicker,
|
||||
Combobox,
|
||||
Multiselect,
|
||||
} from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { cloneDeep } from "lodash"
|
||||
import ColorPicker from "./ColorPicker.svelte"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
import { QueryUtils, Constants, FilterUsers } from "@budibase/frontend-core"
|
||||
import { generate } from "shortid"
|
||||
import { FieldType, FormulaType } from "@budibase/types"
|
||||
import { dndzone } from "svelte-dnd-action"
|
||||
import { flip } from "svelte/animate"
|
||||
|
||||
export let componentInstance
|
||||
export let bindings
|
||||
export let value
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const flipDuration = 130
|
||||
const targetOptions = [
|
||||
{
|
||||
label: "Cell",
|
||||
value: "cell",
|
||||
},
|
||||
{
|
||||
label: "Row",
|
||||
value: "row",
|
||||
},
|
||||
]
|
||||
const conditionOptions = [
|
||||
{
|
||||
label: "Background color",
|
||||
value: "backgroundColor",
|
||||
},
|
||||
{
|
||||
label: "Text color",
|
||||
value: "textColor",
|
||||
},
|
||||
]
|
||||
|
||||
let tempValue = []
|
||||
let drawer
|
||||
let dragDisabled = true
|
||||
|
||||
$: count = value?.length
|
||||
$: conditionText = `${count || "No"} condition${count !== 1 ? "s" : ""} set`
|
||||
$: type = componentInstance.columnType
|
||||
$: valueTypeOptions = getValueTypeOptions(type)
|
||||
$: hasValueOption = type !== FieldType.STRING
|
||||
$: operatorOptions = QueryUtils.getValidOperatorsForType({
|
||||
type,
|
||||
|
||||
// We can filter on any formula columns here since we already have the data
|
||||
// on the page, so adding this ensures formula columns get operators
|
||||
formulaType: FormulaType.STATIC,
|
||||
})
|
||||
|
||||
const getValueTypeOptions = type => {
|
||||
let options = [
|
||||
{
|
||||
label: "Binding",
|
||||
value: FieldType.STRING,
|
||||
},
|
||||
]
|
||||
if (type !== FieldType.STRING) {
|
||||
options.push({
|
||||
label: "Value",
|
||||
value: type,
|
||||
})
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
const openDrawer = () => {
|
||||
tempValue = cloneDeep(value || [])
|
||||
drawer.show()
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
dispatch("change", tempValue)
|
||||
drawer.hide()
|
||||
}
|
||||
|
||||
const addCondition = () => {
|
||||
const condition = {
|
||||
id: generate(),
|
||||
target: targetOptions[0].value,
|
||||
metadataKey: conditionOptions[0].value,
|
||||
operator: operatorOptions[0]?.value,
|
||||
valueType: FieldType.STRING,
|
||||
}
|
||||
tempValue = [...tempValue, condition]
|
||||
}
|
||||
|
||||
const duplicateCondition = condition => {
|
||||
const dupe = { ...condition, id: generate() }
|
||||
tempValue = [...tempValue, dupe]
|
||||
}
|
||||
|
||||
const removeCondition = condition => {
|
||||
tempValue = tempValue.filter(c => c.id !== condition.id)
|
||||
}
|
||||
|
||||
const onOperatorChange = (condition, newOperator) => {
|
||||
const noValueOptions = [
|
||||
Constants.OperatorOptions.Empty.value,
|
||||
Constants.OperatorOptions.NotEmpty.value,
|
||||
]
|
||||
condition.noValue = noValueOptions.includes(newOperator)
|
||||
if (condition.noValue) {
|
||||
condition.referenceValue = null
|
||||
condition.valueType = "string"
|
||||
}
|
||||
}
|
||||
|
||||
const onValueTypeChange = condition => {
|
||||
condition.referenceValue = null
|
||||
}
|
||||
|
||||
const updateConditions = e => {
|
||||
tempValue = e.detail.items
|
||||
}
|
||||
|
||||
const handleFinalize = e => {
|
||||
updateConditions(e)
|
||||
dragDisabled = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<ActionButton on:click={openDrawer}>{conditionText}</ActionButton>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<Drawer
|
||||
bind:this={drawer}
|
||||
title="{componentInstance.field} conditions"
|
||||
on:drawerShow
|
||||
on:drawerHide
|
||||
>
|
||||
<Button cta slot="buttons" on:click={save}>Save</Button>
|
||||
<DrawerContent slot="body">
|
||||
<div class="container">
|
||||
<Layout noPadding>
|
||||
Update the appearance of cells and rows based on their value.
|
||||
{#if tempValue.length}
|
||||
<div
|
||||
class="conditions"
|
||||
use:dndzone={{
|
||||
items: tempValue,
|
||||
flipDurationMs: flipDuration,
|
||||
dropTargetStyle: { outline: "none" },
|
||||
dragDisabled,
|
||||
}}
|
||||
on:consider={updateConditions}
|
||||
on:finalize={handleFinalize}
|
||||
>
|
||||
{#each tempValue as condition (condition.id)}
|
||||
<div
|
||||
class="condition"
|
||||
class:update={condition.action === "update"}
|
||||
class:with-value-option={hasValueOption}
|
||||
animate:flip={{ duration: flipDuration }}
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
aria-label="drag-handle"
|
||||
style={dragDisabled ? "cursor: grab" : "cursor: grabbing"}
|
||||
on:mousedown={() => (dragDisabled = false)}
|
||||
>
|
||||
<Icon name="DragHandle" size="XL" />
|
||||
</div>
|
||||
<span>Update</span>
|
||||
<Select
|
||||
placeholder={null}
|
||||
options={targetOptions}
|
||||
bind:value={condition.target}
|
||||
/>
|
||||
<Select
|
||||
placeholder={null}
|
||||
options={conditionOptions}
|
||||
bind:value={condition.metadataKey}
|
||||
/>
|
||||
<span>to</span>
|
||||
<ColorPicker
|
||||
value={condition.metadataValue}
|
||||
on:change={e => (condition.metadataValue = e.detail)}
|
||||
/>
|
||||
<span>if value</span>
|
||||
<Select
|
||||
placeholder={null}
|
||||
options={operatorOptions}
|
||||
bind:value={condition.operator}
|
||||
on:change={e => onOperatorChange(condition, e.detail)}
|
||||
/>
|
||||
{#if hasValueOption}
|
||||
<Select
|
||||
disabled={condition.noValue}
|
||||
options={valueTypeOptions}
|
||||
bind:value={condition.valueType}
|
||||
placeholder={null}
|
||||
on:change={() => onValueTypeChange(condition)}
|
||||
/>
|
||||
{/if}
|
||||
{#if type === FieldType.DATETIME && condition.valueType === type}
|
||||
<DatePicker
|
||||
placeholder="Value"
|
||||
disabled={condition.noValue}
|
||||
bind:value={condition.referenceValue}
|
||||
/>
|
||||
{:else if type === FieldType.BOOLEAN && condition.valueType === type}
|
||||
<Select
|
||||
placeholder="Value"
|
||||
disabled={condition.noValue}
|
||||
options={["True", "False"]}
|
||||
bind:value={condition.referenceValue}
|
||||
/>
|
||||
{:else if (type === FieldType.OPTIONS || type === FieldType.ARRAY) && condition.valueType === type}
|
||||
{#if condition.operator === Constants.OperatorOptions.In.value}
|
||||
<Multiselect
|
||||
disabled={condition.noValue}
|
||||
options={componentInstance.schema?.[
|
||||
componentInstance.field
|
||||
]?.constraints?.inclusion || []}
|
||||
bind:value={condition.referenceValue}
|
||||
/>
|
||||
{:else}
|
||||
<Combobox
|
||||
disabled={condition.noValue}
|
||||
options={componentInstance.schema?.[
|
||||
componentInstance.field
|
||||
]?.constraints?.inclusion || []}
|
||||
bind:value={condition.referenceValue}
|
||||
/>
|
||||
{/if}
|
||||
{:else if (type === FieldType.BB_REFERENCE || type === FieldType.BB_REFERENCE_SINGLE) && condition.valueType === type}
|
||||
<FilterUsers
|
||||
bind:value={condition.referenceValue}
|
||||
multiselect={[
|
||||
Constants.OperatorOptions.In.value,
|
||||
Constants.OperatorOptions.ContainsAny.value,
|
||||
].includes(condition.operator)}
|
||||
disabled={condition.noValue}
|
||||
type={condition.valueType}
|
||||
/>
|
||||
{:else}
|
||||
<DrawerBindableInput
|
||||
{bindings}
|
||||
placeholder="Value"
|
||||
disabled={condition.noValue}
|
||||
value={condition.referenceValue}
|
||||
on:change={e => (condition.referenceValue = e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
<Icon
|
||||
name="Duplicate"
|
||||
hoverable
|
||||
size="S"
|
||||
on:click={() => duplicateCondition(condition)}
|
||||
/>
|
||||
<Icon
|
||||
name="Close"
|
||||
hoverable
|
||||
size="S"
|
||||
on:click={() => removeCondition(condition)}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
<Button secondary icon="Add" on:click={addCondition}>
|
||||
Add condition
|
||||
</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.conditions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-l);
|
||||
}
|
||||
.condition {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto 1fr 1fr auto auto auto 1fr 1fr auto auto;
|
||||
align-items: center;
|
||||
grid-column-gap: var(--spacing-l);
|
||||
}
|
||||
.condition.with-value-option {
|
||||
grid-template-columns: auto auto 1fr 1fr auto auto auto 1fr 1fr 1fr auto auto;
|
||||
}
|
||||
</style>
|
|
@ -29,7 +29,7 @@ import { JSONUtils, Constants } from "@budibase/frontend-core"
|
|||
import ActionDefinitions from "components/design/settings/controls/ButtonActionEditor/manifest.json"
|
||||
import { environment, licensing } from "stores/portal"
|
||||
import { convertOldFieldFormat } from "components/design/settings/controls/FieldConfiguration/utils"
|
||||
import { FIELDS } from "constants/backend"
|
||||
import { FIELDS, DB_TYPE_INTERNAL } from "constants/backend"
|
||||
import { FieldType } from "@budibase/types"
|
||||
|
||||
const { ContextScopes } = Constants
|
||||
|
@ -991,7 +991,7 @@ export const getSchemaForDatasource = (asset, datasource, options) => {
|
|||
}
|
||||
|
||||
// Determine if we should add ID and rev to the schema
|
||||
const isInternal = table && !table.sql
|
||||
const isInternal = table && table?.sourceType === DB_TYPE_INTERNAL
|
||||
const isDSPlus = ["table", "link", "viewV2"].includes(datasource.type)
|
||||
|
||||
// ID is part of the readable schema for all tables
|
||||
|
|
|
@ -8,8 +8,11 @@
|
|||
} from "@budibase/bbui"
|
||||
import { componentStore } from "stores/builder"
|
||||
import ConditionalUIDrawer from "./ConditionalUIDrawer.svelte"
|
||||
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
||||
|
||||
export let componentInstance
|
||||
export let componentDefinition
|
||||
export let componentBindings
|
||||
export let bindings
|
||||
|
||||
let tempValue
|
||||
|
@ -35,6 +38,19 @@
|
|||
} set`
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Load any general settings or sections tagged as "condition"
|
||||
-->
|
||||
<ComponentSettingsSection
|
||||
{componentInstance}
|
||||
{componentDefinition}
|
||||
isScreen={false}
|
||||
showInstanceName={false}
|
||||
{bindings}
|
||||
{componentBindings}
|
||||
tag="condition"
|
||||
/>
|
||||
|
||||
<DetailSummary name={"Conditions"} collapsible={false}>
|
||||
<ActionButton on:click={openDrawer}>{conditionText}</ActionButton>
|
||||
</DetailSummary>
|
||||
|
|
|
@ -2879,6 +2879,11 @@
|
|||
"placeholder": "Auto",
|
||||
"min": 80,
|
||||
"max": 9999
|
||||
},
|
||||
{
|
||||
"type": "tableConditions",
|
||||
"label": "Conditions",
|
||||
"key": "conditions"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -7303,7 +7308,6 @@
|
|||
{
|
||||
"type": "columns/grid",
|
||||
"key": "columns",
|
||||
"nested": true,
|
||||
"resetOn": "table"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
$: currentTheme = $context?.device?.theme
|
||||
$: darkMode = !currentTheme?.includes("light")
|
||||
$: parsedColumns = getParsedColumns(columns)
|
||||
$: columnWhitelist = parsedColumns.filter(x => x.active).map(x => x.field)
|
||||
$: schemaOverrides = getSchemaOverrides(parsedColumns)
|
||||
$: enrichedButtons = enrichButtons(buttons)
|
||||
$: selectedRows = deriveSelectedRows(gridContext)
|
||||
|
@ -62,7 +61,13 @@
|
|||
const goldenRow = generateGoldenSample(rows)
|
||||
const id = get(component).id
|
||||
return {
|
||||
// Not sure what this one is for...
|
||||
[id]: goldenRow,
|
||||
|
||||
// For row conditions context
|
||||
row: goldenRow,
|
||||
|
||||
// For button action context
|
||||
eventContext: {
|
||||
row: goldenRow,
|
||||
},
|
||||
|
@ -91,6 +96,8 @@
|
|||
overrides[column.field] = {
|
||||
displayName: column.label,
|
||||
order: idx,
|
||||
conditions: column.conditions,
|
||||
visible: !!column.active,
|
||||
}
|
||||
if (column.width) {
|
||||
overrides[column.field].width = column.width
|
||||
|
@ -163,7 +170,6 @@
|
|||
{initialSortColumn}
|
||||
{initialSortOrder}
|
||||
{fixedRowHeight}
|
||||
{columnWhitelist}
|
||||
{schemaOverrides}
|
||||
canAddRows={allowAddRows}
|
||||
canEditRows={allowEditRows}
|
||||
|
@ -187,7 +193,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
border: 1px solid var(--spectrum-global-color-gray-200);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
height: 410px;
|
||||
|
|
|
@ -130,6 +130,10 @@
|
|||
on:mouseup={stopSelectionCallback}
|
||||
on:click={handleClick}
|
||||
width={column.width}
|
||||
metadata={{
|
||||
...row.__metadata?.row,
|
||||
...row.__metadata?.cell[column.name],
|
||||
}}
|
||||
>
|
||||
<svelte:component
|
||||
this={getCellRenderer(column)}
|
||||
|
|
|
@ -11,13 +11,20 @@
|
|||
export let center = false
|
||||
export let readonly = false
|
||||
export let hidden = false
|
||||
export let metadata = null
|
||||
|
||||
$: style = getStyle(width, selectedUser)
|
||||
$: style = getStyle(width, selectedUser, metadata)
|
||||
|
||||
const getStyle = (width, selectedUser) => {
|
||||
const getStyle = (width, selectedUser, metadata) => {
|
||||
let style = width === "auto" ? "width: auto;" : `flex: 0 0 ${width}px;`
|
||||
if (selectedUser) {
|
||||
style += `--user-color:${selectedUser.color};`
|
||||
style += `--user-color :${selectedUser.color};`
|
||||
}
|
||||
if (metadata?.backgroundColor) {
|
||||
style += `--cell-background: ${metadata.backgroundColor};`
|
||||
}
|
||||
if (metadata?.textColor) {
|
||||
style += `--cell-font-color: ${metadata.textColor};`
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
@ -43,7 +50,7 @@
|
|||
on:mouseup
|
||||
on:click
|
||||
on:contextmenu
|
||||
on:touchstart
|
||||
on:touchstart|passive
|
||||
on:touchend
|
||||
on:touchcancel
|
||||
on:mouseenter
|
||||
|
@ -72,7 +79,7 @@
|
|||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
color: var(--spectrum-global-color-gray-800);
|
||||
color: var(--cell-font-color);
|
||||
font-size: var(--cell-font-size);
|
||||
gap: var(--cell-spacing);
|
||||
background: var(--cell-background);
|
||||
|
@ -94,9 +101,9 @@
|
|||
}
|
||||
|
||||
/* Cell border */
|
||||
.cell.focused:after,
|
||||
.cell.error:after,
|
||||
.cell.selected-other:not(.focused):after {
|
||||
.cell.focused::after,
|
||||
.cell.error::after,
|
||||
.cell.selected-other:not(.focused)::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -109,14 +116,30 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Cell background overlay */
|
||||
.cell.selected::before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
box-sizing: border-box;
|
||||
height: calc(100% + 1px);
|
||||
width: calc(100% + 1px);
|
||||
opacity: 0.16;
|
||||
background: var(--spectrum-global-color-blue-400);
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Cell border for cells with labels */
|
||||
.cell.error:after {
|
||||
.cell.error::after {
|
||||
border-radius: 0 2px 2px 2px;
|
||||
}
|
||||
.cell.top.error:after {
|
||||
.cell.top.error::after {
|
||||
border-radius: 2px 2px 2px 0;
|
||||
}
|
||||
.cell.selected-other:not(.focused):after {
|
||||
.cell.selected-other:not(.focused)::after {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
|
@ -151,15 +174,10 @@
|
|||
.cell.focused.readonly {
|
||||
--cell-color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
|
||||
.cell.highlighted:not(.focused),
|
||||
.cell.highlighted:not(.focused):not(.selected),
|
||||
.cell.focused.readonly {
|
||||
--cell-background: var(--cell-background-hover);
|
||||
}
|
||||
.cell.selected.focused,
|
||||
.cell.selected:not(.focused) {
|
||||
--cell-background: var(--spectrum-global-color-blue-100);
|
||||
}
|
||||
|
||||
/* Label for additional text */
|
||||
.label {
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
selected={rowSelected}
|
||||
{defaultHeight}
|
||||
rowIdx={row?.__idx}
|
||||
metadata={row?.__metadata?.row}
|
||||
>
|
||||
<div class="gutter">
|
||||
{#if $$slots.default}
|
||||
|
@ -115,7 +116,7 @@
|
|||
margin: 3px 0 0 0;
|
||||
}
|
||||
.number {
|
||||
color: var(--spectrum-global-color-gray-500);
|
||||
color: val(--cell-font-color, var(--spectrum-global-color-gray-500));
|
||||
}
|
||||
.checkbox.visible,
|
||||
.number.visible {
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
rowIdx={row.__idx}
|
||||
selected={rowSelected}
|
||||
highlighted={rowHovered || rowFocused}
|
||||
metadata={row.__metadata?.row}
|
||||
>
|
||||
<div class="buttons" class:offset={$showVScrollbar}>
|
||||
{#each buttons as button}
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
export let API = null
|
||||
export let datasource = null
|
||||
export let schemaOverrides = null
|
||||
export let columnWhitelist = null
|
||||
export let canAddRows = true
|
||||
export let canExpandRows = true
|
||||
export let canEditRows = true
|
||||
|
@ -59,6 +58,7 @@
|
|||
export let darkMode
|
||||
export let isCloud = null
|
||||
export let allowViewReadonlyColumns = false
|
||||
export let rowConditions = null
|
||||
|
||||
// Unique identifier for DOM nodes inside this instance
|
||||
const gridID = `grid-${Math.random().toString().slice(2)}`
|
||||
|
@ -93,7 +93,6 @@
|
|||
$: props.set({
|
||||
datasource,
|
||||
schemaOverrides,
|
||||
columnWhitelist,
|
||||
canAddRows,
|
||||
canExpandRows,
|
||||
canEditRows,
|
||||
|
@ -114,6 +113,8 @@
|
|||
buttons,
|
||||
darkMode,
|
||||
isCloud,
|
||||
allowViewReadonlyColumns,
|
||||
rowConditions,
|
||||
})
|
||||
|
||||
// Derive min height and make available in context
|
||||
|
@ -231,6 +232,7 @@
|
|||
--cell-spacing: 4px;
|
||||
--cell-border: 1px solid var(--spectrum-global-color-gray-200);
|
||||
--cell-font-size: 14px;
|
||||
--cell-font-color: var(--spectrum-global-color-gray-800);
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -286,7 +288,7 @@
|
|||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 2px solid var(--spectrum-global-color-gray-200);
|
||||
border-bottom: var(--cell-border);
|
||||
padding: var(--cell-padding);
|
||||
gap: var(--cell-spacing);
|
||||
background: var(--grid-background-alt);
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
const generateStyle = (scrollLeft, scrollTop, rowHeight) => {
|
||||
const offsetX = scrollHorizontally ? -1 * scrollLeft : 0
|
||||
const offsetY = scrollVertically ? -1 * (scrollTop % rowHeight) : 0
|
||||
return `transform: translate3d(${offsetX}px, ${offsetY}px, 0);`
|
||||
return `transform: translate(${offsetX}px, ${offsetY}px);`
|
||||
}
|
||||
|
||||
// Handles a mouse wheel event and updates scroll state
|
||||
|
|
|
@ -162,7 +162,7 @@
|
|||
|
||||
/* Don't show borders between cells in the sticky column */
|
||||
.sticky-column :global(.cell:not(:last-child)) {
|
||||
border-right: none;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
.header {
|
||||
|
@ -171,6 +171,9 @@
|
|||
.header :global(.cell) {
|
||||
background: var(--grid-background-alt);
|
||||
}
|
||||
.header :global(.cell::before) {
|
||||
display: none;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
@ -144,6 +144,7 @@ export const initialise = context => {
|
|||
visible: fieldSchema.visible ?? true,
|
||||
readonly: fieldSchema.readonly,
|
||||
order: fieldSchema.order ?? oldColumn?.order,
|
||||
conditions: fieldSchema.conditions,
|
||||
}
|
||||
// Override a few properties for primary display
|
||||
if (field === primaryDisplay) {
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { derivedMemo, QueryUtils } from "../../../utils"
|
||||
import { FieldType, EmptyFilterOption } from "@budibase/types"
|
||||
|
||||
export const createStores = () => {
|
||||
const metadata = writable({})
|
||||
return {
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const { columns } = context
|
||||
|
||||
// Derive and memoize the cell conditions present in our columns so that we
|
||||
// only recompute condition metadata when absolutely necessary
|
||||
const conditions = derivedMemo(columns, $columns => {
|
||||
let newConditions = []
|
||||
for (let column of $columns) {
|
||||
for (let condition of column.conditions || []) {
|
||||
newConditions.push({
|
||||
...condition,
|
||||
column: column.name,
|
||||
type: column.schema.type,
|
||||
})
|
||||
}
|
||||
}
|
||||
return newConditions
|
||||
})
|
||||
|
||||
return {
|
||||
conditions,
|
||||
}
|
||||
}
|
||||
|
||||
export const initialise = context => {
|
||||
const { metadata, conditions, rows } = context
|
||||
|
||||
// Recompute all metadata if conditions change
|
||||
conditions.subscribe($conditions => {
|
||||
let newMetadata = {}
|
||||
if ($conditions?.length) {
|
||||
for (let row of get(rows)) {
|
||||
newMetadata[row._id] = evaluateConditions(row, $conditions)
|
||||
}
|
||||
}
|
||||
metadata.set(newMetadata)
|
||||
})
|
||||
|
||||
// Recompute metadata for specific rows when they change
|
||||
rows.subscribe($rows => {
|
||||
const $conditions = get(conditions)
|
||||
if (!$conditions?.length) {
|
||||
return
|
||||
}
|
||||
const $metadata = get(metadata)
|
||||
let metadataUpdates = {}
|
||||
for (let row of $rows) {
|
||||
if (!row._rev || $metadata[row._id]?.version !== row._rev) {
|
||||
metadataUpdates[row._id] = evaluateConditions(row, $conditions)
|
||||
}
|
||||
}
|
||||
if (Object.keys(metadataUpdates).length) {
|
||||
metadata.update(state => ({
|
||||
...state,
|
||||
...metadataUpdates,
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const TypeCoercionMap = {
|
||||
[FieldType.NUMBER]: parseFloat,
|
||||
[FieldType.DATETIME]: val => {
|
||||
if (val) {
|
||||
return new Date(val).toISOString()
|
||||
}
|
||||
return null
|
||||
},
|
||||
[FieldType.BOOLEAN]: val => {
|
||||
if (`${val}`.toLowerCase().trim() === "true") {
|
||||
return true
|
||||
}
|
||||
if (`${val}`.toLowerCase().trim() === "false") {
|
||||
return false
|
||||
}
|
||||
return null
|
||||
},
|
||||
}
|
||||
|
||||
// Evaluates an array of cell conditions against a certain row and returns the
|
||||
// resultant metadata
|
||||
const evaluateConditions = (row, conditions) => {
|
||||
let metadata = {
|
||||
version: row._rev,
|
||||
row: {},
|
||||
cell: {},
|
||||
}
|
||||
for (let condition of conditions) {
|
||||
try {
|
||||
let {
|
||||
column,
|
||||
type,
|
||||
referenceValue,
|
||||
operator,
|
||||
metadataKey,
|
||||
metadataValue,
|
||||
target,
|
||||
} = condition
|
||||
let value = row[column]
|
||||
|
||||
// Coerce values into correct types for primitives
|
||||
let coercedType = type
|
||||
if (type === FieldType.FORMULA) {
|
||||
// For formulas we want to ensure that the reference type matches the
|
||||
// real type
|
||||
if (value === true || value === false) {
|
||||
coercedType = FieldType.BOOLEAN
|
||||
} else if (typeof value === "number") {
|
||||
coercedType = FieldType.NUMBER
|
||||
}
|
||||
}
|
||||
const coerce = TypeCoercionMap[coercedType]
|
||||
if (coerce) {
|
||||
value = coerce(value)
|
||||
referenceValue = coerce(referenceValue)
|
||||
}
|
||||
|
||||
// Build lucene compatible condition expression
|
||||
const luceneFilter = {
|
||||
operator,
|
||||
type,
|
||||
field: "value",
|
||||
value: referenceValue,
|
||||
}
|
||||
let query = QueryUtils.buildQuery([luceneFilter])
|
||||
query.onEmptyFilter = EmptyFilterOption.RETURN_NONE
|
||||
const result = QueryUtils.runQuery([{ value }], query)
|
||||
if (result.length > 0) {
|
||||
if (target === "row") {
|
||||
metadata.row = {
|
||||
...metadata.row,
|
||||
[metadataKey]: metadataValue,
|
||||
}
|
||||
} else {
|
||||
metadata.cell[column] = {
|
||||
...metadata.cell[column],
|
||||
[metadataKey]: metadataValue,
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Swallow
|
||||
}
|
||||
}
|
||||
return metadata
|
||||
}
|
|
@ -12,9 +12,9 @@ export const createStores = context => {
|
|||
const initialFilter = getProp("initialFilter")
|
||||
const fixedRowHeight = getProp("fixedRowHeight")
|
||||
const schemaOverrides = getProp("schemaOverrides")
|
||||
const columnWhitelist = getProp("columnWhitelist")
|
||||
const notifySuccess = getProp("notifySuccess")
|
||||
const notifyError = getProp("notifyError")
|
||||
const rowConditions = getProp("rowConditions")
|
||||
|
||||
return {
|
||||
datasource,
|
||||
|
@ -23,9 +23,9 @@ export const createStores = context => {
|
|||
initialFilter,
|
||||
fixedRowHeight,
|
||||
schemaOverrides,
|
||||
columnWhitelist,
|
||||
notifySuccess,
|
||||
notifyError,
|
||||
rowConditions,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,14 +13,8 @@ export const createStores = () => {
|
|||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const {
|
||||
API,
|
||||
definition,
|
||||
schemaOverrides,
|
||||
columnWhitelist,
|
||||
datasource,
|
||||
schemaMutations,
|
||||
} = context
|
||||
const { API, definition, schemaOverrides, datasource, schemaMutations } =
|
||||
context
|
||||
|
||||
const schema = derived(definition, $definition => {
|
||||
let schema = getDatasourceSchema({
|
||||
|
@ -46,17 +40,13 @@ export const deriveStores = context => {
|
|||
// Derives the total enriched schema, made up of the saved schema and any
|
||||
// prop and user overrides
|
||||
const enrichedSchema = derived(
|
||||
[schema, schemaOverrides, schemaMutations, columnWhitelist],
|
||||
([$schema, $schemaOverrides, $schemaMutations, $columnWhitelist]) => {
|
||||
[schema, schemaOverrides, schemaMutations],
|
||||
([$schema, $schemaOverrides, $schemaMutations]) => {
|
||||
if (!$schema) {
|
||||
return null
|
||||
}
|
||||
let enrichedSchema = {}
|
||||
Object.keys($schema).forEach(field => {
|
||||
// Apply whitelist if provided
|
||||
if ($columnWhitelist?.length && !$columnWhitelist.includes(field)) {
|
||||
return
|
||||
}
|
||||
enrichedSchema[field] = {
|
||||
...$schema[field],
|
||||
...$schemaOverrides?.[field],
|
||||
|
|
|
@ -20,6 +20,7 @@ import * as Table from "./datasources/table"
|
|||
import * as ViewV2 from "./datasources/viewV2"
|
||||
import * as NonPlus from "./datasources/nonPlus"
|
||||
import * as Cache from "./cache"
|
||||
import * as Conditions from "./conditions"
|
||||
|
||||
const DependencyOrderedStores = [
|
||||
Sort,
|
||||
|
@ -33,6 +34,7 @@ const DependencyOrderedStores = [
|
|||
Scroll,
|
||||
Validation,
|
||||
Rows,
|
||||
Conditions,
|
||||
UI,
|
||||
Resize,
|
||||
Viewport,
|
||||
|
|
|
@ -5,6 +5,7 @@ import { getCellID, parseCellID } from "../lib/utils"
|
|||
import { tick } from "svelte"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { sleep } from "../../../utils/utils"
|
||||
import { FieldType } from "@budibase/types"
|
||||
|
||||
export const createStores = () => {
|
||||
const rows = writable([])
|
||||
|
@ -17,27 +18,6 @@ export const createStores = () => {
|
|||
const error = writable(null)
|
||||
const fetch = writable(null)
|
||||
|
||||
// Enrich rows with an index property and any pending changes
|
||||
const enrichedRows = derived(
|
||||
[rows, rowChangeCache],
|
||||
([$rows, $rowChangeCache]) => {
|
||||
return $rows.map((row, idx) => ({
|
||||
...row,
|
||||
...$rowChangeCache[row._id],
|
||||
__idx: idx,
|
||||
}))
|
||||
}
|
||||
)
|
||||
|
||||
// Generate a lookup map to quick find a row by ID
|
||||
const rowLookupMap = derived(enrichedRows, $enrichedRows => {
|
||||
let map = {}
|
||||
for (let i = 0; i < $enrichedRows.length; i++) {
|
||||
map[$enrichedRows[i]._id] = $enrichedRows[i]
|
||||
}
|
||||
return map
|
||||
})
|
||||
|
||||
// Mark loaded as true if we've ever stopped loading
|
||||
let hasStartedLoading = false
|
||||
loading.subscribe($loading => {
|
||||
|
@ -49,12 +29,8 @@ export const createStores = () => {
|
|||
})
|
||||
|
||||
return {
|
||||
rows: {
|
||||
...rows,
|
||||
subscribe: enrichedRows.subscribe,
|
||||
},
|
||||
rows,
|
||||
fetch,
|
||||
rowLookupMap,
|
||||
loaded,
|
||||
refreshing,
|
||||
loading,
|
||||
|
@ -65,6 +41,35 @@ export const createStores = () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const { rows } = context
|
||||
|
||||
// Enrich rows with an index property and any pending changes
|
||||
const enrichedRows = derived(rows, $rows => {
|
||||
return $rows.map((row, idx) => ({
|
||||
...row,
|
||||
__idx: idx,
|
||||
}))
|
||||
})
|
||||
|
||||
// Generate a lookup map to quick find a row by ID
|
||||
const rowLookupMap = derived(enrichedRows, $enrichedRows => {
|
||||
let map = {}
|
||||
for (let i = 0; i < $enrichedRows.length; i++) {
|
||||
map[$enrichedRows[i]._id] = $enrichedRows[i]
|
||||
}
|
||||
return map
|
||||
})
|
||||
|
||||
return {
|
||||
rows: {
|
||||
...rows,
|
||||
subscribe: enrichedRows.subscribe,
|
||||
},
|
||||
rowLookupMap,
|
||||
}
|
||||
}
|
||||
|
||||
export const createActions = context => {
|
||||
const {
|
||||
rows,
|
||||
|
@ -367,7 +372,7 @@ export const createActions = context => {
|
|||
// Get index of row to check if it exists
|
||||
const $rows = get(rows)
|
||||
const $rowLookupMap = get(rowLookupMap)
|
||||
const index = $rowLookupMap[id].__idx
|
||||
const index = $rowLookupMap[id]?.__idx
|
||||
|
||||
// Process as either an update, addition or deletion
|
||||
if (row) {
|
||||
|
@ -417,8 +422,21 @@ export const createActions = context => {
|
|||
// valid pending change was made or not
|
||||
const stashRowChanges = (rowId, changes) => {
|
||||
const $rowLookupMap = get(rowLookupMap)
|
||||
const $columnLookupMap = get(columnLookupMap)
|
||||
const row = $rowLookupMap[rowId]
|
||||
|
||||
// Coerce some values into the correct types
|
||||
for (let column of Object.keys(changes || {})) {
|
||||
const type = $columnLookupMap[column]?.schema?.type
|
||||
|
||||
// Stringify objects
|
||||
if (type === FieldType.STRING || type == FieldType.LONGFORM) {
|
||||
if (changes[column] != null && typeof changes[column] !== "string") {
|
||||
changes[column] = JSON.stringify(changes[column])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check this is a valid change
|
||||
if (!row || !changesAreValid(row, changes)) {
|
||||
return false
|
||||
|
@ -643,6 +661,7 @@ export const createActions = context => {
|
|||
const cleanRow = row => {
|
||||
let clone = { ...row }
|
||||
delete clone.__idx
|
||||
delete clone.__metadata
|
||||
if (!get(hasBudibaseIdentifiers)) {
|
||||
delete clone._id
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ export const deriveStores = context => {
|
|||
scrollLeft,
|
||||
width,
|
||||
height,
|
||||
rowChangeCache,
|
||||
metadata,
|
||||
} = context
|
||||
|
||||
// Derive visible rows
|
||||
|
@ -19,25 +21,31 @@ export const deriveStores = context => {
|
|||
[scrollTop, rowHeight],
|
||||
([$scrollTop, $rowHeight]) => {
|
||||
return Math.floor($scrollTop / $rowHeight)
|
||||
},
|
||||
0
|
||||
}
|
||||
)
|
||||
const visualRowCapacity = derived(
|
||||
[height, rowHeight],
|
||||
([$height, $rowHeight]) => {
|
||||
return Math.ceil($height / $rowHeight) + 1
|
||||
},
|
||||
0
|
||||
}
|
||||
)
|
||||
const renderedRows = derived(
|
||||
[rows, scrolledRowCount, visualRowCapacity],
|
||||
([$rows, $scrolledRowCount, $visualRowCapacity]) => {
|
||||
return $rows.slice(
|
||||
$scrolledRowCount,
|
||||
$scrolledRowCount + $visualRowCapacity
|
||||
)
|
||||
},
|
||||
[]
|
||||
[rows, scrolledRowCount, visualRowCapacity, rowChangeCache, metadata],
|
||||
([
|
||||
$rows,
|
||||
$scrolledRowCount,
|
||||
$visualRowCapacity,
|
||||
$rowChangeCache,
|
||||
$metadata,
|
||||
]) => {
|
||||
return $rows
|
||||
.slice($scrolledRowCount, $scrolledRowCount + $visualRowCapacity)
|
||||
.map(row => ({
|
||||
...row,
|
||||
...$rowChangeCache[row._id],
|
||||
__metadata: $metadata[row._id],
|
||||
}))
|
||||
}
|
||||
)
|
||||
|
||||
// Derive visible columns
|
||||
|
|
|
@ -8,3 +8,4 @@ export { default as Updating } from "./Updating.svelte"
|
|||
export { Grid } from "./grid"
|
||||
export { default as ClientAppSkeleton } from "./ClientAppSkeleton.svelte"
|
||||
export { default as FilterBuilder } from "./FilterBuilder.svelte"
|
||||
export { default as FilterUsers } from "./FilterUsers.svelte"
|
||||
|
|
|
@ -12,6 +12,7 @@ import { FieldType, BBReferenceFieldSubType } from "@budibase/types"
|
|||
|
||||
export const BannedSearchTypes = [
|
||||
FieldType.LINK,
|
||||
FieldType.ATTACHMENT_SINGLE,
|
||||
FieldType.ATTACHMENTS,
|
||||
FieldType.FORMULA,
|
||||
FieldType.JSON,
|
||||
|
|
|
@ -16,5 +16,6 @@
|
|||
/* Custom additions */
|
||||
--modal-background: var(--spectrum-global-color-gray-50);
|
||||
--drop-shadow: rgba(0, 0, 0, 0.25) !important;
|
||||
--spectrum-global-color-blue-100: rgba(36, 44, 64) !important;
|
||||
--spectrum-global-color-blue-100: hsl(var(--hue), 48%, 24%) !important;
|
||||
--translucent-grey: rgba(255, 255, 255, 0.075) !important;
|
||||
}
|
||||
|
|
|
@ -49,5 +49,6 @@
|
|||
/* Custom additions */
|
||||
--modal-background: var(--spectrum-global-color-gray-50);
|
||||
--drop-shadow: rgba(0, 0, 0, 0.15) !important;
|
||||
--spectrum-global-color-blue-100: rgb(56, 65, 90) !important;
|
||||
--spectrum-global-color-blue-100: hsl(213, 36%, 30%) !important;
|
||||
--translucent-grey: hsla(213, 36%, 80%, 0.11) !important;
|
||||
}
|
||||
|
|
|
@ -6,9 +6,11 @@ import {
|
|||
} from "../../../integrations/tests/utils"
|
||||
import {
|
||||
db as dbCore,
|
||||
context,
|
||||
MAX_VALID_DATE,
|
||||
MIN_VALID_DATE,
|
||||
utils,
|
||||
SQLITE_DESIGN_DOC_ID,
|
||||
} from "@budibase/backend-core"
|
||||
|
||||
import * as setup from "./utilities"
|
||||
|
@ -2524,4 +2526,38 @@ describe.each([
|
|||
}).toContainExactly([{ [" name"]: "foo" }])
|
||||
})
|
||||
})
|
||||
|
||||
isSqs &&
|
||||
describe("duplicate columns", () => {
|
||||
beforeAll(async () => {
|
||||
table = await createTable({
|
||||
name: {
|
||||
name: "name",
|
||||
type: FieldType.STRING,
|
||||
},
|
||||
})
|
||||
await context.doInAppContext(config.getAppId(), async () => {
|
||||
const db = context.getAppDB()
|
||||
const tableDoc = await db.get<Table>(table._id!)
|
||||
tableDoc.schema.Name = {
|
||||
name: "Name",
|
||||
type: FieldType.STRING,
|
||||
}
|
||||
try {
|
||||
// remove the SQLite definitions so that they can be rebuilt as part of the search
|
||||
const sqliteDoc = await db.get(SQLITE_DESIGN_DOC_ID)
|
||||
await db.remove(sqliteDoc)
|
||||
} catch (err) {
|
||||
// no-op
|
||||
}
|
||||
})
|
||||
await createRows([{ name: "foo", Name: "bar" }])
|
||||
})
|
||||
|
||||
it("should handle invalid duplicate column names", async () => {
|
||||
await expectSearch({
|
||||
query: {},
|
||||
}).toContainExactly([{ name: "foo" }])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -49,6 +49,7 @@ import { dataFilters } from "@budibase/shared-core"
|
|||
const builder = new sql.Sql(SqlClient.SQL_LITE)
|
||||
const MISSING_COLUMN_REGEX = new RegExp(`no such column: .+`)
|
||||
const MISSING_TABLE_REGX = new RegExp(`no such table: .+`)
|
||||
const DUPLICATE_COLUMN_REGEX = new RegExp(`duplicate column name: .+`)
|
||||
|
||||
function buildInternalFieldList(
|
||||
table: Table,
|
||||
|
@ -237,9 +238,11 @@ function resyncDefinitionsRequired(status: number, message: string) {
|
|||
// pre data_ prefix on column names, need to resync
|
||||
return (
|
||||
// there are tables missing - try a resync
|
||||
(status === 400 && message.match(MISSING_TABLE_REGX)) ||
|
||||
(status === 400 && message?.match(MISSING_TABLE_REGX)) ||
|
||||
// there are columns missing - try a resync
|
||||
(status === 400 && message.match(MISSING_COLUMN_REGEX)) ||
|
||||
(status === 400 && message?.match(MISSING_COLUMN_REGEX)) ||
|
||||
// duplicate column name in definitions - need to re-run definition sync
|
||||
(status === 400 && message?.match(DUPLICATE_COLUMN_REGEX)) ||
|
||||
// no design document found, needs a full sync
|
||||
(status === 404 && message?.includes(SQLITE_DESIGN_DOC_ID))
|
||||
)
|
||||
|
|
|
@ -94,6 +94,9 @@ export function mapToUserColumn(key: string) {
|
|||
function mapTable(table: Table): SQLiteTables {
|
||||
const tables: SQLiteTables = {}
|
||||
const fields: Record<string, { field: string; type: SQLiteType }> = {}
|
||||
// a list to make sure no duplicates - the fields are mapped by SQS with case sensitivity
|
||||
// but need to make sure there are no duplicate columns
|
||||
const usedColumns: string[] = []
|
||||
for (let [key, column] of Object.entries(table.schema)) {
|
||||
// relationships should be handled differently
|
||||
if (column.type === FieldType.LINK) {
|
||||
|
@ -106,6 +109,12 @@ function mapTable(table: Table): SQLiteTables {
|
|||
if (!FieldTypeMap[column.type]) {
|
||||
throw new Error(`Unable to map type "${column.type}" to SQLite type`)
|
||||
}
|
||||
const lcKey = key.toLowerCase()
|
||||
// ignore duplicates
|
||||
if (usedColumns.includes(lcKey)) {
|
||||
continue
|
||||
}
|
||||
usedColumns.push(lcKey)
|
||||
fields[mapToUserColumn(key)] = {
|
||||
field: key,
|
||||
type: FieldTypeMap[column.type],
|
||||
|
|
Loading…
Reference in New Issue