This commit is contained in:
mike12345567 2023-02-06 18:00:42 +00:00
parent 4d86df057b
commit 9339213910
41 changed files with 603 additions and 288 deletions

View File

@ -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:

View File

@ -1,5 +1,5 @@
{
"version": "2.2.12-alpha.71",
"version": "2.3.0",
"npmClient": "yarn",
"packages": [
"packages/*"

View File

@ -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",

View File

@ -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",

View File

@ -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()
}
}

View File

@ -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)
</script>
<div class={`spectrum-FieldGroup spectrum-FieldGroup--${direction}`}>
{#if options && Array.isArray(options)}
{#each options as option}
{#if parsedOptions && Array.isArray(parsedOptions)}
{#each parsedOptions as option}
<div
title={getOptionTitle(option)}
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
"version": "2.2.12-alpha.71",
"version": "2.3.0",
"license": "GPL-3.0",
"private": true,
"scripts": {
@ -58,10 +58,10 @@
}
},
"dependencies": {
"@budibase/bbui": "2.2.12-alpha.71",
"@budibase/client": "2.2.12-alpha.71",
"@budibase/frontend-core": "2.2.12-alpha.71",
"@budibase/string-templates": "2.2.12-alpha.71",
"@budibase/bbui": "^2.3.0",
"@budibase/client": "^2.3.0",
"@budibase/frontend-core": "^2.3.0",
"@budibase/string-templates": "^2.3.0",
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",

View File

@ -74,8 +74,19 @@
$: schemaFields = Object.values(schema || {})
$: queryLimit = tableId?.includes("datasource") ? "∞" : "1000"
$: isTrigger = block?.type === "TRIGGER"
$: isUpdateRow = stepId === ActionStepID.UPDATE_ROW
const onChange = Utils.sequential(async (e, key) => {
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 @@
<RowSelector
{block}
value={inputData[key]}
on:change={e => 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"}
<WebhookDisplay

View File

@ -1,6 +1,6 @@
<script>
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}
<svelte:component
this={isTestModal ? ModalBindableInput : DrawerBindableInput}
placeholder={placeholders[schema.type]}
panel={AutomationBindingPanel}
value={Array.isArray(value[field])
? value[field].join(" ")
: value[field]}
on:change={e => onChange(e, field, schema.type)}
label={field}
type="string"
bindings={parsedBindings}
fillWidth={true}
allowJS={true}
updateOnChange={false}
/>
<div>
<svelte:component
this={isTestModal ? ModalBindableInput : DrawerBindableInput}
placeholder={placeholders[schema.type]}
panel={AutomationBindingPanel}
value={Array.isArray(value[field])
? value[field].join(" ")
: value[field]}
on:change={e => onChange(e, field, schema.type)}
label={field}
type="string"
bindings={parsedBindings}
fillWidth={true}
allowJS={true}
updateOnChange={false}
/>
{#if isUpdateRow && schema.type === "link"}
<div class="checkbox-field">
<Checkbox
value={meta.fields?.[field]?.clearRelationships}
text={"Clear relationships if empty?"}
size={"S"}
on:change={e => onChangeSetting(e, field)}
/>
</div>
{/if}
</div>
{/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;
}
</style>

View File

@ -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)

View File

@ -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 }
}
})
</script>
<ModalContent
@ -340,19 +412,26 @@
>
<Input
label="Name"
bind:value={field.name}
disabled={uneditable || (linkEditDisabled && field.type === LINK_TYPE)}
bind:value={editableColumn.name}
disabled={uneditable ||
(linkEditDisabled && editableColumn.type === LINK_TYPE)}
error={errors?.name}
/>
<Select
disabled={!typeEnabled}
label="Type"
bind:value={field.type}
bind:value={editableColumn.type}
on:change={handleTypeChange}
options={getAllowedTypes()}
getOptionLabel={field => field.name}
getOptionValue={field => field.type}
isOptionEnabled={option => {
if (option.type == AUTO_TYPE) {
return availableAutoColumnKeys?.length > 0
}
return true
}}
/>
{#if canBeRequired || canBeDisplay}
@ -381,32 +460,32 @@
<div>
<Label>Search Indexes</Label>
<Toggle
value={indexes[0] === field.name}
disabled={indexes[1] === field.name}
value={indexes[0] === editableColumn.name}
disabled={indexes[1] === editableColumn.name}
on:change={onChangePrimaryIndex}
text="Primary"
/>
<Toggle
value={indexes[1] === field.name}
disabled={!indexes[0] || indexes[0] === field.name}
value={indexes[1] === editableColumn.name}
disabled={!indexes[0] || indexes[0] === editableColumn.name}
on:change={onChangeSecondaryIndex}
text="Secondary"
/>
</div>
{/if}
{#if field.type === "string"}
{#if editableColumn.type === "string"}
<Input
type="number"
label="Max Length"
bind:value={field.constraints.length.maximum}
bind:value={editableColumn.constraints.length.maximum}
/>
{:else if field.type === "options"}
{:else if editableColumn.type === "options"}
<ValuesList
label="Options (one per line)"
bind:values={field.constraints.inclusion}
bind:values={editableColumn.constraints.inclusion}
/>
{:else if field.type === "longform"}
{:else if editableColumn.type === "longform"}
<div>
<Label
size="M"
@ -415,21 +494,24 @@
Formatting
</Label>
<Toggle
bind:value={field.useRichText}
bind:value={editableColumn.useRichText}
text="Enable rich text support (markdown)"
/>
</div>
{:else if field.type === "array"}
{:else if editableColumn.type === "array"}
<ValuesList
label="Options (one per line)"
bind:values={field.constraints.inclusion}
bind:values={editableColumn.constraints.inclusion}
/>
{:else if field.type === "datetime"}
{:else if editableColumn.type === "datetime" && !editableColumn.autocolumn}
<DatePicker
label="Earliest"
bind:value={field.constraints.datetime.earliest}
bind:value={editableColumn.constraints.datetime.earliest}
/>
<DatePicker
label="Latest"
bind:value={editableColumn.constraints.datetime.latest}
/>
<DatePicker label="Latest" bind:value={field.constraints.datetime.latest} />
{#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"}
<div>
<Label
@ -439,25 +521,28 @@
>
Time zones
</Label>
<Toggle bind:value={field.ignoreTimezones} text="Ignore time zones" />
<Toggle
bind:value={editableColumn.ignoreTimezones}
text="Ignore time zones"
/>
</div>
{/if}
{:else if field.type === "number"}
{:else if editableColumn.type === "number" && !editableColumn.autocolumn}
<Input
type="number"
label="Min Value"
bind:value={field.constraints.numericality.greaterThanOrEqualTo}
bind:value={editableColumn.constraints.numericality.greaterThanOrEqualTo}
/>
<Input
type="number"
label="Max Value"
bind:value={field.constraints.numericality.lessThanOrEqualTo}
bind:value={editableColumn.constraints.numericality.lessThanOrEqualTo}
/>
{:else if field.type === "link"}
{:else if editableColumn.type === "link"}
<Select
label="Table"
disabled={linkEditDisabled}
bind:value={field.tableId}
bind:value={editableColumn.tableId}
options={tableOptions}
getOptionLabel={table => table.name}
getOptionValue={table => table._id}
@ -466,7 +551,7 @@
<RadioGroup
disabled={linkEditDisabled}
label="Define the relationship"
bind:value={field.relationshipType}
bind:value={editableColumn.relationshipType}
options={relationshipOptions}
getOptionLabel={option => option.name}
getOptionValue={option => option.value}
@ -476,14 +561,14 @@
<Input
disabled={linkEditDisabled}
label={`Column name in other table`}
bind:value={field.fieldName}
bind:value={editableColumn.fieldName}
error={errors.relatedName}
/>
{:else if field.type === FORMULA_TYPE}
{:else if editableColumn.type === FORMULA_TYPE}
{#if !table.sql}
<Select
label="Formula type"
bind:value={field.formulaType}
bind:value={editableColumn.formulaType}
options={[
{ label: "Dynamic", value: "dynamic" },
{ label: "Static", value: "static" },
@ -497,25 +582,28 @@
<ModalBindableInput
title="Formula"
label="Formula"
value={field.formula}
on:change={e => (field.formula = e.detail)}
value={editableColumn.formula}
on:change={e => (editableColumn.formula = e.detail)}
bindings={getBindings({ table })}
allowJS
/>
{:else if field.type === AUTO_TYPE}
<Select
label="Auto column type"
value={field.subtype}
on:change={e => (field.subtype = e.detail)}
options={Object.entries(getAutoColumnInformation())}
getOptionLabel={option => option[1].name}
getOptionValue={option => option[0]}
/>
{:else if field.type === JSON_TYPE}
{:else if editableColumn.type === JSON_TYPE}
<Button primary text on:click={openJsonSchemaEditor}
>Open schema editor</Button
>
{/if}
{#if editableColumn.type === AUTO_TYPE || editableColumn.autocolumn}
<Select
label="Auto column type"
value={editableColumn.subtype}
on:change={e => (editableColumn.subtype = e.detail)}
options={Object.entries(autoColumnOptions)}
getOptionLabel={option => option[1].name}
getOptionValue={option => option[0]}
disabled={!availableAutoColumnKeys?.length || editableColumn.autocolumn}
error={errors?.subtype}
/>
{/if}
<div slot="footer">
{#if !uneditable && originalName != null}
@ -525,11 +613,11 @@
</ModalContent>
<Modal bind:this={jsonSchemaModal}>
<JSONSchemaModal
schema={field.schema}
json={field.json}
schema={editableColumn.schema}
json={editableColumn.json}
on:save={({ detail }) => {
field.schema = detail.schema
field.json = detail.json
editableColumn.schema = detail.schema
editableColumn.json = detail.json
}}
/>
</Modal>

View File

@ -106,4 +106,8 @@
border: var(--border-light);
border-radius: 4px;
}
.control :global(.spectrum-Textfield-input) {
padding-right: 40px;
}
</style>

View File

@ -77,8 +77,18 @@
}
const deleteAction = index => {
// Check if we're deleting the selected action
const selectedIndex = actions.indexOf(selectedAction)
const isSelected = index === selectedIndex
// Delete the action
actions.splice(index, 1)
actions = actions
// Select a new action if we deleted the selected one
if (isSelected) {
selectedAction = actions?.length ? actions[0] : null
}
}
const toggleActionList = () => {

View File

@ -36,7 +36,13 @@
$: selectedSchema = selectedAutomation?.schema
const onFieldsChanged = e => {
parameters.fields = e.detail
parameters.fields = Object.entries(e.detail || {}).reduce(
(acc, [key, value]) => {
acc[key.trim()] = value
return acc
},
{}
)
}
const setNew = () => {

View File

@ -1,9 +1,45 @@
<script>
import TableDataTable from "components/backend/DataTable/DataTable.svelte"
import { tables, database } from "stores/backend"
import { Banner } from "@budibase/bbui"
const verifyAutocolumns = table => {
// Check for duplicates
return Object.values(table?.schema || {}).reduce((acc, fieldSchema) => {
if (!fieldSchema.autocolumn || !fieldSchema.subtype) {
return acc
}
let fieldKey = fieldSchema.tableId
? `${fieldSchema.tableId}-${fieldSchema.subtype}`
: fieldSchema.subtype
acc[fieldKey] = acc[fieldKey] || []
acc[fieldKey].push(fieldSchema)
return acc
}, {})
}
$: autoColumnStatus = verifyAutocolumns($tables?.selected)
$: duplicates = Object.values(autoColumnStatus).reduce((acc, status) => {
if (status.length > 1) {
acc = [...acc, ...status]
}
return acc
}, [])
$: invalidColumnText = duplicates.map(entry => {
return `${entry.name} (${entry.subtype})`
})
</script>
{#if $database?._id && $tables?.selected}
{#if $database?._id && $tables?.selected?.name}
{#if duplicates?.length}
<div class="alert-wrap">
<Banner type="warning" showCloseButton={false}>
{`Schema Invalid - There are duplicate auto column types defined in this schema.
Please delete the duplicate entries where appropriate: -
${invalidColumnText.join(", ")}`}
</Banner>
</div>
{/if}
<TableDataTable />
{:else}
<i>Create your first table to start building</i>
@ -15,4 +51,11 @@
color: var(--grey-5);
margin-top: 2px;
}
.alert-wrap {
display: flex;
width: 100%;
}
.alert-wrap :global(> *) {
flex: 1;
}
</style>

View File

@ -30,7 +30,7 @@
{#if $selectedComponent}
{#key $selectedComponent._id}
<Panel {title} icon={componentDefinition?.icon} borderLeft>
{#if componentDefinition.info}
{#if componentDefinition?.info}
<ComponentInfoSection {componentDefinition} />
{/if}
<ComponentSettingsSection

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/cli",
"version": "2.2.12-alpha.71",
"version": "2.3.0",
"description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js",
"bin": {
@ -26,9 +26,9 @@
"outputPath": "build"
},
"dependencies": {
"@budibase/backend-core": "2.2.12-alpha.71",
"@budibase/string-templates": "2.2.12-alpha.71",
"@budibase/types": "2.2.12-alpha.71",
"@budibase/backend-core": "^2.3.0",
"@budibase/string-templates": "^2.3.0",
"@budibase/types": "^2.3.0",
"axios": "0.21.2",
"chalk": "4.1.0",
"cli-progress": "3.11.2",

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/client",
"version": "2.2.12-alpha.71",
"version": "2.3.0",
"license": "MPL-2.0",
"module": "dist/budibase-client.js",
"main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw"
},
"dependencies": {
"@budibase/bbui": "2.2.12-alpha.71",
"@budibase/frontend-core": "2.2.12-alpha.71",
"@budibase/string-templates": "2.2.12-alpha.71",
"@budibase/bbui": "^2.3.0",
"@budibase/frontend-core": "^2.3.0",
"@budibase/string-templates": "^2.3.0",
"@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3",

View File

@ -171,6 +171,15 @@
$: pad = pad || (interactive && hasChildren && inDndPath)
$: $dndIsDragging, (pad = false)
// Determine whether we should render a skeleton loader for this component
$: showSkeleton =
$loading &&
definition.name !== "Screenslot" &&
children.length === 0 &&
!instance._blockElementHasChildren &&
!definition.block &&
definition.skeleton !== false
// Update component context
$: store.set({
id,
@ -473,14 +482,6 @@
componentStore.actions.unregisterInstance(id)
}
})
$: showSkeleton =
$loading &&
definition.name !== "Screenslot" &&
children.length === 0 &&
!instance._blockElementHasChildren &&
!definition.block &&
definition.skeleton !== false
</script>
{#if showSkeleton}

View File

@ -11,20 +11,23 @@
export let limit
export let paginate
const loading = writable(false)
const { styleable, Provider, ActionTypes, API } = getContext("sdk")
const component = getContext("component")
// Update loading state
const parentLoading = getContext("loading")
const loading = writable(true)
setContext("loading", loading)
// We need to manage our lucene query manually as we want to allow components
// to extend it
let queryExtensions = {}
$: defaultQuery = LuceneUtils.buildLuceneQuery(filter)
$: query = extendQuery(defaultQuery, queryExtensions)
// Keep our data fetch instance up to date
$: fetch = createFetch(dataSource)
$: fetch.update({
// Fetch data and refresh when needed
$: fetch = createFetch(dataSource, $parentLoading)
$: updateFetch({
query,
sortColumn,
sortOrder,
@ -32,6 +35,9 @@
paginate,
})
// Keep loading context updated
$: loading.set($parentLoading || !$fetch.loaded)
// Build our action context
$: actions = [
{
@ -80,14 +86,21 @@
sortColumn: $fetch.sortColumn,
sortOrder: $fetch.sortOrder,
},
limit: limit,
limit,
}
const parentLoading = getContext("loading")
setContext("loading", loading)
$: loading.set($parentLoading || !$fetch.loaded)
const createFetch = (datasource, parentLoading) => {
// Return a dummy fetch if parent is still loading. We do this so that we
// can still properly subscribe to a valid fetch object and check all
// properties, but we want to avoid fetching the real data until all parents
// have finished loading.
// This logic is only needed due to skeleton loaders, as previously we
// simply blocked component rendering until data was ready.
if (parentLoading) {
return fetchData({ API })
}
const createFetch = datasource => {
// Otherwise return the real thing
return fetchData({
API,
datasource,
@ -101,6 +114,14 @@
})
}
const updateFetch = opts => {
// Only update fetch if parents have stopped loading. Otherwise we will
// trigger a fetch of the real data before parents are ready.
if (!$parentLoading) {
fetch.update(opts)
}
}
const addQueryExtension = (key, extension) => {
if (!key || !extension) {
return

View File

@ -1,7 +1,8 @@
<script>
import { getContext } from "svelte"
import { getContext, setContext } from "svelte"
import InnerForm from "./InnerForm.svelte"
import { Helpers } from "@budibase/bbui"
import { writable } from "svelte/store"
export let dataSource
export let theme
@ -20,6 +21,12 @@
const context = getContext("context")
const { API, fetchDatasourceSchema } = getContext("sdk")
// Forms also use loading context as they require loading a schema
const parentLoading = getContext("loading")
const loading = writable(true)
setContext("loading", loading)
let loaded = false
let schema
let table
@ -29,6 +36,7 @@
$: resetKey = Helpers.hashString(
schemaKey + JSON.stringify(initialValues) + disabled
)
$: loading.set($parentLoading || !loaded)
// Returns the closes data context which isn't a built in context
const getInitialValues = (type, dataSource, context) => {
@ -60,6 +68,9 @@
}
const res = await fetchDatasourceSchema(dataSource)
schema = res || {}
if (!loaded) {
loaded = true
}
}
// Generates a predictable string that uniquely identifies a schema. We can't

View File

@ -128,21 +128,15 @@
return fields.find(field => get(field).name === name)
}
const getDefault = (defaultValue, schema, type) => {
// Remove any values not present in the field schema
// Convert any values supplied to string
if (Array.isArray(defaultValue) && type == "array" && schema) {
return defaultValue.reduce((acc, entry) => {
let processedOption = String(entry)
let schemaOptions = schema.constraints.inclusion
if (schemaOptions.indexOf(processedOption) > -1) {
acc.push(processedOption)
}
return acc
}, [])
} else {
return defaultValue
// Sanitises a value by ensuring it doesn't contain any invalid data
const sanitiseValue = (value, schema, type) => {
// Check arrays - remove any values not present in the field schema and
// convert any values supplied to strings
if (Array.isArray(value) && type === "array" && schema) {
const options = schema?.constraints.inclusion || []
return value.map(opt => String(opt)).filter(opt => options.includes(opt))
}
return value
}
const formApi = {
@ -160,7 +154,6 @@
// Create validation function based on field schema
const schemaConstraints = schema?.[field]?.constraints
const validator = disableValidation
? null
: createValidatorFromConstraints(
@ -170,10 +163,11 @@
table
)
const parsedDefault = getDefault(defaultValue, schema?.[field], type)
// Sanitise the default value to ensure it doesn't contain invalid data
defaultValue = sanitiseValue(defaultValue, schema?.[field], type)
// If we've already registered this field then keep some existing state
let initialValue = Helpers.deepGet(initialValues, field) ?? parsedDefault
let initialValue = Helpers.deepGet(initialValues, field) ?? defaultValue
let initialError = null
let fieldId = `id-${Helpers.uuid()}`
const existingField = getField(field)
@ -183,7 +177,9 @@
// Determine the initial value for this field, reusing the current
// value if one exists
initialValue = fieldState.value ?? initialValue
if (fieldState.value != null && fieldState.value !== "") {
initialValue = fieldState.value
}
// If this field has already been registered and we previously had an
// error set, then re-run the validator to see if we can unset it
@ -206,11 +202,11 @@
error: initialError,
disabled:
disabled || fieldDisabled || (isAutoColumn && !editAutoColumns),
defaultValue: parsedDefault,
defaultValue,
validator,
lastUpdate: Date.now(),
},
fieldApi: makeFieldApi(field, parsedDefault),
fieldApi: makeFieldApi(field),
fieldSchema: schema?.[field] ?? {},
})
@ -225,18 +221,9 @@
return fieldInfo
},
validate: () => {
let valid = true
let validationFields = fields
validationFields = fields.filter(f => get(f).step === get(currentStep))
// Validate fields and check if any are invalid
validationFields.forEach(field => {
if (!get(field).fieldApi.validate()) {
valid = false
}
})
return valid
return fields
.filter(field => get(field).step === get(currentStep))
.every(field => get(field).fieldApi.validate())
},
reset: () => {
// Reset the form by resetting each individual field

View File

@ -79,6 +79,7 @@
getOptionLabel={flatOptions ? x => x : x => x.label}
getOptionTitle={flatOptions ? x => x : x => x.label}
getOptionValue={flatOptions ? x => x : x => x.value}
{sort}
/>
{/if}
{/if}

View File

@ -1,12 +1,12 @@
{
"name": "@budibase/frontend-core",
"version": "2.2.12-alpha.71",
"version": "2.3.0",
"description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase",
"license": "MPL-2.0",
"svelte": "src/index.js",
"dependencies": {
"@budibase/bbui": "2.2.12-alpha.71",
"@budibase/bbui": "^2.3.0",
"lodash": "^4.17.21",
"svelte": "^3.46.2"
}

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/sdk",
"version": "2.2.12-alpha.71",
"version": "2.3.0",
"description": "Budibase Public API SDK",
"author": "Budibase",
"license": "MPL-2.0",

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/server",
"email": "hi@budibase.com",
"version": "2.2.12-alpha.71",
"version": "2.3.0",
"description": "Budibase Web Server",
"main": "src/index.ts",
"repository": {
@ -43,11 +43,11 @@
"license": "GPL-3.0",
"dependencies": {
"@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "2.2.12-alpha.71",
"@budibase/client": "2.2.12-alpha.71",
"@budibase/pro": "2.2.12-alpha.71",
"@budibase/string-templates": "2.2.12-alpha.71",
"@budibase/types": "2.2.12-alpha.71",
"@budibase/backend-core": "^2.3.0",
"@budibase/client": "^2.3.0",
"@budibase/pro": "2.3.0",
"@budibase/string-templates": "^2.3.0",
"@budibase/types": "^2.3.0",
"@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0",

View File

@ -315,7 +315,13 @@ export async function checkForViewUpdates(
// Update view if required
if (needsUpdated) {
const newViewTemplate = viewTemplate(view.meta)
const groupByField: any = Object.values(table.schema).find(
(field: any) => field.name == view.groupBy
)
const newViewTemplate = viewTemplate(
view.meta,
groupByField?.type === FieldTypes.ARRAY
)
await saveView(null, view.name, newViewTemplate)
if (!newViewTemplate.meta.schema) {
newViewTemplate.meta.schema = table.schema

View File

@ -6,8 +6,9 @@ import { fetchView } from "../row"
import { context, events } from "@budibase/backend-core"
import { DocumentType } from "../../../db/utils"
import sdk from "../../../sdk"
import { FieldTypes } from "../../../constants"
import {
BBContext,
Ctx,
Row,
Table,
TableExportFormat,
@ -18,14 +19,22 @@ import { cleanExportRows } from "../row/utils"
const { cloneDeep, isEqual } = require("lodash")
export async function fetch(ctx: BBContext) {
export async function fetch(ctx: Ctx) {
ctx.body = await getViews()
}
export async function save(ctx: BBContext) {
export async function save(ctx: Ctx) {
const db = context.getAppDB()
const { originalName, ...viewToSave } = ctx.request.body
const view = viewTemplate(viewToSave)
const existingTable = await db.get(ctx.request.body.tableId)
const table = cloneDeep(existingTable)
const groupByField: any = Object.values(table.schema).find(
(field: any) => field.name == viewToSave.groupBy
)
const view = viewTemplate(viewToSave, groupByField?.type === FieldTypes.ARRAY)
const viewName = viewToSave.name
if (!viewName) {
@ -35,8 +44,6 @@ export async function save(ctx: BBContext) {
await saveView(originalName, viewName, view)
// add views to table document
const existingTable = await db.get(ctx.request.body.tableId)
const table = cloneDeep(existingTable)
if (!table.views) table.views = {}
if (!view.meta.schema) {
view.meta.schema = table.schema
@ -111,7 +118,7 @@ async function handleViewEvents(existingView: View, newView: View) {
await filterEvents(existingView, newView)
}
export async function destroy(ctx: BBContext) {
export async function destroy(ctx: Ctx) {
const db = context.getAppDB()
const viewName = decodeURIComponent(ctx.params.viewName)
const view = await deleteView(viewName)
@ -123,7 +130,7 @@ export async function destroy(ctx: BBContext) {
ctx.body = view
}
export async function exportView(ctx: BBContext) {
export async function exportView(ctx: Ctx) {
const viewName = decodeURIComponent(ctx.query.view as string)
const view = await getView(viewName)

View File

@ -6,6 +6,7 @@ type ViewTemplateOpts = {
groupBy: string
filters: ViewFilter[]
calculation: string
groupByMulti: boolean
}
const TOKEN_MAP: Record<string, string> = {
@ -41,6 +42,12 @@ const GROUP_PROPERTY: Record<string, { type: string }> = {
},
}
const GROUP_PROPERTY_MULTI: Record<string, { type: string }> = {
group: {
type: "array",
},
}
const FIELD_PROPERTY: Record<string, { type: string }> = {
field: {
type: "string",
@ -136,13 +143,10 @@ function parseEmitExpression(field: string, groupBy: string) {
* filters: Array of filter objects containing predicates that are parsed into a JS expression
* calculation: an optional calculation to be performed over the view data.
*/
export default function ({
field,
tableId,
groupBy,
filters = [],
calculation,
}: ViewTemplateOpts) {
export default function (
{ field, tableId, groupBy, filters = [], calculation }: ViewTemplateOpts,
groupByMulti?: boolean
) {
// first filter can't have a conjunction
if (filters && filters.length > 0 && filters[0].conjunction) {
delete filters[0].conjunction
@ -151,9 +155,11 @@ export default function ({
let schema = null,
statFilter = null
let groupBySchema = groupByMulti ? GROUP_PROPERTY_MULTI : GROUP_PROPERTY
if (calculation) {
schema = {
...(groupBy ? GROUP_PROPERTY : FIELD_PROPERTY),
...(groupBy ? groupBySchema : FIELD_PROPERTY),
...SCHEMA_MAP[calculation],
}
if (

View File

@ -52,6 +52,16 @@ export function cleanInputValues(inputs: Record<string, any>, schema: any) {
}
}
}
//Check if input field should be a relationship and cast to array
for (let key in inputs.row) {
if (
inputs.schema?.[key]?.type === "link" &&
inputs.row[key] &&
typeof inputs.row[key] === "string"
) {
inputs.row[key] = JSON.parse(inputs.row[key])
}
}
return inputs
}

View File

@ -19,6 +19,10 @@ export const definition: AutomationStepSchema = {
schema: {
inputs: {
properties: {
meta: {
type: "object",
title: "Field settings",
},
row: {
type: "object",
customType: "row",
@ -73,7 +77,10 @@ export async function run({ inputs, appId, emitter }: AutomationStepInput) {
// clear any undefined, null or empty string properties so that they aren't updated
for (let propKey of Object.keys(inputs.row)) {
if (inputs.row[propKey] == null || inputs.row[propKey] === "") {
if (
(inputs.row[propKey] == null || inputs.row[propKey] === "") &&
!inputs.meta?.fields?.[propKey]?.clearRelationships
) {
delete inputs.row[propKey]
}
}

View File

@ -23,9 +23,6 @@ const MIN_ISO_DATE = "0000-00-00T00:00:00.000Z"
const MAX_ISO_DATE = "9999-00-00T00:00:00.000Z"
function likeKey(client: string, key: string): string {
if (!key.includes(" ")) {
return key
}
let start: string, end: string
switch (client) {
case SqlClient.MY_SQL:
@ -235,7 +232,9 @@ class InternalBuilder {
} else {
const rawFnc = `${fnc}Raw`
// @ts-ignore
query = query[rawFnc](`LOWER(${key}) LIKE ?`, [`${value}%`])
query = query[rawFnc](`LOWER(${likeKey(this.client, key)}) LIKE ?`, [
`${value}%`,
])
}
})
}

View File

@ -147,7 +147,8 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
if (
field.type == "DATETIME" ||
field.type === "DATE" ||
field.type === "TIMESTAMP"
field.type === "TIMESTAMP" ||
field.type === "LONGLONG"
) {
return field.string()
}

View File

@ -352,7 +352,7 @@ describe("SQL query builder", () => {
)
expect(query).toEqual({
bindings: [10, "%20%", "%25%", `%"John"%`, `%"Mary"%`],
sql: `select * from (select top (@p0) * from [${TABLE_NAME}] where (LOWER(${TABLE_NAME}.age) LIKE @p1 AND LOWER(${TABLE_NAME}.age) LIKE @p2) and (LOWER(${TABLE_NAME}.name) LIKE @p3 AND LOWER(${TABLE_NAME}.name) LIKE @p4)) as [${TABLE_NAME}]`,
sql: `select * from (select top (@p0) * from [${TABLE_NAME}] where (LOWER([${TABLE_NAME}].[age]) LIKE @p1 AND LOWER([${TABLE_NAME}].[age]) LIKE @p2) and (LOWER([${TABLE_NAME}].[name]) LIKE @p3 AND LOWER([${TABLE_NAME}].[name]) LIKE @p4)) as [${TABLE_NAME}]`,
})
})
@ -403,7 +403,7 @@ describe("SQL query builder", () => {
)
expect(query).toEqual({
bindings: [10, "%20%", `%"John"%`],
sql: `select * from (select top (@p0) * from [${TABLE_NAME}] where NOT (LOWER(${TABLE_NAME}.age) LIKE @p1) and NOT (LOWER(${TABLE_NAME}.name) LIKE @p2)) as [${TABLE_NAME}]`,
sql: `select * from (select top (@p0) * from [${TABLE_NAME}] where NOT (LOWER([${TABLE_NAME}].[age]) LIKE @p1) and NOT (LOWER([${TABLE_NAME}].[name]) LIKE @p2)) as [${TABLE_NAME}]`,
})
})
@ -454,7 +454,7 @@ describe("SQL query builder", () => {
)
expect(query).toEqual({
bindings: [10, "%20%", "%25%", `%"John"%`, `%"Mary"%`],
sql: `select * from (select top (@p0) * from [${TABLE_NAME}] where (LOWER(${TABLE_NAME}.age) LIKE @p1 OR LOWER(${TABLE_NAME}.age) LIKE @p2) and (LOWER(${TABLE_NAME}.name) LIKE @p3 OR LOWER(${TABLE_NAME}.name) LIKE @p4)) as [${TABLE_NAME}]`,
sql: `select * from (select top (@p0) * from [${TABLE_NAME}] where (LOWER([${TABLE_NAME}].[age]) LIKE @p1 OR LOWER([${TABLE_NAME}].[age]) LIKE @p2) and (LOWER([${TABLE_NAME}].[name]) LIKE @p3 OR LOWER([${TABLE_NAME}].[name]) LIKE @p4)) as [${TABLE_NAME}]`,
})
})
@ -517,4 +517,40 @@ describe("SQL query builder", () => {
sql: `select "stores"."store_id" as "stores.store_id", "stores"."store_name" as "stores.store_name", "products"."product_id" as "products.product_id", "products"."product_name" as "products.product_name" from (select * from "production"."stores" limit $1) as "stores" left join "production"."stocks" on "stores"."store_id" = "stocks"."store_id" left join "production"."products" on "products"."product_id" = "stocks"."product_id" limit $2`,
})
})
it("should handle table names with dashes when performing a LIKE in MySQL", () => {
const tableName = "Table-Name-With-Dashes"
const query = new Sql(SqlClient.MY_SQL, limit)._query(
generateReadJson({
table: tableName,
filters: {
string: {
name: "John",
},
},
})
)
expect(query).toEqual({
bindings: ["John%", limit],
sql: `select * from (select * from \`${tableName}\` where LOWER(\`${tableName}\`.\`name\`) LIKE ? limit ?) as \`${tableName}\``,
})
})
it("should handle table names with dashes when performing a LIKE in SQL Server", () => {
const tableName = "Table-Name-With-Dashes"
const query = new Sql(SqlClient.MS_SQL, limit)._query(
generateReadJson({
table: tableName,
filters: {
string: {
name: "John",
},
},
})
)
expect(query).toEqual({
bindings: [limit, "John%"],
sql: `select * from (select top (@p0) * from [${tableName}] where LOWER([${tableName}].[name]) LIKE @p1) as [${tableName}]`,
})
})
})

View File

@ -8,7 +8,6 @@ const ROW_ID_REGEX = /^\[.*]$/g
const SQL_NUMBER_TYPE_MAP = {
integer: FieldTypes.NUMBER,
int: FieldTypes.NUMBER,
bigint: FieldTypes.NUMBER,
decimal: FieldTypes.NUMBER,
smallint: FieldTypes.NUMBER,
real: FieldTypes.NUMBER,
@ -47,6 +46,7 @@ const SQL_STRING_TYPE_MAP = {
blob: FieldTypes.STRING,
long: FieldTypes.STRING,
text: FieldTypes.STRING,
bigint: FieldTypes.STRING,
}
const SQL_BOOLEAN_TYPE_MAP = {
@ -141,12 +141,18 @@ export function breakRowIdField(_id: string | { _id: string }): any[] {
export function convertSqlType(type: string) {
let foundType = FieldTypes.STRING
const lcType = type.toLowerCase()
let matchingTypes = []
for (let [external, internal] of Object.entries(SQL_TYPE_MAP)) {
if (lcType.includes(external)) {
foundType = internal
break
matchingTypes.push({ external, internal })
}
}
//Set the foundType based the longest match
if (matchingTypes.length > 0) {
foundType = matchingTypes.reduce((acc, val) => {
return acc.external.length >= val.external.length ? acc : val
}).internal
}
const schema: any = { type: foundType }
if (foundType === FieldTypes.DATETIME) {
schema.dateOnly = SQL_DATE_ONLY_TYPES.includes(lcType)

View File

@ -91,7 +91,7 @@ class QueryRunner {
let query
// handle SQL injections by interpolating the variables
if (isSQL(datasourceClone)) {
query = await interpolateSQL(fieldsClone, enrichedParameters, integration)
query = await interpolateSQL(fieldsClone, enrichedContext, integration)
} else {
query = await sdk.queries.enrichContext(fieldsClone, enrichedContext)
}

View File

@ -1273,13 +1273,13 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.2.12-alpha.71":
version "2.2.12-alpha.71"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.71.tgz#bd61a9158ed601d7860e6fa9afaf233217944dd3"
integrity sha512-QDhBsiqTrWFhXZMaotLpt4+StKDGTL/HzTm0kwTgAAkvsA2tkkiCZCjx2fOsdpYBvTfJ0vN+h4Kg6gKDY3I7NA==
"@budibase/backend-core@2.3.0":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.0.tgz#fb40b77db6ae655e527aa43467e5a9145522bb44"
integrity sha512-Qvt9Tr6em6KPUhnj16jggcGR1l9b2qaOcrzoDfzCbSRcZcHuQ8mBHYvdHqlGyH6rHbJCsDrz8/KqNT8qRc2IyQ==
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"
@ -1374,13 +1374,13 @@
qs "^6.11.0"
tough-cookie "^4.1.2"
"@budibase/pro@2.2.12-alpha.71":
version "2.2.12-alpha.71"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.71.tgz#c1272a512c0ab5d4252c175d91ebbc69bb1596e0"
integrity sha512-qZP0RLzZ08nvDPwmIxbRgHI/C/jgjMlx8liiOU6/RhL6c9HT1Rxa3ABSpBxmQvLu5OrxUgC8cJY1cJ7v9LD8MQ==
"@budibase/pro@2.3.0":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.0.tgz#adc43a2132025593b9da2ad9449a82c5196f7216"
integrity sha512-hfjcncN/g6fTxmQHiXXf1oPeo2oB5XUi17xju7LVnQEDwmyrtW3rbrCuvCQpYQII1C3nsuFsakanXLpCouLCGA==
dependencies:
"@budibase/backend-core" "2.2.12-alpha.71"
"@budibase/types" "2.2.12-alpha.71"
"@budibase/backend-core" "2.3.0"
"@budibase/types" "2.3.0"
"@koa/router" "8.0.8"
bull "4.10.1"
joi "17.6.0"
@ -1406,10 +1406,10 @@
svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0"
"@budibase/types@2.2.12-alpha.71":
version "2.2.12-alpha.71"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.71.tgz#ba8c1e8bb6945a6b9cebc0ae03c61a51b6e9a6f7"
integrity sha512-JdtZ0AxoyTIAnJpnh17Yb0VgaNk6xEHy/Fb6AbSMQ0tAvAFK1xbVdJhvpyxbyFqOpB6ZxlV7ekE3VfOrR0YQRA==
"@budibase/types@2.3.0", "@budibase/types@^2.3.0":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.0.tgz#ab5ce3451ac0ad3a3cfb5846228e820d4f597b28"
integrity sha512-rTLXoCElUOG+EMD/qbrUyGwXk5cwatOFtylyI01pYIyM7HS/Hy0YekLJVcWQKOf2PGTcYah3AdtkYSIYaZDSfA==
"@bull-board/api@3.7.0":
version "3.7.0"

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/string-templates",
"version": "2.2.12-alpha.71",
"version": "2.3.0",
"description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs",
"module": "dist/bundle.mjs",

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/types",
"version": "2.2.12-alpha.71",
"version": "2.3.0",
"description": "Budibase types",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/worker",
"email": "hi@budibase.com",
"version": "2.2.12-alpha.71",
"version": "2.3.0",
"description": "Budibase background service",
"main": "src/index.ts",
"repository": {
@ -36,10 +36,10 @@
"author": "Budibase",
"license": "GPL-3.0",
"dependencies": {
"@budibase/backend-core": "2.2.12-alpha.71",
"@budibase/pro": "2.2.12-alpha.71",
"@budibase/string-templates": "2.2.12-alpha.71",
"@budibase/types": "2.2.12-alpha.71",
"@budibase/backend-core": "^2.3.0",
"@budibase/pro": "2.3.0",
"@budibase/string-templates": "^2.3.0",
"@budibase/types": "^2.3.0",
"@koa/router": "8.0.8",
"@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "0.3.2",

View File

@ -470,13 +470,13 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.2.12-alpha.71":
version "2.2.12-alpha.71"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.71.tgz#bd61a9158ed601d7860e6fa9afaf233217944dd3"
integrity sha512-QDhBsiqTrWFhXZMaotLpt4+StKDGTL/HzTm0kwTgAAkvsA2tkkiCZCjx2fOsdpYBvTfJ0vN+h4Kg6gKDY3I7NA==
"@budibase/backend-core@2.3.0":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.0.tgz#fb40b77db6ae655e527aa43467e5a9145522bb44"
integrity sha512-Qvt9Tr6em6KPUhnj16jggcGR1l9b2qaOcrzoDfzCbSRcZcHuQ8mBHYvdHqlGyH6rHbJCsDrz8/KqNT8qRc2IyQ==
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"
@ -521,13 +521,13 @@
qs "^6.11.0"
tough-cookie "^4.1.2"
"@budibase/pro@2.2.12-alpha.71":
version "2.2.12-alpha.71"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.71.tgz#c1272a512c0ab5d4252c175d91ebbc69bb1596e0"
integrity sha512-qZP0RLzZ08nvDPwmIxbRgHI/C/jgjMlx8liiOU6/RhL6c9HT1Rxa3ABSpBxmQvLu5OrxUgC8cJY1cJ7v9LD8MQ==
"@budibase/pro@2.3.0":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.0.tgz#adc43a2132025593b9da2ad9449a82c5196f7216"
integrity sha512-hfjcncN/g6fTxmQHiXXf1oPeo2oB5XUi17xju7LVnQEDwmyrtW3rbrCuvCQpYQII1C3nsuFsakanXLpCouLCGA==
dependencies:
"@budibase/backend-core" "2.2.12-alpha.71"
"@budibase/types" "2.2.12-alpha.71"
"@budibase/backend-core" "2.3.0"
"@budibase/types" "2.3.0"
"@koa/router" "8.0.8"
bull "4.10.1"
joi "17.6.0"
@ -535,10 +535,10 @@
lru-cache "^7.14.1"
node-fetch "^2.6.1"
"@budibase/types@2.2.12-alpha.71":
version "2.2.12-alpha.71"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.71.tgz#ba8c1e8bb6945a6b9cebc0ae03c61a51b6e9a6f7"
integrity sha512-JdtZ0AxoyTIAnJpnh17Yb0VgaNk6xEHy/Fb6AbSMQ0tAvAFK1xbVdJhvpyxbyFqOpB6ZxlV7ekE3VfOrR0YQRA==
"@budibase/types@2.3.0", "@budibase/types@^2.3.0":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.0.tgz#ab5ce3451ac0ad3a3cfb5846228e820d4f597b28"
integrity sha512-rTLXoCElUOG+EMD/qbrUyGwXk5cwatOFtylyI01pYIyM7HS/Hy0YekLJVcWQKOf2PGTcYah3AdtkYSIYaZDSfA==
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"