Merge branch 'master' of github.com:Budibase/budibase into develop

This commit is contained in:
mike12345567 2023-06-23 15:19:49 +01:00
commit 3d3efadb37
16 changed files with 110 additions and 21 deletions

View File

@ -1,5 +1,5 @@
{ {
"version": "2.7.26-alpha.5", "version": "2.7.33",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -87,7 +87,7 @@
border-color: var(--spectrum-global-color-gray-400); border-color: var(--spectrum-global-color-gray-400);
} }
/* Toolbar button color */ /* Toolbar button color */
:global(.EasyMDEContainer .editor-toolbar button i) { :global(.EasyMDEContainer .editor-toolbar button) {
color: var(--spectrum-global-color-gray-800); color: var(--spectrum-global-color-gray-800);
} }
/* Separator between toolbar buttons*/ /* Separator between toolbar buttons*/

View File

@ -1,5 +1,5 @@
<script> <script>
import { tables } from "stores/backend" import { datasources, tables } from "stores/backend"
import EditRolesButton from "./buttons/EditRolesButton.svelte" import EditRolesButton from "./buttons/EditRolesButton.svelte"
import { TableNames } from "constants" import { TableNames } from "constants"
import { Grid } from "@budibase/frontend-core" import { Grid } from "@budibase/frontend-core"
@ -26,6 +26,18 @@
$: id = $tables.selected?._id $: id = $tables.selected?._id
$: isUsersTable = id === TableNames.USERS $: isUsersTable = id === TableNames.USERS
$: isInternal = $tables.selected?.type !== "external" $: isInternal = $tables.selected?.type !== "external"
const handleGridTableUpdate = async e => {
tables.replaceTable(id, e.detail)
// We need to refresh datasources when an external table changes.
// Type "external" may exist - sometimes type is "table" and sometimes it
// is "external" - it has different meanings in different endpoints.
// If we check both these then we hopefully catch all external tables.
if (e.detail?.type === "external" || e.detail?.sql) {
await datasources.fetch()
}
}
</script> </script>
<div class="wrapper"> <div class="wrapper">
@ -37,7 +49,7 @@
allowDeleteRows={!isUsersTable} allowDeleteRows={!isUsersTable}
schemaOverrides={isUsersTable ? userSchemaOverrides : null} schemaOverrides={isUsersTable ? userSchemaOverrides : null}
showAvatars={false} showAvatars={false}
on:updatetable={e => tables.replaceTable(id, e.detail)} on:updatetable={handleGridTableUpdate}
> >
<svelte:fragment slot="controls"> <svelte:fragment slot="controls">
{#if isInternal} {#if isInternal}

View File

@ -59,7 +59,6 @@
$: valid = getErrorCount(errors) === 0 && allRequiredAttributesSet() $: valid = getErrorCount(errors) === 0 && allRequiredAttributesSet()
$: isManyToMany = relationshipType === RelationshipTypes.MANY_TO_MANY $: isManyToMany = relationshipType === RelationshipTypes.MANY_TO_MANY
$: isManyToOne = relationshipType === RelationshipTypes.MANY_TO_ONE $: isManyToOne = relationshipType === RelationshipTypes.MANY_TO_ONE
$: toRelationship.relationshipType = fromRelationship?.relationshipType
function getTable(id) { function getTable(id) {
return plusTables.find(table => table._id === id) return plusTables.find(table => table._id === id)
@ -180,6 +179,16 @@
return getErrorCount(errors) === 0 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() { function buildRelationships() {
const id = Helpers.uuid() const id = Helpers.uuid()
//Map temporary variables //Map temporary variables
@ -200,6 +209,7 @@
...toRelationship, ...toRelationship,
tableId: fromId, tableId: fromId,
name: fromColumn, name: fromColumn,
relationshipType: otherRelationshipType(relationshipType),
through: throughId, through: throughId,
type: "link", type: "link",
_id: id, _id: id,

View File

@ -93,6 +93,7 @@
try { try {
await beforeSave() await beforeSave()
table = await tables.save(newTable) table = await tables.save(newTable)
await datasources.fetch()
await afterSave(table) await afterSave(table)
} catch (e) { } catch (e) {
notifications.error(e) notifications.error(e)

View File

@ -65,6 +65,7 @@
const updatedTable = cloneDeep(table) const updatedTable = cloneDeep(table)
updatedTable.name = updatedName updatedTable.name = updatedName
await tables.save(updatedTable) await tables.save(updatedTable)
await datasources.fetch()
notifications.success("Table renamed successfully") notifications.success("Table renamed successfully")
} }

View File

@ -9,6 +9,18 @@
faFileArrowUp, faFileArrowUp,
faChevronLeft, faChevronLeft,
faCircleInfo, faCircleInfo,
faBold,
faItalic,
faHeading,
faQuoteLeft,
faListUl,
faListOl,
faLink,
faImage,
faEye,
faColumns,
faArrowsAlt,
faQuestionCircle,
} from "@fortawesome/free-solid-svg-icons" } from "@fortawesome/free-solid-svg-icons"
import { faGithub, faDiscord } from "@fortawesome/free-brands-svg-icons" import { faGithub, faDiscord } from "@fortawesome/free-brands-svg-icons"
@ -22,7 +34,22 @@
faEnvelope, faEnvelope,
faFileArrowUp, faFileArrowUp,
faChevronLeft, faChevronLeft,
faCircleInfo faCircleInfo,
// -- Required for easyMDE use in the builder.
faBold,
faItalic,
faHeading,
faQuoteLeft,
faListUl,
faListOl,
faLink,
faImage,
faEye,
faColumns,
faArrowsAlt,
faQuestionCircle
// --
) )
dom.watch() dom.watch()
</script> </script>

View File

@ -117,6 +117,10 @@ export function createDatasourcesStore() {
...state, ...state,
list: [...state.list, datasource], 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 // Update existing datasource

View File

@ -1,5 +1,4 @@
import { get, writable, derived } from "svelte/store" import { get, writable, derived } from "svelte/store"
import { datasources } from "./"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { API } from "api" import { API } from "api"
import { SWITCHABLE_TYPES } from "constants/backend" import { SWITCHABLE_TYPES } from "constants/backend"
@ -63,7 +62,6 @@ export function createTablesStore() {
const savedTable = await API.saveTable(updatedTable) const savedTable = await API.saveTable(updatedTable)
replaceTable(savedTable._id, savedTable) replaceTable(savedTable._id, savedTable)
await datasources.fetch()
select(savedTable._id) select(savedTable._id)
return savedTable return savedTable
} }

View File

@ -283,7 +283,7 @@
// Skip if the value is the same // Skip if the value is the same
if (!skipCheck && fieldState.value === value) { if (!skipCheck && fieldState.value === value) {
return true return false
} }
// Update field state // Update field state
@ -295,7 +295,7 @@
return state return state
}) })
return !error return true
} }
// Clears the value of a certain field back to the default value // Clears the value of a certain field back to the default value
@ -376,8 +376,9 @@
deregister, deregister,
validate: () => { validate: () => {
// Validate the field by force setting the same value again // Validate the field by force setting the same value again
const { fieldState } = get(getField(field)) const fieldInfo = getField(field)
return setValue(fieldState.value, true) setValue(get(fieldInfo).fieldState.value, true)
return !get(fieldInfo).fieldState.error
}, },
} }
} }

View File

@ -90,12 +90,12 @@ export const deriveStores = context => {
// Update local state // Update local state
table.set(newTable) table.set(newTable)
// Update server
await API.saveTable(newTable)
// Broadcast change to external state can be updated, as this change // Broadcast change to external state can be updated, as this change
// will not be received by the builder websocket because we caused it ourselves // will not be received by the builder websocket because we caused it ourselves
dispatch("updatetable", newTable) dispatch("updatetable", newTable)
// Update server
await API.saveTable(newTable)
} }
return { return {

View File

@ -2,6 +2,7 @@ import { writable, derived, get } from "svelte/store"
import { fetchData } from "../../../fetch/fetchData" import { fetchData } from "../../../fetch/fetchData"
import { notifications } from "@budibase/bbui" import { notifications } from "@budibase/bbui"
import { NewRowID, RowPageSize } from "../lib/constants" import { NewRowID, RowPageSize } from "../lib/constants"
import { tick } from "svelte"
const initialSortState = { const initialSortState = {
column: null, column: null,
@ -124,13 +125,22 @@ export const deriveStores = context => {
}) })
// Subscribe to changes of this fetch model // Subscribe to changes of this fetch model
unsubscribe = newFetch.subscribe($fetch => { unsubscribe = newFetch.subscribe(async $fetch => {
if ($fetch.loaded && !$fetch.loading) { if ($fetch.loaded && !$fetch.loading) {
hasNextPage.set($fetch.hasNextPage) hasNextPage.set($fetch.hasNextPage)
const $instanceLoaded = get(instanceLoaded) const $instanceLoaded = get(instanceLoaded)
const resetRows = $fetch.resetKey !== lastResetKey const resetRows = $fetch.resetKey !== lastResetKey
const previousResetKey = lastResetKey
lastResetKey = $fetch.resetKey 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 // Reset state properties when dataset changes
if (!$instanceLoaded || resetRows) { if (!$instanceLoaded || resetRows) {
table.set($fetch.definition) table.set($fetch.definition)

View File

@ -3,10 +3,10 @@ import * as userController from "../user"
import { FieldTypes } from "../../../constants" import { FieldTypes } from "../../../constants"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import { makeExternalQuery } from "../../../integrations/base/query" 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 { Format } from "../view/exporters"
import { UserCtx } from "@budibase/types"
import sdk from "../../../sdk" import sdk from "../../../sdk"
const validateJs = require("validate.js") const validateJs = require("validate.js")
const { cloneDeep } = require("lodash/fp") 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) { export async function getDatasourceAndQuery(json: any) {
const datasourceId = json.endpoint.datasourceId const datasourceId = json.endpoint.datasourceId
const datasource = await sdk.datasources.get(datasourceId) const datasource = await sdk.datasources.get(datasourceId)
@ -65,6 +72,10 @@ export async function validate({
const column = fetchedTable.schema[fieldName] const column = fetchedTable.schema[fieldName]
const constraints = cloneDeep(column.constraints) const constraints = cloneDeep(column.constraints)
const type = column.type 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 // formulas shouldn't validated, data will be deleted anyway
if (type === FieldTypes.FORMULA || column.autocolumn) { if (type === FieldTypes.FORMULA || column.autocolumn) {
continue continue

View File

@ -26,6 +26,7 @@ import {
RelationshipTypes, RelationshipTypes,
} from "@budibase/types" } from "@budibase/types"
import sdk from "../../../sdk" import sdk from "../../../sdk"
import { builderSocket } from "../../../websockets"
const { cloneDeep } = require("lodash/fp") const { cloneDeep } = require("lodash/fp")
async function makeTableRequest( async function makeTableRequest(
@ -318,6 +319,11 @@ export async function save(ctx: UserCtx) {
datasource.entities[tableToSave.name] = tableToSave datasource.entities[tableToSave.name] = tableToSave
await db.put(datasource) 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 return tableToSave
} }
@ -344,6 +350,11 @@ export async function destroy(ctx: UserCtx) {
await db.put(datasource) 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 return tableToDelete
} }

View File

@ -385,7 +385,7 @@ class MongoIntegration implements IntegrationBase {
createObjectIds(json: any) { createObjectIds(json: any) {
const self = this const self = this
function interpolateObjectIds(json: any) { function interpolateObjectIds(json: any) {
for (let field of Object.keys(json)) { for (let field of Object.keys(json || {})) {
if (json[field] instanceof Object) { if (json[field] instanceof Object) {
json[field] = self.createObjectIds(json[field]) json[field] = self.createObjectIds(json[field])
} }

View File

@ -36,11 +36,14 @@ export function checkDatasourceTypes(schema: Integration, config: any) {
async function enrichDatasourceWithValues(datasource: Datasource) { async function enrichDatasourceWithValues(datasource: Datasource) {
const cloned = cloneDeep(datasource) const cloned = cloneDeep(datasource)
const env = await getEnvironmentVariables() const env = await getEnvironmentVariables()
//Do not process entities, as we do not want to process formulas
const { entities, ...clonedWithoutEntities } = cloned
const processed = processObjectSync( const processed = processObjectSync(
cloned, clonedWithoutEntities,
{ env }, { env },
{ onlyFound: true } { onlyFound: true }
) as Datasource ) as Datasource
processed.entities = entities
const definition = await getDefinition(processed.source) const definition = await getDefinition(processed.source)
processed.config = checkDatasourceTypes(definition!, processed.config) processed.config = checkDatasourceTypes(definition!, processed.config)
return { return {