Merge branch 'develop' into grid-all-datasources
This commit is contained in:
commit
e5eb3c98f2
|
@ -26,13 +26,6 @@ jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Maximize build space
|
|
||||||
uses: easimon/maximize-build-space@master
|
|
||||||
with:
|
|
||||||
root-reserve-mb: 35000
|
|
||||||
swap-size-mb: 1024
|
|
||||||
remove-android: "true"
|
|
||||||
remove-dotnet: "true"
|
|
||||||
- name: Checkout repo and submodules
|
- name: Checkout repo and submodules
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.11.15-alpha.2",
|
"version": "2.11.20-alpha.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -948,12 +948,15 @@ export const buildFormSchema = (component, asset) => {
|
||||||
|
|
||||||
if (component._component.endsWith("formblock")) {
|
if (component._component.endsWith("formblock")) {
|
||||||
let schema = {}
|
let schema = {}
|
||||||
|
|
||||||
const datasource = getDatasourceForProvider(asset, component)
|
const datasource = getDatasourceForProvider(asset, component)
|
||||||
const info = getSchemaForDatasource(component, datasource)
|
const info = getSchemaForDatasource(component, datasource)
|
||||||
|
|
||||||
|
if (!info?.schema) {
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
if (!component.fields) {
|
if (!component.fields) {
|
||||||
Object.values(info?.schema)
|
Object.values(info.schema)
|
||||||
.filter(
|
.filter(
|
||||||
({ autocolumn, name }) =>
|
({ autocolumn, name }) =>
|
||||||
!autocolumn && !["_rev", "_id"].includes(name)
|
!autocolumn && !["_rev", "_id"].includes(name)
|
||||||
|
|
|
@ -64,6 +64,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
componentValidation: false,
|
componentValidation: false,
|
||||||
|
disableUserMetadata: false,
|
||||||
},
|
},
|
||||||
errors: [],
|
errors: [],
|
||||||
hasAppPackage: false,
|
hasAppPackage: false,
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import { TableNames } from "constants"
|
import { TableNames } from "constants"
|
||||||
import { Grid } from "@budibase/frontend-core"
|
import { Grid } from "@budibase/frontend-core"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
import { store } from "builderStore"
|
||||||
import GridAddColumnModal from "components/backend/DataTable/modals/grid/GridCreateColumnModal.svelte"
|
import GridAddColumnModal from "components/backend/DataTable/modals/grid/GridCreateColumnModal.svelte"
|
||||||
import GridCreateEditRowModal from "components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte"
|
import GridCreateEditRowModal from "components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte"
|
||||||
import GridEditUserModal from "components/backend/DataTable/modals/grid/GridEditUserModal.svelte"
|
import GridEditUserModal from "components/backend/DataTable/modals/grid/GridEditUserModal.svelte"
|
||||||
|
@ -17,11 +18,11 @@
|
||||||
import GridUsersTableButton from "components/backend/DataTable/modals/grid/GridUsersTableButton.svelte"
|
import GridUsersTableButton from "components/backend/DataTable/modals/grid/GridUsersTableButton.svelte"
|
||||||
|
|
||||||
const userSchemaOverrides = {
|
const userSchemaOverrides = {
|
||||||
firstName: { displayName: "First name" },
|
firstName: { displayName: "First name", disabled: true },
|
||||||
lastName: { displayName: "Last name" },
|
lastName: { displayName: "Last name", disabled: true },
|
||||||
email: { displayName: "Email" },
|
email: { displayName: "Email", disabled: true },
|
||||||
roleId: { displayName: "Role" },
|
roleId: { displayName: "Role", disabled: true },
|
||||||
status: { displayName: "Status" },
|
status: { displayName: "Status", disabled: true },
|
||||||
}
|
}
|
||||||
|
|
||||||
$: id = $tables.selected?._id
|
$: id = $tables.selected?._id
|
||||||
|
@ -60,14 +61,14 @@
|
||||||
datasource={gridDatasource}
|
datasource={gridDatasource}
|
||||||
canAddRows={!isUsersTable}
|
canAddRows={!isUsersTable}
|
||||||
canDeleteRows={!isUsersTable}
|
canDeleteRows={!isUsersTable}
|
||||||
canEditRows={!isUsersTable}
|
canEditRows={!isUsersTable || !$store.features.disableUserMetadata}
|
||||||
canEditColumns={!isUsersTable}
|
canEditColumns={!isUsersTable || !$store.features.disableUserMetadata}
|
||||||
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
|
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
|
||||||
showAvatars={false}
|
showAvatars={false}
|
||||||
on:updatedatasource={handleGridTableUpdate}
|
on:updatedatasource={handleGridTableUpdate}
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="filter">
|
<svelte:fragment slot="filter">
|
||||||
{#if isUsersTable}
|
{#if isUsersTable && $store.features.disableUserMetadata}
|
||||||
<GridUsersTableButton />
|
<GridUsersTableButton />
|
||||||
{/if}
|
{/if}
|
||||||
<GridFilterButton />
|
<GridFilterButton />
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import { RelationshipErrorChecker } from "./relationshipErrors"
|
import { RelationshipErrorChecker } from "./relationshipErrors"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
import RelationshipSelector from "components/common/RelationshipSelector.svelte"
|
||||||
|
import { PrettyRelationshipDefinitions } from "constants/backend"
|
||||||
|
|
||||||
export let save
|
export let save
|
||||||
export let datasource
|
export let datasource
|
||||||
|
@ -22,16 +24,21 @@
|
||||||
export let selectedFromTable
|
export let selectedFromTable
|
||||||
export let close
|
export let close
|
||||||
|
|
||||||
const relationshipTypes = [
|
let relationshipMap = {
|
||||||
{
|
[RelationshipType.MANY_TO_MANY]: {
|
||||||
label: "One to Many",
|
part1: PrettyRelationshipDefinitions.MANY,
|
||||||
value: RelationshipType.MANY_TO_ONE,
|
part2: PrettyRelationshipDefinitions.MANY,
|
||||||
},
|
},
|
||||||
{
|
[RelationshipType.MANY_TO_ONE]: {
|
||||||
label: "Many to Many",
|
part1: PrettyRelationshipDefinitions.ONE,
|
||||||
value: RelationshipType.MANY_TO_MANY,
|
part2: PrettyRelationshipDefinitions.MANY,
|
||||||
},
|
},
|
||||||
]
|
}
|
||||||
|
let relationshipOpts1 = Object.values(PrettyRelationshipDefinitions)
|
||||||
|
let relationshipOpts2 = Object.values(PrettyRelationshipDefinitions)
|
||||||
|
|
||||||
|
let relationshipPart1 = PrettyRelationshipDefinitions.MANY
|
||||||
|
let relationshipPart2 = PrettyRelationshipDefinitions.ONE
|
||||||
|
|
||||||
let originalFromColumnName = toRelationship.name,
|
let originalFromColumnName = toRelationship.name,
|
||||||
originalToColumnName = fromRelationship.name
|
originalToColumnName = fromRelationship.name
|
||||||
|
@ -49,14 +56,32 @@
|
||||||
)
|
)
|
||||||
let errors = {}
|
let errors = {}
|
||||||
let fromPrimary, fromForeign, fromColumn, toColumn
|
let fromPrimary, fromForeign, fromColumn, toColumn
|
||||||
let fromId, toId, throughId, throughToKey, throughFromKey
|
|
||||||
|
let throughId, throughToKey, throughFromKey
|
||||||
let isManyToMany, isManyToOne, relationshipType
|
let isManyToMany, isManyToOne, relationshipType
|
||||||
let hasValidated = false
|
let hasValidated = false
|
||||||
|
|
||||||
|
$: fromId = null
|
||||||
|
$: toId = null
|
||||||
|
|
||||||
$: tableOptions = plusTables.map(table => ({
|
$: tableOptions = plusTables.map(table => ({
|
||||||
label: table.name,
|
label: table.name,
|
||||||
value: table._id,
|
value: table._id,
|
||||||
|
name: table.name,
|
||||||
|
_id: table._id,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
$: {
|
||||||
|
// Determine the relationship type based on the selected values of both parts
|
||||||
|
relationshipType = Object.entries(relationshipMap).find(
|
||||||
|
([_, parts]) =>
|
||||||
|
parts.part1 === relationshipPart1 && parts.part2 === relationshipPart2
|
||||||
|
)?.[0]
|
||||||
|
|
||||||
|
changed(() => {
|
||||||
|
hasValidated = false
|
||||||
|
})
|
||||||
|
}
|
||||||
$: valid =
|
$: valid =
|
||||||
getErrorCount(errors) === 0 && allRequiredAttributesSet(relationshipType)
|
getErrorCount(errors) === 0 && allRequiredAttributesSet(relationshipType)
|
||||||
$: isManyToMany = relationshipType === RelationshipType.MANY_TO_MANY
|
$: isManyToMany = relationshipType === RelationshipType.MANY_TO_MANY
|
||||||
|
@ -338,33 +363,34 @@
|
||||||
onConfirm={saveRelationship}
|
onConfirm={saveRelationship}
|
||||||
disabled={!valid}
|
disabled={!valid}
|
||||||
>
|
>
|
||||||
<Select
|
|
||||||
label="Relationship type"
|
|
||||||
options={relationshipTypes}
|
|
||||||
bind:value={relationshipType}
|
|
||||||
bind:error={errors.relationshipType}
|
|
||||||
on:change={() =>
|
|
||||||
changed(() => {
|
|
||||||
hasValidated = false
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<div class="headings">
|
<div class="headings">
|
||||||
<Detail>Tables</Detail>
|
<Detail>Tables</Detail>
|
||||||
</div>
|
</div>
|
||||||
{#if !selectedFromTable}
|
|
||||||
<Select
|
<RelationshipSelector
|
||||||
label="Select from table"
|
bind:relationshipPart1
|
||||||
options={tableOptions}
|
bind:relationshipPart2
|
||||||
bind:value={fromId}
|
bind:relationshipTableIdPrimary={fromId}
|
||||||
bind:error={errors.fromTable}
|
bind:relationshipTableIdSecondary={toId}
|
||||||
on:change={e =>
|
{relationshipOpts1}
|
||||||
changed(() => {
|
{relationshipOpts2}
|
||||||
const table = plusTables.find(tbl => tbl._id === e.detail)
|
{tableOptions}
|
||||||
fromColumn = table?.name || ""
|
{errors}
|
||||||
fromPrimary = table?.primary?.[0]
|
primaryDisabled={selectedFromTable}
|
||||||
})}
|
primaryTableChanged={e =>
|
||||||
/>
|
changed(() => {
|
||||||
{/if}
|
const table = plusTables.find(tbl => tbl._id === e.detail)
|
||||||
|
fromColumn = table?.name || ""
|
||||||
|
fromPrimary = table?.primary?.[0]
|
||||||
|
})}
|
||||||
|
secondaryTableChanged={e =>
|
||||||
|
changed(() => {
|
||||||
|
const table = plusTables.find(tbl => tbl._id === e.detail)
|
||||||
|
toColumn = table.name || ""
|
||||||
|
fromForeign = null
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
|
||||||
{#if isManyToOne && fromId}
|
{#if isManyToOne && fromId}
|
||||||
<Select
|
<Select
|
||||||
label={`Primary Key (${getTable(fromId).name})`}
|
label={`Primary Key (${getTable(fromId).name})`}
|
||||||
|
@ -374,18 +400,6 @@
|
||||||
on:change={changed}
|
on:change={changed}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<Select
|
|
||||||
label={"Select to table"}
|
|
||||||
options={tableOptions}
|
|
||||||
bind:value={toId}
|
|
||||||
bind:error={errors.toTable}
|
|
||||||
on:change={e =>
|
|
||||||
changed(() => {
|
|
||||||
const table = plusTables.find(tbl => tbl._id === e.detail)
|
|
||||||
toColumn = table.name || ""
|
|
||||||
fromForeign = null
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
{#if isManyToMany}
|
{#if isManyToMany}
|
||||||
<Select
|
<Select
|
||||||
label={"Through"}
|
label={"Through"}
|
||||||
|
|
|
@ -6,11 +6,14 @@
|
||||||
export let relationshipTableIdPrimary
|
export let relationshipTableIdPrimary
|
||||||
export let relationshipTableIdSecondary
|
export let relationshipTableIdSecondary
|
||||||
export let editableColumn
|
export let editableColumn
|
||||||
export let linkEditDisabled
|
export let linkEditDisabled = false
|
||||||
export let tableOptions
|
export let tableOptions
|
||||||
export let errors
|
export let errors
|
||||||
export let relationshipOpts1
|
export let relationshipOpts1
|
||||||
export let relationshipOpts2
|
export let relationshipOpts2
|
||||||
|
export let primaryTableChanged
|
||||||
|
export let secondaryTableChanged
|
||||||
|
export let primaryDisabled = true
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relationship-container">
|
<div class="relationship-container">
|
||||||
|
@ -19,16 +22,19 @@
|
||||||
disabled={linkEditDisabled}
|
disabled={linkEditDisabled}
|
||||||
bind:value={relationshipPart1}
|
bind:value={relationshipPart1}
|
||||||
options={relationshipOpts1}
|
options={relationshipOpts1}
|
||||||
|
bind:error={errors.relationshipType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="relationship-label">in</div>
|
<div class="relationship-label">in</div>
|
||||||
<div class="relationship-part">
|
<div class="relationship-part">
|
||||||
<Select
|
<Select
|
||||||
disabled
|
disabled={primaryDisabled}
|
||||||
options={tableOptions}
|
options={tableOptions}
|
||||||
getOptionLabel={table => table.name}
|
getOptionLabel={table => table.name}
|
||||||
getOptionValue={table => table._id}
|
getOptionValue={table => table._id}
|
||||||
bind:value={relationshipTableIdPrimary}
|
bind:value={relationshipTableIdPrimary}
|
||||||
|
on:change={primaryTableChanged}
|
||||||
|
bind:error={errors.fromTable}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,20 +52,24 @@
|
||||||
<Select
|
<Select
|
||||||
disabled={linkEditDisabled}
|
disabled={linkEditDisabled}
|
||||||
bind:value={relationshipTableIdSecondary}
|
bind:value={relationshipTableIdSecondary}
|
||||||
|
bind:error={errors.toTable}
|
||||||
options={tableOptions.filter(
|
options={tableOptions.filter(
|
||||||
table => table._id !== relationshipTableIdPrimary
|
table => table._id !== relationshipTableIdPrimary
|
||||||
)}
|
)}
|
||||||
getOptionLabel={table => table.name}
|
getOptionLabel={table => table.name}
|
||||||
getOptionValue={table => table._id}
|
getOptionValue={table => table._id}
|
||||||
|
on:change={secondaryTableChanged}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
{#if editableColumn}
|
||||||
disabled={linkEditDisabled}
|
<Input
|
||||||
label={`Column name in other table`}
|
disabled={linkEditDisabled}
|
||||||
bind:value={editableColumn.fieldName}
|
label={`Column name in other table`}
|
||||||
error={errors.relatedName}
|
bind:value={editableColumn.fieldName}
|
||||||
/>
|
error={errors.relatedName}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.relationship-container {
|
.relationship-container {
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
$: resourceId = datasource.resourceId || datasource.tableId
|
$: resourceId = datasource?.resourceId || datasource?.tableId
|
||||||
|
|
||||||
$: if (!isEqual(value, cachedValue)) {
|
$: if (!isEqual(value, cachedValue)) {
|
||||||
cachedValue = cloneDeep(value)
|
cachedValue = cloneDeep(value)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 30385682141e5ba9d98de7d71d5be1672109cd15
|
Subproject commit 044bec6447066b215932d6726c437e7ec5a9e42e
|
|
@ -289,6 +289,7 @@ async function performAppCreate(ctx: UserCtx) {
|
||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
componentValidation: true,
|
componentValidation: true,
|
||||||
|
disableUserMetadata: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,10 +311,13 @@ async function performAppCreate(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Keep existing validation setting
|
// Keep existing feature flags
|
||||||
if (!existing.features?.componentValidation) {
|
if (!existing.features?.componentValidation) {
|
||||||
newApplication.features!.componentValidation = false
|
newApplication.features!.componentValidation = false
|
||||||
}
|
}
|
||||||
|
if (!existing.features?.disableUserMetadata) {
|
||||||
|
newApplication.features!.disableUserMetadata = false
|
||||||
|
}
|
||||||
|
|
||||||
// Migrate navigation settings and screens if required
|
// Migrate navigation settings and screens if required
|
||||||
if (existing) {
|
if (existing) {
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { cache } from "@budibase/backend-core"
|
import { cache, db as dbCore } from "@budibase/backend-core"
|
||||||
import { utils } from "@budibase/shared-core"
|
import { utils } from "@budibase/shared-core"
|
||||||
import { FieldSubtype } from "@budibase/types"
|
import { FieldSubtype, DocumentType, SEPARATOR } from "@budibase/types"
|
||||||
import { InvalidBBRefError } from "./errors"
|
import { InvalidBBRefError } from "./errors"
|
||||||
|
|
||||||
|
const ROW_PREFIX = DocumentType.ROW + SEPARATOR
|
||||||
|
|
||||||
export async function processInputBBReferences(
|
export async function processInputBBReferences(
|
||||||
value: string | string[] | { _id: string } | { _id: string }[],
|
value: string | string[] | { _id: string } | { _id: string }[],
|
||||||
subtype: FieldSubtype
|
subtype: FieldSubtype
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
const referenceIds: string[] = []
|
let referenceIds: string[] = []
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
referenceIds.push(
|
referenceIds.push(
|
||||||
|
@ -26,6 +28,17 @@ export async function processInputBBReferences(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure all reference IDs are correct global user IDs
|
||||||
|
// they may be user metadata references (start with row prefix)
|
||||||
|
// and these need to be converted to global IDs
|
||||||
|
referenceIds = referenceIds.map(id => {
|
||||||
|
if (id?.startsWith(ROW_PREFIX)) {
|
||||||
|
return dbCore.getGlobalIDFromUserMetadataID(id)
|
||||||
|
} else {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
switch (subtype) {
|
switch (subtype) {
|
||||||
case FieldSubtype.USER:
|
case FieldSubtype.USER:
|
||||||
const { notFoundIds } = await cache.user.getUsers(referenceIds)
|
const { notFoundIds } = await cache.user.getUsers(referenceIds)
|
||||||
|
|
|
@ -154,6 +154,15 @@ describe("bbReferenceProcessor", () => {
|
||||||
|
|
||||||
expect(result).toEqual(null)
|
expect(result).toEqual(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should convert user medata IDs to global IDs", async () => {
|
||||||
|
const userId = _.sample(users)!._id!
|
||||||
|
const userMetadataId = backendCore.db.generateUserMetadataID(userId)
|
||||||
|
const result = await config.doInTenant(() =>
|
||||||
|
processInputBBReferences(userMetadataId, FieldSubtype.USER)
|
||||||
|
)
|
||||||
|
expect(result).toBe(userId)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -66,4 +66,5 @@ export interface AppIcon {
|
||||||
|
|
||||||
export interface AppFeatures {
|
export interface AppFeatures {
|
||||||
componentValidation?: boolean
|
componentValidation?: boolean
|
||||||
|
disableUserMetadata?: boolean
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue