Merge branch 'master' of github.com:Budibase/budibase into develop
This commit is contained in:
commit
bb032852e5
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.8.18-alpha.5",
|
"version": "2.8.21",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
@ -19,4 +19,4 @@
|
||||||
"loadEnvFiles": false
|
"loadEnvFiles": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue