Merge remote-tracking branch 'origin/master' into fix/oidc-custom-icons-fixed
This commit is contained in:
commit
a61cf5fa45
|
@ -12,31 +12,22 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# - name: Fail if not a tag
|
- name: Fail if not a tag
|
||||||
# run: |
|
|
||||||
# if [[ $GITHUB_REF != refs/tags/* ]]; then
|
|
||||||
# echo "Workflow Dispatch can only be run on tags"
|
|
||||||
# exit 1
|
|
||||||
# fi
|
|
||||||
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
# with:
|
|
||||||
# fetch-depth: 0
|
|
||||||
|
|
||||||
# - name: Fail if tag is not in master
|
|
||||||
# run: |
|
|
||||||
# if ! git merge-base --is-ancestor ${{ github.sha }} origin/master; then
|
|
||||||
# echo "Tag is not in master. This pipeline can only execute tags that are present on the master branch"
|
|
||||||
# exit 1
|
|
||||||
# fi
|
|
||||||
|
|
||||||
- name: Pull values.yaml from budibase-infra
|
|
||||||
run: |
|
run: |
|
||||||
curl -H "Authorization: token ${{ secrets.GH_ACCESS_TOKEN }}" \
|
if [[ $GITHUB_REF != refs/tags/* ]]; then
|
||||||
-H 'Accept: application/vnd.github.v3.raw' \
|
echo "Workflow Dispatch can only be run on tags"
|
||||||
-o values.production.yaml \
|
exit 1
|
||||||
-L https://api.github.com/repos/budibase/budibase-infra/contents/kubernetes/values.yaml
|
fi
|
||||||
wc -l values.production.yaml
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Fail if tag is not in master
|
||||||
|
run: |
|
||||||
|
if ! git merge-base --is-ancestor ${{ github.sha }} origin/master; then
|
||||||
|
echo "Tag is not in master. This pipeline can only execute tags that are present on the master branch"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Get the latest budibase release version
|
- name: Get the latest budibase release version
|
||||||
id: version
|
id: version
|
||||||
|
@ -48,29 +39,10 @@ jobs:
|
||||||
fi
|
fi
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Configure AWS Credentials
|
- uses: passeidireto/trigger-external-workflow-action@main
|
||||||
uses: aws-actions/configure-aws-credentials@v1
|
env:
|
||||||
|
PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }}
|
||||||
with:
|
with:
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
repository: budibase/budibase-deploys
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
event: budicloud-prod-deploy
|
||||||
aws-region: eu-west-1
|
github_pat: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||||
|
|
||||||
- name: Deploy to EKS
|
|
||||||
uses: craftech-io/eks-helm-deploy-action@v1
|
|
||||||
with:
|
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS__KEY_ID }}
|
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
aws-region: eu-west-1
|
|
||||||
cluster-name: budibase-eks-production
|
|
||||||
config-files: values.production.yaml
|
|
||||||
chart-path: charts/budibase
|
|
||||||
namespace: budibase
|
|
||||||
values: globals.appVersion=v${{ env.RELEASE_VERSION }},services.couchdb.url=${{ secrets.PRODUCTION_COUCHDB_URL }},services.couchdb.password=${{ secrets.PRODUCTION_COUCHDB_PASSWORD }}
|
|
||||||
name: budibase-prod
|
|
||||||
|
|
||||||
- name: Discord Webhook Action
|
|
||||||
uses: tsickert/discord-webhook@v4.0.0
|
|
||||||
with:
|
|
||||||
webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }}
|
|
||||||
content: "Production Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Cloud."
|
|
||||||
embed-title: ${{ env.RELEASE_VERSION }}
|
|
||||||
|
|
|
@ -25,50 +25,17 @@ jobs:
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
- name: Get the latest budibase release version
|
- name: Get the latest budibase release version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
release_version=$(cat lerna.json | jq -r '.version')
|
release_version=$(cat lerna.json | jq -r '.version')
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
- name: Configure AWS Credentials
|
|
||||||
uses: aws-actions/configure-aws-credentials@v1
|
|
||||||
with:
|
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
aws-region: eu-west-1
|
|
||||||
|
|
||||||
- name: Pull values.yaml from budibase-infra
|
- uses: passeidireto/trigger-external-workflow-action@main
|
||||||
run: |
|
|
||||||
curl -H "Authorization: token ${{ secrets.GH_ACCESS_TOKEN }}" \
|
|
||||||
-H 'Accept: application/vnd.github.v3.raw' \
|
|
||||||
-o values.preprod.yaml \
|
|
||||||
-L https://api.github.com/repos/budibase/budibase-infra/contents/kubernetes/budibase-preprod/values.yaml
|
|
||||||
wc -l values.preprod.yaml
|
|
||||||
- name: Deploy to Preprod Environment
|
|
||||||
uses: budibase/helm@v1.8.0
|
|
||||||
with:
|
|
||||||
release: budibase-preprod
|
|
||||||
namespace: budibase
|
|
||||||
chart: charts/budibase
|
|
||||||
token: ${{ github.token }}
|
|
||||||
helm: helm3
|
|
||||||
values: |
|
|
||||||
globals:
|
|
||||||
appVersion: v${{ env.RELEASE_VERSION }}
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
nginx: true
|
|
||||||
value-files: >-
|
|
||||||
[
|
|
||||||
"values.preprod.yaml"
|
|
||||||
]
|
|
||||||
env:
|
env:
|
||||||
KUBECONFIG_FILE: '${{ secrets.PREPROD_KUBECONFIG }}'
|
PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }}
|
||||||
|
|
||||||
- name: Discord Webhook Action
|
|
||||||
uses: tsickert/discord-webhook@v4.0.0
|
|
||||||
with:
|
with:
|
||||||
webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }}
|
repository: budibase/budibase-deploys
|
||||||
content: "Preprod Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Pre-prod."
|
event: budicloud-preprod-deploy
|
||||||
embed-title: ${{ env.RELEASE_VERSION }}
|
github_pat: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.7.11",
|
"version": "2.7.33",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/backend-core",
|
"packages/backend-core",
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
border-color: var(--spectrum-global-color-gray-400);
|
border-color: var(--spectrum-global-color-gray-400);
|
||||||
}
|
}
|
||||||
/* Toolbar button color */
|
/* Toolbar button color */
|
||||||
:global(.EasyMDEContainer .editor-toolbar button i) {
|
:global(.EasyMDEContainer .editor-toolbar button) {
|
||||||
color: var(--spectrum-global-color-gray-800);
|
color: var(--spectrum-global-color-gray-800);
|
||||||
}
|
}
|
||||||
/* Separator between toolbar buttons*/
|
/* Separator between toolbar buttons*/
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
<meta charset='utf8'>
|
<meta charset='utf8'>
|
||||||
<meta name='viewport' content='width=device-width'>
|
<meta name='viewport' content='width=device-width'>
|
||||||
<title>Budibase</title>
|
<title>Budibase</title>
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
<link href="/builder/fonts/source-sans-pro/400.css" rel="stylesheet" />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap"
|
<link href="/builder/fonts/source-sans-pro/600.css" rel="stylesheet" />
|
||||||
rel="stylesheet" />
|
<link href="/builder/fonts/source-sans-pro/700.css" rel="stylesheet" />
|
||||||
|
<link href="/builder/fonts/remixicon.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="app">
|
<body id="app">
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
"@codemirror/state": "^6.2.0",
|
"@codemirror/state": "^6.2.0",
|
||||||
"@codemirror/theme-one-dark": "^6.1.2",
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
"@codemirror/view": "^6.11.2",
|
"@codemirror/view": "^6.11.2",
|
||||||
|
"@fontsource/source-sans-pro": "^5.0.3",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||||
|
@ -122,6 +123,7 @@
|
||||||
"tsconfig-paths": "4.0.0",
|
"tsconfig-paths": "4.0.0",
|
||||||
"typescript": "4.7.3",
|
"typescript": "4.7.3",
|
||||||
"vite": "^3.0.8",
|
"vite": "^3.0.8",
|
||||||
|
"vite-plugin-static-copy": "^0.16.0",
|
||||||
"vitest": "^0.29.2"
|
"vitest": "^0.29.2"
|
||||||
},
|
},
|
||||||
"nx": {
|
"nx": {
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 787 B |
|
@ -12,7 +12,7 @@
|
||||||
import { automationStore, selectedAutomation } from "builderStore"
|
import { automationStore, selectedAutomation } from "builderStore"
|
||||||
import { admin, licensing } from "stores/portal"
|
import { admin, licensing } from "stores/portal"
|
||||||
import { externalActions } from "./ExternalActions"
|
import { externalActions } from "./ExternalActions"
|
||||||
import { TriggerStepID } from "constants/backend/automations"
|
import { TriggerStepID, ActionStepID } from "constants/backend/automations"
|
||||||
import { checkForCollectStep } from "builderStore/utils"
|
import { checkForCollectStep } from "builderStore/utils"
|
||||||
|
|
||||||
export let blockIdx
|
export let blockIdx
|
||||||
|
@ -149,7 +149,7 @@
|
||||||
<div class="item-body">
|
<div class="item-body">
|
||||||
<Icon name={action.icon} />
|
<Icon name={action.icon} />
|
||||||
<Body size="XS">{action.name}</Body>
|
<Body size="XS">{action.name}</Body>
|
||||||
{#if isDisabled && !syncAutomationsEnabled}
|
{#if isDisabled && !syncAutomationsEnabled && action.stepId === ActionStepID.COLLECT}
|
||||||
<div class="tag-color">
|
<div class="tag-color">
|
||||||
<Tags>
|
<Tags>
|
||||||
<Tag icon="LockClosed">Business</Tag>
|
<Tag icon="LockClosed">Business</Tag>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { tables } from "stores/backend"
|
import { datasources, tables } from "stores/backend"
|
||||||
import EditRolesButton from "./buttons/EditRolesButton.svelte"
|
import EditRolesButton from "./buttons/EditRolesButton.svelte"
|
||||||
import { TableNames } from "constants"
|
import { TableNames } from "constants"
|
||||||
import { Grid } from "@budibase/frontend-core"
|
import { Grid } from "@budibase/frontend-core"
|
||||||
|
@ -26,6 +26,18 @@
|
||||||
$: id = $tables.selected?._id
|
$: id = $tables.selected?._id
|
||||||
$: isUsersTable = id === TableNames.USERS
|
$: isUsersTable = id === TableNames.USERS
|
||||||
$: isInternal = $tables.selected?.type !== "external"
|
$: isInternal = $tables.selected?.type !== "external"
|
||||||
|
|
||||||
|
const handleGridTableUpdate = async e => {
|
||||||
|
tables.replaceTable(id, e.detail)
|
||||||
|
|
||||||
|
// We need to refresh datasources when an external table changes.
|
||||||
|
// Type "external" may exist - sometimes type is "table" and sometimes it
|
||||||
|
// is "external" - it has different meanings in different endpoints.
|
||||||
|
// If we check both these then we hopefully catch all external tables.
|
||||||
|
if (e.detail?.type === "external" || e.detail?.sql) {
|
||||||
|
await datasources.fetch()
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
@ -37,7 +49,7 @@
|
||||||
allowDeleteRows={!isUsersTable}
|
allowDeleteRows={!isUsersTable}
|
||||||
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
|
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
|
||||||
showAvatars={false}
|
showAvatars={false}
|
||||||
on:updatetable={e => tables.replaceTable(id, e.detail)}
|
on:updatetable={handleGridTableUpdate}
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="controls">
|
<svelte:fragment slot="controls">
|
||||||
{#if isInternal}
|
{#if isInternal}
|
||||||
|
|
|
@ -76,6 +76,10 @@ export function getBindings({
|
||||||
// will be replaced by the main array binding
|
// will be replaced by the main array binding
|
||||||
readableBinding: label,
|
readableBinding: label,
|
||||||
runtimeBinding: binding,
|
runtimeBinding: binding,
|
||||||
|
display: {
|
||||||
|
name: label,
|
||||||
|
type: field.name === FIELDS.LINK.name ? "Array" : field.name,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return bindings
|
return bindings
|
||||||
|
|
|
@ -59,7 +59,6 @@
|
||||||
$: valid = getErrorCount(errors) === 0 && allRequiredAttributesSet()
|
$: valid = getErrorCount(errors) === 0 && allRequiredAttributesSet()
|
||||||
$: isManyToMany = relationshipType === RelationshipTypes.MANY_TO_MANY
|
$: isManyToMany = relationshipType === RelationshipTypes.MANY_TO_MANY
|
||||||
$: isManyToOne = relationshipType === RelationshipTypes.MANY_TO_ONE
|
$: isManyToOne = relationshipType === RelationshipTypes.MANY_TO_ONE
|
||||||
$: toRelationship.relationshipType = fromRelationship?.relationshipType
|
|
||||||
|
|
||||||
function getTable(id) {
|
function getTable(id) {
|
||||||
return plusTables.find(table => table._id === id)
|
return plusTables.find(table => table._id === id)
|
||||||
|
@ -180,6 +179,16 @@
|
||||||
return getErrorCount(errors) === 0
|
return getErrorCount(errors) === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function otherRelationshipType(type) {
|
||||||
|
if (type === RelationshipTypes.MANY_TO_ONE) {
|
||||||
|
return RelationshipTypes.ONE_TO_MANY
|
||||||
|
} else if (type === RelationshipTypes.ONE_TO_MANY) {
|
||||||
|
return RelationshipTypes.MANY_TO_ONE
|
||||||
|
} else if (type === RelationshipTypes.MANY_TO_MANY) {
|
||||||
|
return RelationshipTypes.MANY_TO_MANY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildRelationships() {
|
function buildRelationships() {
|
||||||
const id = Helpers.uuid()
|
const id = Helpers.uuid()
|
||||||
//Map temporary variables
|
//Map temporary variables
|
||||||
|
@ -200,6 +209,7 @@
|
||||||
...toRelationship,
|
...toRelationship,
|
||||||
tableId: fromId,
|
tableId: fromId,
|
||||||
name: fromColumn,
|
name: fromColumn,
|
||||||
|
relationshipType: otherRelationshipType(relationshipType),
|
||||||
through: throughId,
|
through: throughId,
|
||||||
type: "link",
|
type: "link",
|
||||||
_id: id,
|
_id: id,
|
||||||
|
|
|
@ -93,6 +93,7 @@
|
||||||
try {
|
try {
|
||||||
await beforeSave()
|
await beforeSave()
|
||||||
table = await tables.save(newTable)
|
table = await tables.save(newTable)
|
||||||
|
await datasources.fetch()
|
||||||
await afterSave(table)
|
await afterSave(table)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notifications.error(e)
|
notifications.error(e)
|
||||||
|
|
|
@ -65,6 +65,7 @@
|
||||||
const updatedTable = cloneDeep(table)
|
const updatedTable = cloneDeep(table)
|
||||||
updatedTable.name = updatedName
|
updatedTable.name = updatedName
|
||||||
await tables.save(updatedTable)
|
await tables.save(updatedTable)
|
||||||
|
await datasources.fetch()
|
||||||
notifications.success("Table renamed successfully")
|
notifications.success("Table renamed successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,18 @@
|
||||||
faFileArrowUp,
|
faFileArrowUp,
|
||||||
faChevronLeft,
|
faChevronLeft,
|
||||||
faCircleInfo,
|
faCircleInfo,
|
||||||
|
faBold,
|
||||||
|
faItalic,
|
||||||
|
faHeading,
|
||||||
|
faQuoteLeft,
|
||||||
|
faListUl,
|
||||||
|
faListOl,
|
||||||
|
faLink,
|
||||||
|
faImage,
|
||||||
|
faEye,
|
||||||
|
faColumns,
|
||||||
|
faArrowsAlt,
|
||||||
|
faQuestionCircle,
|
||||||
} from "@fortawesome/free-solid-svg-icons"
|
} from "@fortawesome/free-solid-svg-icons"
|
||||||
import { faGithub, faDiscord } from "@fortawesome/free-brands-svg-icons"
|
import { faGithub, faDiscord } from "@fortawesome/free-brands-svg-icons"
|
||||||
|
|
||||||
|
@ -22,7 +34,22 @@
|
||||||
faEnvelope,
|
faEnvelope,
|
||||||
faFileArrowUp,
|
faFileArrowUp,
|
||||||
faChevronLeft,
|
faChevronLeft,
|
||||||
faCircleInfo
|
faCircleInfo,
|
||||||
|
|
||||||
|
// -- Required for easyMDE use in the builder.
|
||||||
|
faBold,
|
||||||
|
faItalic,
|
||||||
|
faHeading,
|
||||||
|
faQuoteLeft,
|
||||||
|
faListUl,
|
||||||
|
faListOl,
|
||||||
|
faLink,
|
||||||
|
faImage,
|
||||||
|
faEye,
|
||||||
|
faColumns,
|
||||||
|
faArrowsAlt,
|
||||||
|
faQuestionCircle
|
||||||
|
// --
|
||||||
)
|
)
|
||||||
dom.watch()
|
dom.watch()
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
runtimeToReadableBinding,
|
runtimeToReadableBinding,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
import { store } from "builderStore"
|
|
||||||
import { convertToJS } from "@budibase/string-templates"
|
import { convertToJS } from "@budibase/string-templates"
|
||||||
import { admin } from "stores/portal"
|
import { admin } from "stores/portal"
|
||||||
import CodeEditor from "../CodeEditor/CodeEditor.svelte"
|
import CodeEditor from "../CodeEditor/CodeEditor.svelte"
|
||||||
|
@ -339,16 +339,18 @@
|
||||||
</Tab>
|
</Tab>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="drawer-actions">
|
<div class="drawer-actions">
|
||||||
|
{#if drawerActions?.hide}
|
||||||
<Button
|
<Button
|
||||||
secondary
|
secondary
|
||||||
quiet
|
quiet
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
store.actions.settings.propertyFocus(null)
|
|
||||||
drawerActions.hide()
|
drawerActions.hide()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
{/if}
|
||||||
|
{#if bindingDrawerActions?.save}
|
||||||
<Button
|
<Button
|
||||||
cta
|
cta
|
||||||
disabled={!valid}
|
disabled={!valid}
|
||||||
|
@ -358,6 +360,7 @@
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
.map(([name, categoryBindings]) => ({
|
.map(([name, categoryBindings]) => ({
|
||||||
name,
|
name,
|
||||||
bindings: categoryBindings?.filter(binding => {
|
bindings: categoryBindings?.filter(binding => {
|
||||||
return binding.readableBinding.match(searchRgx)
|
return !search || binding.readableBinding.match(searchRgx)
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
.filter(category => {
|
.filter(category => {
|
||||||
|
@ -46,7 +46,11 @@
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
$: filteredHelpers = helpers?.filter(helper => {
|
$: filteredHelpers = helpers?.filter(helper => {
|
||||||
return helper.label.match(searchRgx) || helper.description.match(searchRgx)
|
return (
|
||||||
|
!search ||
|
||||||
|
helper.label.match(searchRgx) ||
|
||||||
|
helper.description.match(searchRgx)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const getHelperExample = (helper, js) => {
|
const getHelperExample = (helper, js) => {
|
||||||
|
@ -124,9 +128,6 @@
|
||||||
<span
|
<span
|
||||||
class="search-input-icon"
|
class="search-input-icon"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
if (!search) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
search = null
|
search = null
|
||||||
}}
|
}}
|
||||||
class:searching={search}
|
class:searching={search}
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Drawer bind:this={bindingDrawer} {title}>
|
<Drawer bind:this={bindingDrawer} {title} headless>
|
||||||
<svelte:fragment slot="description">
|
<svelte:fragment slot="description">
|
||||||
Add the objects on the left to enrich your text.
|
Add the objects on the left to enrich your text.
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
runtimeToReadableBinding,
|
runtimeToReadableBinding,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
|
|
||||||
import { store } from "builderStore"
|
|
||||||
|
|
||||||
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
||||||
import { createEventDispatcher, setContext } from "svelte"
|
import { createEventDispatcher, setContext } from "svelte"
|
||||||
import { isJSBinding } from "@budibase/string-templates"
|
import { isJSBinding } from "@budibase/string-templates"
|
||||||
|
@ -36,7 +34,6 @@
|
||||||
|
|
||||||
const saveBinding = () => {
|
const saveBinding = () => {
|
||||||
onChange(tempValue)
|
onChange(tempValue)
|
||||||
store.actions.settings.propertyFocus(null)
|
|
||||||
onBlur()
|
onBlur()
|
||||||
bindingDrawer.hide()
|
bindingDrawer.hide()
|
||||||
}
|
}
|
||||||
|
@ -70,7 +67,6 @@
|
||||||
<div
|
<div
|
||||||
class="icon"
|
class="icon"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
store.actions.settings.propertyFocus(key)
|
|
||||||
bindingDrawer.show()
|
bindingDrawer.show()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -73,10 +73,6 @@
|
||||||
if (highlighted) {
|
if (highlighted) {
|
||||||
store.actions.settings.highlight(null)
|
store.actions.settings.highlight(null)
|
||||||
}
|
}
|
||||||
// To fix focus 'affect' when property is target of a drawer other actions in the builder.
|
|
||||||
if (propertyFocus) {
|
|
||||||
store.actions.settings.propertyFocus(null)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -186,7 +186,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
div :global(.CodeMirror) {
|
div :global(.CodeMirror) {
|
||||||
width: var(--code-mirror-width) !important;
|
|
||||||
height: var(--code-mirror-height) !important;
|
height: var(--code-mirror-height) !important;
|
||||||
border-radius: var(--border-radius-s);
|
border-radius: var(--border-radius-s);
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
$: platformTitle =
|
$: platformTitle =
|
||||||
!$auth.user && platformTitleText ? platformTitleText : "Budibase"
|
!$auth.user && platformTitleText ? platformTitleText : "Budibase"
|
||||||
|
|
||||||
$: faviconUrl = $organisation.faviconUrl || "https://i.imgur.com/Xhdt1YP.png"
|
$: faviconUrl = $organisation.faviconUrl || "/builder/bblogo.png"
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await organisation.init()
|
await organisation.init()
|
||||||
|
@ -27,6 +27,6 @@
|
||||||
<link rel="icon" href={faviconUrl} />
|
<link rel="icon" href={faviconUrl} />
|
||||||
{:else}
|
{:else}
|
||||||
<!-- A default must be set or the browser defaults to favicon.ico behaviour -->
|
<!-- A default must be set or the browser defaults to favicon.ico behaviour -->
|
||||||
<link rel="icon" href={"https://i.imgur.com/Xhdt1YP.png"} />
|
<link rel="icon" href={"/builder/bblogo.png"} />
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
<div tabindex="-1" class="exampleApp">
|
<div tabindex="-1" class="exampleApp">
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<img alt="Budibase Logo" src={"https://i.imgur.com/Xhdt1YP.png"} />
|
<img alt="Budibase Logo" src={"/builder/bblogo.png"} />
|
||||||
<h1>{name}</h1>
|
<h1>{name}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav">Home</div>
|
<div class="nav">Home</div>
|
||||||
|
|
|
@ -113,6 +113,10 @@ export function createDatasourcesStore() {
|
||||||
...state,
|
...state,
|
||||||
list: [...state.list, datasource],
|
list: [...state.list, datasource],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// If this is a new datasource then we should refresh the tables list,
|
||||||
|
// because otherwise we'll never see the new tables
|
||||||
|
tables.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update existing datasource
|
// Update existing datasource
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { get, writable, derived } from "svelte/store"
|
import { get, writable, derived } from "svelte/store"
|
||||||
import { datasources } from "./"
|
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { SWITCHABLE_TYPES } from "constants/backend"
|
import { SWITCHABLE_TYPES } from "constants/backend"
|
||||||
|
@ -63,7 +62,6 @@ export function createTablesStore() {
|
||||||
|
|
||||||
const savedTable = await API.saveTable(updatedTable)
|
const savedTable = await API.saveTable(updatedTable)
|
||||||
replaceTable(savedTable._id, savedTable)
|
replaceTable(savedTable._id, savedTable)
|
||||||
await datasources.fetch()
|
|
||||||
select(savedTable._id)
|
select(savedTable._id)
|
||||||
return savedTable
|
return savedTable
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { svelte } from "@sveltejs/vite-plugin-svelte"
|
import { svelte } from "@sveltejs/vite-plugin-svelte"
|
||||||
import replace from "@rollup/plugin-replace"
|
import replace from "@rollup/plugin-replace"
|
||||||
import { defineConfig, loadEnv } from "vite"
|
import { defineConfig, loadEnv } from "vite"
|
||||||
|
import { viteStaticCopy } from "vite-plugin-static-copy"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
const ignoredWarnings = [
|
const ignoredWarnings = [
|
||||||
|
@ -59,6 +60,18 @@ export default defineConfig(({ mode }) => {
|
||||||
),
|
),
|
||||||
"process.env.SENTRY_DSN": JSON.stringify(process.env.SENTRY_DSN),
|
"process.env.SENTRY_DSN": JSON.stringify(process.env.SENTRY_DSN),
|
||||||
}),
|
}),
|
||||||
|
viteStaticCopy({
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
src: "../../node_modules/@fontsource/source-sans-pro",
|
||||||
|
dest: "fonts",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "../../node_modules/remixicon/fonts/*",
|
||||||
|
dest: "fonts",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
exclude: ["@roxi/routify"],
|
exclude: ["@roxi/routify"],
|
||||||
|
|
|
@ -180,10 +180,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
{#if !hideLogo}
|
{#if !hideLogo}
|
||||||
<img
|
<img src={logoUrl || "/builder/bblogo.png"} alt={title} />
|
||||||
src={logoUrl || "https://i.imgur.com/Xhdt1YP.png"}
|
|
||||||
alt={title}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if !hideTitle && title}
|
{#if !hideTitle && title}
|
||||||
<Heading size="S">{title}</Heading>
|
<Heading size="S">{title}</Heading>
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<img
|
<img
|
||||||
class="logo"
|
class="logo"
|
||||||
alt="logo"
|
alt="logo"
|
||||||
src={logoUrl || "https://i.imgur.com/Xhdt1YP.png"}
|
src={logoUrl || "/builder/bblogo.png"}
|
||||||
height="48"
|
height="48"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -283,7 +283,7 @@
|
||||||
|
|
||||||
// Skip if the value is the same
|
// Skip if the value is the same
|
||||||
if (!skipCheck && fieldState.value === value) {
|
if (!skipCheck && fieldState.value === value) {
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update field state
|
// Update field state
|
||||||
|
@ -295,7 +295,7 @@
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
||||||
return !error
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clears the value of a certain field back to the default value
|
// Clears the value of a certain field back to the default value
|
||||||
|
@ -376,8 +376,9 @@
|
||||||
deregister,
|
deregister,
|
||||||
validate: () => {
|
validate: () => {
|
||||||
// Validate the field by force setting the same value again
|
// Validate the field by force setting the same value again
|
||||||
const { fieldState } = get(getField(field))
|
const fieldInfo = getField(field)
|
||||||
return setValue(fieldState.value, true)
|
setValue(get(fieldInfo).fieldState.value, true)
|
||||||
|
return !get(fieldInfo).fieldState.error
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,30 @@
|
||||||
import { authStore } from "../stores/auth.js"
|
import { authStore } from "../stores/auth.js"
|
||||||
|
import { appStore } from "../stores/app.js"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { Constants } from "@budibase/frontend-core"
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
const getLicense = () => {
|
const getUserLicense = () => {
|
||||||
const user = get(authStore)
|
const user = get(authStore)
|
||||||
if (user) {
|
if (user) {
|
||||||
return user.license
|
return user.license
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getAppLicenseType = () => {
|
||||||
|
const appDef = get(appStore)
|
||||||
|
if (appDef?.licenseType) {
|
||||||
|
return appDef.licenseType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const isFreePlan = () => {
|
export const isFreePlan = () => {
|
||||||
const license = getLicense()
|
let licenseType = getAppLicenseType()
|
||||||
if (license) {
|
if (!licenseType) {
|
||||||
return license.plan.type === Constants.PlanType.FREE
|
const license = getUserLicense()
|
||||||
|
licenseType = license?.plan?.type
|
||||||
|
}
|
||||||
|
if (licenseType) {
|
||||||
|
return licenseType === Constants.PlanType.FREE
|
||||||
} else {
|
} else {
|
||||||
// safety net - no license means free plan
|
// safety net - no license means free plan
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -90,12 +90,12 @@ export const deriveStores = context => {
|
||||||
// Update local state
|
// Update local state
|
||||||
table.set(newTable)
|
table.set(newTable)
|
||||||
|
|
||||||
|
// Update server
|
||||||
|
await API.saveTable(newTable)
|
||||||
|
|
||||||
// Broadcast change to external state can be updated, as this change
|
// Broadcast change to external state can be updated, as this change
|
||||||
// will not be received by the builder websocket because we caused it ourselves
|
// will not be received by the builder websocket because we caused it ourselves
|
||||||
dispatch("updatetable", newTable)
|
dispatch("updatetable", newTable)
|
||||||
|
|
||||||
// Update server
|
|
||||||
await API.saveTable(newTable)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { writable, derived, get } from "svelte/store"
|
||||||
import { fetchData } from "../../../fetch/fetchData"
|
import { fetchData } from "../../../fetch/fetchData"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import { NewRowID, RowPageSize } from "../lib/constants"
|
import { NewRowID, RowPageSize } from "../lib/constants"
|
||||||
|
import { tick } from "svelte"
|
||||||
|
|
||||||
const initialSortState = {
|
const initialSortState = {
|
||||||
column: null,
|
column: null,
|
||||||
|
@ -124,13 +125,22 @@ export const deriveStores = context => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Subscribe to changes of this fetch model
|
// Subscribe to changes of this fetch model
|
||||||
unsubscribe = newFetch.subscribe($fetch => {
|
unsubscribe = newFetch.subscribe(async $fetch => {
|
||||||
if ($fetch.loaded && !$fetch.loading) {
|
if ($fetch.loaded && !$fetch.loading) {
|
||||||
hasNextPage.set($fetch.hasNextPage)
|
hasNextPage.set($fetch.hasNextPage)
|
||||||
const $instanceLoaded = get(instanceLoaded)
|
const $instanceLoaded = get(instanceLoaded)
|
||||||
const resetRows = $fetch.resetKey !== lastResetKey
|
const resetRows = $fetch.resetKey !== lastResetKey
|
||||||
|
const previousResetKey = lastResetKey
|
||||||
lastResetKey = $fetch.resetKey
|
lastResetKey = $fetch.resetKey
|
||||||
|
|
||||||
|
// If resetting rows due to a table change, wipe data and wait for
|
||||||
|
// derived stores to compute. This prevents stale data being passed
|
||||||
|
// to cells when we save the new schema.
|
||||||
|
if (!$instanceLoaded && previousResetKey) {
|
||||||
|
rows.set([])
|
||||||
|
await tick()
|
||||||
|
}
|
||||||
|
|
||||||
// Reset state properties when dataset changes
|
// Reset state properties when dataset changes
|
||||||
if (!$instanceLoaded || resetRows) {
|
if (!$instanceLoaded || resetRows) {
|
||||||
table.set($fetch.definition)
|
table.set($fetch.definition)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 01fbc8670021c5a275c2a1a36ee18b984eeafad5
|
Subproject commit f4b8449aac9bd265214396afbdce7ff984a2ae34
|
|
@ -1,53 +1,54 @@
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
import {
|
import {
|
||||||
|
createAllSearchIndex,
|
||||||
createLinkView,
|
createLinkView,
|
||||||
createRoutingView,
|
createRoutingView,
|
||||||
createAllSearchIndex,
|
|
||||||
} from "../../db/views/staticViews"
|
} from "../../db/views/staticViews"
|
||||||
import { createApp, deleteApp } from "../../utilities/fileSystem"
|
|
||||||
import {
|
import {
|
||||||
|
backupClientLibrary,
|
||||||
|
createApp,
|
||||||
|
deleteApp,
|
||||||
|
revertClientLibrary,
|
||||||
|
updateClientLibrary,
|
||||||
|
} from "../../utilities/fileSystem"
|
||||||
|
import {
|
||||||
|
AppStatus,
|
||||||
|
DocumentType,
|
||||||
generateAppID,
|
generateAppID,
|
||||||
|
generateDevAppID,
|
||||||
getLayoutParams,
|
getLayoutParams,
|
||||||
getScreenParams,
|
getScreenParams,
|
||||||
generateDevAppID,
|
|
||||||
DocumentType,
|
|
||||||
AppStatus,
|
|
||||||
} from "../../db/utils"
|
} from "../../db/utils"
|
||||||
import {
|
import {
|
||||||
db as dbCore,
|
|
||||||
roles,
|
|
||||||
cache,
|
cache,
|
||||||
tenancy,
|
|
||||||
context,
|
context,
|
||||||
|
db as dbCore,
|
||||||
|
env as envCore,
|
||||||
|
ErrorCode,
|
||||||
events,
|
events,
|
||||||
migrations,
|
migrations,
|
||||||
objectStore,
|
objectStore,
|
||||||
ErrorCode,
|
roles,
|
||||||
env as envCore,
|
tenancy,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { USERS_TABLE_SCHEMA } from "../../constants"
|
import { USERS_TABLE_SCHEMA } from "../../constants"
|
||||||
import {
|
import {
|
||||||
DEFAULT_BB_DATASOURCE_ID,
|
|
||||||
buildDefaultDocs,
|
buildDefaultDocs,
|
||||||
|
DEFAULT_BB_DATASOURCE_ID,
|
||||||
} from "../../db/defaultData/datasource_bb_default"
|
} from "../../db/defaultData/datasource_bb_default"
|
||||||
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
|
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
|
||||||
import { stringToReadStream, isQsTrue } from "../../utilities"
|
import { stringToReadStream } from "../../utilities"
|
||||||
import { getLocksById, doesUserHaveLock } from "../../utilities/redis"
|
import { doesUserHaveLock, getLocksById } from "../../utilities/redis"
|
||||||
import {
|
|
||||||
updateClientLibrary,
|
|
||||||
backupClientLibrary,
|
|
||||||
revertClientLibrary,
|
|
||||||
} from "../../utilities/fileSystem"
|
|
||||||
import { cleanupAutomations } from "../../automations/utils"
|
import { cleanupAutomations } from "../../automations/utils"
|
||||||
import { checkAppMetadata } from "../../automations/logging"
|
import { checkAppMetadata } from "../../automations/logging"
|
||||||
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
||||||
import { quotas, groups } from "@budibase/pro"
|
import { groups, licensing, quotas } from "@budibase/pro"
|
||||||
import {
|
import {
|
||||||
App,
|
App,
|
||||||
Layout,
|
Layout,
|
||||||
Screen,
|
|
||||||
MigrationType,
|
MigrationType,
|
||||||
Database,
|
PlanType,
|
||||||
|
Screen,
|
||||||
UserCtx,
|
UserCtx,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
||||||
|
@ -207,6 +208,7 @@ export async function fetchAppPackage(ctx: UserCtx) {
|
||||||
let application = await db.get(DocumentType.APP_METADATA)
|
let application = await db.get(DocumentType.APP_METADATA)
|
||||||
const layouts = await getLayouts()
|
const layouts = await getLayouts()
|
||||||
let screens = await getScreens()
|
let screens = await getScreens()
|
||||||
|
const license = await licensing.cache.getCachedLicense()
|
||||||
|
|
||||||
// Enrich plugin URLs
|
// Enrich plugin URLs
|
||||||
application.usedPlugins = objectStore.enrichPluginURLs(
|
application.usedPlugins = objectStore.enrichPluginURLs(
|
||||||
|
@ -227,6 +229,7 @@ export async function fetchAppPackage(ctx: UserCtx) {
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
application: { ...application, upgradableVersion: envCore.VERSION },
|
application: { ...application, upgradableVersion: envCore.VERSION },
|
||||||
|
licenseType: license?.plan.type || PlanType.FREE,
|
||||||
screens,
|
screens,
|
||||||
layouts,
|
layouts,
|
||||||
clientLibPath,
|
clientLibPath,
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
breakRowIdField,
|
breakRowIdField,
|
||||||
convertRowId,
|
convertRowId,
|
||||||
generateRowIdField,
|
generateRowIdField,
|
||||||
|
getPrimaryDisplay,
|
||||||
isRowId,
|
isRowId,
|
||||||
isSQL,
|
isSQL,
|
||||||
} from "../../../integrations/utils"
|
} from "../../../integrations/utils"
|
||||||
|
@ -391,7 +392,10 @@ export class ExternalRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
relatedRow = processFormulas(linkedTable, relatedRow)
|
relatedRow = processFormulas(linkedTable, relatedRow)
|
||||||
const relatedDisplay = display ? relatedRow[display] : undefined
|
let relatedDisplay
|
||||||
|
if (display) {
|
||||||
|
relatedDisplay = getPrimaryDisplay(relatedRow[display])
|
||||||
|
}
|
||||||
row[relationship.column][key] = {
|
row[relationship.column][key] = {
|
||||||
primaryDisplay: relatedDisplay || "Invalid display column",
|
primaryDisplay: relatedDisplay || "Invalid display column",
|
||||||
_id: relatedRow._id,
|
_id: relatedRow._id,
|
||||||
|
|
|
@ -3,10 +3,10 @@ import * as userController from "../user"
|
||||||
import { FieldTypes } from "../../../constants"
|
import { FieldTypes } from "../../../constants"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import { makeExternalQuery } from "../../../integrations/base/query"
|
import { makeExternalQuery } from "../../../integrations/base/query"
|
||||||
import { Row, Table } from "@budibase/types"
|
import { FieldType, Row, Table, UserCtx } from "@budibase/types"
|
||||||
import { Format } from "../view/exporters"
|
import { Format } from "../view/exporters"
|
||||||
import { UserCtx } from "@budibase/types"
|
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
|
||||||
const validateJs = require("validate.js")
|
const validateJs = require("validate.js")
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
|
||||||
|
@ -20,6 +20,13 @@ validateJs.extend(validateJs.validators.datetime, {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function isForeignKey(key: string, table: Table) {
|
||||||
|
const relationships = Object.values(table.schema).filter(
|
||||||
|
column => column.type === FieldType.LINK
|
||||||
|
)
|
||||||
|
return relationships.some(relationship => relationship.foreignKey === key)
|
||||||
|
}
|
||||||
|
|
||||||
export async function getDatasourceAndQuery(json: any) {
|
export async function getDatasourceAndQuery(json: any) {
|
||||||
const datasourceId = json.endpoint.datasourceId
|
const datasourceId = json.endpoint.datasourceId
|
||||||
const datasource = await sdk.datasources.get(datasourceId)
|
const datasource = await sdk.datasources.get(datasourceId)
|
||||||
|
@ -65,6 +72,10 @@ export async function validate({
|
||||||
const column = fetchedTable.schema[fieldName]
|
const column = fetchedTable.schema[fieldName]
|
||||||
const constraints = cloneDeep(column.constraints)
|
const constraints = cloneDeep(column.constraints)
|
||||||
const type = column.type
|
const type = column.type
|
||||||
|
// foreign keys are likely to be enriched
|
||||||
|
if (isForeignKey(fieldName, fetchedTable)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// formulas shouldn't validated, data will be deleted anyway
|
// formulas shouldn't validated, data will be deleted anyway
|
||||||
if (type === FieldTypes.FORMULA || column.autocolumn) {
|
if (type === FieldTypes.FORMULA || column.autocolumn) {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -40,19 +40,14 @@
|
||||||
{#if favicon !== ""}
|
{#if favicon !== ""}
|
||||||
<link rel="icon" type="image/png" href={favicon} />
|
<link rel="icon" type="image/png" href={favicon} />
|
||||||
{:else}
|
{:else}
|
||||||
<link rel="icon" type="image/png" href="https://i.imgur.com/Xhdt1YP.png" />
|
<link rel="icon" type="image/png" href="/builder/bblogo.png" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
<link href="/builder/fonts/source-sans-pro/400.css" rel="stylesheet" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
<link href="/builder/fonts/source-sans-pro/600.css" rel="stylesheet" />
|
||||||
<link
|
<link href="/builder/fonts/source-sans-pro/700.css" rel="stylesheet" />
|
||||||
href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap"
|
<link href="/builder/fonts/remixicon.css" rel="stylesheet" />
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
|
|
|
@ -1,16 +1,10 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Budibase Builder Preview</title>
|
<title>Budibase Builder Preview</title>
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
<link href="/builder/fonts/source-sans-pro/400.css" rel="stylesheet" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
<link href="/builder/fonts/source-sans-pro/600.css" rel="stylesheet" />
|
||||||
<link
|
<link href="/builder/fonts/source-sans-pro/700.css" rel="stylesheet" />
|
||||||
href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap"
|
<link href="/builder/fonts/remixicon.css" rel="stylesheet" />
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<style>
|
<style>
|
||||||
html, body {
|
html, body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
RelationshipTypes,
|
RelationshipTypes,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
import { builderSocket } from "../../../websockets"
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
|
||||||
async function makeTableRequest(
|
async function makeTableRequest(
|
||||||
|
@ -318,6 +319,11 @@ export async function save(ctx: UserCtx) {
|
||||||
datasource.entities[tableToSave.name] = tableToSave
|
datasource.entities[tableToSave.name] = tableToSave
|
||||||
await db.put(datasource)
|
await db.put(datasource)
|
||||||
|
|
||||||
|
// Since tables are stored inside datasources, we need to notify clients
|
||||||
|
// that the datasource definition changed
|
||||||
|
const updatedDatasource = await db.get(datasource._id)
|
||||||
|
builderSocket?.emitDatasourceUpdate(ctx, updatedDatasource)
|
||||||
|
|
||||||
return tableToSave
|
return tableToSave
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,6 +350,11 @@ export async function destroy(ctx: UserCtx) {
|
||||||
|
|
||||||
await db.put(datasource)
|
await db.put(datasource)
|
||||||
|
|
||||||
|
// Since tables are stored inside datasources, we need to notify clients
|
||||||
|
// that the datasource definition changed
|
||||||
|
const updatedDatasource = await db.get(datasource._id)
|
||||||
|
builderSocket?.emitDatasourceUpdate(ctx, updatedDatasource)
|
||||||
|
|
||||||
return tableToDelete
|
return tableToDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
Row,
|
Row,
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
SortJson,
|
SortJson,
|
||||||
Table,
|
ExternalTable,
|
||||||
TableRequest,
|
TableRequest,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { OAuth2Client } from "google-auth-library"
|
import { OAuth2Client } from "google-auth-library"
|
||||||
|
@ -136,7 +136,7 @@ const SCHEMA: Integration = {
|
||||||
class GoogleSheetsIntegration implements DatasourcePlus {
|
class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
private readonly config: GoogleSheetsConfig
|
private readonly config: GoogleSheetsConfig
|
||||||
private client: GoogleSpreadsheet
|
private client: GoogleSpreadsheet
|
||||||
public tables: Record<string, Table> = {}
|
public tables: Record<string, ExternalTable> = {}
|
||||||
public schemaErrors: Record<string, string> = {}
|
public schemaErrors: Record<string, string> = {}
|
||||||
|
|
||||||
constructor(config: GoogleSheetsConfig) {
|
constructor(config: GoogleSheetsConfig) {
|
||||||
|
@ -248,12 +248,18 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
return sheets.map(s => s.title)
|
return sheets.map(s => s.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
getTableSchema(title: string, headerValues: string[], id?: string) {
|
getTableSchema(
|
||||||
|
title: string,
|
||||||
|
headerValues: string[],
|
||||||
|
datasourceId: string,
|
||||||
|
id?: string
|
||||||
|
) {
|
||||||
// base table
|
// base table
|
||||||
const table: Table = {
|
const table: ExternalTable = {
|
||||||
name: title,
|
name: title,
|
||||||
primary: [GOOGLE_SHEETS_PRIMARY_KEY],
|
primary: [GOOGLE_SHEETS_PRIMARY_KEY],
|
||||||
schema: {},
|
schema: {},
|
||||||
|
sourceId: datasourceId,
|
||||||
}
|
}
|
||||||
if (id) {
|
if (id) {
|
||||||
table._id = id
|
table._id = id
|
||||||
|
@ -268,14 +274,17 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
|
async buildSchema(
|
||||||
|
datasourceId: string,
|
||||||
|
entities: Record<string, ExternalTable>
|
||||||
|
) {
|
||||||
// not fully configured yet
|
// not fully configured yet
|
||||||
if (!this.config.auth) {
|
if (!this.config.auth) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await this.connect()
|
await this.connect()
|
||||||
const sheets = this.client.sheetsByIndex
|
const sheets = this.client.sheetsByIndex
|
||||||
const tables: Record<string, Table> = {}
|
const tables: Record<string, ExternalTable> = {}
|
||||||
for (let sheet of sheets) {
|
for (let sheet of sheets) {
|
||||||
// must fetch rows to determine schema
|
// must fetch rows to determine schema
|
||||||
await sheet.getRows()
|
await sheet.getRows()
|
||||||
|
@ -284,6 +293,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
tables[sheet.title] = this.getTableSchema(
|
tables[sheet.title] = this.getTableSchema(
|
||||||
sheet.title,
|
sheet.title,
|
||||||
sheet.headerValues,
|
sheet.headerValues,
|
||||||
|
datasourceId,
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {
|
||||||
DatasourceFieldType,
|
DatasourceFieldType,
|
||||||
Integration,
|
Integration,
|
||||||
Operation,
|
Operation,
|
||||||
Table,
|
ExternalTable,
|
||||||
TableSchema,
|
TableSchema,
|
||||||
QueryJson,
|
QueryJson,
|
||||||
QueryType,
|
QueryType,
|
||||||
|
@ -97,7 +97,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
|
||||||
private index: number = 0
|
private index: number = 0
|
||||||
private readonly pool: any
|
private readonly pool: any
|
||||||
private client: any
|
private client: any
|
||||||
public tables: Record<string, Table> = {}
|
public tables: Record<string, ExternalTable> = {}
|
||||||
public schemaErrors: Record<string, string> = {}
|
public schemaErrors: Record<string, string> = {}
|
||||||
|
|
||||||
MASTER_TABLES = [
|
MASTER_TABLES = [
|
||||||
|
@ -220,7 +220,10 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
|
||||||
* @param {*} datasourceId - datasourceId to fetch
|
* @param {*} datasourceId - datasourceId to fetch
|
||||||
* @param entities - the tables that are to be built
|
* @param entities - the tables that are to be built
|
||||||
*/
|
*/
|
||||||
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
|
async buildSchema(
|
||||||
|
datasourceId: string,
|
||||||
|
entities: Record<string, ExternalTable>
|
||||||
|
) {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
let tableInfo: MSSQLTablesResponse[] = await this.runSQL(this.TABLES_SQL)
|
let tableInfo: MSSQLTablesResponse[] = await this.runSQL(this.TABLES_SQL)
|
||||||
if (tableInfo == null || !Array.isArray(tableInfo)) {
|
if (tableInfo == null || !Array.isArray(tableInfo)) {
|
||||||
|
@ -233,7 +236,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
|
||||||
.map((record: any) => record.TABLE_NAME)
|
.map((record: any) => record.TABLE_NAME)
|
||||||
.filter((name: string) => this.MASTER_TABLES.indexOf(name) === -1)
|
.filter((name: string) => this.MASTER_TABLES.indexOf(name) === -1)
|
||||||
|
|
||||||
const tables: Record<string, Table> = {}
|
const tables: Record<string, ExternalTable> = {}
|
||||||
for (let tableName of tableNames) {
|
for (let tableName of tableNames) {
|
||||||
// get the column definition (type)
|
// get the column definition (type)
|
||||||
const definition = await this.runSQL(this.getDefinitionSQL(tableName))
|
const definition = await this.runSQL(this.getDefinitionSQL(tableName))
|
||||||
|
@ -276,6 +279,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
|
||||||
}
|
}
|
||||||
tables[tableName] = {
|
tables[tableName] = {
|
||||||
_id: buildExternalTableId(datasourceId, tableName),
|
_id: buildExternalTableId(datasourceId, tableName),
|
||||||
|
sourceId: datasourceId,
|
||||||
primary: primaryKeys,
|
primary: primaryKeys,
|
||||||
name: tableName,
|
name: tableName,
|
||||||
schema,
|
schema,
|
||||||
|
|
|
@ -383,7 +383,7 @@ class MongoIntegration implements IntegrationBase {
|
||||||
createObjectIds(json: any): object {
|
createObjectIds(json: any): object {
|
||||||
const self = this
|
const self = this
|
||||||
function interpolateObjectIds(json: any) {
|
function interpolateObjectIds(json: any) {
|
||||||
for (let field of Object.keys(json)) {
|
for (let field of Object.keys(json || {})) {
|
||||||
if (json[field] instanceof Object) {
|
if (json[field] instanceof Object) {
|
||||||
json[field] = self.createObjectIds(json[field])
|
json[field] = self.createObjectIds(json[field])
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
QueryType,
|
QueryType,
|
||||||
QueryJson,
|
QueryJson,
|
||||||
SqlQuery,
|
SqlQuery,
|
||||||
Table,
|
ExternalTable,
|
||||||
TableSchema,
|
TableSchema,
|
||||||
DatasourcePlus,
|
DatasourcePlus,
|
||||||
DatasourceFeature,
|
DatasourceFeature,
|
||||||
|
@ -91,7 +91,7 @@ const SCHEMA: Integration = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindingTypeCoerce(bindings: any[]) {
|
export function bindingTypeCoerce(bindings: any[]) {
|
||||||
for (let i = 0; i < bindings.length; i++) {
|
for (let i = 0; i < bindings.length; i++) {
|
||||||
const binding = bindings[i]
|
const binding = bindings[i]
|
||||||
if (typeof binding !== "string") {
|
if (typeof binding !== "string") {
|
||||||
|
@ -109,7 +109,12 @@ function bindingTypeCoerce(bindings: any[]) {
|
||||||
dayjs(binding).isValid() &&
|
dayjs(binding).isValid() &&
|
||||||
!binding.includes(",")
|
!binding.includes(",")
|
||||||
) {
|
) {
|
||||||
bindings[i] = dayjs(binding).toDate()
|
let value: any
|
||||||
|
value = new Date(binding)
|
||||||
|
if (isNaN(value)) {
|
||||||
|
value = binding
|
||||||
|
}
|
||||||
|
bindings[i] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bindings
|
return bindings
|
||||||
|
@ -118,7 +123,7 @@ function bindingTypeCoerce(bindings: any[]) {
|
||||||
class MySQLIntegration extends Sql implements DatasourcePlus {
|
class MySQLIntegration extends Sql implements DatasourcePlus {
|
||||||
private config: MySQLConfig
|
private config: MySQLConfig
|
||||||
private client?: mysql.Connection
|
private client?: mysql.Connection
|
||||||
public tables: Record<string, Table> = {}
|
public tables: Record<string, ExternalTable> = {}
|
||||||
public schemaErrors: Record<string, string> = {}
|
public schemaErrors: Record<string, string> = {}
|
||||||
|
|
||||||
constructor(config: MySQLConfig) {
|
constructor(config: MySQLConfig) {
|
||||||
|
@ -215,8 +220,11 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
|
async buildSchema(
|
||||||
const tables: { [key: string]: Table } = {}
|
datasourceId: string,
|
||||||
|
entities: Record<string, ExternalTable>
|
||||||
|
) {
|
||||||
|
const tables: { [key: string]: ExternalTable } = {}
|
||||||
await this.connect()
|
await this.connect()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -254,6 +262,7 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
|
||||||
if (!tables[tableName]) {
|
if (!tables[tableName]) {
|
||||||
tables[tableName] = {
|
tables[tableName] = {
|
||||||
_id: buildExternalTableId(datasourceId, tableName),
|
_id: buildExternalTableId(datasourceId, tableName),
|
||||||
|
sourceId: datasourceId,
|
||||||
primary: primaryKeys,
|
primary: primaryKeys,
|
||||||
name: tableName,
|
name: tableName,
|
||||||
schema,
|
schema,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
QueryJson,
|
QueryJson,
|
||||||
QueryType,
|
QueryType,
|
||||||
SqlQuery,
|
SqlQuery,
|
||||||
Table,
|
ExternalTable,
|
||||||
DatasourcePlus,
|
DatasourcePlus,
|
||||||
DatasourceFeature,
|
DatasourceFeature,
|
||||||
ConnectionInfo,
|
ConnectionInfo,
|
||||||
|
@ -108,7 +108,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
||||||
private readonly config: OracleConfig
|
private readonly config: OracleConfig
|
||||||
private index: number = 1
|
private index: number = 1
|
||||||
|
|
||||||
public tables: Record<string, Table> = {}
|
public tables: Record<string, ExternalTable> = {}
|
||||||
public schemaErrors: Record<string, string> = {}
|
public schemaErrors: Record<string, string> = {}
|
||||||
|
|
||||||
private readonly COLUMNS_SQL = `
|
private readonly COLUMNS_SQL = `
|
||||||
|
@ -262,13 +262,16 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
||||||
* @param {*} datasourceId - datasourceId to fetch
|
* @param {*} datasourceId - datasourceId to fetch
|
||||||
* @param entities - the tables that are to be built
|
* @param entities - the tables that are to be built
|
||||||
*/
|
*/
|
||||||
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
|
async buildSchema(
|
||||||
|
datasourceId: string,
|
||||||
|
entities: Record<string, ExternalTable>
|
||||||
|
) {
|
||||||
const columnsResponse = await this.internalQuery<OracleColumnsResponse>({
|
const columnsResponse = await this.internalQuery<OracleColumnsResponse>({
|
||||||
sql: this.COLUMNS_SQL,
|
sql: this.COLUMNS_SQL,
|
||||||
})
|
})
|
||||||
const oracleTables = this.mapColumns(columnsResponse)
|
const oracleTables = this.mapColumns(columnsResponse)
|
||||||
|
|
||||||
const tables: { [key: string]: Table } = {}
|
const tables: { [key: string]: ExternalTable } = {}
|
||||||
|
|
||||||
// iterate each table
|
// iterate each table
|
||||||
Object.values(oracleTables).forEach(oracleTable => {
|
Object.values(oracleTables).forEach(oracleTable => {
|
||||||
|
@ -279,6 +282,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
||||||
primary: [],
|
primary: [],
|
||||||
name: oracleTable.name,
|
name: oracleTable.name,
|
||||||
schema: {},
|
schema: {},
|
||||||
|
sourceId: datasourceId,
|
||||||
}
|
}
|
||||||
tables[oracleTable.name] = table
|
tables[oracleTable.name] = table
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
QueryType,
|
QueryType,
|
||||||
QueryJson,
|
QueryJson,
|
||||||
SqlQuery,
|
SqlQuery,
|
||||||
Table,
|
ExternalTable,
|
||||||
DatasourcePlus,
|
DatasourcePlus,
|
||||||
DatasourceFeature,
|
DatasourceFeature,
|
||||||
ConnectionInfo,
|
ConnectionInfo,
|
||||||
|
@ -124,7 +124,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
||||||
private readonly config: PostgresConfig
|
private readonly config: PostgresConfig
|
||||||
private index: number = 1
|
private index: number = 1
|
||||||
private open: boolean
|
private open: boolean
|
||||||
public tables: Record<string, Table> = {}
|
public tables: Record<string, ExternalTable> = {}
|
||||||
public schemaErrors: Record<string, string> = {}
|
public schemaErrors: Record<string, string> = {}
|
||||||
|
|
||||||
COLUMNS_SQL!: string
|
COLUMNS_SQL!: string
|
||||||
|
@ -239,7 +239,10 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
||||||
* @param {*} datasourceId - datasourceId to fetch
|
* @param {*} datasourceId - datasourceId to fetch
|
||||||
* @param entities - the tables that are to be built
|
* @param entities - the tables that are to be built
|
||||||
*/
|
*/
|
||||||
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
|
async buildSchema(
|
||||||
|
datasourceId: string,
|
||||||
|
entities: Record<string, ExternalTable>
|
||||||
|
) {
|
||||||
let tableKeys: { [key: string]: string[] } = {}
|
let tableKeys: { [key: string]: string[] } = {}
|
||||||
await this.openConnection()
|
await this.openConnection()
|
||||||
try {
|
try {
|
||||||
|
@ -265,7 +268,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
||||||
const columnsResponse: { rows: PostgresColumn[] } =
|
const columnsResponse: { rows: PostgresColumn[] } =
|
||||||
await this.client.query(this.COLUMNS_SQL)
|
await this.client.query(this.COLUMNS_SQL)
|
||||||
|
|
||||||
const tables: { [key: string]: Table } = {}
|
const tables: { [key: string]: ExternalTable } = {}
|
||||||
|
|
||||||
for (let column of columnsResponse.rows) {
|
for (let column of columnsResponse.rows) {
|
||||||
const tableName: string = column.table_name
|
const tableName: string = column.table_name
|
||||||
|
@ -278,6 +281,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
||||||
primary: tableKeys[tableName] || [],
|
primary: tableKeys[tableName] || [],
|
||||||
name: tableName,
|
name: tableName,
|
||||||
schema: {},
|
schema: {},
|
||||||
|
sourceId: datasourceId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { default as MySQLIntegration } from "../mysql"
|
import { default as MySQLIntegration, bindingTypeCoerce } from "../mysql"
|
||||||
jest.mock("mysql2")
|
jest.mock("mysql2")
|
||||||
|
|
||||||
class TestConfiguration {
|
class TestConfiguration {
|
||||||
|
@ -131,3 +131,21 @@ describe("MySQL Integration", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("bindingTypeCoercion", () => {
|
||||||
|
it("shouldn't coerce something that looks like a date", () => {
|
||||||
|
const response = bindingTypeCoerce(["202205-1500"])
|
||||||
|
expect(response[0]).toBe("202205-1500")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should coerce an actual date", () => {
|
||||||
|
const date = new Date("2023-06-13T14:24:22.620Z")
|
||||||
|
const response = bindingTypeCoerce(["2023-06-13T14:24:22.620Z"])
|
||||||
|
expect(response[0]).toEqual(date)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should coerce numbers", () => {
|
||||||
|
const response = bindingTypeCoerce(["0"])
|
||||||
|
expect(response[0]).toEqual(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -328,3 +328,27 @@ export function finaliseExternalTables(
|
||||||
.reduce((r, [k, v]) => ({ ...r, [k]: v }), {})
|
.reduce((r, [k, v]) => ({ ...r, [k]: v }), {})
|
||||||
return { tables: finalTables, errors }
|
return { tables: finalTables, errors }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the provided input is an object, but specifically not a date type object.
|
||||||
|
* Used during coercion of types and relationship handling, dates are considered valid
|
||||||
|
* and can be used as a display field, but objects and arrays cannot.
|
||||||
|
* @param testValue an unknown type which this function will attempt to extract
|
||||||
|
* a valid primary display string from.
|
||||||
|
*/
|
||||||
|
export function getPrimaryDisplay(testValue: unknown): string | undefined {
|
||||||
|
if (testValue instanceof Date) {
|
||||||
|
return testValue.toISOString()
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
Array.isArray(testValue) &&
|
||||||
|
testValue[0] &&
|
||||||
|
typeof testValue[0] !== "object"
|
||||||
|
) {
|
||||||
|
return testValue.join(", ")
|
||||||
|
}
|
||||||
|
if (typeof testValue === "object") {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return testValue as string
|
||||||
|
}
|
||||||
|
|
|
@ -36,11 +36,14 @@ export function checkDatasourceTypes(schema: Integration, config: any) {
|
||||||
async function enrichDatasourceWithValues(datasource: Datasource) {
|
async function enrichDatasourceWithValues(datasource: Datasource) {
|
||||||
const cloned = cloneDeep(datasource)
|
const cloned = cloneDeep(datasource)
|
||||||
const env = await getEnvironmentVariables()
|
const env = await getEnvironmentVariables()
|
||||||
|
//Do not process entities, as we do not want to process formulas
|
||||||
|
const { entities, ...clonedWithoutEntities } = cloned
|
||||||
const processed = processObjectSync(
|
const processed = processObjectSync(
|
||||||
cloned,
|
clonedWithoutEntities,
|
||||||
{ env },
|
{ env },
|
||||||
{ onlyFound: true }
|
{ onlyFound: true }
|
||||||
) as Datasource
|
) as Datasource
|
||||||
|
processed.entities = entities
|
||||||
const definition = await getDefinition(processed.source)
|
const definition = await getDefinition(processed.source)
|
||||||
processed.config = checkDatasourceTypes(definition!, processed.config)
|
processed.config = checkDatasourceTypes(definition!, processed.config)
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { groups } from "@budibase/pro"
|
import { groups } from "@budibase/pro"
|
||||||
import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types"
|
import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types"
|
||||||
import { global } from "yargs"
|
import { cloneDeep } from "lodash"
|
||||||
|
|
||||||
export function updateAppRole(
|
export function updateAppRole(
|
||||||
user: ContextUser,
|
user: ContextUser,
|
||||||
|
@ -65,16 +65,20 @@ export async function processUser(
|
||||||
user: ContextUser,
|
user: ContextUser,
|
||||||
opts: { appId?: string; groups?: UserGroup[] } = {}
|
opts: { appId?: string; groups?: UserGroup[] } = {}
|
||||||
) {
|
) {
|
||||||
if (user) {
|
let clonedUser = cloneDeep(user)
|
||||||
delete user.password
|
if (clonedUser) {
|
||||||
|
delete clonedUser.password
|
||||||
}
|
}
|
||||||
const appId = opts.appId || context.getAppId()
|
const appId = opts.appId || context.getAppId()
|
||||||
user = updateAppRole(user, { appId })
|
clonedUser = updateAppRole(clonedUser, { appId })
|
||||||
if (!user.roleId && user?.userGroups?.length) {
|
if (!clonedUser.roleId && clonedUser?.userGroups?.length) {
|
||||||
user = await checkGroupRoles(user, { appId, groups: opts?.groups })
|
clonedUser = await checkGroupRoles(clonedUser, {
|
||||||
|
appId,
|
||||||
|
groups: opts?.groups,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return user
|
return clonedUser
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCachedSelf(ctx: UserCtx, appId: string) {
|
export async function getCachedSelf(ctx: UserCtx, appId: string) {
|
||||||
|
|
|
@ -82,6 +82,10 @@ export interface Table extends Document {
|
||||||
rowHeight?: number
|
rowHeight?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExternalTable extends Table {
|
||||||
|
sourceId: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface TableRequest extends Table {
|
export interface TableRequest extends Table {
|
||||||
_rename?: RenameColumn
|
_rename?: RenameColumn
|
||||||
created?: boolean
|
created?: boolean
|
||||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -2489,6 +2489,11 @@
|
||||||
minimatch "^3.0.4"
|
minimatch "^3.0.4"
|
||||||
strip-json-comments "^3.1.1"
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
|
"@fontsource/source-sans-pro@^5.0.3":
|
||||||
|
version "5.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@fontsource/source-sans-pro/-/source-sans-pro-5.0.3.tgz#7d6e84a8169ba12fa5e6ce70757aa2ca7e74d855"
|
||||||
|
integrity sha512-mQnjuif/37VxwRloHZ+wQdoozd2VPWutbFSt1AuSkk7nFXIBQxHJLw80rgCF/osL0t7N/3Gx1V7UJuOX2zxzhQ==
|
||||||
|
|
||||||
"@fortawesome/fontawesome-common-types@6.3.0":
|
"@fortawesome/fontawesome-common-types@6.3.0":
|
||||||
version "6.3.0"
|
version "6.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz#51f734e64511dbc3674cd347044d02f4dd26e86b"
|
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz#51f734e64511dbc3674cd347044d02f4dd26e86b"
|
||||||
|
@ -8408,7 +8413,7 @@ chmodr@1.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/chmodr/-/chmodr-1.2.0.tgz#720e96caa09b7f1cdbb01529b7d0ab6bc5e118b9"
|
resolved "https://registry.yarnpkg.com/chmodr/-/chmodr-1.2.0.tgz#720e96caa09b7f1cdbb01529b7d0ab6bc5e118b9"
|
||||||
integrity sha512-Y5uI7Iq/Az6HgJEL6pdw7THVd7jbVOTPwsmcPOBjQL8e3N+pz872kzK5QxYGEy21iRys+iHWV0UZQXDFJo1hyA==
|
integrity sha512-Y5uI7Iq/Az6HgJEL6pdw7THVd7jbVOTPwsmcPOBjQL8e3N+pz872kzK5QxYGEy21iRys+iHWV0UZQXDFJo1hyA==
|
||||||
|
|
||||||
chokidar@3.5.3, chokidar@^3.0.0, chokidar@^3.5.1, chokidar@^3.5.2:
|
chokidar@3.5.3, chokidar@^3.0.0, chokidar@^3.5.1, chokidar@^3.5.2, chokidar@^3.5.3:
|
||||||
version "3.5.3"
|
version "3.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
||||||
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
|
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
|
||||||
|
@ -11852,7 +11857,7 @@ fast-glob@3.2.7:
|
||||||
merge2 "^1.3.0"
|
merge2 "^1.3.0"
|
||||||
micromatch "^4.0.4"
|
micromatch "^4.0.4"
|
||||||
|
|
||||||
fast-glob@^3.0.3:
|
fast-glob@^3.0.3, fast-glob@^3.2.11:
|
||||||
version "3.2.12"
|
version "3.2.12"
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
|
||||||
integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
|
integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
|
||||||
|
@ -25402,6 +25407,16 @@ vite-node@0.29.8:
|
||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
vite "^3.0.0 || ^4.0.0"
|
vite "^3.0.0 || ^4.0.0"
|
||||||
|
|
||||||
|
vite-plugin-static-copy@^0.16.0:
|
||||||
|
version "0.16.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/vite-plugin-static-copy/-/vite-plugin-static-copy-0.16.0.tgz#2f65227037f17fc99c0782fd0b344e962935e69e"
|
||||||
|
integrity sha512-dMVEg5Z2SwYRgQnHZaeokvSKB4p/TOTf65JU4sP3U6ccSBsukqdtDOjpmT+xzTFHAA8WJjcS31RMLjUdWQCBzw==
|
||||||
|
dependencies:
|
||||||
|
chokidar "^3.5.3"
|
||||||
|
fast-glob "^3.2.11"
|
||||||
|
fs-extra "^11.1.0"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
|
||||||
"vite@^3.0.0 || ^4.0.0":
|
"vite@^3.0.0 || ^4.0.0":
|
||||||
version "4.2.2"
|
version "4.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/vite/-/vite-4.2.2.tgz#014c30e5163844f6e96d7fe18fbb702236516dc6"
|
resolved "https://registry.yarnpkg.com/vite/-/vite-4.2.2.tgz#014c30e5163844f6e96d7fe18fbb702236516dc6"
|
||||||
|
|
Loading…
Reference in New Issue