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/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.certificateArn }} alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.certificateArn }}
{{- end }} {{- end }}
{{- if .Values.ingress.securityGroups }}
alb.ingress.kubernetes.io/security-groups: {{ .Values.ingress.securityGroups }}
{{- end }}
spec: spec:
rules: rules:
- http: - http:

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "name": "@budibase/backend-core",
"version": "2.2.12-alpha.71", "version": "2.3.0",
"description": "Budibase backend core libraries used in server and worker", "description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js", "main": "dist/src/index.js",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
@ -23,7 +23,7 @@
}, },
"dependencies": { "dependencies": {
"@budibase/nano": "10.1.1", "@budibase/nano": "10.1.1",
"@budibase/types": "2.2.12-alpha.71", "@budibase/types": "^2.3.0",
"@shopify/jest-koa-mocks": "5.0.1", "@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",
"aws-cloudfront-sign": "2.2.0", "aws-cloudfront-sign": "2.2.0",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "2.2.12-alpha.71", "version": "2.3.0",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",
@ -38,7 +38,7 @@
], ],
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "1.2.1", "@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/accordion": "3.0.24",
"@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actionbutton": "1.0.1",
"@spectrum-css/actiongroup": "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 time only set date component to 2000-01-01
if (timeOnly) { 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]}` newValue = `2000-01-01T${newValue.split("T")[1]}`
} }
@ -113,7 +106,7 @@
const clearDateOnBackspace = event => { const clearDateOnBackspace = event => {
if (["Backspace", "Clear", "Delete"].includes(event.key)) { if (["Backspace", "Clear", "Delete"].includes(event.key)) {
dispatch("change", null) dispatch("change", "")
flatpickr.close() flatpickr.close()
} }
} }

View File

@ -11,14 +11,31 @@
export let getOptionLabel = option => option export let getOptionLabel = option => option
export let getOptionValue = option => option export let getOptionValue = option => option
export let getOptionTitle = option => option export let getOptionTitle = option => option
export let sort = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => dispatch("change", e.target.value) 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> </script>
<div class={`spectrum-FieldGroup spectrum-FieldGroup--${direction}`}> <div class={`spectrum-FieldGroup spectrum-FieldGroup--${direction}`}>
{#if options && Array.isArray(options)} {#if parsedOptions && Array.isArray(parsedOptions)}
{#each options as option} {#each parsedOptions as option}
<div <div
title={getOptionTitle(option)} title={getOptionTitle(option)}
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized" class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"

View File

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

View File

@ -74,8 +74,19 @@
$: schemaFields = Object.values(schema || {}) $: schemaFields = Object.values(schema || {})
$: queryLimit = tableId?.includes("datasource") ? "∞" : "1000" $: queryLimit = tableId?.includes("datasource") ? "∞" : "1000"
$: isTrigger = block?.type === "TRIGGER" $: isTrigger = block?.type === "TRIGGER"
$: isUpdateRow = stepId === ActionStepID.UPDATE_ROW
const onChange = Utils.sequential(async (e, key) => { 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 { try {
if (isTestModal) { if (isTestModal) {
// Special case for webhook, as it requires a body, but the schema already brings back the body's contents // Special case for webhook, as it requires a body, but the schema already brings back the body's contents
@ -321,9 +332,17 @@
<RowSelector <RowSelector
{block} {block}
value={inputData[key]} 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} {bindings}
{isTestModal} {isTestModal}
{isUpdateRow}
/> />
{:else if value.customType === "webhookUrl"} {:else if value.customType === "webhookUrl"}
<WebhookDisplay <WebhookDisplay

View File

@ -1,6 +1,6 @@
<script> <script>
import { tables } from "stores/backend" import { tables } from "stores/backend"
import { Select } from "@budibase/bbui" import { Select, Checkbox } from "@budibase/bbui"
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte" import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte" import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
@ -10,9 +10,11 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export let value export let value
export let meta
export let bindings export let bindings
export let block export let block
export let isTestModal export let isTestModal
export let isUpdateRow
$: parsedBindings = bindings.map(binding => { $: parsedBindings = bindings.map(binding => {
let clone = Object.assign({}, binding) let clone = Object.assign({}, binding)
@ -97,6 +99,17 @@
dispatch("change", value) 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 // Ensure any nullish tableId values get set to empty string so
// that the select works // that the select works
$: if (value?.tableId == null) value = { tableId: "" } $: if (value?.tableId == null) value = { tableId: "" }
@ -124,21 +137,33 @@
{onChange} {onChange}
/> />
{:else} {:else}
<svelte:component <div>
this={isTestModal ? ModalBindableInput : DrawerBindableInput} <svelte:component
placeholder={placeholders[schema.type]} this={isTestModal ? ModalBindableInput : DrawerBindableInput}
panel={AutomationBindingPanel} placeholder={placeholders[schema.type]}
value={Array.isArray(value[field]) panel={AutomationBindingPanel}
? value[field].join(" ") value={Array.isArray(value[field])
: value[field]} ? value[field].join(" ")
on:change={e => onChange(e, field, schema.type)} : value[field]}
label={field} on:change={e => onChange(e, field, schema.type)}
type="string" label={field}
bindings={parsedBindings} type="string"
fillWidth={true} bindings={parsedBindings}
allowJS={true} fillWidth={true}
updateOnChange={false} 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} {/if}
{/if} {/if}
@ -155,4 +180,12 @@
.schema-fields :global(label) { .schema-fields :global(label) {
text-transform: capitalize; 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> </style>

View File

@ -58,7 +58,7 @@
entries = entries.filter(f => f.name !== originalName) entries = entries.filter(f => f.name !== originalName)
} }
value = entries.reduce((newVals, current) => { value = entries.reduce((newVals, current) => {
newVals[current.name] = current.type newVals[current.name.trim()] = current.type
return newVals return newVals
}, {}) }, {})
dispatch("change", value) dispatch("change", value)

View File

@ -12,7 +12,7 @@
Modal, Modal,
notifications, notifications,
} from "@budibase/bbui" } from "@budibase/bbui"
import { createEventDispatcher, onMount } from "svelte" import { createEventDispatcher } from "svelte"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { tables, datasources } from "stores/backend" import { tables, datasources } from "stores/backend"
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants" import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
@ -48,7 +48,22 @@
const { hide } = getContext(Context.Modal) const { hide } = getContext(Context.Modal)
let fieldDefinitions = cloneDeep(FIELDS) 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", type: "string",
constraints: fieldDefinitions.STRING.constraints, constraints: fieldDefinitions.STRING.constraints,
@ -56,48 +71,80 @@
fieldName: $tables.selected.name, fieldName: $tables.selected.name,
} }
let originalName = field.name $: if (primaryDisplay) {
const linkEditDisabled = originalName != null editableColumn.constraints.presence = { allowEmpty: false }
let primaryDisplay = }
$tables.selected.primaryDisplay == null ||
$tables.selected.primaryDisplay === field.name
let isCreating = originalName == null
let table = $tables.selected $: if (field && !savingColumn) {
let indexes = [...($tables.selected.indexes || [])] editableColumn = cloneDeep(field)
let confirmDeleteDialog originalName = editableColumn.name ? editableColumn.name + "" : null
let deletion linkEditDisabled = originalName != null
let deleteColName isCreating = originalName == null
let jsonSchemaModal primaryDisplay =
$tables.selected.primaryDisplay == null ||
$tables.selected.primaryDisplay === editableColumn.name
}
$: checkConstraints(field) $: checkConstraints(editableColumn)
$: required = !!field?.constraints?.presence || primaryDisplay $: required = !!editableColumn?.constraints?.presence || primaryDisplay
$: uneditable = $: uneditable =
$tables.selected?._id === TableNames.USERS && $tables.selected?._id === TableNames.USERS &&
UNEDITABLE_USER_FIELDS.includes(field.name) UNEDITABLE_USER_FIELDS.includes(editableColumn.name)
$: invalid = $: invalid =
!field.name || !editableColumn?.name ||
(field.type === LINK_TYPE && !field.tableId) || (editableColumn?.type === LINK_TYPE && !editableColumn?.tableId) ||
Object.keys(errors).length !== 0 Object.keys(errors).length !== 0
$: errors = checkErrors(field) $: errors = checkErrors(editableColumn)
$: datasource = $datasources.list.find( $: datasource = $datasources.list.find(
source => source._id === table?.sourceId 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 // used to select what different options can be displayed for column type
$: canBeSearched = $: canBeSearched =
field.type !== LINK_TYPE && editableColumn?.type !== LINK_TYPE &&
field.type !== JSON_TYPE && editableColumn?.type !== JSON_TYPE &&
field.subtype !== AUTO_COLUMN_SUB_TYPES.CREATED_BY && editableColumn?.subtype !== AUTO_COLUMN_SUB_TYPES.CREATED_BY &&
field.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY && editableColumn?.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY &&
field.type !== FORMULA_TYPE editableColumn?.type !== FORMULA_TYPE
$: canBeDisplay = $: canBeDisplay =
field.type !== LINK_TYPE && editableColumn?.type !== LINK_TYPE &&
field.type !== AUTO_TYPE && editableColumn?.type !== AUTO_TYPE &&
field.type !== JSON_TYPE editableColumn?.type !== JSON_TYPE &&
!editableColumn.autocolumn
$: canBeRequired = $: canBeRequired =
field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_TYPE editableColumn?.type !== LINK_TYPE &&
$: relationshipOptions = getRelationshipOptions(field) !uneditable &&
editableColumn?.type !== AUTO_TYPE &&
!editableColumn.autocolumn
$: relationshipOptions = getRelationshipOptions(editableColumn)
$: external = table.type === "external" $: external = table.type === "external"
// in the case of internal tables the sourceId will just be undefined // in the case of internal tables the sourceId will just be undefined
$: tableOptions = $tables.list.filter( $: tableOptions = $tables.list.filter(
@ -108,76 +155,90 @@
) )
$: typeEnabled = $: typeEnabled =
!originalName || !originalName ||
(originalName && SWITCHABLE_TYPES.indexOf(field.type) !== -1) (originalName &&
SWITCHABLE_TYPES.indexOf(editableColumn.type) !== -1 &&
!editableColumn?.autocolumn)
async function saveColumn() { async function saveColumn() {
if (field.type === AUTO_TYPE) { savingColumn = true
field = buildAutoColumn($tables.selected.name, field.name, field.subtype) 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 { try {
await tables.saveField({ await tables.saveField({
originalName, originalName,
field, field: saveColumn,
primaryDisplay, primaryDisplay,
indexes, indexes,
}) })
dispatch("updatecolumns") dispatch("updatecolumns")
} catch (err) { } catch (err) {
notifications.error("Error saving column") console.log(err)
notifications.error(`Error saving column: ${err.message}`)
} }
} }
function cancelEdit() { function cancelEdit() {
field.name = originalName editableColumn.name = originalName
} }
function deleteColumn() { function deleteColumn() {
try { try {
field.name = deleteColName editableColumn.name = deleteColName
if (field.name === $tables.selected.primaryDisplay) { if (editableColumn.name === $tables.selected.primaryDisplay) {
notifications.error("You cannot delete the display column") notifications.error("You cannot delete the display column")
} else { } else {
tables.deleteField(field) tables.deleteField(editableColumn)
notifications.success(`Column ${field.name} deleted.`) notifications.success(`Column ${editableColumn.name} deleted.`)
confirmDeleteDialog.hide() confirmDeleteDialog.hide()
hide() hide()
deletion = false deletion = false
dispatch("updatecolumns") dispatch("updatecolumns")
} }
} catch (error) { } catch (error) {
notifications.error("Error deleting column") notifications.error(`Error deleting column: ${error.message}`)
} }
} }
function handleTypeChange(event) { function handleTypeChange(event) {
// remove any extra fields that may not be related to this type // remove any extra fields that may not be related to this type
delete field.autocolumn delete editableColumn.autocolumn
delete field.subtype delete editableColumn.subtype
delete field.tableId delete editableColumn.tableId
delete field.relationshipType delete editableColumn.relationshipType
delete field.formulaType delete editableColumn.formulaType
// Add in defaults and initial definition // Add in defaults and initial definition
const definition = fieldDefinitions[event.detail?.toUpperCase()] const definition = fieldDefinitions[event.detail?.toUpperCase()]
if (definition?.constraints) { if (definition?.constraints) {
field.constraints = definition.constraints editableColumn.constraints = definition.constraints
} }
// Default relationships many to many // Default relationships many to many
if (field.type === LINK_TYPE) { if (editableColumn.type === LINK_TYPE) {
field.relationshipType = RelationshipTypes.MANY_TO_MANY editableColumn.relationshipType = RelationshipTypes.MANY_TO_MANY
} }
if (field.type === FORMULA_TYPE) { if (editableColumn.type === FORMULA_TYPE) {
field.formulaType = "dynamic" editableColumn.formulaType = "dynamic"
} }
} }
function onChangeRequired(e) { function onChangeRequired(e) {
const req = e.detail const req = e.detail
field.constraints.presence = req ? { allowEmpty: false } : false editableColumn.constraints.presence = req ? { allowEmpty: false } : false
required = req required = req
} }
@ -185,17 +246,17 @@
const isPrimary = e.detail const isPrimary = e.detail
// primary display is always required // primary display is always required
if (isPrimary) { if (isPrimary) {
field.constraints.presence = { allowEmpty: false } editableColumn.constraints.presence = { allowEmpty: false }
} }
} }
function onChangePrimaryIndex(e) { function onChangePrimaryIndex(e) {
indexes = e.detail ? [field.name] : [] indexes = e.detail ? [editableColumn.name] : []
} }
function onChangeSecondaryIndex(e) { function onChangeSecondaryIndex(e) {
if (e.detail) { if (e.detail) {
indexes[1] = field.name indexes[1] = editableColumn.name
} else { } else {
indexes = indexes.slice(0, 1) indexes = indexes.slice(0, 1)
} }
@ -246,11 +307,14 @@
} }
function getAllowedTypes() { function getAllowedTypes() {
if (originalName && ALLOWABLE_STRING_TYPES.indexOf(field.type) !== -1) { if (
originalName &&
ALLOWABLE_STRING_TYPES.indexOf(editableColumn.type) !== -1
) {
return ALLOWABLE_STRING_OPTIONS return ALLOWABLE_STRING_OPTIONS
} else if ( } else if (
originalName && originalName &&
ALLOWABLE_NUMBER_TYPES.indexOf(field.type) !== -1 ALLOWABLE_NUMBER_TYPES.indexOf(editableColumn.type) !== -1
) { ) {
return ALLOWABLE_NUMBER_OPTIONS return ALLOWABLE_NUMBER_OPTIONS
} else if (!external) { } else if (!external) {
@ -275,6 +339,9 @@
} }
function checkConstraints(fieldToCheck) { function checkConstraints(fieldToCheck) {
if (!fieldToCheck) {
return
}
// most types need this, just make sure its always present // most types need this, just make sure its always present
if (fieldToCheck && !fieldToCheck.constraints) { if (fieldToCheck && !fieldToCheck.constraints) {
fieldToCheck.constraints = {} fieldToCheck.constraints = {}
@ -296,10 +363,16 @@
} }
function checkErrors(fieldInfo) { function checkErrors(fieldInfo) {
if (!editableColumn) {
return {}
}
function inUse(tbl, column, ogName = null) { function inUse(tbl, column, ogName = null) {
return Object.keys(tbl?.schema || {}).some( const parsedColumn = column ? column.toLowerCase().trim() : column
key => key !== ogName && key === column
) return Object.keys(tbl?.schema || {}).some(key => {
let lowerKey = key.toLowerCase()
return lowerKey !== ogName?.toLowerCase() && lowerKey === parsedColumn
})
} }
const newError = {} const newError = {}
if (!external && fieldInfo.name?.startsWith("_")) { if (!external && fieldInfo.name?.startsWith("_")) {
@ -313,6 +386,11 @@
} else if (inUse($tables.selected, fieldInfo.name, originalName)) { } else if (inUse($tables.selected, fieldInfo.name, originalName)) {
newError.name = `Column name already in use.` 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) { if (fieldInfo.fieldName && fieldInfo.tableId) {
const relatedTable = $tables.list.find( const relatedTable = $tables.list.find(
tbl => tbl._id === fieldInfo.tableId tbl => tbl._id === fieldInfo.tableId
@ -323,12 +401,6 @@
} }
return newError return newError
} }
onMount(() => {
if (primaryDisplay) {
field.constraints.presence = { allowEmpty: false }
}
})
</script> </script>
<ModalContent <ModalContent
@ -340,19 +412,26 @@
> >
<Input <Input
label="Name" label="Name"
bind:value={field.name} bind:value={editableColumn.name}
disabled={uneditable || (linkEditDisabled && field.type === LINK_TYPE)} disabled={uneditable ||
(linkEditDisabled && editableColumn.type === LINK_TYPE)}
error={errors?.name} error={errors?.name}
/> />
<Select <Select
disabled={!typeEnabled} disabled={!typeEnabled}
label="Type" label="Type"
bind:value={field.type} bind:value={editableColumn.type}
on:change={handleTypeChange} on:change={handleTypeChange}
options={getAllowedTypes()} options={getAllowedTypes()}
getOptionLabel={field => field.name} getOptionLabel={field => field.name}
getOptionValue={field => field.type} getOptionValue={field => field.type}
isOptionEnabled={option => {
if (option.type == AUTO_TYPE) {
return availableAutoColumnKeys?.length > 0
}
return true
}}
/> />
{#if canBeRequired || canBeDisplay} {#if canBeRequired || canBeDisplay}
@ -381,32 +460,32 @@
<div> <div>
<Label>Search Indexes</Label> <Label>Search Indexes</Label>
<Toggle <Toggle
value={indexes[0] === field.name} value={indexes[0] === editableColumn.name}
disabled={indexes[1] === field.name} disabled={indexes[1] === editableColumn.name}
on:change={onChangePrimaryIndex} on:change={onChangePrimaryIndex}
text="Primary" text="Primary"
/> />
<Toggle <Toggle
value={indexes[1] === field.name} value={indexes[1] === editableColumn.name}
disabled={!indexes[0] || indexes[0] === field.name} disabled={!indexes[0] || indexes[0] === editableColumn.name}
on:change={onChangeSecondaryIndex} on:change={onChangeSecondaryIndex}
text="Secondary" text="Secondary"
/> />
</div> </div>
{/if} {/if}
{#if field.type === "string"} {#if editableColumn.type === "string"}
<Input <Input
type="number" type="number"
label="Max Length" 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 <ValuesList
label="Options (one per line)" 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> <div>
<Label <Label
size="M" size="M"
@ -415,21 +494,24 @@
Formatting Formatting
</Label> </Label>
<Toggle <Toggle
bind:value={field.useRichText} bind:value={editableColumn.useRichText}
text="Enable rich text support (markdown)" text="Enable rich text support (markdown)"
/> />
</div> </div>
{:else if field.type === "array"} {:else if editableColumn.type === "array"}
<ValuesList <ValuesList
label="Options (one per line)" 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 <DatePicker
label="Earliest" 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"} {#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"}
<div> <div>
<Label <Label
@ -439,25 +521,28 @@
> >
Time zones Time zones
</Label> </Label>
<Toggle bind:value={field.ignoreTimezones} text="Ignore time zones" /> <Toggle
bind:value={editableColumn.ignoreTimezones}
text="Ignore time zones"
/>
</div> </div>
{/if} {/if}
{:else if field.type === "number"} {:else if editableColumn.type === "number" && !editableColumn.autocolumn}
<Input <Input
type="number" type="number"
label="Min Value" label="Min Value"
bind:value={field.constraints.numericality.greaterThanOrEqualTo} bind:value={editableColumn.constraints.numericality.greaterThanOrEqualTo}
/> />
<Input <Input
type="number" type="number"
label="Max Value" 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 <Select
label="Table" label="Table"
disabled={linkEditDisabled} disabled={linkEditDisabled}
bind:value={field.tableId} bind:value={editableColumn.tableId}
options={tableOptions} options={tableOptions}
getOptionLabel={table => table.name} getOptionLabel={table => table.name}
getOptionValue={table => table._id} getOptionValue={table => table._id}
@ -466,7 +551,7 @@
<RadioGroup <RadioGroup
disabled={linkEditDisabled} disabled={linkEditDisabled}
label="Define the relationship" label="Define the relationship"
bind:value={field.relationshipType} bind:value={editableColumn.relationshipType}
options={relationshipOptions} options={relationshipOptions}
getOptionLabel={option => option.name} getOptionLabel={option => option.name}
getOptionValue={option => option.value} getOptionValue={option => option.value}
@ -476,14 +561,14 @@
<Input <Input
disabled={linkEditDisabled} disabled={linkEditDisabled}
label={`Column name in other table`} label={`Column name in other table`}
bind:value={field.fieldName} bind:value={editableColumn.fieldName}
error={errors.relatedName} error={errors.relatedName}
/> />
{:else if field.type === FORMULA_TYPE} {:else if editableColumn.type === FORMULA_TYPE}
{#if !table.sql} {#if !table.sql}
<Select <Select
label="Formula type" label="Formula type"
bind:value={field.formulaType} bind:value={editableColumn.formulaType}
options={[ options={[
{ label: "Dynamic", value: "dynamic" }, { label: "Dynamic", value: "dynamic" },
{ label: "Static", value: "static" }, { label: "Static", value: "static" },
@ -497,25 +582,28 @@
<ModalBindableInput <ModalBindableInput
title="Formula" title="Formula"
label="Formula" label="Formula"
value={field.formula} value={editableColumn.formula}
on:change={e => (field.formula = e.detail)} on:change={e => (editableColumn.formula = e.detail)}
bindings={getBindings({ table })} bindings={getBindings({ table })}
allowJS allowJS
/> />
{:else if field.type === AUTO_TYPE} {:else if editableColumn.type === JSON_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}
<Button primary text on:click={openJsonSchemaEditor} <Button primary text on:click={openJsonSchemaEditor}
>Open schema editor</Button >Open schema editor</Button
> >
{/if} {/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"> <div slot="footer">
{#if !uneditable && originalName != null} {#if !uneditable && originalName != null}
@ -525,11 +613,11 @@
</ModalContent> </ModalContent>
<Modal bind:this={jsonSchemaModal}> <Modal bind:this={jsonSchemaModal}>
<JSONSchemaModal <JSONSchemaModal
schema={field.schema} schema={editableColumn.schema}
json={field.json} json={editableColumn.json}
on:save={({ detail }) => { on:save={({ detail }) => {
field.schema = detail.schema editableColumn.schema = detail.schema
field.json = detail.json editableColumn.json = detail.json
}} }}
/> />
</Modal> </Modal>

View File

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

View File

@ -77,8 +77,18 @@
} }
const deleteAction = index => { 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.splice(index, 1)
actions = actions actions = actions
// Select a new action if we deleted the selected one
if (isSelected) {
selectedAction = actions?.length ? actions[0] : null
}
} }
const toggleActionList = () => { const toggleActionList = () => {

View File

@ -36,7 +36,13 @@
$: selectedSchema = selectedAutomation?.schema $: selectedSchema = selectedAutomation?.schema
const onFieldsChanged = e => { 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 = () => { const setNew = () => {

View File

@ -1,9 +1,45 @@
<script> <script>
import TableDataTable from "components/backend/DataTable/DataTable.svelte" import TableDataTable from "components/backend/DataTable/DataTable.svelte"
import { tables, database } from "stores/backend" 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> </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 /> <TableDataTable />
{:else} {:else}
<i>Create your first table to start building</i> <i>Create your first table to start building</i>
@ -15,4 +51,11 @@
color: var(--grey-5); color: var(--grey-5);
margin-top: 2px; margin-top: 2px;
} }
.alert-wrap {
display: flex;
width: 100%;
}
.alert-wrap :global(> *) {
flex: 1;
}
</style> </style>

View File

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

View File

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

View File

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

View File

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

View File

@ -11,20 +11,23 @@
export let limit export let limit
export let paginate export let paginate
const loading = writable(false)
const { styleable, Provider, ActionTypes, API } = getContext("sdk") const { styleable, Provider, ActionTypes, API } = getContext("sdk")
const component = getContext("component") 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 // We need to manage our lucene query manually as we want to allow components
// to extend it // to extend it
let queryExtensions = {} let queryExtensions = {}
$: defaultQuery = LuceneUtils.buildLuceneQuery(filter) $: defaultQuery = LuceneUtils.buildLuceneQuery(filter)
$: query = extendQuery(defaultQuery, queryExtensions) $: query = extendQuery(defaultQuery, queryExtensions)
// Keep our data fetch instance up to date // Fetch data and refresh when needed
$: fetch = createFetch(dataSource) $: fetch = createFetch(dataSource, $parentLoading)
$: fetch.update({ $: updateFetch({
query, query,
sortColumn, sortColumn,
sortOrder, sortOrder,
@ -32,6 +35,9 @@
paginate, paginate,
}) })
// Keep loading context updated
$: loading.set($parentLoading || !$fetch.loaded)
// Build our action context // Build our action context
$: actions = [ $: actions = [
{ {
@ -80,14 +86,21 @@
sortColumn: $fetch.sortColumn, sortColumn: $fetch.sortColumn,
sortOrder: $fetch.sortOrder, sortOrder: $fetch.sortOrder,
}, },
limit: limit, limit,
} }
const parentLoading = getContext("loading") const createFetch = (datasource, parentLoading) => {
setContext("loading", loading) // Return a dummy fetch if parent is still loading. We do this so that we
$: loading.set($parentLoading || !$fetch.loaded) // 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({ return fetchData({
API, API,
datasource, 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) => { const addQueryExtension = (key, extension) => {
if (!key || !extension) { if (!key || !extension) {
return return

View File

@ -1,7 +1,8 @@
<script> <script>
import { getContext } from "svelte" import { getContext, setContext } from "svelte"
import InnerForm from "./InnerForm.svelte" import InnerForm from "./InnerForm.svelte"
import { Helpers } from "@budibase/bbui" import { Helpers } from "@budibase/bbui"
import { writable } from "svelte/store"
export let dataSource export let dataSource
export let theme export let theme
@ -20,6 +21,12 @@
const context = getContext("context") const context = getContext("context")
const { API, fetchDatasourceSchema } = getContext("sdk") 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 schema
let table let table
@ -29,6 +36,7 @@
$: resetKey = Helpers.hashString( $: resetKey = Helpers.hashString(
schemaKey + JSON.stringify(initialValues) + disabled schemaKey + JSON.stringify(initialValues) + disabled
) )
$: loading.set($parentLoading || !loaded)
// Returns the closes data context which isn't a built in context // Returns the closes data context which isn't a built in context
const getInitialValues = (type, dataSource, context) => { const getInitialValues = (type, dataSource, context) => {
@ -60,6 +68,9 @@
} }
const res = await fetchDatasourceSchema(dataSource) const res = await fetchDatasourceSchema(dataSource)
schema = res || {} schema = res || {}
if (!loaded) {
loaded = true
}
} }
// Generates a predictable string that uniquely identifies a schema. We can't // 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) return fields.find(field => get(field).name === name)
} }
const getDefault = (defaultValue, schema, type) => { // Sanitises a value by ensuring it doesn't contain any invalid data
// Remove any values not present in the field schema const sanitiseValue = (value, schema, type) => {
// Convert any values supplied to string // Check arrays - remove any values not present in the field schema and
if (Array.isArray(defaultValue) && type == "array" && schema) { // convert any values supplied to strings
return defaultValue.reduce((acc, entry) => { if (Array.isArray(value) && type === "array" && schema) {
let processedOption = String(entry) const options = schema?.constraints.inclusion || []
let schemaOptions = schema.constraints.inclusion return value.map(opt => String(opt)).filter(opt => options.includes(opt))
if (schemaOptions.indexOf(processedOption) > -1) {
acc.push(processedOption)
}
return acc
}, [])
} else {
return defaultValue
} }
return value
} }
const formApi = { const formApi = {
@ -160,7 +154,6 @@
// Create validation function based on field schema // Create validation function based on field schema
const schemaConstraints = schema?.[field]?.constraints const schemaConstraints = schema?.[field]?.constraints
const validator = disableValidation const validator = disableValidation
? null ? null
: createValidatorFromConstraints( : createValidatorFromConstraints(
@ -170,10 +163,11 @@
table 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 // 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 initialError = null
let fieldId = `id-${Helpers.uuid()}` let fieldId = `id-${Helpers.uuid()}`
const existingField = getField(field) const existingField = getField(field)
@ -183,7 +177,9 @@
// Determine the initial value for this field, reusing the current // Determine the initial value for this field, reusing the current
// value if one exists // 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 // 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 // error set, then re-run the validator to see if we can unset it
@ -206,11 +202,11 @@
error: initialError, error: initialError,
disabled: disabled:
disabled || fieldDisabled || (isAutoColumn && !editAutoColumns), disabled || fieldDisabled || (isAutoColumn && !editAutoColumns),
defaultValue: parsedDefault, defaultValue,
validator, validator,
lastUpdate: Date.now(), lastUpdate: Date.now(),
}, },
fieldApi: makeFieldApi(field, parsedDefault), fieldApi: makeFieldApi(field),
fieldSchema: schema?.[field] ?? {}, fieldSchema: schema?.[field] ?? {},
}) })
@ -225,18 +221,9 @@
return fieldInfo return fieldInfo
}, },
validate: () => { validate: () => {
let valid = true return fields
let validationFields = fields .filter(field => get(field).step === get(currentStep))
.every(field => get(field).fieldApi.validate())
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
}, },
reset: () => { reset: () => {
// Reset the form by resetting each individual field // Reset the form by resetting each individual field

View File

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

View File

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

View File

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

View File

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

View File

@ -315,7 +315,13 @@ export async function checkForViewUpdates(
// Update view if required // Update view if required
if (needsUpdated) { 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) await saveView(null, view.name, newViewTemplate)
if (!newViewTemplate.meta.schema) { if (!newViewTemplate.meta.schema) {
newViewTemplate.meta.schema = table.schema newViewTemplate.meta.schema = table.schema

View File

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

View File

@ -6,6 +6,7 @@ type ViewTemplateOpts = {
groupBy: string groupBy: string
filters: ViewFilter[] filters: ViewFilter[]
calculation: string calculation: string
groupByMulti: boolean
} }
const TOKEN_MAP: Record<string, string> = { 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 }> = { const FIELD_PROPERTY: Record<string, { type: string }> = {
field: { field: {
type: "string", 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 * 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. * calculation: an optional calculation to be performed over the view data.
*/ */
export default function ({ export default function (
field, { field, tableId, groupBy, filters = [], calculation }: ViewTemplateOpts,
tableId, groupByMulti?: boolean
groupBy, ) {
filters = [],
calculation,
}: ViewTemplateOpts) {
// first filter can't have a conjunction // first filter can't have a conjunction
if (filters && filters.length > 0 && filters[0].conjunction) { if (filters && filters.length > 0 && filters[0].conjunction) {
delete filters[0].conjunction delete filters[0].conjunction
@ -151,9 +155,11 @@ export default function ({
let schema = null, let schema = null,
statFilter = null statFilter = null
let groupBySchema = groupByMulti ? GROUP_PROPERTY_MULTI : GROUP_PROPERTY
if (calculation) { if (calculation) {
schema = { schema = {
...(groupBy ? GROUP_PROPERTY : FIELD_PROPERTY), ...(groupBy ? groupBySchema : FIELD_PROPERTY),
...SCHEMA_MAP[calculation], ...SCHEMA_MAP[calculation],
} }
if ( 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 return inputs
} }

View File

@ -19,6 +19,10 @@ export const definition: AutomationStepSchema = {
schema: { schema: {
inputs: { inputs: {
properties: { properties: {
meta: {
type: "object",
title: "Field settings",
},
row: { row: {
type: "object", type: "object",
customType: "row", 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 // clear any undefined, null or empty string properties so that they aren't updated
for (let propKey of Object.keys(inputs.row)) { 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] 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" const MAX_ISO_DATE = "9999-00-00T00:00:00.000Z"
function likeKey(client: string, key: string): string { function likeKey(client: string, key: string): string {
if (!key.includes(" ")) {
return key
}
let start: string, end: string let start: string, end: string
switch (client) { switch (client) {
case SqlClient.MY_SQL: case SqlClient.MY_SQL:
@ -235,7 +232,9 @@ class InternalBuilder {
} else { } else {
const rawFnc = `${fnc}Raw` const rawFnc = `${fnc}Raw`
// @ts-ignore // @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 ( if (
field.type == "DATETIME" || field.type == "DATETIME" ||
field.type === "DATE" || field.type === "DATE" ||
field.type === "TIMESTAMP" field.type === "TIMESTAMP" ||
field.type === "LONGLONG"
) { ) {
return field.string() return field.string()
} }

View File

@ -352,7 +352,7 @@ describe("SQL query builder", () => {
) )
expect(query).toEqual({ expect(query).toEqual({
bindings: [10, "%20%", "%25%", `%"John"%`, `%"Mary"%`], 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({ expect(query).toEqual({
bindings: [10, "%20%", `%"John"%`], 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({ expect(query).toEqual({
bindings: [10, "%20%", "%25%", `%"John"%`, `%"Mary"%`], 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`, 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 = { const SQL_NUMBER_TYPE_MAP = {
integer: FieldTypes.NUMBER, integer: FieldTypes.NUMBER,
int: FieldTypes.NUMBER, int: FieldTypes.NUMBER,
bigint: FieldTypes.NUMBER,
decimal: FieldTypes.NUMBER, decimal: FieldTypes.NUMBER,
smallint: FieldTypes.NUMBER, smallint: FieldTypes.NUMBER,
real: FieldTypes.NUMBER, real: FieldTypes.NUMBER,
@ -47,6 +46,7 @@ const SQL_STRING_TYPE_MAP = {
blob: FieldTypes.STRING, blob: FieldTypes.STRING,
long: FieldTypes.STRING, long: FieldTypes.STRING,
text: FieldTypes.STRING, text: FieldTypes.STRING,
bigint: FieldTypes.STRING,
} }
const SQL_BOOLEAN_TYPE_MAP = { const SQL_BOOLEAN_TYPE_MAP = {
@ -141,12 +141,18 @@ export function breakRowIdField(_id: string | { _id: string }): any[] {
export function convertSqlType(type: string) { export function convertSqlType(type: string) {
let foundType = FieldTypes.STRING let foundType = FieldTypes.STRING
const lcType = type.toLowerCase() const lcType = type.toLowerCase()
let matchingTypes = []
for (let [external, internal] of Object.entries(SQL_TYPE_MAP)) { for (let [external, internal] of Object.entries(SQL_TYPE_MAP)) {
if (lcType.includes(external)) { if (lcType.includes(external)) {
foundType = internal matchingTypes.push({ external, internal })
break
} }
} }
//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 } const schema: any = { type: foundType }
if (foundType === FieldTypes.DATETIME) { if (foundType === FieldTypes.DATETIME) {
schema.dateOnly = SQL_DATE_ONLY_TYPES.includes(lcType) schema.dateOnly = SQL_DATE_ONLY_TYPES.includes(lcType)

View File

@ -91,7 +91,7 @@ class QueryRunner {
let query let query
// handle SQL injections by interpolating the variables // handle SQL injections by interpolating the variables
if (isSQL(datasourceClone)) { if (isSQL(datasourceClone)) {
query = await interpolateSQL(fieldsClone, enrichedParameters, integration) query = await interpolateSQL(fieldsClone, enrichedContext, integration)
} else { } else {
query = await sdk.queries.enrichContext(fieldsClone, enrichedContext) 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" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.2.12-alpha.71": "@budibase/backend-core@2.3.0":
version "2.2.12-alpha.71" version "2.3.0"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.71.tgz#bd61a9158ed601d7860e6fa9afaf233217944dd3" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.0.tgz#fb40b77db6ae655e527aa43467e5a9145522bb44"
integrity sha512-QDhBsiqTrWFhXZMaotLpt4+StKDGTL/HzTm0kwTgAAkvsA2tkkiCZCjx2fOsdpYBvTfJ0vN+h4Kg6gKDY3I7NA== integrity sha512-Qvt9Tr6em6KPUhnj16jggcGR1l9b2qaOcrzoDfzCbSRcZcHuQ8mBHYvdHqlGyH6rHbJCsDrz8/KqNT8qRc2IyQ==
dependencies: dependencies:
"@budibase/nano" "10.1.1" "@budibase/nano" "10.1.1"
"@budibase/types" "2.2.12-alpha.71" "@budibase/types" "^2.3.0"
"@shopify/jest-koa-mocks" "5.0.1" "@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2" "@techpass/passport-openidconnect" "0.3.2"
aws-cloudfront-sign "2.2.0" aws-cloudfront-sign "2.2.0"
@ -1374,13 +1374,13 @@
qs "^6.11.0" qs "^6.11.0"
tough-cookie "^4.1.2" tough-cookie "^4.1.2"
"@budibase/pro@2.2.12-alpha.71": "@budibase/pro@2.3.0":
version "2.2.12-alpha.71" version "2.3.0"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.71.tgz#c1272a512c0ab5d4252c175d91ebbc69bb1596e0" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.0.tgz#adc43a2132025593b9da2ad9449a82c5196f7216"
integrity sha512-qZP0RLzZ08nvDPwmIxbRgHI/C/jgjMlx8liiOU6/RhL6c9HT1Rxa3ABSpBxmQvLu5OrxUgC8cJY1cJ7v9LD8MQ== integrity sha512-hfjcncN/g6fTxmQHiXXf1oPeo2oB5XUi17xju7LVnQEDwmyrtW3rbrCuvCQpYQII1C3nsuFsakanXLpCouLCGA==
dependencies: dependencies:
"@budibase/backend-core" "2.2.12-alpha.71" "@budibase/backend-core" "2.3.0"
"@budibase/types" "2.2.12-alpha.71" "@budibase/types" "2.3.0"
"@koa/router" "8.0.8" "@koa/router" "8.0.8"
bull "4.10.1" bull "4.10.1"
joi "17.6.0" joi "17.6.0"
@ -1406,10 +1406,10 @@
svelte-apexcharts "^1.0.2" svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0" svelte-flatpickr "^3.1.0"
"@budibase/types@2.2.12-alpha.71": "@budibase/types@2.3.0", "@budibase/types@^2.3.0":
version "2.2.12-alpha.71" version "2.3.0"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.71.tgz#ba8c1e8bb6945a6b9cebc0ae03c61a51b6e9a6f7" resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.0.tgz#ab5ce3451ac0ad3a3cfb5846228e820d4f597b28"
integrity sha512-JdtZ0AxoyTIAnJpnh17Yb0VgaNk6xEHy/Fb6AbSMQ0tAvAFK1xbVdJhvpyxbyFqOpB6ZxlV7ekE3VfOrR0YQRA== integrity sha512-rTLXoCElUOG+EMD/qbrUyGwXk5cwatOFtylyI01pYIyM7HS/Hy0YekLJVcWQKOf2PGTcYah3AdtkYSIYaZDSfA==
"@bull-board/api@3.7.0": "@bull-board/api@3.7.0":
version "3.7.0" version "3.7.0"

View File

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

View File

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

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "2.2.12-alpha.71", "version": "2.3.0",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -36,10 +36,10 @@
"author": "Budibase", "author": "Budibase",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@budibase/backend-core": "2.2.12-alpha.71", "@budibase/backend-core": "^2.3.0",
"@budibase/pro": "2.2.12-alpha.71", "@budibase/pro": "2.3.0",
"@budibase/string-templates": "2.2.12-alpha.71", "@budibase/string-templates": "^2.3.0",
"@budibase/types": "2.2.12-alpha.71", "@budibase/types": "^2.3.0",
"@koa/router": "8.0.8", "@koa/router": "8.0.8",
"@sentry/node": "6.17.7", "@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "0.3.2", "@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" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.2.12-alpha.71": "@budibase/backend-core@2.3.0":
version "2.2.12-alpha.71" version "2.3.0"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.71.tgz#bd61a9158ed601d7860e6fa9afaf233217944dd3" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.0.tgz#fb40b77db6ae655e527aa43467e5a9145522bb44"
integrity sha512-QDhBsiqTrWFhXZMaotLpt4+StKDGTL/HzTm0kwTgAAkvsA2tkkiCZCjx2fOsdpYBvTfJ0vN+h4Kg6gKDY3I7NA== integrity sha512-Qvt9Tr6em6KPUhnj16jggcGR1l9b2qaOcrzoDfzCbSRcZcHuQ8mBHYvdHqlGyH6rHbJCsDrz8/KqNT8qRc2IyQ==
dependencies: dependencies:
"@budibase/nano" "10.1.1" "@budibase/nano" "10.1.1"
"@budibase/types" "2.2.12-alpha.71" "@budibase/types" "^2.3.0"
"@shopify/jest-koa-mocks" "5.0.1" "@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2" "@techpass/passport-openidconnect" "0.3.2"
aws-cloudfront-sign "2.2.0" aws-cloudfront-sign "2.2.0"
@ -521,13 +521,13 @@
qs "^6.11.0" qs "^6.11.0"
tough-cookie "^4.1.2" tough-cookie "^4.1.2"
"@budibase/pro@2.2.12-alpha.71": "@budibase/pro@2.3.0":
version "2.2.12-alpha.71" version "2.3.0"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.71.tgz#c1272a512c0ab5d4252c175d91ebbc69bb1596e0" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.0.tgz#adc43a2132025593b9da2ad9449a82c5196f7216"
integrity sha512-qZP0RLzZ08nvDPwmIxbRgHI/C/jgjMlx8liiOU6/RhL6c9HT1Rxa3ABSpBxmQvLu5OrxUgC8cJY1cJ7v9LD8MQ== integrity sha512-hfjcncN/g6fTxmQHiXXf1oPeo2oB5XUi17xju7LVnQEDwmyrtW3rbrCuvCQpYQII1C3nsuFsakanXLpCouLCGA==
dependencies: dependencies:
"@budibase/backend-core" "2.2.12-alpha.71" "@budibase/backend-core" "2.3.0"
"@budibase/types" "2.2.12-alpha.71" "@budibase/types" "2.3.0"
"@koa/router" "8.0.8" "@koa/router" "8.0.8"
bull "4.10.1" bull "4.10.1"
joi "17.6.0" joi "17.6.0"
@ -535,10 +535,10 @@
lru-cache "^7.14.1" lru-cache "^7.14.1"
node-fetch "^2.6.1" node-fetch "^2.6.1"
"@budibase/types@2.2.12-alpha.71": "@budibase/types@2.3.0", "@budibase/types@^2.3.0":
version "2.2.12-alpha.71" version "2.3.0"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.71.tgz#ba8c1e8bb6945a6b9cebc0ae03c61a51b6e9a6f7" resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.0.tgz#ab5ce3451ac0ad3a3cfb5846228e820d4f597b28"
integrity sha512-JdtZ0AxoyTIAnJpnh17Yb0VgaNk6xEHy/Fb6AbSMQ0tAvAFK1xbVdJhvpyxbyFqOpB6ZxlV7ekE3VfOrR0YQRA== integrity sha512-rTLXoCElUOG+EMD/qbrUyGwXk5cwatOFtylyI01pYIyM7HS/Hy0YekLJVcWQKOf2PGTcYah3AdtkYSIYaZDSfA==
"@cspotcode/source-map-support@^0.8.0": "@cspotcode/source-map-support@^0.8.0":
version "0.8.1" version "0.8.1"