diff --git a/charts/budibase/templates/alb-ingress.yaml b/charts/budibase/templates/alb-ingress.yaml
index 388bcf1d3e..c128b70843 100644
--- a/charts/budibase/templates/alb-ingress.yaml
+++ b/charts/budibase/templates/alb-ingress.yaml
@@ -14,6 +14,9 @@ metadata:
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.certificateArn }}
{{- end }}
+ {{- if .Values.ingress.securityGroups }}
+ alb.ingress.kubernetes.io/security-groups: {{ .Values.ingress.securityGroups }}
+ {{- end }}
spec:
rules:
- http:
diff --git a/lerna.json b/lerna.json
index 8db64ebdca..5f433c0b1f 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "2.2.12-alpha.71",
+ "version": "2.3.0",
"npmClient": "yarn",
"packages": [
"packages/*"
diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json
index b058b13001..94b1541499 100644
--- a/packages/backend-core/package.json
+++ b/packages/backend-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/backend-core",
- "version": "2.2.12-alpha.71",
+ "version": "2.3.0",
"description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@@ -23,7 +23,7 @@
},
"dependencies": {
"@budibase/nano": "10.1.1",
- "@budibase/types": "2.2.12-alpha.71",
+ "@budibase/types": "^2.3.0",
"@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2",
"aws-cloudfront-sign": "2.2.0",
diff --git a/packages/bbui/package.json b/packages/bbui/package.json
index a85e970c43..5456a1fb3a 100644
--- a/packages/bbui/package.json
+++ b/packages/bbui/package.json
@@ -1,7 +1,7 @@
{
"name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.",
- "version": "2.2.12-alpha.71",
+ "version": "2.3.0",
"license": "MPL-2.0",
"svelte": "src/index.js",
"module": "dist/bbui.es.js",
@@ -38,7 +38,7 @@
],
"dependencies": {
"@adobe/spectrum-css-workflow-icons": "1.2.1",
- "@budibase/string-templates": "2.2.12-alpha.71",
+ "@budibase/string-templates": "^2.3.0",
"@spectrum-css/accordion": "3.0.24",
"@spectrum-css/actionbutton": "1.0.1",
"@spectrum-css/actiongroup": "1.0.1",
diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker.svelte
index 10aae67ec6..2c89a538a3 100644
--- a/packages/bbui/src/Form/Core/DatePicker.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker.svelte
@@ -76,13 +76,6 @@
}
// If time only set date component to 2000-01-01
if (timeOnly) {
- // Classic flackpickr causing issues.
- // When selecting a value for the first time for a "time only" field,
- // the time is always offset by 1 hour for some reason (regardless of time
- // zone) so we need to correct it.
- if (!value && newValue) {
- newValue = new Date(dates[0].getTime() + 60 * 60 * 1000).toISOString()
- }
newValue = `2000-01-01T${newValue.split("T")[1]}`
}
@@ -113,7 +106,7 @@
const clearDateOnBackspace = event => {
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
- dispatch("change", null)
+ dispatch("change", "")
flatpickr.close()
}
}
diff --git a/packages/bbui/src/Form/Core/RadioGroup.svelte b/packages/bbui/src/Form/Core/RadioGroup.svelte
index a3952a9759..f7afc10bbc 100644
--- a/packages/bbui/src/Form/Core/RadioGroup.svelte
+++ b/packages/bbui/src/Form/Core/RadioGroup.svelte
@@ -11,14 +11,31 @@
export let getOptionLabel = option => option
export let getOptionValue = option => option
export let getOptionTitle = option => option
+ export let sort = false
const dispatch = createEventDispatcher()
const onChange = e => dispatch("change", e.target.value)
+
+ const getSortedOptions = (options, getLabel, sort) => {
+ if (!options?.length || !Array.isArray(options)) {
+ return []
+ }
+ if (!sort) {
+ return options
+ }
+ return [...options].sort((a, b) => {
+ const labelA = getLabel(a)
+ const labelB = getLabel(b)
+ return labelA > labelB ? 1 : -1
+ })
+ }
+
+ $: parsedOptions = getSortedOptions(options, getOptionLabel, sort)
- {#if options && Array.isArray(options)}
- {#each options as option}
+ {#if parsedOptions && Array.isArray(parsedOptions)}
+ {#each parsedOptions as option}
{
+ if (e.detail?.tableId) {
+ const tableSchema = getSchemaForTable(e.detail.tableId, {
+ searchableSchema: true,
+ }).schema
+ if (isTestModal) {
+ testData.schema = tableSchema
+ } else {
+ block.inputs.schema = tableSchema
+ }
+ }
try {
if (isTestModal) {
// Special case for webhook, as it requires a body, but the schema already brings back the body's contents
@@ -321,9 +332,17 @@
onChange(e, key)}
+ meta={inputData["meta"] || {}}
+ on:change={e => {
+ if (e.detail?.key) {
+ onChange(e, e.detail.key)
+ } else {
+ onChange(e, key)
+ }
+ }}
{bindings}
{isTestModal}
+ {isUpdateRow}
/>
{:else if value.customType === "webhookUrl"}
import { tables } from "stores/backend"
- import { Select } from "@budibase/bbui"
+ import { Select, Checkbox } from "@budibase/bbui"
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
import { createEventDispatcher } from "svelte"
@@ -10,9 +10,11 @@
const dispatch = createEventDispatcher()
export let value
+ export let meta
export let bindings
export let block
export let isTestModal
+ export let isUpdateRow
$: parsedBindings = bindings.map(binding => {
let clone = Object.assign({}, binding)
@@ -97,6 +99,17 @@
dispatch("change", value)
}
+ const onChangeSetting = (e, field) => {
+ let fields = {}
+ fields[field] = {
+ clearRelationships: e.detail,
+ }
+ dispatch("change", {
+ key: "meta",
+ fields,
+ })
+ }
+
// Ensure any nullish tableId values get set to empty string so
// that the select works
$: if (value?.tableId == null) value = { tableId: "" }
@@ -124,21 +137,33 @@
{onChange}
/>
{:else}
- onChange(e, field, schema.type)}
- label={field}
- type="string"
- bindings={parsedBindings}
- fillWidth={true}
- allowJS={true}
- updateOnChange={false}
- />
+
+
onChange(e, field, schema.type)}
+ label={field}
+ type="string"
+ bindings={parsedBindings}
+ fillWidth={true}
+ allowJS={true}
+ updateOnChange={false}
+ />
+ {#if isUpdateRow && schema.type === "link"}
+
+ onChangeSetting(e, field)}
+ />
+
+ {/if}
+
{/if}
{/if}
{/if}
@@ -155,4 +180,12 @@
.schema-fields :global(label) {
text-transform: capitalize;
}
+ .checkbox-field {
+ padding-bottom: var(--spacing-s);
+ padding-left: 1px;
+ padding-top: var(--spacing-s);
+ }
+ .checkbox-field :global(label) {
+ text-transform: none;
+ }
diff --git a/packages/builder/src/components/automation/SetupPanel/SchemaSetup.svelte b/packages/builder/src/components/automation/SetupPanel/SchemaSetup.svelte
index cb80072694..b5173682d0 100644
--- a/packages/builder/src/components/automation/SetupPanel/SchemaSetup.svelte
+++ b/packages/builder/src/components/automation/SetupPanel/SchemaSetup.svelte
@@ -58,7 +58,7 @@
entries = entries.filter(f => f.name !== originalName)
}
value = entries.reduce((newVals, current) => {
- newVals[current.name] = current.type
+ newVals[current.name.trim()] = current.type
return newVals
}, {})
dispatch("change", value)
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index 4ed77d55b2..3e5549fcf5 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -12,7 +12,7 @@
Modal,
notifications,
} from "@budibase/bbui"
- import { createEventDispatcher, onMount } from "svelte"
+ import { createEventDispatcher } from "svelte"
import { cloneDeep } from "lodash/fp"
import { tables, datasources } from "stores/backend"
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
@@ -48,7 +48,22 @@
const { hide } = getContext(Context.Modal)
let fieldDefinitions = cloneDeep(FIELDS)
- export let field = {
+ export let field
+
+ let originalName
+ let linkEditDisabled
+ let primaryDisplay
+ let indexes = [...($tables.selected.indexes || [])]
+ let isCreating
+
+ let table = $tables.selected
+ let confirmDeleteDialog
+ let deletion
+ let savingColumn
+ let deleteColName
+ let jsonSchemaModal
+
+ let editableColumn = {
type: "string",
constraints: fieldDefinitions.STRING.constraints,
@@ -56,48 +71,80 @@
fieldName: $tables.selected.name,
}
- let originalName = field.name
- const linkEditDisabled = originalName != null
- let primaryDisplay =
- $tables.selected.primaryDisplay == null ||
- $tables.selected.primaryDisplay === field.name
- let isCreating = originalName == null
+ $: if (primaryDisplay) {
+ editableColumn.constraints.presence = { allowEmpty: false }
+ }
- let table = $tables.selected
- let indexes = [...($tables.selected.indexes || [])]
- let confirmDeleteDialog
- let deletion
- let deleteColName
- let jsonSchemaModal
+ $: if (field && !savingColumn) {
+ editableColumn = cloneDeep(field)
+ originalName = editableColumn.name ? editableColumn.name + "" : null
+ linkEditDisabled = originalName != null
+ isCreating = originalName == null
+ primaryDisplay =
+ $tables.selected.primaryDisplay == null ||
+ $tables.selected.primaryDisplay === editableColumn.name
+ }
- $: checkConstraints(field)
- $: required = !!field?.constraints?.presence || primaryDisplay
+ $: checkConstraints(editableColumn)
+ $: required = !!editableColumn?.constraints?.presence || primaryDisplay
$: uneditable =
$tables.selected?._id === TableNames.USERS &&
- UNEDITABLE_USER_FIELDS.includes(field.name)
+ UNEDITABLE_USER_FIELDS.includes(editableColumn.name)
$: invalid =
- !field.name ||
- (field.type === LINK_TYPE && !field.tableId) ||
+ !editableColumn?.name ||
+ (editableColumn?.type === LINK_TYPE && !editableColumn?.tableId) ||
Object.keys(errors).length !== 0
- $: errors = checkErrors(field)
+ $: errors = checkErrors(editableColumn)
$: datasource = $datasources.list.find(
source => source._id === table?.sourceId
)
+ const getTableAutoColumnTypes = table => {
+ return Object.keys(table?.schema).reduce((acc, key) => {
+ let fieldSchema = table?.schema[key]
+ if (fieldSchema.autocolumn) {
+ acc.push(fieldSchema.subtype)
+ }
+ return acc
+ }, [])
+ }
+
+ let autoColumnInfo = getAutoColumnInformation()
+
+ $: tableAutoColumnsTypes = getTableAutoColumnTypes($tables?.selected)
+ $: availableAutoColumns = Object.keys(autoColumnInfo).reduce((acc, key) => {
+ if (!tableAutoColumnsTypes.includes(key)) {
+ acc[key] = autoColumnInfo[key]
+ }
+ return acc
+ }, {})
+
+ $: availableAutoColumnKeys = availableAutoColumns
+ ? Object.keys(availableAutoColumns)
+ : []
+
+ $: autoColumnOptions = editableColumn.autocolumn
+ ? autoColumnInfo
+ : availableAutoColumns
+
// used to select what different options can be displayed for column type
$: canBeSearched =
- field.type !== LINK_TYPE &&
- field.type !== JSON_TYPE &&
- field.subtype !== AUTO_COLUMN_SUB_TYPES.CREATED_BY &&
- field.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY &&
- field.type !== FORMULA_TYPE
+ editableColumn?.type !== LINK_TYPE &&
+ editableColumn?.type !== JSON_TYPE &&
+ editableColumn?.subtype !== AUTO_COLUMN_SUB_TYPES.CREATED_BY &&
+ editableColumn?.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY &&
+ editableColumn?.type !== FORMULA_TYPE
$: canBeDisplay =
- field.type !== LINK_TYPE &&
- field.type !== AUTO_TYPE &&
- field.type !== JSON_TYPE
+ editableColumn?.type !== LINK_TYPE &&
+ editableColumn?.type !== AUTO_TYPE &&
+ editableColumn?.type !== JSON_TYPE &&
+ !editableColumn.autocolumn
$: canBeRequired =
- field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_TYPE
- $: relationshipOptions = getRelationshipOptions(field)
+ editableColumn?.type !== LINK_TYPE &&
+ !uneditable &&
+ editableColumn?.type !== AUTO_TYPE &&
+ !editableColumn.autocolumn
+ $: relationshipOptions = getRelationshipOptions(editableColumn)
$: external = table.type === "external"
// in the case of internal tables the sourceId will just be undefined
$: tableOptions = $tables.list.filter(
@@ -108,76 +155,90 @@
)
$: typeEnabled =
!originalName ||
- (originalName && SWITCHABLE_TYPES.indexOf(field.type) !== -1)
+ (originalName &&
+ SWITCHABLE_TYPES.indexOf(editableColumn.type) !== -1 &&
+ !editableColumn?.autocolumn)
async function saveColumn() {
- if (field.type === AUTO_TYPE) {
- field = buildAutoColumn($tables.selected.name, field.name, field.subtype)
+ savingColumn = true
+ if (errors?.length) {
+ return
}
- if (field.type !== LINK_TYPE) {
- delete field.fieldName
+
+ let saveColumn = cloneDeep(editableColumn)
+
+ if (saveColumn.type === AUTO_TYPE) {
+ saveColumn = buildAutoColumn(
+ $tables.selected.name,
+ saveColumn.name,
+ saveColumn.subtype
+ )
+ }
+ if (saveColumn.type !== LINK_TYPE) {
+ delete saveColumn.fieldName
}
try {
await tables.saveField({
originalName,
- field,
+ field: saveColumn,
primaryDisplay,
indexes,
})
dispatch("updatecolumns")
} catch (err) {
- notifications.error("Error saving column")
+ console.log(err)
+ notifications.error(`Error saving column: ${err.message}`)
}
}
function cancelEdit() {
- field.name = originalName
+ editableColumn.name = originalName
}
function deleteColumn() {
try {
- field.name = deleteColName
- if (field.name === $tables.selected.primaryDisplay) {
+ editableColumn.name = deleteColName
+ if (editableColumn.name === $tables.selected.primaryDisplay) {
notifications.error("You cannot delete the display column")
} else {
- tables.deleteField(field)
- notifications.success(`Column ${field.name} deleted.`)
+ tables.deleteField(editableColumn)
+ notifications.success(`Column ${editableColumn.name} deleted.`)
confirmDeleteDialog.hide()
hide()
deletion = false
dispatch("updatecolumns")
}
} catch (error) {
- notifications.error("Error deleting column")
+ notifications.error(`Error deleting column: ${error.message}`)
}
}
function handleTypeChange(event) {
// remove any extra fields that may not be related to this type
- delete field.autocolumn
- delete field.subtype
- delete field.tableId
- delete field.relationshipType
- delete field.formulaType
+ delete editableColumn.autocolumn
+ delete editableColumn.subtype
+ delete editableColumn.tableId
+ delete editableColumn.relationshipType
+ delete editableColumn.formulaType
// Add in defaults and initial definition
const definition = fieldDefinitions[event.detail?.toUpperCase()]
if (definition?.constraints) {
- field.constraints = definition.constraints
+ editableColumn.constraints = definition.constraints
}
// Default relationships many to many
- if (field.type === LINK_TYPE) {
- field.relationshipType = RelationshipTypes.MANY_TO_MANY
+ if (editableColumn.type === LINK_TYPE) {
+ editableColumn.relationshipType = RelationshipTypes.MANY_TO_MANY
}
- if (field.type === FORMULA_TYPE) {
- field.formulaType = "dynamic"
+ if (editableColumn.type === FORMULA_TYPE) {
+ editableColumn.formulaType = "dynamic"
}
}
function onChangeRequired(e) {
const req = e.detail
- field.constraints.presence = req ? { allowEmpty: false } : false
+ editableColumn.constraints.presence = req ? { allowEmpty: false } : false
required = req
}
@@ -185,17 +246,17 @@
const isPrimary = e.detail
// primary display is always required
if (isPrimary) {
- field.constraints.presence = { allowEmpty: false }
+ editableColumn.constraints.presence = { allowEmpty: false }
}
}
function onChangePrimaryIndex(e) {
- indexes = e.detail ? [field.name] : []
+ indexes = e.detail ? [editableColumn.name] : []
}
function onChangeSecondaryIndex(e) {
if (e.detail) {
- indexes[1] = field.name
+ indexes[1] = editableColumn.name
} else {
indexes = indexes.slice(0, 1)
}
@@ -246,11 +307,14 @@
}
function getAllowedTypes() {
- if (originalName && ALLOWABLE_STRING_TYPES.indexOf(field.type) !== -1) {
+ if (
+ originalName &&
+ ALLOWABLE_STRING_TYPES.indexOf(editableColumn.type) !== -1
+ ) {
return ALLOWABLE_STRING_OPTIONS
} else if (
originalName &&
- ALLOWABLE_NUMBER_TYPES.indexOf(field.type) !== -1
+ ALLOWABLE_NUMBER_TYPES.indexOf(editableColumn.type) !== -1
) {
return ALLOWABLE_NUMBER_OPTIONS
} else if (!external) {
@@ -275,6 +339,9 @@
}
function checkConstraints(fieldToCheck) {
+ if (!fieldToCheck) {
+ return
+ }
// most types need this, just make sure its always present
if (fieldToCheck && !fieldToCheck.constraints) {
fieldToCheck.constraints = {}
@@ -296,10 +363,16 @@
}
function checkErrors(fieldInfo) {
+ if (!editableColumn) {
+ return {}
+ }
function inUse(tbl, column, ogName = null) {
- return Object.keys(tbl?.schema || {}).some(
- key => key !== ogName && key === column
- )
+ const parsedColumn = column ? column.toLowerCase().trim() : column
+
+ return Object.keys(tbl?.schema || {}).some(key => {
+ let lowerKey = key.toLowerCase()
+ return lowerKey !== ogName?.toLowerCase() && lowerKey === parsedColumn
+ })
}
const newError = {}
if (!external && fieldInfo.name?.startsWith("_")) {
@@ -313,6 +386,11 @@
} else if (inUse($tables.selected, fieldInfo.name, originalName)) {
newError.name = `Column name already in use.`
}
+
+ if (fieldInfo.type == "auto" && !fieldInfo.subtype) {
+ newError.subtype = `Auto Column requires a type`
+ }
+
if (fieldInfo.fieldName && fieldInfo.tableId) {
const relatedTable = $tables.list.find(
tbl => tbl._id === fieldInfo.tableId
@@ -323,12 +401,6 @@
}
return newError
}
-
- onMount(() => {
- if (primaryDisplay) {
- field.constraints.presence = { allowEmpty: false }
- }
- })