Merge branch 'develop' of github.com:Budibase/budibase into feature/BUDI-7052
This commit is contained in:
commit
87d80e08f5
|
@ -40,6 +40,24 @@ spec:
|
||||||
- image: budibase/proxy:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
- image: budibase/proxy:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
name: proxy-service
|
name: proxy-service
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: {{ .Values.services.proxy.port }}
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 2
|
||||||
|
timeoutSeconds: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: {{ .Values.services.proxy.port }}
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 2
|
||||||
|
timeoutSeconds: 3
|
||||||
ports:
|
ports:
|
||||||
- containerPort: {{ .Values.services.proxy.port }}
|
- containerPort: {{ .Values.services.proxy.port }}
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -231,18 +231,33 @@ An overview of the CI pipelines can be found [here](../.github/workflows/README.
|
||||||
|
|
||||||
### Pro
|
### Pro
|
||||||
|
|
||||||
@budibase/pro is the closed source package that supports licensed features in budibase. By default the package will be pulled from NPM and will not normally need to be touched in local development. If you require to update code inside the pro package it can be cloned to the same root level as budibase, e.g.
|
@budibase/pro is the closed source package that supports licensed features in budibase. By default the package will be pulled from NPM and will not normally need to be touched in local development. If you need to make an update to pro and have access to the repo, then you can update your submodule within the mono-repo by running `git submodule update --init` - from here you can use normal submodule flow to develop a change within pro.
|
||||||
|
|
||||||
|
Once you have updated to use the pro submodule, it will be linked into all of your local dependencies by NX as with all other monorepo packages. If you have been using the NPM version of `@budibase/pro` then you may need to run a `git reset --hard` to fix all of the pro versions back to `0.0.0` to be monorepo aware.
|
||||||
|
|
||||||
|
From here - to develop a change in pro, you can follow the below flow:
|
||||||
|
|
||||||
```
|
```
|
||||||
.
|
# enter the pro submodule
|
||||||
|_ budibase
|
cd packages/pro
|
||||||
|_ budibase-pro
|
# get the base branch you are working from (same as monorepo)
|
||||||
|
git fetch
|
||||||
|
git checkout <develop | master>
|
||||||
|
# create a branch, named the same as the branch in your monorepo
|
||||||
|
git checkout -b <some branch>
|
||||||
|
... make changes
|
||||||
|
# commit the changes you've made, with a message for pro
|
||||||
|
git commit <something>
|
||||||
|
# within the monorepo, add the pro reference to your branch, commit it with a message like "Update pro ref"
|
||||||
|
cd ../..
|
||||||
|
git add packages/pro
|
||||||
|
git commit <add the new reference to main repo>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
From here, you will have created a branch in the pro repository and commited the reference to your branch on the monorepo. When you eventually PR this work back into the mainline branch, you will need to first merge your pro PR to the pro mainline, then go into your PR in the monorepo and update the reference again to the new mainline.
|
||||||
|
|
||||||
Note that only budibase maintainers will be able to access the pro repo.
|
Note that only budibase maintainers will be able to access the pro repo.
|
||||||
|
|
||||||
By default, NX will make sure that dependencies are replaced with local source aware version. This is achieved using the `yarn link` command. To see specifically how dependencies are linked see [scripts/link-dependencies.sh](../scripts/link-dependencies.sh). The same link script is used to link dependencies to account-portal in local dev.
|
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above to create a fresh Budibase installation.
|
Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above to create a fresh Budibase installation.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.8.18-alpha.0",
|
"version": "2.8.22-alpha.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -12,29 +12,44 @@ import { localFileDestination } from "../system"
|
||||||
|
|
||||||
let pinoInstance: pino.Logger | undefined
|
let pinoInstance: pino.Logger | undefined
|
||||||
if (!env.DISABLE_PINO_LOGGER) {
|
if (!env.DISABLE_PINO_LOGGER) {
|
||||||
|
const level = env.LOG_LEVEL
|
||||||
const pinoOptions: LoggerOptions = {
|
const pinoOptions: LoggerOptions = {
|
||||||
level: env.LOG_LEVEL,
|
level,
|
||||||
formatters: {
|
formatters: {
|
||||||
level: label => {
|
level: level => {
|
||||||
return { level: label.toUpperCase() }
|
return { level: level.toUpperCase() }
|
||||||
},
|
},
|
||||||
bindings: () => {
|
bindings: () => {
|
||||||
return {
|
if (env.SELF_HOSTED) {
|
||||||
service: env.SERVICE_NAME,
|
// "service" is being injected in datadog using the pod names,
|
||||||
|
// so we should leave it blank to allow the default behaviour if it's not running self-hosted
|
||||||
|
return {
|
||||||
|
service: env.SERVICE_NAME,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`,
|
timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`,
|
||||||
}
|
}
|
||||||
|
|
||||||
const destinations: pino.DestinationStream[] = []
|
const destinations: pino.StreamEntry[] = []
|
||||||
|
|
||||||
if (env.isDev()) {
|
destinations.push(
|
||||||
destinations.push(pinoPretty({ singleLine: true }))
|
env.isDev()
|
||||||
}
|
? {
|
||||||
|
stream: pinoPretty({ singleLine: true }),
|
||||||
|
level: level as pino.Level,
|
||||||
|
}
|
||||||
|
: { stream: process.stdout, level: level as pino.Level }
|
||||||
|
)
|
||||||
|
|
||||||
if (env.SELF_HOSTED) {
|
if (env.SELF_HOSTED) {
|
||||||
destinations.push(localFileDestination())
|
destinations.push({
|
||||||
|
stream: localFileDestination(),
|
||||||
|
level: level as pino.Level,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pinoInstance = destinations.length
|
pinoInstance = destinations.length
|
||||||
|
|
|
@ -12,23 +12,24 @@
|
||||||
export let getOptionValue = option => option
|
export let getOptionValue = option => option
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
let tempValue = value
|
const optionValue = e.target.value
|
||||||
let isChecked = e.target.checked
|
if (e.target.checked && !value.includes(optionValue)) {
|
||||||
if (!tempValue.includes(e.target.value) && isChecked) {
|
dispatch("change", [...value, optionValue])
|
||||||
tempValue.push(e.target.value)
|
} else {
|
||||||
|
dispatch(
|
||||||
|
"change",
|
||||||
|
value.filter(x => x !== optionValue)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
value = tempValue
|
|
||||||
dispatch(
|
|
||||||
"change",
|
|
||||||
tempValue.filter(val => val !== e.target.value || isChecked)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={`spectrum-FieldGroup spectrum-FieldGroup--${direction}`}>
|
<div class={`spectrum-FieldGroup spectrum-FieldGroup--${direction}`}>
|
||||||
{#if options && Array.isArray(options)}
|
{#if options && Array.isArray(options)}
|
||||||
{#each options as option}
|
{#each options as option}
|
||||||
|
{@const optionValue = getOptionValue(option)}
|
||||||
<div
|
<div
|
||||||
title={getOptionLabel(option)}
|
title={getOptionLabel(option)}
|
||||||
class="spectrum-Checkbox spectrum-FieldGroup-item"
|
class="spectrum-Checkbox spectrum-FieldGroup-item"
|
||||||
|
@ -39,11 +40,11 @@
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
value={getOptionValue(option)}
|
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="spectrum-Checkbox-input"
|
class="spectrum-Checkbox-input"
|
||||||
|
value={optionValue}
|
||||||
|
checked={value.includes(optionValue)}
|
||||||
{disabled}
|
{disabled}
|
||||||
checked={value.includes(getOptionValue(option))}
|
|
||||||
/>
|
/>
|
||||||
<span class="spectrum-Checkbox-box">
|
<span class="spectrum-Checkbox-box">
|
||||||
<svg
|
<svg
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
{#if tooltip && showTooltip}
|
{#if tooltip && showTooltip}
|
||||||
<div class="tooltip" in:fade={{ duration: 130, delay: 250 }}>
|
<div class="tooltip" in:fade={{ duration: 130, delay: 250 }}>
|
||||||
<Tooltip textWrapping direction="bottom" text={tooltip} />
|
<Tooltip textWrapping direction="top" text={tooltip} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,15 +80,14 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: calc(100% + 4px);
|
bottom: calc(100% + 4px);
|
||||||
width: 100vw;
|
|
||||||
max-width: 150px;
|
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spectrum-Icon--sizeXS {
|
.spectrum-Icon--sizeXS {
|
||||||
width: 10px;
|
width: var(--spectrum-global-dimension-size-150);
|
||||||
height: 10px;
|
height: var(--spectrum-global-dimension-size-150);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -109,6 +109,7 @@
|
||||||
{disableSorting}
|
{disableSorting}
|
||||||
{customPlaceholder}
|
{customPlaceholder}
|
||||||
allowEditRows={allowEditing}
|
allowEditRows={allowEditing}
|
||||||
|
allowEditColumns={allowEditing}
|
||||||
showAutoColumns={!hideAutocolumns}
|
showAutoColumns={!hideAutocolumns}
|
||||||
{allowClickRows}
|
{allowClickRows}
|
||||||
on:clickrelationship={e => selectRelationship(e.detail)}
|
on:clickrelationship={e => selectRelationship(e.detail)}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
|
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
|
||||||
import {
|
import {
|
||||||
FIELDS,
|
FIELDS,
|
||||||
RelationshipTypes,
|
RelationshipType,
|
||||||
ALLOWABLE_STRING_OPTIONS,
|
ALLOWABLE_STRING_OPTIONS,
|
||||||
ALLOWABLE_NUMBER_OPTIONS,
|
ALLOWABLE_NUMBER_OPTIONS,
|
||||||
ALLOWABLE_STRING_TYPES,
|
ALLOWABLE_STRING_TYPES,
|
||||||
|
@ -33,6 +33,7 @@
|
||||||
import { getBindings } from "components/backend/DataTable/formula"
|
import { getBindings } from "components/backend/DataTable/formula"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import JSONSchemaModal from "./JSONSchemaModal.svelte"
|
import JSONSchemaModal from "./JSONSchemaModal.svelte"
|
||||||
|
import { ValidColumnNameRegex } from "@budibase/shared-core"
|
||||||
|
|
||||||
const AUTO_TYPE = "auto"
|
const AUTO_TYPE = "auto"
|
||||||
const FORMULA_TYPE = FIELDS.FORMULA.type
|
const FORMULA_TYPE = FIELDS.FORMULA.type
|
||||||
|
@ -183,7 +184,7 @@
|
||||||
dispatch("updatecolumns")
|
dispatch("updatecolumns")
|
||||||
if (
|
if (
|
||||||
saveColumn.type === LINK_TYPE &&
|
saveColumn.type === LINK_TYPE &&
|
||||||
saveColumn.relationshipType === RelationshipTypes.MANY_TO_MANY
|
saveColumn.relationshipType === RelationshipType.MANY_TO_MANY
|
||||||
) {
|
) {
|
||||||
// Fetching the new tables
|
// Fetching the new tables
|
||||||
tables.fetch()
|
tables.fetch()
|
||||||
|
@ -237,7 +238,7 @@
|
||||||
|
|
||||||
// Default relationships many to many
|
// Default relationships many to many
|
||||||
if (editableColumn.type === LINK_TYPE) {
|
if (editableColumn.type === LINK_TYPE) {
|
||||||
editableColumn.relationshipType = RelationshipTypes.MANY_TO_MANY
|
editableColumn.relationshipType = RelationshipType.MANY_TO_MANY
|
||||||
}
|
}
|
||||||
if (editableColumn.type === FORMULA_TYPE) {
|
if (editableColumn.type === FORMULA_TYPE) {
|
||||||
editableColumn.formulaType = "dynamic"
|
editableColumn.formulaType = "dynamic"
|
||||||
|
@ -285,17 +286,17 @@
|
||||||
{
|
{
|
||||||
name: `Many ${thisName} rows → many ${linkName} rows`,
|
name: `Many ${thisName} rows → many ${linkName} rows`,
|
||||||
alt: `Many ${table.name} rows → many ${linkTable.name} rows`,
|
alt: `Many ${table.name} rows → many ${linkTable.name} rows`,
|
||||||
value: RelationshipTypes.MANY_TO_MANY,
|
value: RelationshipType.MANY_TO_MANY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `One ${linkName} row → many ${thisName} rows`,
|
name: `One ${linkName} row → many ${thisName} rows`,
|
||||||
alt: `One ${linkTable.name} rows → many ${table.name} rows`,
|
alt: `One ${linkTable.name} rows → many ${table.name} rows`,
|
||||||
value: RelationshipTypes.ONE_TO_MANY,
|
value: RelationshipType.ONE_TO_MANY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `One ${thisName} row → many ${linkName} rows`,
|
name: `One ${thisName} row → many ${linkName} rows`,
|
||||||
alt: `One ${table.name} rows → many ${linkTable.name} rows`,
|
alt: `One ${table.name} rows → many ${linkTable.name} rows`,
|
||||||
value: RelationshipTypes.MANY_TO_ONE,
|
value: RelationshipType.MANY_TO_ONE,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -375,7 +376,7 @@
|
||||||
const newError = {}
|
const newError = {}
|
||||||
if (!external && fieldInfo.name?.startsWith("_")) {
|
if (!external && fieldInfo.name?.startsWith("_")) {
|
||||||
newError.name = `Column name cannot start with an underscore.`
|
newError.name = `Column name cannot start with an underscore.`
|
||||||
} else if (fieldInfo.name && !fieldInfo.name.match(/^[_a-zA-Z0-9\s]*$/g)) {
|
} else if (fieldInfo.name && !fieldInfo.name.match(ValidColumnNameRegex)) {
|
||||||
newError.name = `Illegal character; must be alpha-numeric.`
|
newError.name = `Illegal character; must be alpha-numeric.`
|
||||||
} else if (PROHIBITED_COLUMN_NAMES.some(name => fieldInfo.name === name)) {
|
} else if (PROHIBITED_COLUMN_NAMES.some(name => fieldInfo.name === name)) {
|
||||||
newError.name = `${PROHIBITED_COLUMN_NAMES.join(
|
newError.name = `${PROHIBITED_COLUMN_NAMES.join(
|
||||||
|
|
|
@ -95,9 +95,9 @@
|
||||||
{#if !creating}
|
{#if !creating}
|
||||||
<div>
|
<div>
|
||||||
A user's email, role, first and last names cannot be changed from within
|
A user's email, role, first and last names cannot be changed from within
|
||||||
the app builder. Please go to the <Link
|
the app builder. Please go to the
|
||||||
on:click={$goto("/builder/portal/manage/users")}>user portal</Link
|
<Link on:click={$goto("/builder/portal/users/users")}>user portal</Link>
|
||||||
> to do this.
|
to do this.
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<RowFieldControl
|
<RowFieldControl
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { RelationshipTypes } from "constants/backend"
|
import { RelationshipType } from "constants/backend"
|
||||||
import {
|
import {
|
||||||
keepOpen,
|
keepOpen,
|
||||||
Button,
|
Button,
|
||||||
|
@ -25,11 +25,11 @@
|
||||||
const relationshipTypes = [
|
const relationshipTypes = [
|
||||||
{
|
{
|
||||||
label: "One to Many",
|
label: "One to Many",
|
||||||
value: RelationshipTypes.MANY_TO_ONE,
|
value: RelationshipType.MANY_TO_ONE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Many to Many",
|
label: "Many to Many",
|
||||||
value: RelationshipTypes.MANY_TO_MANY,
|
value: RelationshipType.MANY_TO_MANY,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@
|
||||||
value: table._id,
|
value: table._id,
|
||||||
}))
|
}))
|
||||||
$: valid = getErrorCount(errors) === 0 && allRequiredAttributesSet()
|
$: valid = getErrorCount(errors) === 0 && allRequiredAttributesSet()
|
||||||
$: isManyToMany = relationshipType === RelationshipTypes.MANY_TO_MANY
|
$: isManyToMany = relationshipType === RelationshipType.MANY_TO_MANY
|
||||||
$: isManyToOne = relationshipType === RelationshipTypes.MANY_TO_ONE
|
$: isManyToOne = relationshipType === RelationshipType.MANY_TO_ONE
|
||||||
|
|
||||||
function getTable(id) {
|
function getTable(id) {
|
||||||
return plusTables.find(table => table._id === id)
|
return plusTables.find(table => table._id === id)
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
|
|
||||||
function allRequiredAttributesSet() {
|
function allRequiredAttributesSet() {
|
||||||
const base = getTable(fromId) && getTable(toId) && fromColumn && toColumn
|
const base = getTable(fromId) && getTable(toId) && fromColumn && toColumn
|
||||||
if (relationshipType === RelationshipTypes.MANY_TO_ONE) {
|
if (relationshipType === RelationshipType.MANY_TO_ONE) {
|
||||||
return base && fromPrimary && fromForeign
|
return base && fromPrimary && fromForeign
|
||||||
} else {
|
} else {
|
||||||
return base && getTable(throughId) && throughFromKey && throughToKey
|
return base && getTable(throughId) && throughFromKey && throughToKey
|
||||||
|
@ -181,12 +181,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function otherRelationshipType(type) {
|
function otherRelationshipType(type) {
|
||||||
if (type === RelationshipTypes.MANY_TO_ONE) {
|
if (type === RelationshipType.MANY_TO_ONE) {
|
||||||
return RelationshipTypes.ONE_TO_MANY
|
return RelationshipType.ONE_TO_MANY
|
||||||
} else if (type === RelationshipTypes.ONE_TO_MANY) {
|
} else if (type === RelationshipType.ONE_TO_MANY) {
|
||||||
return RelationshipTypes.MANY_TO_ONE
|
return RelationshipType.MANY_TO_ONE
|
||||||
} else if (type === RelationshipTypes.MANY_TO_MANY) {
|
} else if (type === RelationshipType.MANY_TO_MANY) {
|
||||||
return RelationshipTypes.MANY_TO_MANY
|
return RelationshipType.MANY_TO_MANY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,7 +218,7 @@
|
||||||
|
|
||||||
// if any to many only need to check from
|
// if any to many only need to check from
|
||||||
const manyToMany =
|
const manyToMany =
|
||||||
relateFrom.relationshipType === RelationshipTypes.MANY_TO_MANY
|
relateFrom.relationshipType === RelationshipType.MANY_TO_MANY
|
||||||
|
|
||||||
if (!manyToMany) {
|
if (!manyToMany) {
|
||||||
delete relateFrom.through
|
delete relateFrom.through
|
||||||
|
@ -253,7 +253,7 @@
|
||||||
}
|
}
|
||||||
relateTo = {
|
relateTo = {
|
||||||
...relateTo,
|
...relateTo,
|
||||||
relationshipType: RelationshipTypes.ONE_TO_MANY,
|
relationshipType: RelationshipType.ONE_TO_MANY,
|
||||||
foreignKey: relateFrom.fieldName,
|
foreignKey: relateFrom.fieldName,
|
||||||
fieldName: fromPrimary,
|
fieldName: fromPrimary,
|
||||||
}
|
}
|
||||||
|
@ -321,7 +321,7 @@
|
||||||
fromColumn = toRelationship.name
|
fromColumn = toRelationship.name
|
||||||
}
|
}
|
||||||
relationshipType =
|
relationshipType =
|
||||||
fromRelationship.relationshipType || RelationshipTypes.MANY_TO_ONE
|
fromRelationship.relationshipType || RelationshipType.MANY_TO_ONE
|
||||||
if (selectedFromTable) {
|
if (selectedFromTable) {
|
||||||
fromId = selectedFromTable._id
|
fromId = selectedFromTable._id
|
||||||
fromColumn = selectedFromTable.name
|
fromColumn = selectedFromTable.name
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { RelationshipTypes } from "constants/backend"
|
import { RelationshipType } from "constants/backend"
|
||||||
|
|
||||||
const typeMismatch = "Column type of the foreign key must match the primary key"
|
const typeMismatch = "Column type of the foreign key must match the primary key"
|
||||||
const columnBeingUsed = "Column name cannot be an existing column"
|
const columnBeingUsed = "Column name cannot be an existing column"
|
||||||
|
@ -40,7 +40,7 @@ export class RelationshipErrorChecker {
|
||||||
}
|
}
|
||||||
|
|
||||||
isMany() {
|
isMany() {
|
||||||
return this.type === RelationshipTypes.MANY_TO_MANY
|
return this.type === RelationshipType.MANY_TO_MANY
|
||||||
}
|
}
|
||||||
|
|
||||||
relationshipTypeSet(type) {
|
relationshipTypeSet(type) {
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select } from "@budibase/bbui"
|
import { Select, Icon } from "@budibase/bbui"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { parseFile } from "./utils"
|
import { parseFile } from "./utils"
|
||||||
|
|
||||||
let fileInput
|
|
||||||
let error = null
|
|
||||||
let fileName = null
|
|
||||||
|
|
||||||
let loading = false
|
|
||||||
let validation = {}
|
|
||||||
let validateHash = ""
|
|
||||||
|
|
||||||
export let rows = []
|
export let rows = []
|
||||||
export let schema = {}
|
export let schema = {}
|
||||||
export let allValid = true
|
export let allValid = true
|
||||||
|
@ -49,6 +41,28 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
let fileInput
|
||||||
|
let error = null
|
||||||
|
let fileName = null
|
||||||
|
let fileType = null
|
||||||
|
let loading = false
|
||||||
|
let validation = {}
|
||||||
|
let validateHash = ""
|
||||||
|
let errors = {}
|
||||||
|
|
||||||
|
$: displayColumnOptions = Object.keys(schema || {}).filter(column => {
|
||||||
|
return validation[column]
|
||||||
|
})
|
||||||
|
$: {
|
||||||
|
// binding in consumer is causing double renders here
|
||||||
|
const newValidateHash = JSON.stringify(rows) + JSON.stringify(schema)
|
||||||
|
if (newValidateHash !== validateHash) {
|
||||||
|
validate(rows, schema)
|
||||||
|
}
|
||||||
|
validateHash = newValidateHash
|
||||||
|
}
|
||||||
|
$: openFileUpload(promptUpload, fileInput)
|
||||||
|
|
||||||
async function handleFile(e) {
|
async function handleFile(e) {
|
||||||
loading = true
|
loading = true
|
||||||
error = null
|
error = null
|
||||||
|
@ -67,34 +81,23 @@
|
||||||
|
|
||||||
async function validate(rows, schema) {
|
async function validate(rows, schema) {
|
||||||
loading = true
|
loading = true
|
||||||
error = null
|
|
||||||
validation = {}
|
|
||||||
allValid = false
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (rows.length > 0) {
|
if (rows.length > 0) {
|
||||||
const response = await API.validateNewTableImport({ rows, schema })
|
const response = await API.validateNewTableImport({ rows, schema })
|
||||||
validation = response.schemaValidation
|
validation = response.schemaValidation
|
||||||
allValid = response.allValid
|
allValid = response.allValid
|
||||||
|
errors = response.errors
|
||||||
|
error = null
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e.message
|
error = e.message
|
||||||
|
validation = {}
|
||||||
|
allValid = false
|
||||||
|
errors = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
$: {
|
|
||||||
// binding in consumer is causing double renders here
|
|
||||||
const newValidateHash = JSON.stringify(rows) + JSON.stringify(schema)
|
|
||||||
|
|
||||||
if (newValidateHash !== validateHash) {
|
|
||||||
validate(rows, schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
validateHash = newValidateHash
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (name, e) => {
|
const handleChange = (name, e) => {
|
||||||
schema[name].type = e.detail
|
schema[name].type = e.detail
|
||||||
schema[name].constraints = FIELDS[e.detail.toUpperCase()].constraints
|
schema[name].constraints = FIELDS[e.detail.toUpperCase()].constraints
|
||||||
|
@ -106,7 +109,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: openFileUpload(promptUpload, fileInput)
|
const deleteColumn = name => {
|
||||||
|
if (loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete schema[name]
|
||||||
|
schema = schema
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="dropzone">
|
<div class="dropzone">
|
||||||
|
@ -119,10 +128,8 @@
|
||||||
on:change={handleFile}
|
on:change={handleFile}
|
||||||
/>
|
/>
|
||||||
<label for="file-upload" class:uploaded={rows.length > 0}>
|
<label for="file-upload" class:uploaded={rows.length > 0}>
|
||||||
{#if loading}
|
{#if error}
|
||||||
loading...
|
Error: {error}
|
||||||
{:else if error}
|
|
||||||
error: {error}
|
|
||||||
{:else if fileName}
|
{:else if fileName}
|
||||||
{fileName}
|
{fileName}
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -142,23 +149,26 @@
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
getOptionLabel={option => option.label}
|
getOptionLabel={option => option.label}
|
||||||
getOptionValue={option => option.value}
|
getOptionValue={option => option.value}
|
||||||
disabled={loading}
|
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class={loading || validation[column.name]
|
class={validation[column.name]
|
||||||
? "fieldStatusSuccess"
|
? "fieldStatusSuccess"
|
||||||
: "fieldStatusFailure"}
|
: "fieldStatusFailure"}
|
||||||
>
|
>
|
||||||
{validation[column.name] ? "Success" : "Failure"}
|
{#if validation[column.name]}
|
||||||
|
Success
|
||||||
|
{:else}
|
||||||
|
Failure
|
||||||
|
{#if errors[column.name]}
|
||||||
|
<Icon name="Help" tooltip={errors[column.name]} />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
<i
|
<Icon
|
||||||
class={`omit-button ri-close-circle-fill ${
|
size="S"
|
||||||
loading ? "omit-button-disabled" : ""
|
name="Close"
|
||||||
}`}
|
hoverable
|
||||||
on:click={() => {
|
on:click={() => deleteColumn(column.name)}
|
||||||
delete schema[column.name]
|
|
||||||
schema = schema
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -167,7 +177,7 @@
|
||||||
<Select
|
<Select
|
||||||
label="Display Column"
|
label="Display Column"
|
||||||
bind:value={displayColumn}
|
bind:value={displayColumn}
|
||||||
options={Object.keys(schema)}
|
options={displayColumnOptions}
|
||||||
sort
|
sort
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -235,23 +245,16 @@
|
||||||
justify-self: center;
|
justify-self: center;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fieldStatusFailure {
|
.fieldStatusFailure {
|
||||||
color: var(--red);
|
color: var(--red);
|
||||||
justify-self: center;
|
justify-self: center;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
.fieldStatusFailure :global(.spectrum-Icon) {
|
||||||
.omit-button {
|
width: 12px;
|
||||||
font-size: 1.2em;
|
|
||||||
color: var(--grey-7);
|
|
||||||
cursor: pointer;
|
|
||||||
justify-self: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.omit-button-disabled {
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 70%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.display-column {
|
.display-column {
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
import { licensing } from "stores/portal"
|
import { licensing } from "stores/portal"
|
||||||
import { isEnabled, TENANT_FEATURE_FLAGS } from "helpers/featureFlags"
|
import { isEnabled, TENANT_FEATURE_FLAGS } from "helpers/featureFlags"
|
||||||
|
|
||||||
$: isPremiumUser = $licensing.license && !$licensing.isFreePlan
|
$: isBusinessAndAbove =
|
||||||
|
$licensing.isBusinessPlan || $licensing.isEnterprisePlan
|
||||||
|
|
||||||
let show
|
let show
|
||||||
let hide
|
let hide
|
||||||
|
@ -55,22 +56,22 @@
|
||||||
<div class="divider" />
|
<div class="divider" />
|
||||||
{#if isEnabled(TENANT_FEATURE_FLAGS.LICENSING)}
|
{#if isEnabled(TENANT_FEATURE_FLAGS.LICENSING)}
|
||||||
<a
|
<a
|
||||||
href={isPremiumUser
|
href={isBusinessAndAbove
|
||||||
? "mailto:support@budibase.com"
|
? "mailto:support@budibase.com"
|
||||||
: "/builder/portal/account/usage"}
|
: "/builder/portal/account/usage"}
|
||||||
>
|
>
|
||||||
<div class="premiumLinkContent" class:disabled={!isPremiumUser}>
|
<div class="premiumLinkContent" class:disabled={!isBusinessAndAbove}>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<FontAwesomeIcon name="fa-solid fa-envelope" />
|
<FontAwesomeIcon name="fa-solid fa-envelope" />
|
||||||
</div>
|
</div>
|
||||||
<Body size="S">Email support</Body>
|
<Body size="S">Email support</Body>
|
||||||
</div>
|
</div>
|
||||||
{#if !isPremiumUser}
|
{#if !isBusinessAndAbove}
|
||||||
<div class="premiumBadge">
|
<div class="premiumBadge">
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<FontAwesomeIcon name="fa-solid fa-lock" />
|
<FontAwesomeIcon name="fa-solid fa-lock" />
|
||||||
</div>
|
</div>
|
||||||
<Body size="XS">Premium</Body>
|
<Body size="XS">Business</Body>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -132,7 +132,6 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
.nav-item.scrollable {
|
.nav-item.scrollable {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -419,16 +419,22 @@
|
||||||
if (query && !query.fields.pagination) {
|
if (query && !query.fields.pagination) {
|
||||||
query.fields.pagination = {}
|
query.fields.pagination = {}
|
||||||
}
|
}
|
||||||
dynamicVariables = getDynamicVariables(
|
// if query doesn't have ID then its new - don't try to copy existing dynamic variables
|
||||||
datasource,
|
if (!queryId) {
|
||||||
query._id,
|
dynamicVariables = []
|
||||||
(variable, queryId) => variable.queryId === queryId
|
globalDynamicBindings = getDynamicVariables(datasource)
|
||||||
)
|
} else {
|
||||||
globalDynamicBindings = getDynamicVariables(
|
dynamicVariables = getDynamicVariables(
|
||||||
datasource,
|
datasource,
|
||||||
query._id,
|
query._id,
|
||||||
(variable, queryId) => variable.queryId !== queryId
|
(variable, queryId) => variable.queryId === queryId
|
||||||
)
|
)
|
||||||
|
globalDynamicBindings = getDynamicVariables(
|
||||||
|
datasource,
|
||||||
|
query._id,
|
||||||
|
(variable, queryId) => variable.queryId !== queryId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
prettifyQueryRequestBody(
|
prettifyQueryRequestBody(
|
||||||
query,
|
query,
|
||||||
|
|
|
@ -151,7 +151,7 @@ export function isAutoColumnUserRelationship(subtype) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RelationshipTypes = {
|
export const RelationshipType = {
|
||||||
MANY_TO_MANY: "many-to-many",
|
MANY_TO_MANY: "many-to-many",
|
||||||
ONE_TO_MANY: "one-to-many",
|
ONE_TO_MANY: "one-to-many",
|
||||||
MANY_TO_ONE: "many-to-one",
|
MANY_TO_ONE: "many-to-one",
|
||||||
|
|
|
@ -74,11 +74,12 @@
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #00000047;
|
background: var(--spectrum-global-color-gray-50);
|
||||||
color: white;
|
color: white;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
transition: background 130ms ease-out;
|
||||||
}
|
}
|
||||||
.left {
|
.left {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@ -94,7 +95,7 @@
|
||||||
}
|
}
|
||||||
.button:hover {
|
.button:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
filter: brightness(1.2);
|
background: var(--spectrum-global-color-gray-100);
|
||||||
}
|
}
|
||||||
.connected {
|
.connected {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
const onClick = dynamicVariable => {
|
const onClick = dynamicVariable => {
|
||||||
const queryId = dynamicVariable.queryId
|
const queryId = dynamicVariable.queryId
|
||||||
queries.select({ _id: queryId })
|
queries.select({ _id: queryId })
|
||||||
$goto(`./${queryId}`)
|
$goto(`../../query/${queryId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2341,10 +2341,6 @@
|
||||||
"label": "Left",
|
"label": "Left",
|
||||||
"value": "left"
|
"value": "left"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "Right",
|
|
||||||
"value": "right"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Above",
|
"label": "Above",
|
||||||
"value": "above"
|
"value": "above"
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
if (Array.isArray(values)) {
|
if (Array.isArray(values)) {
|
||||||
return values
|
return values.slice()
|
||||||
}
|
}
|
||||||
return values.split(",").map(value => value.trim())
|
return values.split(",").map(value => value.trim())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
import {
|
import { FieldTypes, RelationshipType, FormulaTypes } from "../../src/constants"
|
||||||
FieldTypes,
|
|
||||||
RelationshipTypes,
|
|
||||||
FormulaTypes,
|
|
||||||
} from "../../src/constants"
|
|
||||||
import { object } from "./utils"
|
import { object } from "./utils"
|
||||||
import Resource from "./utils/Resource"
|
import Resource from "./utils/Resource"
|
||||||
|
|
||||||
|
@ -100,7 +96,7 @@ const tableSchema = {
|
||||||
},
|
},
|
||||||
relationshipType: {
|
relationshipType: {
|
||||||
type: "string",
|
type: "string",
|
||||||
enum: Object.values(RelationshipTypes),
|
enum: Object.values(RelationshipType),
|
||||||
description:
|
description:
|
||||||
"Defines the type of relationship that this column will be used for.",
|
"Defines the type of relationship that this column will be used for.",
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,34 +1,33 @@
|
||||||
import {
|
import {
|
||||||
generateDatasourceID,
|
|
||||||
getDatasourceParams,
|
|
||||||
getQueryParams,
|
|
||||||
DocumentType,
|
DocumentType,
|
||||||
BudibaseInternalDB,
|
generateDatasourceID,
|
||||||
|
getQueryParams,
|
||||||
getTableParams,
|
getTableParams,
|
||||||
} from "../../db/utils"
|
} from "../../db/utils"
|
||||||
import { destroy as tableDestroy } from "./table/internal"
|
import { destroy as tableDestroy } from "./table/internal"
|
||||||
import { BuildSchemaErrors, InvalidColumns } from "../../constants"
|
import { BuildSchemaErrors, InvalidColumns } from "../../constants"
|
||||||
import { getIntegration } from "../../integrations"
|
import { getIntegration } from "../../integrations"
|
||||||
import { invalidateDynamicVariables } from "../../threads/utils"
|
import { invalidateDynamicVariables } from "../../threads/utils"
|
||||||
import { db as dbCore, context, events } from "@budibase/backend-core"
|
import { context, db as dbCore, events } from "@budibase/backend-core"
|
||||||
import {
|
import {
|
||||||
UserCtx,
|
|
||||||
Datasource,
|
|
||||||
Row,
|
|
||||||
CreateDatasourceResponse,
|
|
||||||
UpdateDatasourceResponse,
|
|
||||||
CreateDatasourceRequest,
|
CreateDatasourceRequest,
|
||||||
VerifyDatasourceRequest,
|
CreateDatasourceResponse,
|
||||||
VerifyDatasourceResponse,
|
Datasource,
|
||||||
|
DatasourcePlus,
|
||||||
FetchDatasourceInfoRequest,
|
FetchDatasourceInfoRequest,
|
||||||
FetchDatasourceInfoResponse,
|
FetchDatasourceInfoResponse,
|
||||||
IntegrationBase,
|
IntegrationBase,
|
||||||
DatasourcePlus,
|
RestConfig,
|
||||||
SourceName,
|
SourceName,
|
||||||
|
UpdateDatasourceResponse,
|
||||||
|
UserCtx,
|
||||||
|
VerifyDatasourceRequest,
|
||||||
|
VerifyDatasourceResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../sdk"
|
import sdk from "../../sdk"
|
||||||
import { builderSocket } from "../../websockets"
|
import { builderSocket } from "../../websockets"
|
||||||
import { setupCreationAuth as googleSetupCreationAuth } from "../../integrations/googlesheets"
|
import { setupCreationAuth as googleSetupCreationAuth } from "../../integrations/googlesheets"
|
||||||
|
import { areRESTVariablesValid } from "../../sdk/app/datasources/datasources"
|
||||||
|
|
||||||
function getErrorTables(errors: any, errorType: string) {
|
function getErrorTables(errors: any, errorType: string) {
|
||||||
return Object.entries(errors)
|
return Object.entries(errors)
|
||||||
|
@ -119,46 +118,7 @@ async function buildFilteredSchema(datasource: Datasource, filter?: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetch(ctx: UserCtx) {
|
export async function fetch(ctx: UserCtx) {
|
||||||
// Get internal tables
|
ctx.body = await sdk.datasources.fetch()
|
||||||
const db = context.getAppDB()
|
|
||||||
const internalTables = await db.allDocs(
|
|
||||||
getTableParams(null, {
|
|
||||||
include_docs: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const internal = internalTables.rows.reduce((acc: any, row: Row) => {
|
|
||||||
const sourceId = row.doc.sourceId || "bb_internal"
|
|
||||||
acc[sourceId] = acc[sourceId] || []
|
|
||||||
acc[sourceId].push(row.doc)
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
const bbInternalDb = {
|
|
||||||
...BudibaseInternalDB,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get external datasources
|
|
||||||
const datasources = (
|
|
||||||
await db.allDocs(
|
|
||||||
getDatasourceParams(null, {
|
|
||||||
include_docs: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
).rows.map(row => row.doc)
|
|
||||||
|
|
||||||
const allDatasources: Datasource[] = await sdk.datasources.removeSecrets([
|
|
||||||
bbInternalDb,
|
|
||||||
...datasources,
|
|
||||||
])
|
|
||||||
|
|
||||||
for (let datasource of allDatasources) {
|
|
||||||
if (datasource.type === dbCore.BUDIBASE_DATASOURCE_TYPE) {
|
|
||||||
datasource.entities = internal[datasource._id!]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.body = [bbInternalDb, ...datasources]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verify(
|
export async function verify(
|
||||||
|
@ -290,6 +250,14 @@ export async function update(ctx: UserCtx<any, UpdateDatasourceResponse>) {
|
||||||
datasource.config!.auth = auth
|
datasource.config!.auth = auth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check all variables are unique
|
||||||
|
if (
|
||||||
|
datasource.source === SourceName.REST &&
|
||||||
|
!sdk.datasources.areRESTVariablesValid(datasource)
|
||||||
|
) {
|
||||||
|
ctx.throw(400, "Duplicate dynamic/static variable names are invalid.")
|
||||||
|
}
|
||||||
|
|
||||||
const response = await db.put(
|
const response = await db.put(
|
||||||
sdk.tables.populateExternalTableSchemas(datasource)
|
sdk.tables.populateExternalTableSchemas(datasource)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { generateQueryID, getQueryParams, isProdAppID } from "../../../db/utils"
|
import { generateQueryID } from "../../../db/utils"
|
||||||
import { BaseQueryVerbs, FieldTypes } from "../../../constants"
|
import { BaseQueryVerbs, FieldTypes } from "../../../constants"
|
||||||
import { Thread, ThreadType } from "../../../threads"
|
import { Thread, ThreadType } from "../../../threads"
|
||||||
import { save as saveDatasource } from "../datasource"
|
import { save as saveDatasource } from "../datasource"
|
||||||
|
@ -28,15 +28,7 @@ function enrichQueries(input: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetch(ctx: any) {
|
export async function fetch(ctx: any) {
|
||||||
const db = context.getAppDB()
|
ctx.body = await sdk.queries.fetch()
|
||||||
|
|
||||||
const body = await db.allDocs(
|
|
||||||
getQueryParams(null, {
|
|
||||||
include_docs: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx.body = enrichQueries(body.rows.map((row: any) => row.doc))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _import = async (ctx: any) => {
|
const _import = async (ctx: any) => {
|
||||||
|
@ -103,14 +95,8 @@ export async function save(ctx: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function find(ctx: any) {
|
export async function find(ctx: any) {
|
||||||
const db = context.getAppDB()
|
const queryId = ctx.params.queryId
|
||||||
const query = enrichQueries(await db.get(ctx.params.queryId))
|
ctx.body = await sdk.queries.find(queryId)
|
||||||
// remove properties that could be dangerous in real app
|
|
||||||
if (isProdAppID(ctx.appId)) {
|
|
||||||
delete query.fields
|
|
||||||
delete query.parameters
|
|
||||||
}
|
|
||||||
ctx.body = query
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Required to discern between OIDC OAuth config entries
|
//Required to discern between OIDC OAuth config entries
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
Operation,
|
Operation,
|
||||||
PaginationJson,
|
PaginationJson,
|
||||||
RelationshipsJson,
|
RelationshipsJson,
|
||||||
RelationshipTypes,
|
RelationshipType,
|
||||||
Row,
|
Row,
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
SortJson,
|
SortJson,
|
||||||
|
@ -577,7 +577,7 @@ export class ExternalRequest {
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const isMany = field.relationshipType === RelationshipTypes.MANY_TO_MANY
|
const isMany = field.relationshipType === RelationshipType.MANY_TO_MANY
|
||||||
const tableId = isMany ? field.through : field.tableId
|
const tableId = isMany ? field.through : field.tableId
|
||||||
const { tableName: relatedTableName } = breakExternalTableId(tableId)
|
const { tableName: relatedTableName } = breakExternalTableId(tableId)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {
|
||||||
FieldSchema,
|
FieldSchema,
|
||||||
Operation,
|
Operation,
|
||||||
QueryJson,
|
QueryJson,
|
||||||
RelationshipTypes,
|
RelationshipType,
|
||||||
RenameColumn,
|
RenameColumn,
|
||||||
Table,
|
Table,
|
||||||
TableRequest,
|
TableRequest,
|
||||||
|
@ -103,12 +103,12 @@ function getDatasourceId(table: Table) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function otherRelationshipType(type?: string) {
|
function otherRelationshipType(type?: string) {
|
||||||
if (type === RelationshipTypes.MANY_TO_MANY) {
|
if (type === RelationshipType.MANY_TO_MANY) {
|
||||||
return RelationshipTypes.MANY_TO_MANY
|
return RelationshipType.MANY_TO_MANY
|
||||||
}
|
}
|
||||||
return type === RelationshipTypes.ONE_TO_MANY
|
return type === RelationshipType.ONE_TO_MANY
|
||||||
? RelationshipTypes.MANY_TO_ONE
|
? RelationshipType.MANY_TO_ONE
|
||||||
: RelationshipTypes.ONE_TO_MANY
|
: RelationshipType.ONE_TO_MANY
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateManyLinkSchema(
|
function generateManyLinkSchema(
|
||||||
|
@ -151,12 +151,12 @@ function generateLinkSchema(
|
||||||
column: FieldSchema,
|
column: FieldSchema,
|
||||||
table: Table,
|
table: Table,
|
||||||
relatedTable: Table,
|
relatedTable: Table,
|
||||||
type: RelationshipTypes
|
type: RelationshipType
|
||||||
) {
|
) {
|
||||||
if (!table.primary || !relatedTable.primary) {
|
if (!table.primary || !relatedTable.primary) {
|
||||||
throw new Error("Unable to generate link schema, no primary keys")
|
throw new Error("Unable to generate link schema, no primary keys")
|
||||||
}
|
}
|
||||||
const isOneSide = type === RelationshipTypes.ONE_TO_MANY
|
const isOneSide = type === RelationshipType.ONE_TO_MANY
|
||||||
const primary = isOneSide ? relatedTable.primary[0] : table.primary[0]
|
const primary = isOneSide ? relatedTable.primary[0] : table.primary[0]
|
||||||
// generate a foreign key
|
// generate a foreign key
|
||||||
const foreignKey = generateForeignKey(column, relatedTable)
|
const foreignKey = generateForeignKey(column, relatedTable)
|
||||||
|
@ -251,7 +251,7 @@ export async function save(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
const relatedColumnName = schema.fieldName!
|
const relatedColumnName = schema.fieldName!
|
||||||
const relationType = schema.relationshipType!
|
const relationType = schema.relationshipType!
|
||||||
if (relationType === RelationshipTypes.MANY_TO_MANY) {
|
if (relationType === RelationshipType.MANY_TO_MANY) {
|
||||||
const junctionTable = generateManyLinkSchema(
|
const junctionTable = generateManyLinkSchema(
|
||||||
datasource,
|
datasource,
|
||||||
schema,
|
schema,
|
||||||
|
@ -265,7 +265,7 @@ export async function save(ctx: UserCtx) {
|
||||||
extraTablesToUpdate.push(junctionTable)
|
extraTablesToUpdate.push(junctionTable)
|
||||||
} else {
|
} else {
|
||||||
const fkTable =
|
const fkTable =
|
||||||
relationType === RelationshipTypes.ONE_TO_MANY
|
relationType === RelationshipType.ONE_TO_MANY
|
||||||
? tableToSave
|
? tableToSave
|
||||||
: relatedTable
|
: relatedTable
|
||||||
const foreignKey = generateLinkSchema(
|
const foreignKey = generateLinkSchema(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { objectStore, roles, constants } from "@budibase/backend-core"
|
import { objectStore, roles, constants } from "@budibase/backend-core"
|
||||||
import { FieldType as FieldTypes } from "@budibase/types"
|
import { FieldType as FieldTypes } from "@budibase/types"
|
||||||
export { FieldType as FieldTypes, RelationshipTypes } from "@budibase/types"
|
export { FieldType as FieldTypes, RelationshipType } from "@budibase/types"
|
||||||
|
|
||||||
export enum FilterTypes {
|
export enum FilterTypes {
|
||||||
STRING = "string",
|
STRING = "string",
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { employeeImport } from "./employeeImport"
|
||||||
import { jobsImport } from "./jobsImport"
|
import { jobsImport } from "./jobsImport"
|
||||||
import { expensesImport } from "./expensesImport"
|
import { expensesImport } from "./expensesImport"
|
||||||
import { db as dbCore } from "@budibase/backend-core"
|
import { db as dbCore } from "@budibase/backend-core"
|
||||||
import { Table, Row, RelationshipTypes } from "@budibase/types"
|
import { Table, Row, RelationshipType } from "@budibase/types"
|
||||||
|
|
||||||
export const DEFAULT_JOBS_TABLE_ID = "ta_bb_jobs"
|
export const DEFAULT_JOBS_TABLE_ID = "ta_bb_jobs"
|
||||||
export const DEFAULT_INVENTORY_TABLE_ID = "ta_bb_inventory"
|
export const DEFAULT_INVENTORY_TABLE_ID = "ta_bb_inventory"
|
||||||
|
@ -299,7 +299,7 @@ export const DEFAULT_EMPLOYEE_TABLE_SCHEMA: Table = {
|
||||||
},
|
},
|
||||||
fieldName: "Assigned",
|
fieldName: "Assigned",
|
||||||
name: "Jobs",
|
name: "Jobs",
|
||||||
relationshipType: RelationshipTypes.MANY_TO_MANY,
|
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||||
tableId: DEFAULT_JOBS_TABLE_ID,
|
tableId: DEFAULT_JOBS_TABLE_ID,
|
||||||
},
|
},
|
||||||
"Start Date": {
|
"Start Date": {
|
||||||
|
@ -458,7 +458,7 @@ export const DEFAULT_JOBS_TABLE_SCHEMA: Table = {
|
||||||
type: FieldTypes.LINK,
|
type: FieldTypes.LINK,
|
||||||
tableId: DEFAULT_EMPLOYEE_TABLE_ID,
|
tableId: DEFAULT_EMPLOYEE_TABLE_ID,
|
||||||
fieldName: "Jobs",
|
fieldName: "Jobs",
|
||||||
relationshipType: RelationshipTypes.MANY_TO_MANY,
|
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||||
// sortable: true,
|
// sortable: true,
|
||||||
},
|
},
|
||||||
"Works End": {
|
"Works End": {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
Database,
|
Database,
|
||||||
FieldSchema,
|
FieldSchema,
|
||||||
LinkDocumentValue,
|
LinkDocumentValue,
|
||||||
RelationshipTypes,
|
RelationshipType,
|
||||||
Row,
|
Row,
|
||||||
Table,
|
Table,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
@ -136,16 +136,16 @@ class LinkController {
|
||||||
handleRelationshipType(linkerField: FieldSchema, linkedField: FieldSchema) {
|
handleRelationshipType(linkerField: FieldSchema, linkedField: FieldSchema) {
|
||||||
if (
|
if (
|
||||||
!linkerField.relationshipType ||
|
!linkerField.relationshipType ||
|
||||||
linkerField.relationshipType === RelationshipTypes.MANY_TO_MANY
|
linkerField.relationshipType === RelationshipType.MANY_TO_MANY
|
||||||
) {
|
) {
|
||||||
linkedField.relationshipType = RelationshipTypes.MANY_TO_MANY
|
linkedField.relationshipType = RelationshipType.MANY_TO_MANY
|
||||||
// make sure by default all are many to many (if not specified)
|
// make sure by default all are many to many (if not specified)
|
||||||
linkerField.relationshipType = RelationshipTypes.MANY_TO_MANY
|
linkerField.relationshipType = RelationshipType.MANY_TO_MANY
|
||||||
} else if (linkerField.relationshipType === RelationshipTypes.MANY_TO_ONE) {
|
} else if (linkerField.relationshipType === RelationshipType.MANY_TO_ONE) {
|
||||||
// Ensure that the other side of the relationship is locked to one record
|
// Ensure that the other side of the relationship is locked to one record
|
||||||
linkedField.relationshipType = RelationshipTypes.ONE_TO_MANY
|
linkedField.relationshipType = RelationshipType.ONE_TO_MANY
|
||||||
} else if (linkerField.relationshipType === RelationshipTypes.ONE_TO_MANY) {
|
} else if (linkerField.relationshipType === RelationshipType.ONE_TO_MANY) {
|
||||||
linkedField.relationshipType = RelationshipTypes.MANY_TO_ONE
|
linkedField.relationshipType = RelationshipType.MANY_TO_ONE
|
||||||
}
|
}
|
||||||
return { linkerField, linkedField }
|
return { linkerField, linkedField }
|
||||||
}
|
}
|
||||||
|
@ -200,9 +200,7 @@ class LinkController {
|
||||||
|
|
||||||
// iterate through the link IDs in the row field, see if any don't exist already
|
// iterate through the link IDs in the row field, see if any don't exist already
|
||||||
for (let linkId of rowField) {
|
for (let linkId of rowField) {
|
||||||
if (
|
if (linkedSchema?.relationshipType === RelationshipType.ONE_TO_MANY) {
|
||||||
linkedSchema?.relationshipType === RelationshipTypes.ONE_TO_MANY
|
|
||||||
) {
|
|
||||||
let links = (
|
let links = (
|
||||||
(await getLinkDocuments({
|
(await getLinkDocuments({
|
||||||
tableId: field.tableId,
|
tableId: field.tableId,
|
||||||
|
|
|
@ -2,7 +2,7 @@ const TestConfig = require("../../tests/utilities/TestConfiguration")
|
||||||
const { basicRow, basicLinkedRow, basicTable } = require("../../tests/utilities/structures")
|
const { basicRow, basicLinkedRow, basicTable } = require("../../tests/utilities/structures")
|
||||||
const LinkController = require("../linkedRows/LinkController").default
|
const LinkController = require("../linkedRows/LinkController").default
|
||||||
const { context } = require("@budibase/backend-core")
|
const { context } = require("@budibase/backend-core")
|
||||||
const { RelationshipTypes } = require("../../constants")
|
const { RelationshipType } = require("../../constants")
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
|
||||||
describe("test the link controller", () => {
|
describe("test the link controller", () => {
|
||||||
|
@ -16,7 +16,7 @@ describe("test the link controller", () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const { _id } = await config.createTable()
|
const { _id } = await config.createTable()
|
||||||
table2 = await config.createLinkedTable(RelationshipTypes.MANY_TO_MANY, ["link", "link2"])
|
table2 = await config.createLinkedTable(RelationshipType.MANY_TO_MANY, ["link", "link2"])
|
||||||
// update table after creating link
|
// update table after creating link
|
||||||
table1 = await config.getTable(_id)
|
table1 = await config.getTable(_id)
|
||||||
})
|
})
|
||||||
|
@ -57,17 +57,17 @@ describe("test the link controller", () => {
|
||||||
const controller = await createLinkController(table1)
|
const controller = await createLinkController(table1)
|
||||||
// empty case
|
// empty case
|
||||||
let output = controller.handleRelationshipType({}, {})
|
let output = controller.handleRelationshipType({}, {})
|
||||||
expect(output.linkedField.relationshipType).toEqual(RelationshipTypes.MANY_TO_MANY)
|
expect(output.linkedField.relationshipType).toEqual(RelationshipType.MANY_TO_MANY)
|
||||||
expect(output.linkerField.relationshipType).toEqual(RelationshipTypes.MANY_TO_MANY)
|
expect(output.linkerField.relationshipType).toEqual(RelationshipType.MANY_TO_MANY)
|
||||||
output = controller.handleRelationshipType({ relationshipType: RelationshipTypes.MANY_TO_MANY }, {})
|
output = controller.handleRelationshipType({ relationshipType: RelationshipType.MANY_TO_MANY }, {})
|
||||||
expect(output.linkedField.relationshipType).toEqual(RelationshipTypes.MANY_TO_MANY)
|
expect(output.linkedField.relationshipType).toEqual(RelationshipType.MANY_TO_MANY)
|
||||||
expect(output.linkerField.relationshipType).toEqual(RelationshipTypes.MANY_TO_MANY)
|
expect(output.linkerField.relationshipType).toEqual(RelationshipType.MANY_TO_MANY)
|
||||||
output = controller.handleRelationshipType({ relationshipType: RelationshipTypes.MANY_TO_ONE }, {})
|
output = controller.handleRelationshipType({ relationshipType: RelationshipType.MANY_TO_ONE }, {})
|
||||||
expect(output.linkedField.relationshipType).toEqual(RelationshipTypes.ONE_TO_MANY)
|
expect(output.linkedField.relationshipType).toEqual(RelationshipType.ONE_TO_MANY)
|
||||||
expect(output.linkerField.relationshipType).toEqual(RelationshipTypes.MANY_TO_ONE)
|
expect(output.linkerField.relationshipType).toEqual(RelationshipType.MANY_TO_ONE)
|
||||||
output = controller.handleRelationshipType({ relationshipType: RelationshipTypes.ONE_TO_MANY }, {})
|
output = controller.handleRelationshipType({ relationshipType: RelationshipType.ONE_TO_MANY }, {})
|
||||||
expect(output.linkedField.relationshipType).toEqual(RelationshipTypes.MANY_TO_ONE)
|
expect(output.linkedField.relationshipType).toEqual(RelationshipType.MANY_TO_ONE)
|
||||||
expect(output.linkerField.relationshipType).toEqual(RelationshipTypes.ONE_TO_MANY)
|
expect(output.linkerField.relationshipType).toEqual(RelationshipType.ONE_TO_MANY)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to delete a row", async () => {
|
it("should be able to delete a row", async () => {
|
||||||
|
@ -157,7 +157,7 @@ describe("test the link controller", () => {
|
||||||
|
|
||||||
it("should throw an error when overwriting a link column", async () => {
|
it("should throw an error when overwriting a link column", async () => {
|
||||||
const update = cloneDeep(table1)
|
const update = cloneDeep(table1)
|
||||||
update.schema.link.relationshipType = RelationshipTypes.MANY_TO_ONE
|
update.schema.link.relationshipType = RelationshipType.MANY_TO_ONE
|
||||||
let error
|
let error
|
||||||
try {
|
try {
|
||||||
const controller = await createLinkController(update)
|
const controller = await createLinkController(update)
|
||||||
|
@ -183,7 +183,7 @@ describe("test the link controller", () => {
|
||||||
|
|
||||||
it("shouldn't allow one to many having many relationships against it", async () => {
|
it("shouldn't allow one to many having many relationships against it", async () => {
|
||||||
const firstTable = await config.createTable()
|
const firstTable = await config.createTable()
|
||||||
const { _id } = await config.createLinkedTable(RelationshipTypes.MANY_TO_ONE, ["link"])
|
const { _id } = await config.createLinkedTable(RelationshipType.MANY_TO_ONE, ["link"])
|
||||||
const linkTable = await config.getTable(_id)
|
const linkTable = await config.getTable(_id)
|
||||||
// an initial row to link around
|
// an initial row to link around
|
||||||
const row = await createLinkedRow("link", linkTable, firstTable)
|
const row = await createLinkedRow("link", linkTable, firstTable)
|
||||||
|
|
|
@ -10,7 +10,7 @@ import * as setup from "../api/routes/tests/utilities"
|
||||||
import {
|
import {
|
||||||
Datasource,
|
Datasource,
|
||||||
FieldType,
|
FieldType,
|
||||||
RelationshipTypes,
|
RelationshipType,
|
||||||
Row,
|
Row,
|
||||||
SourceName,
|
SourceName,
|
||||||
Table,
|
Table,
|
||||||
|
@ -101,17 +101,17 @@ describe("postgres integrations", () => {
|
||||||
oneToManyRelationshipInfo = {
|
oneToManyRelationshipInfo = {
|
||||||
table: await createAuxTable("o2m"),
|
table: await createAuxTable("o2m"),
|
||||||
fieldName: "oneToManyRelation",
|
fieldName: "oneToManyRelation",
|
||||||
relationshipType: RelationshipTypes.ONE_TO_MANY,
|
relationshipType: RelationshipType.ONE_TO_MANY,
|
||||||
}
|
}
|
||||||
manyToOneRelationshipInfo = {
|
manyToOneRelationshipInfo = {
|
||||||
table: await createAuxTable("m2o"),
|
table: await createAuxTable("m2o"),
|
||||||
fieldName: "manyToOneRelation",
|
fieldName: "manyToOneRelation",
|
||||||
relationshipType: RelationshipTypes.MANY_TO_ONE,
|
relationshipType: RelationshipType.MANY_TO_ONE,
|
||||||
}
|
}
|
||||||
manyToManyRelationshipInfo = {
|
manyToManyRelationshipInfo = {
|
||||||
table: await createAuxTable("m2m"),
|
table: await createAuxTable("m2m"),
|
||||||
fieldName: "manyToManyRelation",
|
fieldName: "manyToManyRelation",
|
||||||
relationshipType: RelationshipTypes.MANY_TO_MANY,
|
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||||
}
|
}
|
||||||
|
|
||||||
primaryPostgresTable = await config.createTable({
|
primaryPostgresTable = await config.createTable({
|
||||||
|
@ -143,7 +143,7 @@ describe("postgres integrations", () => {
|
||||||
},
|
},
|
||||||
fieldName: oneToManyRelationshipInfo.fieldName,
|
fieldName: oneToManyRelationshipInfo.fieldName,
|
||||||
name: "oneToManyRelation",
|
name: "oneToManyRelation",
|
||||||
relationshipType: RelationshipTypes.ONE_TO_MANY,
|
relationshipType: RelationshipType.ONE_TO_MANY,
|
||||||
tableId: oneToManyRelationshipInfo.table._id,
|
tableId: oneToManyRelationshipInfo.table._id,
|
||||||
main: true,
|
main: true,
|
||||||
},
|
},
|
||||||
|
@ -154,7 +154,7 @@ describe("postgres integrations", () => {
|
||||||
},
|
},
|
||||||
fieldName: manyToOneRelationshipInfo.fieldName,
|
fieldName: manyToOneRelationshipInfo.fieldName,
|
||||||
name: "manyToOneRelation",
|
name: "manyToOneRelation",
|
||||||
relationshipType: RelationshipTypes.MANY_TO_ONE,
|
relationshipType: RelationshipType.MANY_TO_ONE,
|
||||||
tableId: manyToOneRelationshipInfo.table._id,
|
tableId: manyToOneRelationshipInfo.table._id,
|
||||||
main: true,
|
main: true,
|
||||||
},
|
},
|
||||||
|
@ -165,7 +165,7 @@ describe("postgres integrations", () => {
|
||||||
},
|
},
|
||||||
fieldName: manyToManyRelationshipInfo.fieldName,
|
fieldName: manyToManyRelationshipInfo.fieldName,
|
||||||
name: "manyToManyRelation",
|
name: "manyToManyRelation",
|
||||||
relationshipType: RelationshipTypes.MANY_TO_MANY,
|
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||||
tableId: manyToManyRelationshipInfo.table._id,
|
tableId: manyToManyRelationshipInfo.table._id,
|
||||||
main: true,
|
main: true,
|
||||||
},
|
},
|
||||||
|
@ -193,12 +193,12 @@ describe("postgres integrations", () => {
|
||||||
type ForeignTableInfo = {
|
type ForeignTableInfo = {
|
||||||
table: Table
|
table: Table
|
||||||
fieldName: string
|
fieldName: string
|
||||||
relationshipType: RelationshipTypes
|
relationshipType: RelationshipType
|
||||||
}
|
}
|
||||||
|
|
||||||
type ForeignRowsInfo = {
|
type ForeignRowsInfo = {
|
||||||
row: Row
|
row: Row
|
||||||
relationshipType: RelationshipTypes
|
relationshipType: RelationshipType
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createPrimaryRow(opts: {
|
async function createPrimaryRow(opts: {
|
||||||
|
@ -263,7 +263,7 @@ describe("postgres integrations", () => {
|
||||||
rowData[manyToOneRelationshipInfo.fieldName].push(foreignRow._id)
|
rowData[manyToOneRelationshipInfo.fieldName].push(foreignRow._id)
|
||||||
foreignRows.push({
|
foreignRows.push({
|
||||||
row: foreignRow,
|
row: foreignRow,
|
||||||
relationshipType: RelationshipTypes.MANY_TO_ONE,
|
relationshipType: RelationshipType.MANY_TO_ONE,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,7 +281,7 @@ describe("postgres integrations", () => {
|
||||||
rowData[manyToManyRelationshipInfo.fieldName].push(foreignRow._id)
|
rowData[manyToManyRelationshipInfo.fieldName].push(foreignRow._id)
|
||||||
foreignRows.push({
|
foreignRows.push({
|
||||||
row: foreignRow,
|
row: foreignRow,
|
||||||
relationshipType: RelationshipTypes.MANY_TO_MANY,
|
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -559,7 +559,7 @@ describe("postgres integrations", () => {
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
|
|
||||||
const one2ManyForeignRows = foreignRows.filter(
|
const one2ManyForeignRows = foreignRows.filter(
|
||||||
x => x.relationshipType === RelationshipTypes.ONE_TO_MANY
|
x => x.relationshipType === RelationshipType.ONE_TO_MANY
|
||||||
)
|
)
|
||||||
expect(one2ManyForeignRows).toHaveLength(1)
|
expect(one2ManyForeignRows).toHaveLength(1)
|
||||||
|
|
||||||
|
@ -921,7 +921,7 @@ describe("postgres integrations", () => {
|
||||||
(row: Row) => row.id === 2
|
(row: Row) => row.id === 2
|
||||||
)
|
)
|
||||||
expect(m2mRow1).toEqual({
|
expect(m2mRow1).toEqual({
|
||||||
...foreignRowsByType[RelationshipTypes.MANY_TO_MANY][0].row,
|
...foreignRowsByType[RelationshipType.MANY_TO_MANY][0].row,
|
||||||
[m2mFieldName]: [
|
[m2mFieldName]: [
|
||||||
{
|
{
|
||||||
_id: row._id,
|
_id: row._id,
|
||||||
|
@ -930,7 +930,7 @@ describe("postgres integrations", () => {
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
expect(m2mRow2).toEqual({
|
expect(m2mRow2).toEqual({
|
||||||
...foreignRowsByType[RelationshipTypes.MANY_TO_MANY][1].row,
|
...foreignRowsByType[RelationshipType.MANY_TO_MANY][1].row,
|
||||||
[m2mFieldName]: [
|
[m2mFieldName]: [
|
||||||
{
|
{
|
||||||
_id: row._id,
|
_id: row._id,
|
||||||
|
@ -940,24 +940,24 @@ describe("postgres integrations", () => {
|
||||||
})
|
})
|
||||||
expect(res.body[m2oFieldName]).toEqual([
|
expect(res.body[m2oFieldName]).toEqual([
|
||||||
{
|
{
|
||||||
...foreignRowsByType[RelationshipTypes.MANY_TO_ONE][0].row,
|
...foreignRowsByType[RelationshipType.MANY_TO_ONE][0].row,
|
||||||
[`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
|
[`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
|
||||||
row.id,
|
row.id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...foreignRowsByType[RelationshipTypes.MANY_TO_ONE][1].row,
|
...foreignRowsByType[RelationshipType.MANY_TO_ONE][1].row,
|
||||||
[`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
|
[`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
|
||||||
row.id,
|
row.id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...foreignRowsByType[RelationshipTypes.MANY_TO_ONE][2].row,
|
...foreignRowsByType[RelationshipType.MANY_TO_ONE][2].row,
|
||||||
[`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
|
[`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
|
||||||
row.id,
|
row.id,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
expect(res.body[o2mFieldName]).toEqual([
|
expect(res.body[o2mFieldName]).toEqual([
|
||||||
{
|
{
|
||||||
...foreignRowsByType[RelationshipTypes.ONE_TO_MANY][0].row,
|
...foreignRowsByType[RelationshipType.ONE_TO_MANY][0].row,
|
||||||
_id: expect.any(String),
|
_id: expect.any(String),
|
||||||
_rev: expect.any(String),
|
_rev: expect.any(String),
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Operation, QueryJson, RenameColumn, Table } from "@budibase/types"
|
||||||
import { breakExternalTableId } from "../utils"
|
import { breakExternalTableId } from "../utils"
|
||||||
import SchemaBuilder = Knex.SchemaBuilder
|
import SchemaBuilder = Knex.SchemaBuilder
|
||||||
import CreateTableBuilder = Knex.CreateTableBuilder
|
import CreateTableBuilder = Knex.CreateTableBuilder
|
||||||
import { FieldTypes, RelationshipTypes } from "../../constants"
|
import { FieldTypes, RelationshipType } from "../../constants"
|
||||||
|
|
||||||
function generateSchema(
|
function generateSchema(
|
||||||
schema: CreateTableBuilder,
|
schema: CreateTableBuilder,
|
||||||
|
@ -70,8 +70,8 @@ function generateSchema(
|
||||||
case FieldTypes.LINK:
|
case FieldTypes.LINK:
|
||||||
// this side of the relationship doesn't need any SQL work
|
// this side of the relationship doesn't need any SQL work
|
||||||
if (
|
if (
|
||||||
column.relationshipType !== RelationshipTypes.MANY_TO_ONE &&
|
column.relationshipType !== RelationshipType.MANY_TO_ONE &&
|
||||||
column.relationshipType !== RelationshipTypes.MANY_TO_MANY
|
column.relationshipType !== RelationshipType.MANY_TO_MANY
|
||||||
) {
|
) {
|
||||||
if (!column.foreignKey || !column.tableId) {
|
if (!column.foreignKey || !column.tableId) {
|
||||||
throw "Invalid relationship schema"
|
throw "Invalid relationship schema"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { context } from "@budibase/backend-core"
|
import { context, db as dbCore } from "@budibase/backend-core"
|
||||||
import { findHBSBlocks, processObjectSync } from "@budibase/string-templates"
|
import { findHBSBlocks, processObjectSync } from "@budibase/string-templates"
|
||||||
import {
|
import {
|
||||||
Datasource,
|
Datasource,
|
||||||
|
@ -8,15 +8,88 @@ import {
|
||||||
RestAuthConfig,
|
RestAuthConfig,
|
||||||
RestAuthType,
|
RestAuthType,
|
||||||
RestBasicAuthConfig,
|
RestBasicAuthConfig,
|
||||||
|
Row,
|
||||||
|
RestConfig,
|
||||||
SourceName,
|
SourceName,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { getEnvironmentVariables } from "../../utils"
|
import { getEnvironmentVariables } from "../../utils"
|
||||||
import { getDefinitions, getDefinition } from "../../../integrations"
|
import { getDefinitions, getDefinition } from "../../../integrations"
|
||||||
import _ from "lodash"
|
import _ from "lodash"
|
||||||
|
import {
|
||||||
|
BudibaseInternalDB,
|
||||||
|
getDatasourceParams,
|
||||||
|
getTableParams,
|
||||||
|
} from "../../../db/utils"
|
||||||
|
import sdk from "../../index"
|
||||||
|
|
||||||
const ENV_VAR_PREFIX = "env."
|
const ENV_VAR_PREFIX = "env."
|
||||||
|
|
||||||
|
export async function fetch() {
|
||||||
|
// Get internal tables
|
||||||
|
const db = context.getAppDB()
|
||||||
|
const internalTables = await db.allDocs(
|
||||||
|
getTableParams(null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const internal = internalTables.rows.reduce((acc: any, row: Row) => {
|
||||||
|
const sourceId = row.doc.sourceId || "bb_internal"
|
||||||
|
acc[sourceId] = acc[sourceId] || []
|
||||||
|
acc[sourceId].push(row.doc)
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
const bbInternalDb = {
|
||||||
|
...BudibaseInternalDB,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get external datasources
|
||||||
|
const datasources = (
|
||||||
|
await db.allDocs(
|
||||||
|
getDatasourceParams(null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).rows.map(row => row.doc)
|
||||||
|
|
||||||
|
const allDatasources: Datasource[] = await sdk.datasources.removeSecrets([
|
||||||
|
bbInternalDb,
|
||||||
|
...datasources,
|
||||||
|
])
|
||||||
|
|
||||||
|
for (let datasource of allDatasources) {
|
||||||
|
if (datasource.type === dbCore.BUDIBASE_DATASOURCE_TYPE) {
|
||||||
|
datasource.entities = internal[datasource._id!]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [bbInternalDb, ...datasources]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function areRESTVariablesValid(datasource: Datasource) {
|
||||||
|
const restConfig = datasource.config as RestConfig
|
||||||
|
const varNames: string[] = []
|
||||||
|
if (restConfig.dynamicVariables) {
|
||||||
|
for (let variable of restConfig.dynamicVariables) {
|
||||||
|
if (varNames.includes(variable.name)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
varNames.push(variable.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (restConfig.staticVariables) {
|
||||||
|
for (let name of Object.keys(restConfig.staticVariables)) {
|
||||||
|
if (varNames.includes(name)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
varNames.push(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
export function checkDatasourceTypes(schema: Integration, config: any) {
|
export function checkDatasourceTypes(schema: Integration, config: any) {
|
||||||
for (let key of Object.keys(config)) {
|
for (let key of Object.keys(config)) {
|
||||||
if (!schema.datasource[key]) {
|
if (!schema.datasource[key]) {
|
||||||
|
|
|
@ -1,5 +1,49 @@
|
||||||
import { getEnvironmentVariables } from "../../utils"
|
import { getEnvironmentVariables } from "../../utils"
|
||||||
import { processStringSync } from "@budibase/string-templates"
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
|
import { context } from "@budibase/backend-core"
|
||||||
|
import { getQueryParams, isProdAppID } from "../../../db/utils"
|
||||||
|
import { BaseQueryVerbs } from "../../../constants"
|
||||||
|
|
||||||
|
// simple function to append "readable" to all read queries
|
||||||
|
function enrichQueries(input: any) {
|
||||||
|
const wasArray = Array.isArray(input)
|
||||||
|
const queries = wasArray ? input : [input]
|
||||||
|
for (let query of queries) {
|
||||||
|
if (query.queryVerb === BaseQueryVerbs.READ) {
|
||||||
|
query.readable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wasArray ? queries : queries[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function find(queryId: string) {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
const appId = context.getAppId()
|
||||||
|
const query = enrichQueries(await db.get(queryId))
|
||||||
|
// remove properties that could be dangerous in real app
|
||||||
|
if (isProdAppID(appId)) {
|
||||||
|
delete query.fields
|
||||||
|
delete query.parameters
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetch(opts: { enrich: boolean } = { enrich: true }) {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
|
||||||
|
const body = await db.allDocs(
|
||||||
|
getQueryParams(null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const queries = body.rows.map((row: any) => row.doc)
|
||||||
|
if (opts.enrich) {
|
||||||
|
return enrichQueries(queries)
|
||||||
|
} else {
|
||||||
|
return queries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function enrichContext(
|
export async function enrichContext(
|
||||||
fields: Record<string, any>,
|
fields: Record<string, any>,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {
|
||||||
Datasource,
|
Datasource,
|
||||||
FieldSchema,
|
FieldSchema,
|
||||||
FieldType,
|
FieldType,
|
||||||
RelationshipTypes,
|
RelationshipType,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { FieldTypes } from "../../../constants"
|
import { FieldTypes } from "../../../constants"
|
||||||
|
|
||||||
|
@ -19,14 +19,14 @@ function checkForeignKeysAreAutoColumns(datasource: Datasource) {
|
||||||
column => column.type === FieldType.LINK
|
column => column.type === FieldType.LINK
|
||||||
)
|
)
|
||||||
relationships.forEach(relationship => {
|
relationships.forEach(relationship => {
|
||||||
if (relationship.relationshipType === RelationshipTypes.MANY_TO_MANY) {
|
if (relationship.relationshipType === RelationshipType.MANY_TO_MANY) {
|
||||||
const tableId = relationship.through!
|
const tableId = relationship.through!
|
||||||
foreignKeys.push({ key: relationship.throughTo!, tableId })
|
foreignKeys.push({ key: relationship.throughTo!, tableId })
|
||||||
foreignKeys.push({ key: relationship.throughFrom!, tableId })
|
foreignKeys.push({ key: relationship.throughFrom!, tableId })
|
||||||
} else {
|
} else {
|
||||||
const fk = relationship.foreignKey!
|
const fk = relationship.foreignKey!
|
||||||
const oneSide =
|
const oneSide =
|
||||||
relationship.relationshipType === RelationshipTypes.ONE_TO_MANY
|
relationship.relationshipType === RelationshipType.ONE_TO_MANY
|
||||||
foreignKeys.push({
|
foreignKeys.push({
|
||||||
tableId: oneSide ? table._id! : relationship.tableId!,
|
tableId: oneSide ? table._id! : relationship.tableId!,
|
||||||
key: fk,
|
key: fk,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { FieldTypes } from "../constants"
|
import { FieldTypes } from "../constants"
|
||||||
|
import { ValidColumnNameRegex } from "@budibase/shared-core"
|
||||||
|
|
||||||
interface SchemaColumn {
|
interface SchemaColumn {
|
||||||
readonly name: string
|
readonly name: string
|
||||||
|
@ -27,6 +28,7 @@ interface ValidationResults {
|
||||||
schemaValidation: SchemaValidation
|
schemaValidation: SchemaValidation
|
||||||
allValid: boolean
|
allValid: boolean
|
||||||
invalidColumns: Array<string>
|
invalidColumns: Array<string>
|
||||||
|
errors: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
const PARSERS: any = {
|
const PARSERS: any = {
|
||||||
|
@ -69,6 +71,7 @@ export function validate(rows: Rows, schema: Schema): ValidationResults {
|
||||||
schemaValidation: {},
|
schemaValidation: {},
|
||||||
allValid: false,
|
allValid: false,
|
||||||
invalidColumns: [],
|
invalidColumns: [],
|
||||||
|
errors: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
|
@ -79,6 +82,11 @@ export function validate(rows: Rows, schema: Schema): ValidationResults {
|
||||||
// If the columnType is not a string, then it's not present in the schema, and should be added to the invalid columns array
|
// If the columnType is not a string, then it's not present in the schema, and should be added to the invalid columns array
|
||||||
if (typeof columnType !== "string") {
|
if (typeof columnType !== "string") {
|
||||||
results.invalidColumns.push(columnName)
|
results.invalidColumns.push(columnName)
|
||||||
|
} else if (!columnName.match(ValidColumnNameRegex)) {
|
||||||
|
// Check for special characters in column names
|
||||||
|
results.schemaValidation[columnName] = false
|
||||||
|
results.errors[columnName] =
|
||||||
|
"Column names can't contain special characters"
|
||||||
} else if (
|
} else if (
|
||||||
columnData == null &&
|
columnData == null &&
|
||||||
!schema[columnName].constraints?.presence
|
!schema[columnName].constraints?.presence
|
||||||
|
|
|
@ -94,3 +94,4 @@ export enum BuilderSocketEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SocketSessionTTL = 60
|
export const SocketSessionTTL = 60
|
||||||
|
export const ValidColumnNameRegex = /^[_a-zA-Z0-9\s]*$/g
|
||||||
|
|
|
@ -7,9 +7,7 @@ export interface Datasource extends Document {
|
||||||
name?: string
|
name?: string
|
||||||
source: SourceName
|
source: SourceName
|
||||||
// the config is defined by the schema
|
// the config is defined by the schema
|
||||||
config?: {
|
config?: Record<string, any>
|
||||||
[key: string]: string | number | boolean | any[]
|
|
||||||
}
|
|
||||||
plus?: boolean
|
plus?: boolean
|
||||||
entities?: {
|
entities?: {
|
||||||
[key: string]: Table
|
[key: string]: Table
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
import { Document } from "../document"
|
|
||||||
import { View } from "./view"
|
|
||||||
import { RenameColumn } from "../../sdk"
|
|
||||||
import { FieldType } from "./row"
|
|
||||||
|
|
||||||
export enum RelationshipTypes {
|
|
||||||
ONE_TO_MANY = "one-to-many",
|
|
||||||
MANY_TO_ONE = "many-to-one",
|
|
||||||
MANY_TO_MANY = "many-to-many",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AutoReason {
|
|
||||||
FOREIGN_KEY = "foreign_key",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FieldSchema {
|
|
||||||
type: FieldType
|
|
||||||
externalType?: string
|
|
||||||
fieldName?: string
|
|
||||||
name: string
|
|
||||||
sortable?: boolean
|
|
||||||
tableId?: string
|
|
||||||
relationshipType?: RelationshipTypes
|
|
||||||
through?: string
|
|
||||||
foreignKey?: string
|
|
||||||
icon?: string
|
|
||||||
autocolumn?: boolean
|
|
||||||
autoReason?: AutoReason
|
|
||||||
subtype?: string
|
|
||||||
throughFrom?: string
|
|
||||||
throughTo?: string
|
|
||||||
formula?: string
|
|
||||||
formulaType?: string
|
|
||||||
main?: boolean
|
|
||||||
ignoreTimezones?: boolean
|
|
||||||
timeOnly?: boolean
|
|
||||||
lastID?: number
|
|
||||||
useRichText?: boolean | null
|
|
||||||
order?: number
|
|
||||||
width?: number
|
|
||||||
meta?: {
|
|
||||||
toTable: string
|
|
||||||
toKey: string
|
|
||||||
}
|
|
||||||
constraints?: {
|
|
||||||
type?: string
|
|
||||||
email?: boolean
|
|
||||||
inclusion?: string[]
|
|
||||||
length?: {
|
|
||||||
minimum?: string | number | null
|
|
||||||
maximum?: string | number | null
|
|
||||||
}
|
|
||||||
numericality?: {
|
|
||||||
greaterThanOrEqualTo: string | null
|
|
||||||
lessThanOrEqualTo: string | null
|
|
||||||
}
|
|
||||||
presence?:
|
|
||||||
| boolean
|
|
||||||
| {
|
|
||||||
allowEmpty?: boolean
|
|
||||||
}
|
|
||||||
datetime?: {
|
|
||||||
latest: string
|
|
||||||
earliest: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TableSchema {
|
|
||||||
[key: string]: FieldSchema
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Table extends Document {
|
|
||||||
type?: string
|
|
||||||
views?: { [key: string]: View }
|
|
||||||
name: string
|
|
||||||
primary?: string[]
|
|
||||||
schema: TableSchema
|
|
||||||
primaryDisplay?: string
|
|
||||||
sourceId?: string
|
|
||||||
relatedFormula?: string[]
|
|
||||||
constrained?: string[]
|
|
||||||
sql?: boolean
|
|
||||||
indexes?: { [key: string]: any }
|
|
||||||
rows?: { [key: string]: any }
|
|
||||||
created?: boolean
|
|
||||||
rowHeight?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExternalTable extends Table {
|
|
||||||
sourceId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TableRequest extends Table {
|
|
||||||
_rename?: RenameColumn
|
|
||||||
created?: boolean
|
|
||||||
}
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
export enum RelationshipType {
|
||||||
|
ONE_TO_MANY = "one-to-many",
|
||||||
|
MANY_TO_ONE = "many-to-one",
|
||||||
|
MANY_TO_MANY = "many-to-many",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AutoReason {
|
||||||
|
FOREIGN_KEY = "foreign_key",
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from "./table"
|
||||||
|
export * from "./schema"
|
||||||
|
export * from "./constants"
|
|
@ -0,0 +1,98 @@
|
||||||
|
// all added by grid/table when defining the
|
||||||
|
// column size, position and whether it can be viewed
|
||||||
|
import { FieldType } from "../row"
|
||||||
|
import { AutoReason, RelationshipType } from "./constants"
|
||||||
|
|
||||||
|
export interface UIFieldMetadata {
|
||||||
|
order?: number
|
||||||
|
width?: number
|
||||||
|
visible?: boolean
|
||||||
|
icon?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RelationshipFieldMetadata {
|
||||||
|
main?: boolean
|
||||||
|
fieldName?: string
|
||||||
|
tableId?: string
|
||||||
|
// below is used for SQL relationships, needed to define the foreign keys
|
||||||
|
// or the tables used for many-to-many relationships (through)
|
||||||
|
relationshipType?: RelationshipType
|
||||||
|
through?: string
|
||||||
|
foreignKey?: string
|
||||||
|
throughFrom?: string
|
||||||
|
throughTo?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AutoColumnFieldMetadata {
|
||||||
|
autocolumn?: boolean
|
||||||
|
subtype?: string
|
||||||
|
lastID?: number
|
||||||
|
// if the column was turned to an auto-column for SQL, explains why (primary, foreign etc)
|
||||||
|
autoReason?: AutoReason
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NumberFieldMetadata {
|
||||||
|
// used specifically when Budibase generates external tables, this denotes if a number field
|
||||||
|
// is a foreign key used for a many-to-many relationship
|
||||||
|
meta?: {
|
||||||
|
toTable: string
|
||||||
|
toKey: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DateFieldMetadata {
|
||||||
|
ignoreTimezones?: boolean
|
||||||
|
timeOnly?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StringFieldMetadata {
|
||||||
|
useRichText?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormulaFieldMetadata {
|
||||||
|
formula?: string
|
||||||
|
formulaType?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FieldConstraints {
|
||||||
|
type?: string
|
||||||
|
email?: boolean
|
||||||
|
inclusion?: string[]
|
||||||
|
length?: {
|
||||||
|
minimum?: string | number | null
|
||||||
|
maximum?: string | number | null
|
||||||
|
}
|
||||||
|
numericality?: {
|
||||||
|
greaterThanOrEqualTo: string | null
|
||||||
|
lessThanOrEqualTo: string | null
|
||||||
|
}
|
||||||
|
presence?:
|
||||||
|
| boolean
|
||||||
|
| {
|
||||||
|
allowEmpty?: boolean
|
||||||
|
}
|
||||||
|
datetime?: {
|
||||||
|
latest: string
|
||||||
|
earliest: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FieldSchema
|
||||||
|
extends UIFieldMetadata,
|
||||||
|
DateFieldMetadata,
|
||||||
|
RelationshipFieldMetadata,
|
||||||
|
AutoColumnFieldMetadata,
|
||||||
|
StringFieldMetadata,
|
||||||
|
FormulaFieldMetadata,
|
||||||
|
NumberFieldMetadata {
|
||||||
|
type: FieldType
|
||||||
|
name: string
|
||||||
|
sortable?: boolean
|
||||||
|
// only used by external databases, to denote the real type
|
||||||
|
externalType?: string
|
||||||
|
constraints?: FieldConstraints
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableSchema {
|
||||||
|
[key: string]: FieldSchema
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { Document } from "../../document"
|
||||||
|
import { View } from "../view"
|
||||||
|
import { RenameColumn } from "../../../sdk"
|
||||||
|
import { TableSchema } from "./schema"
|
||||||
|
|
||||||
|
export interface Table extends Document {
|
||||||
|
type?: string
|
||||||
|
views?: { [key: string]: View }
|
||||||
|
name: string
|
||||||
|
primary?: string[]
|
||||||
|
schema: TableSchema
|
||||||
|
primaryDisplay?: string
|
||||||
|
sourceId?: string
|
||||||
|
relatedFormula?: string[]
|
||||||
|
constrained?: string[]
|
||||||
|
sql?: boolean
|
||||||
|
indexes?: { [key: string]: any }
|
||||||
|
rows?: { [key: string]: any }
|
||||||
|
created?: boolean
|
||||||
|
rowHeight?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalTable extends Table {
|
||||||
|
sourceId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableRequest extends Table {
|
||||||
|
_rename?: RenameColumn
|
||||||
|
created?: boolean
|
||||||
|
}
|
|
@ -507,17 +507,17 @@ export async function configChecklist(ctx: Ctx) {
|
||||||
smtp: {
|
smtp: {
|
||||||
checked: !!smtpConfig,
|
checked: !!smtpConfig,
|
||||||
label: "Set up email",
|
label: "Set up email",
|
||||||
link: "/builder/portal/manage/email",
|
link: "/builder/portal/settings/email",
|
||||||
},
|
},
|
||||||
adminUser: {
|
adminUser: {
|
||||||
checked: userExists,
|
checked: userExists,
|
||||||
label: "Create your first user",
|
label: "Create your first user",
|
||||||
link: "/builder/portal/manage/users",
|
link: "/builder/portal/users/users",
|
||||||
},
|
},
|
||||||
sso: {
|
sso: {
|
||||||
checked: !!googleConfig || !!oidcConfig,
|
checked: !!googleConfig || !!oidcConfig,
|
||||||
label: "Set up single sign-on",
|
label: "Set up single sign-on",
|
||||||
link: "/builder/portal/manage/auth",
|
link: "/builder/portal/settings/auth",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue