diff --git a/lerna.json b/lerna.json index 369e8c59bc..3171d1bf15 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.7.26-alpha.5", + "version": "2.7.33", "npmClient": "yarn", "packages": [ "packages/*" @@ -19,4 +19,4 @@ "loadEnvFiles": false } } -} \ No newline at end of file +} diff --git a/packages/bbui/src/Markdown/SpectrumMDE.svelte b/packages/bbui/src/Markdown/SpectrumMDE.svelte index 9b0832c91f..8e7b1bbdfd 100644 --- a/packages/bbui/src/Markdown/SpectrumMDE.svelte +++ b/packages/bbui/src/Markdown/SpectrumMDE.svelte @@ -87,7 +87,7 @@ border-color: var(--spectrum-global-color-gray-400); } /* Toolbar button color */ - :global(.EasyMDEContainer .editor-toolbar button i) { + :global(.EasyMDEContainer .editor-toolbar button) { color: var(--spectrum-global-color-gray-800); } /* Separator between toolbar buttons*/ diff --git a/packages/builder/src/components/backend/DataTable/DataTable.svelte b/packages/builder/src/components/backend/DataTable/DataTable.svelte index 1b0c92bde0..981a619ebd 100644 --- a/packages/builder/src/components/backend/DataTable/DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/DataTable.svelte @@ -1,5 +1,5 @@
@@ -37,7 +49,7 @@ allowDeleteRows={!isUsersTable} schemaOverrides={isUsersTable ? userSchemaOverrides : null} showAvatars={false} - on:updatetable={e => tables.replaceTable(id, e.detail)} + on:updatetable={handleGridTableUpdate} > {#if isInternal} diff --git a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte index 1413cd157e..4908512515 100644 --- a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte +++ b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte @@ -59,7 +59,6 @@ $: valid = getErrorCount(errors) === 0 && allRequiredAttributesSet() $: isManyToMany = relationshipType === RelationshipTypes.MANY_TO_MANY $: isManyToOne = relationshipType === RelationshipTypes.MANY_TO_ONE - $: toRelationship.relationshipType = fromRelationship?.relationshipType function getTable(id) { return plusTables.find(table => table._id === id) @@ -180,6 +179,16 @@ return getErrorCount(errors) === 0 } + function otherRelationshipType(type) { + if (type === RelationshipTypes.MANY_TO_ONE) { + return RelationshipTypes.ONE_TO_MANY + } else if (type === RelationshipTypes.ONE_TO_MANY) { + return RelationshipTypes.MANY_TO_ONE + } else if (type === RelationshipTypes.MANY_TO_MANY) { + return RelationshipTypes.MANY_TO_MANY + } + } + function buildRelationships() { const id = Helpers.uuid() //Map temporary variables @@ -200,6 +209,7 @@ ...toRelationship, tableId: fromId, name: fromColumn, + relationshipType: otherRelationshipType(relationshipType), through: throughId, type: "link", _id: id, diff --git a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte index bd1761620d..bfca91afaa 100644 --- a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte +++ b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte @@ -93,6 +93,7 @@ try { await beforeSave() table = await tables.save(newTable) + await datasources.fetch() await afterSave(table) } catch (e) { notifications.error(e) diff --git a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte index 01c62d56f7..11ef60480b 100644 --- a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte +++ b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte @@ -65,6 +65,7 @@ const updatedTable = cloneDeep(table) updatedTable.name = updatedName await tables.save(updatedTable) + await datasources.fetch() notifications.success("Table renamed successfully") } diff --git a/packages/builder/src/components/common/FontAwesomeIcon.svelte b/packages/builder/src/components/common/FontAwesomeIcon.svelte index 364b3af25f..97d33f5790 100644 --- a/packages/builder/src/components/common/FontAwesomeIcon.svelte +++ b/packages/builder/src/components/common/FontAwesomeIcon.svelte @@ -9,6 +9,18 @@ faFileArrowUp, faChevronLeft, faCircleInfo, + faBold, + faItalic, + faHeading, + faQuoteLeft, + faListUl, + faListOl, + faLink, + faImage, + faEye, + faColumns, + faArrowsAlt, + faQuestionCircle, } from "@fortawesome/free-solid-svg-icons" import { faGithub, faDiscord } from "@fortawesome/free-brands-svg-icons" @@ -22,7 +34,22 @@ faEnvelope, faFileArrowUp, faChevronLeft, - faCircleInfo + faCircleInfo, + + // -- Required for easyMDE use in the builder. + faBold, + faItalic, + faHeading, + faQuoteLeft, + faListUl, + faListOl, + faLink, + faImage, + faEye, + faColumns, + faArrowsAlt, + faQuestionCircle + // -- ) dom.watch() diff --git a/packages/builder/src/stores/backend/datasources.js b/packages/builder/src/stores/backend/datasources.js index e774aae8c6..1a19cd5638 100644 --- a/packages/builder/src/stores/backend/datasources.js +++ b/packages/builder/src/stores/backend/datasources.js @@ -117,6 +117,10 @@ export function createDatasourcesStore() { ...state, list: [...state.list, datasource], })) + + // If this is a new datasource then we should refresh the tables list, + // because otherwise we'll never see the new tables + tables.fetch() } // Update existing datasource diff --git a/packages/builder/src/stores/backend/tables.js b/packages/builder/src/stores/backend/tables.js index f8796712a8..d79ed6f072 100644 --- a/packages/builder/src/stores/backend/tables.js +++ b/packages/builder/src/stores/backend/tables.js @@ -1,5 +1,4 @@ import { get, writable, derived } from "svelte/store" -import { datasources } from "./" import { cloneDeep } from "lodash/fp" import { API } from "api" import { SWITCHABLE_TYPES } from "constants/backend" @@ -63,7 +62,6 @@ export function createTablesStore() { const savedTable = await API.saveTable(updatedTable) replaceTable(savedTable._id, savedTable) - await datasources.fetch() select(savedTable._id) return savedTable } diff --git a/packages/client/src/components/app/forms/InnerForm.svelte b/packages/client/src/components/app/forms/InnerForm.svelte index 3dc85ecaf6..f24ab1f681 100644 --- a/packages/client/src/components/app/forms/InnerForm.svelte +++ b/packages/client/src/components/app/forms/InnerForm.svelte @@ -283,7 +283,7 @@ // Skip if the value is the same if (!skipCheck && fieldState.value === value) { - return true + return false } // Update field state @@ -295,7 +295,7 @@ return state }) - return !error + return true } // Clears the value of a certain field back to the default value @@ -376,8 +376,9 @@ deregister, validate: () => { // Validate the field by force setting the same value again - const { fieldState } = get(getField(field)) - return setValue(fieldState.value, true) + const fieldInfo = getField(field) + setValue(get(fieldInfo).fieldState.value, true) + return !get(fieldInfo).fieldState.error }, } } diff --git a/packages/frontend-core/src/components/grid/stores/columns.js b/packages/frontend-core/src/components/grid/stores/columns.js index 024fdc29bc..82a26d923c 100644 --- a/packages/frontend-core/src/components/grid/stores/columns.js +++ b/packages/frontend-core/src/components/grid/stores/columns.js @@ -90,12 +90,12 @@ export const deriveStores = context => { // Update local state table.set(newTable) + // Update server + await API.saveTable(newTable) + // Broadcast change to external state can be updated, as this change // will not be received by the builder websocket because we caused it ourselves dispatch("updatetable", newTable) - - // Update server - await API.saveTable(newTable) } return { diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 198c05025c..2020708fa8 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -2,6 +2,7 @@ import { writable, derived, get } from "svelte/store" import { fetchData } from "../../../fetch/fetchData" import { notifications } from "@budibase/bbui" import { NewRowID, RowPageSize } from "../lib/constants" +import { tick } from "svelte" const initialSortState = { column: null, @@ -124,13 +125,22 @@ export const deriveStores = context => { }) // Subscribe to changes of this fetch model - unsubscribe = newFetch.subscribe($fetch => { + unsubscribe = newFetch.subscribe(async $fetch => { if ($fetch.loaded && !$fetch.loading) { hasNextPage.set($fetch.hasNextPage) const $instanceLoaded = get(instanceLoaded) const resetRows = $fetch.resetKey !== lastResetKey + const previousResetKey = lastResetKey lastResetKey = $fetch.resetKey + // If resetting rows due to a table change, wipe data and wait for + // derived stores to compute. This prevents stale data being passed + // to cells when we save the new schema. + if (!$instanceLoaded && previousResetKey) { + rows.set([]) + await tick() + } + // Reset state properties when dataset changes if (!$instanceLoaded || resetRows) { table.set($fetch.definition) diff --git a/packages/server/src/api/controllers/row/utils.ts b/packages/server/src/api/controllers/row/utils.ts index f1edbf538b..a213f14a08 100644 --- a/packages/server/src/api/controllers/row/utils.ts +++ b/packages/server/src/api/controllers/row/utils.ts @@ -3,10 +3,10 @@ import * as userController from "../user" import { FieldTypes } from "../../../constants" import { context } from "@budibase/backend-core" import { makeExternalQuery } from "../../../integrations/base/query" -import { Row, Table } from "@budibase/types" +import { FieldType, Row, Table, UserCtx } from "@budibase/types" import { Format } from "../view/exporters" -import { UserCtx } from "@budibase/types" import sdk from "../../../sdk" + const validateJs = require("validate.js") const { cloneDeep } = require("lodash/fp") @@ -20,6 +20,13 @@ validateJs.extend(validateJs.validators.datetime, { }, }) +function isForeignKey(key: string, table: Table) { + const relationships = Object.values(table.schema).filter( + column => column.type === FieldType.LINK + ) + return relationships.some(relationship => relationship.foreignKey === key) +} + export async function getDatasourceAndQuery(json: any) { const datasourceId = json.endpoint.datasourceId const datasource = await sdk.datasources.get(datasourceId) @@ -65,6 +72,10 @@ export async function validate({ const column = fetchedTable.schema[fieldName] const constraints = cloneDeep(column.constraints) const type = column.type + // foreign keys are likely to be enriched + if (isForeignKey(fieldName, fetchedTable)) { + continue + } // formulas shouldn't validated, data will be deleted anyway if (type === FieldTypes.FORMULA || column.autocolumn) { continue diff --git a/packages/server/src/api/controllers/table/external.ts b/packages/server/src/api/controllers/table/external.ts index ee789ddd3a..24d4242478 100644 --- a/packages/server/src/api/controllers/table/external.ts +++ b/packages/server/src/api/controllers/table/external.ts @@ -26,6 +26,7 @@ import { RelationshipTypes, } from "@budibase/types" import sdk from "../../../sdk" +import { builderSocket } from "../../../websockets" const { cloneDeep } = require("lodash/fp") async function makeTableRequest( @@ -318,6 +319,11 @@ export async function save(ctx: UserCtx) { datasource.entities[tableToSave.name] = tableToSave await db.put(datasource) + // Since tables are stored inside datasources, we need to notify clients + // that the datasource definition changed + const updatedDatasource = await db.get(datasource._id) + builderSocket?.emitDatasourceUpdate(ctx, updatedDatasource) + return tableToSave } @@ -344,6 +350,11 @@ export async function destroy(ctx: UserCtx) { await db.put(datasource) + // Since tables are stored inside datasources, we need to notify clients + // that the datasource definition changed + const updatedDatasource = await db.get(datasource._id) + builderSocket?.emitDatasourceUpdate(ctx, updatedDatasource) + return tableToDelete } diff --git a/packages/server/src/integrations/mongodb.ts b/packages/server/src/integrations/mongodb.ts index 0a2dfd856c..215a9034af 100644 --- a/packages/server/src/integrations/mongodb.ts +++ b/packages/server/src/integrations/mongodb.ts @@ -385,7 +385,7 @@ class MongoIntegration implements IntegrationBase { createObjectIds(json: any) { const self = this function interpolateObjectIds(json: any) { - for (let field of Object.keys(json)) { + for (let field of Object.keys(json || {})) { if (json[field] instanceof Object) { json[field] = self.createObjectIds(json[field]) } diff --git a/packages/server/src/sdk/app/datasources/datasources.ts b/packages/server/src/sdk/app/datasources/datasources.ts index 176bdb1171..430f90c159 100644 --- a/packages/server/src/sdk/app/datasources/datasources.ts +++ b/packages/server/src/sdk/app/datasources/datasources.ts @@ -36,11 +36,14 @@ export function checkDatasourceTypes(schema: Integration, config: any) { async function enrichDatasourceWithValues(datasource: Datasource) { const cloned = cloneDeep(datasource) const env = await getEnvironmentVariables() + //Do not process entities, as we do not want to process formulas + const { entities, ...clonedWithoutEntities } = cloned const processed = processObjectSync( - cloned, + clonedWithoutEntities, { env }, { onlyFound: true } ) as Datasource + processed.entities = entities const definition = await getDefinition(processed.source) processed.config = checkDatasourceTypes(definition!, processed.config) return {