From c2dab0a60bccf6c6abfc1cabbcd0d542df1a0d8a Mon Sep 17 00:00:00 2001 From: Dean Date: Tue, 3 Jan 2023 16:04:11 +0000 Subject: [PATCH 1/4] Refactored the create/edit column UI, fixed auto column validation issues and some other bugs --- .../DataTable/modals/CreateEditColumn.svelte | 312 +++++++++++------- .../data/table/[selectedTable]/index.svelte | 46 +++ 2 files changed, 246 insertions(+), 112 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 960283842d..7266318691 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -12,7 +12,7 @@ Modal, notifications, } from "@budibase/bbui" - import { createEventDispatcher, onMount } from "svelte" + import { createEventDispatcher } from "svelte" import { cloneDeep } from "lodash/fp" import { tables, datasources } from "stores/backend" import { TableNames, UNEDITABLE_USER_FIELDS } from "constants" @@ -48,7 +48,22 @@ const { hide } = getContext(Context.Modal) let fieldDefinitions = cloneDeep(FIELDS) - export let field = { + export let field + + let originalName + let linkEditDisabled + let primaryDisplay + let indexes = [...($tables.selected.indexes || [])] + let isCreating + + let table = $tables.selected + let confirmDeleteDialog + let deletion + let savingColumn + let deleteColName + let jsonSchemaModal + + let editableColumn = { type: "string", constraints: fieldDefinitions.STRING.constraints, @@ -56,48 +71,80 @@ fieldName: $tables.selected.name, } - let originalName = field.name - const linkEditDisabled = originalName != null - let primaryDisplay = - $tables.selected.primaryDisplay == null || - $tables.selected.primaryDisplay === field.name - let isCreating = originalName == null + $: if (primaryDisplay) { + editableColumn.constraints.presence = { allowEmpty: false } + } - let table = $tables.selected - let indexes = [...($tables.selected.indexes || [])] - let confirmDeleteDialog - let deletion - let deleteColName - let jsonSchemaModal + $: if (field && !savingColumn) { + editableColumn = cloneDeep(field) + originalName = editableColumn.name ? editableColumn.name + "" : null + linkEditDisabled = originalName != null + isCreating = originalName == null + primaryDisplay = + $tables.selected.primaryDisplay == null || + $tables.selected.primaryDisplay === editableColumn.name + } - $: checkConstraints(field) - $: required = !!field?.constraints?.presence || primaryDisplay + $: checkConstraints(editableColumn) + $: required = !!editableColumn?.constraints?.presence || primaryDisplay $: uneditable = $tables.selected?._id === TableNames.USERS && - UNEDITABLE_USER_FIELDS.includes(field.name) + UNEDITABLE_USER_FIELDS.includes(editableColumn.name) $: invalid = - !field.name || - (field.type === LINK_TYPE && !field.tableId) || + !editableColumn?.name || + (editableColumn?.type === LINK_TYPE && !editableColumn?.tableId) || Object.keys(errors).length !== 0 - $: errors = checkErrors(field) + $: errors = checkErrors(editableColumn) $: datasource = $datasources.list.find( source => source._id === table?.sourceId ) + const getTableAutoColumnTypes = table => { + return Object.keys(table?.schema).reduce((acc, key) => { + let fieldSchema = table?.schema[key] + if (fieldSchema.autocolumn) { + acc.push(fieldSchema.subtype) + } + return acc + }, []) + } + + let autoColumnInfo = getAutoColumnInformation() + + $: tableAutoColumnsTypes = getTableAutoColumnTypes($tables?.selected) + $: availableAutoColumns = Object.keys(autoColumnInfo).reduce((acc, key) => { + if (!tableAutoColumnsTypes.includes(key)) { + acc[key] = autoColumnInfo[key] + } + return acc + }, {}) + + $: availableAutoColumnKeys = availableAutoColumns + ? Object.keys(availableAutoColumns) + : [] + + $: autoColumnOptions = editableColumn.autocolumn + ? autoColumnInfo + : availableAutoColumns + // used to select what different options can be displayed for column type $: canBeSearched = - field.type !== LINK_TYPE && - field.type !== JSON_TYPE && - field.subtype !== AUTO_COLUMN_SUB_TYPES.CREATED_BY && - field.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY && - field.type !== FORMULA_TYPE + editableColumn?.type !== LINK_TYPE && + editableColumn?.type !== JSON_TYPE && + editableColumn?.subtype !== AUTO_COLUMN_SUB_TYPES.CREATED_BY && + editableColumn?.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY && + editableColumn?.type !== FORMULA_TYPE && + !editableColumn.autocolumn $: 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.draft.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.draft.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.draft, 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 } - } - }) - {:else if field.type === "options"} + {:else if editableColumn.type === "options"} - {:else if field.type === "longform"} + {:else if editableColumn.type === "longform"}
- {:else if field.type === "array"} + {:else if editableColumn.type === "array"} - {:else if field.type === "datetime"} + {:else if editableColumn.type === "datetime" && !editableColumn.autocolumn} + - {#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"}
- +
{/if} - {:else if field.type === "number"} + {:else if editableColumn.type === "number" && !editableColumn.autocolumn} - {:else if field.type === "link"} + {:else if editableColumn.type === "link"} - {:else if field.type === FORMULA_TYPE} + {:else if editableColumn.type === FORMULA_TYPE} {#if !table.sql} (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} {/if} + {#if editableColumn.type === AUTO_TYPE || editableColumn.autocolumn} +