Merge branch 'master' of github.com:Budibase/budibase into update-aliasing
This commit is contained in:
commit
b91f915e40
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bulma": "^0.9.3",
|
"bulma": "^0.9.3",
|
||||||
"next": "14.2.10",
|
"next": "14.2.15",
|
||||||
"node-fetch": "^3.2.10",
|
"node-fetch": "^3.2.10",
|
||||||
"sass": "^1.52.3",
|
"sass": "^1.52.3",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
|
|
|
@ -46,10 +46,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
||||||
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
||||||
|
|
||||||
"@next/env@14.2.10":
|
"@next/env@14.2.15":
|
||||||
version "14.2.10"
|
version "14.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.10.tgz#1d3178340028ced2d679f84140877db4f420333c"
|
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.15.tgz#06d984e37e670d93ddd6790af1844aeb935f332f"
|
||||||
integrity sha512-dZIu93Bf5LUtluBXIv4woQw2cZVZ2DJTjax5/5DOs3lzEOeKLy7GxRSr4caK9/SCPdaW6bCgpye6+n4Dh9oJPw==
|
integrity sha512-S1qaj25Wru2dUpcIZMjxeMVSwkt8BK4dmWHHiBuRstcIyOsMapqT4A4jSB6onvqeygkSSmOkyny9VVx8JIGamQ==
|
||||||
|
|
||||||
"@next/eslint-plugin-next@12.1.0":
|
"@next/eslint-plugin-next@12.1.0":
|
||||||
version "12.1.0"
|
version "12.1.0"
|
||||||
|
@ -58,50 +58,50 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
glob "7.1.7"
|
glob "7.1.7"
|
||||||
|
|
||||||
"@next/swc-darwin-arm64@14.2.10":
|
"@next/swc-darwin-arm64@14.2.15":
|
||||||
version "14.2.10"
|
version "14.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.10.tgz#49d10ca4086fbd59ee68e204f75d7136eda2aa80"
|
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.15.tgz#6386d585f39a1c490c60b72b1f76612ba4434347"
|
||||||
integrity sha512-V3z10NV+cvMAfxQUMhKgfQnPbjw+Ew3cnr64b0lr8MDiBJs3eLnM6RpGC46nhfMZsiXgQngCJKWGTC/yDcgrDQ==
|
integrity sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==
|
||||||
|
|
||||||
"@next/swc-darwin-x64@14.2.10":
|
"@next/swc-darwin-x64@14.2.15":
|
||||||
version "14.2.10"
|
version "14.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.10.tgz#0ebeae3afb8eac433882b79543295ab83624a1a8"
|
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.15.tgz#b7baeedc6a28f7545ad2bc55adbab25f7b45cb89"
|
||||||
integrity sha512-Y0TC+FXbFUQ2MQgimJ/7Ina2mXIKhE7F+GUe1SgnzRmwFY3hX2z8nyVCxE82I2RicspdkZnSWMn4oTjIKz4uzA==
|
integrity sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==
|
||||||
|
|
||||||
"@next/swc-linux-arm64-gnu@14.2.10":
|
"@next/swc-linux-arm64-gnu@14.2.15":
|
||||||
version "14.2.10"
|
version "14.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.10.tgz#7e602916d2fb55a3c532f74bed926a0137c16f20"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.15.tgz#fa13c59d3222f70fb4cb3544ac750db2c6e34d02"
|
||||||
integrity sha512-ZfQ7yOy5zyskSj9rFpa0Yd7gkrBnJTkYVSya95hX3zeBG9E55Z6OTNPn1j2BTFWvOVVj65C3T+qsjOyVI9DQpA==
|
integrity sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==
|
||||||
|
|
||||||
"@next/swc-linux-arm64-musl@14.2.10":
|
"@next/swc-linux-arm64-musl@14.2.15":
|
||||||
version "14.2.10"
|
version "14.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.10.tgz#6b143f628ccee490b527562e934f8de578d4be47"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.15.tgz#30e45b71831d9a6d6d18d7ac7d611a8d646a17f9"
|
||||||
integrity sha512-n2i5o3y2jpBfXFRxDREr342BGIQCJbdAUi/K4q6Env3aSx8erM9VuKXHw5KNROK9ejFSPf0LhoSkU/ZiNdacpQ==
|
integrity sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==
|
||||||
|
|
||||||
"@next/swc-linux-x64-gnu@14.2.10":
|
"@next/swc-linux-x64-gnu@14.2.15":
|
||||||
version "14.2.10"
|
version "14.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.10.tgz#086f2f16a0678890a1eb46518c4dda381b046082"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.15.tgz#5065db17fc86f935ad117483f21f812dc1b39254"
|
||||||
integrity sha512-GXvajAWh2woTT0GKEDlkVhFNxhJS/XdDmrVHrPOA83pLzlGPQnixqxD8u3bBB9oATBKB//5e4vpACnx5Vaxdqg==
|
integrity sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==
|
||||||
|
|
||||||
"@next/swc-linux-x64-musl@14.2.10":
|
"@next/swc-linux-x64-musl@14.2.15":
|
||||||
version "14.2.10"
|
version "14.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.10.tgz#1befef10ed8dbcc5047b5d637a25ae3c30a0bfc3"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.15.tgz#3c4a4568d8be7373a820f7576cf33388b5dab47e"
|
||||||
integrity sha512-opFFN5B0SnO+HTz4Wq4HaylXGFV+iHrVxd3YvREUX9K+xfc4ePbRrxqOuPOFjtSuiVouwe6uLeDtabjEIbkmDA==
|
integrity sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==
|
||||||
|
|
||||||
"@next/swc-win32-arm64-msvc@14.2.10":
|
"@next/swc-win32-arm64-msvc@14.2.15":
|
||||||
version "14.2.10"
|
version "14.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.10.tgz#731f52c3ae3c56a26cf21d474b11ae1529531209"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.15.tgz#fb812cc4ca0042868e32a6a021da91943bb08b98"
|
||||||
integrity sha512-9NUzZuR8WiXTvv+EiU/MXdcQ1XUvFixbLIMNQiVHuzs7ZIFrJDLJDaOF1KaqttoTujpcxljM/RNAOmw1GhPPQQ==
|
integrity sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==
|
||||||
|
|
||||||
"@next/swc-win32-ia32-msvc@14.2.10":
|
"@next/swc-win32-ia32-msvc@14.2.15":
|
||||||
version "14.2.10"
|
version "14.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.10.tgz#32723ef7f04e25be12af357cc72ddfdd42fd1041"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.15.tgz#ec26e6169354f8ced240c1427be7fd485c5df898"
|
||||||
integrity sha512-fr3aEbSd1GeW3YUMBkWAu4hcdjZ6g4NBl1uku4gAn661tcxd1bHs1THWYzdsbTRLcCKLjrDZlNp6j2HTfrw+Bg==
|
integrity sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc@14.2.10":
|
"@next/swc-win32-x64-msvc@14.2.15":
|
||||||
version "14.2.10"
|
version "14.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.10.tgz#ee1d036cb5ec871816f96baee7991035bb242455"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.15.tgz#18d68697002b282006771f8d92d79ade9efd35c4"
|
||||||
integrity sha512-UjeVoRGKNL2zfbcQ6fscmgjBAS/inHBh63mjIlfPg/NG8Yn2ztqylXt5qilYb6hoHIwaU2ogHknHWWmahJjgZQ==
|
integrity sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.5":
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
|
@ -1253,12 +1253,12 @@ natural-compare@^1.4.0:
|
||||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
|
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
|
||||||
|
|
||||||
next@14.2.10:
|
next@14.2.15:
|
||||||
version "14.2.10"
|
version "14.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/next/-/next-14.2.10.tgz#331981a4fecb1ae8af1817d4db98fc9687ee1cb6"
|
resolved "https://registry.yarnpkg.com/next/-/next-14.2.15.tgz#348e5603e22649775d19c785c09a89c9acb5189a"
|
||||||
integrity sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww==
|
integrity sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@next/env" "14.2.10"
|
"@next/env" "14.2.15"
|
||||||
"@swc/helpers" "0.5.5"
|
"@swc/helpers" "0.5.5"
|
||||||
busboy "1.6.0"
|
busboy "1.6.0"
|
||||||
caniuse-lite "^1.0.30001579"
|
caniuse-lite "^1.0.30001579"
|
||||||
|
@ -1266,15 +1266,15 @@ next@14.2.10:
|
||||||
postcss "8.4.31"
|
postcss "8.4.31"
|
||||||
styled-jsx "5.1.1"
|
styled-jsx "5.1.1"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@next/swc-darwin-arm64" "14.2.10"
|
"@next/swc-darwin-arm64" "14.2.15"
|
||||||
"@next/swc-darwin-x64" "14.2.10"
|
"@next/swc-darwin-x64" "14.2.15"
|
||||||
"@next/swc-linux-arm64-gnu" "14.2.10"
|
"@next/swc-linux-arm64-gnu" "14.2.15"
|
||||||
"@next/swc-linux-arm64-musl" "14.2.10"
|
"@next/swc-linux-arm64-musl" "14.2.15"
|
||||||
"@next/swc-linux-x64-gnu" "14.2.10"
|
"@next/swc-linux-x64-gnu" "14.2.15"
|
||||||
"@next/swc-linux-x64-musl" "14.2.10"
|
"@next/swc-linux-x64-musl" "14.2.15"
|
||||||
"@next/swc-win32-arm64-msvc" "14.2.10"
|
"@next/swc-win32-arm64-msvc" "14.2.15"
|
||||||
"@next/swc-win32-ia32-msvc" "14.2.10"
|
"@next/swc-win32-ia32-msvc" "14.2.15"
|
||||||
"@next/swc-win32-x64-msvc" "14.2.10"
|
"@next/swc-win32-x64-msvc" "14.2.15"
|
||||||
|
|
||||||
node-domexception@^1.0.0:
|
node-domexception@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
}
|
}
|
||||||
input.hide-arrows {
|
input.hide-arrows {
|
||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
}
|
}
|
||||||
input[type="time"]::-webkit-calendar-picker-indicator {
|
input[type="time"]::-webkit-calendar-picker-indicator {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
input::-webkit-slider-thumb {
|
input::-webkit-slider-thumb {
|
||||||
|
|
|
@ -124,8 +124,6 @@
|
||||||
.spectrum-Tabs-selectionIndicator.emphasized {
|
.spectrum-Tabs-selectionIndicator.emphasized {
|
||||||
background-color: var(--spectrum-global-color-blue-400);
|
background-color: var(--spectrum-global-color-blue-400);
|
||||||
}
|
}
|
||||||
.spectrum-Tabs--horizontal .spectrum-Tabs-selectionIndicator {
|
|
||||||
}
|
|
||||||
.noHorizPadding {
|
.noHorizPadding {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,6 +134,7 @@
|
||||||
.spectrum-Tooltip-label {
|
.spectrum-Tooltip-label {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 3;
|
||||||
|
line-clamp: 3;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
|
@ -94,6 +94,7 @@
|
||||||
"@sveltejs/vite-plugin-svelte": "1.4.0",
|
"@sveltejs/vite-plugin-svelte": "1.4.0",
|
||||||
"@testing-library/jest-dom": "6.4.2",
|
"@testing-library/jest-dom": "6.4.2",
|
||||||
"@testing-library/svelte": "^4.1.0",
|
"@testing-library/svelte": "^4.1.0",
|
||||||
|
"@types/shortid": "^2.2.0",
|
||||||
"babel-jest": "^29.6.2",
|
"babel-jest": "^29.6.2",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
$: blockRefs = $selectedAutomation?.blockRefs || {}
|
$: blockRefs = $selectedAutomation?.blockRefs || {}
|
||||||
$: stepNames = automation?.definition.stepNames
|
$: stepNames = automation?.definition.stepNames || {}
|
||||||
$: allSteps = automation?.definition.steps || []
|
$: allSteps = automation?.definition.steps || []
|
||||||
$: automationName = itemName || stepNames?.[block.id] || block?.name || ""
|
$: automationName = itemName || stepNames?.[block.id] || block?.name || ""
|
||||||
$: automationNameError = getAutomationNameError(automationName)
|
$: automationNameError = getAutomationNameError(automationName)
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
const getAutomationNameError = name => {
|
const getAutomationNameError = name => {
|
||||||
const duplicateError =
|
const duplicateError =
|
||||||
"This name already exists, please enter a unique name"
|
"This name already exists, please enter a unique name"
|
||||||
if (stepNames && editing) {
|
if (editing) {
|
||||||
for (const [key, value] of Object.entries(stepNames)) {
|
for (const [key, value] of Object.entries(stepNames)) {
|
||||||
if (name !== block.name && name === value && key !== block.id) {
|
if (name !== block.name && name === value && key !== block.id) {
|
||||||
return duplicateError
|
return duplicateError
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
...datasource,
|
...datasource,
|
||||||
name,
|
name,
|
||||||
}
|
}
|
||||||
await datasources.update({
|
await datasources.save({
|
||||||
datasource: updatedDatasource,
|
datasource: updatedDatasource,
|
||||||
integration: integrationForDatasource(get(integrations), datasource),
|
integration: integrationForDatasource(get(integrations), datasource),
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
export let name
|
export let name
|
||||||
export let config
|
export let config
|
||||||
export let showModal = () => {}
|
export let showModal = () => {}
|
||||||
|
export let placeholder
|
||||||
|
|
||||||
const selectComponent = type => {
|
const selectComponent = type => {
|
||||||
if (type === "object") {
|
if (type === "object") {
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
{name}
|
{name}
|
||||||
{config}
|
{config}
|
||||||
{showModal}
|
{showModal}
|
||||||
|
{placeholder}
|
||||||
on:blur
|
on:blur
|
||||||
on:change
|
on:change
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -5,11 +5,12 @@
|
||||||
export let name
|
export let name
|
||||||
export let value
|
export let value
|
||||||
export let error
|
export let error
|
||||||
|
export let placeholder
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label>{name}</Label>
|
<Label>{name}</Label>
|
||||||
<TextArea on:blur on:change {type} {value} {error} />
|
<TextArea on:blur on:change {type} {value} {error} {placeholder} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
export let value
|
export let value
|
||||||
export let error
|
export let error
|
||||||
export let config
|
export let config
|
||||||
|
export let placeholder
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
{type}
|
{type}
|
||||||
value={value || undefined}
|
value={value || undefined}
|
||||||
{error}
|
{error}
|
||||||
|
{placeholder}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
export let name
|
export let name
|
||||||
export let value
|
export let value
|
||||||
export let error
|
export let error
|
||||||
|
export let placeholder
|
||||||
export let showModal = () => {}
|
export let showModal = () => {}
|
||||||
|
|
||||||
async function handleUpgradePanel() {
|
async function handleUpgradePanel() {
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
type={type === "port" ? "string" : type}
|
type={type === "port" ? "string" : type}
|
||||||
{value}
|
{value}
|
||||||
{error}
|
{error}
|
||||||
|
{placeholder}
|
||||||
variables={$environment.variables}
|
variables={$environment.variables}
|
||||||
environmentVariablesEnabled={$licensing.environmentVariablesEnabled}
|
environmentVariablesEnabled={$licensing.environmentVariablesEnabled}
|
||||||
{showModal}
|
{showModal}
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#each $configStore.validatedConfig as { type, key, value, error, name, hidden, config }}
|
{#each $configStore.validatedConfig as { type, key, value, error, name, hidden, config, placeholder }}
|
||||||
{#if hidden === undefined || !eval(processStringSync(hidden, $configStore.config))}
|
{#if hidden === undefined || !eval(processStringSync(hidden, $configStore.config))}
|
||||||
<ConfigInput
|
<ConfigInput
|
||||||
{type}
|
{type}
|
||||||
|
@ -93,6 +93,7 @@
|
||||||
{error}
|
{error}
|
||||||
{name}
|
{name}
|
||||||
{config}
|
{config}
|
||||||
|
{placeholder}
|
||||||
showModal={() =>
|
showModal={() =>
|
||||||
showModal(newValue => configStore.updateFieldValue(key, newValue))}
|
showModal(newValue => configStore.updateFieldValue(key, newValue))}
|
||||||
on:blur={() => configStore.markFieldActive(key)}
|
on:blur={() => configStore.markFieldActive(key)}
|
||||||
|
|
|
@ -114,6 +114,7 @@ export const createValidatedConfigStore = (integration, config) => {
|
||||||
value: getValue(),
|
value: getValue(),
|
||||||
error: $errorsStore[key],
|
error: $errorsStore[key],
|
||||||
name: capitalise(properties.display || key),
|
name: capitalise(properties.display || key),
|
||||||
|
placeholder: properties.placeholder,
|
||||||
type: properties.type,
|
type: properties.type,
|
||||||
hidden: properties.hidden,
|
hidden: properties.hidden,
|
||||||
config: properties.config,
|
config: properties.config,
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
get(integrations),
|
get(integrations),
|
||||||
datasource
|
datasource
|
||||||
)
|
)
|
||||||
await datasources.update({ datasource, integration })
|
await datasources.save({ datasource, integration })
|
||||||
|
|
||||||
await afterSave({ datasource, action })
|
await afterSave({ datasource, action })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -176,7 +176,7 @@
|
||||||
notifications.success(`Request saved successfully`)
|
notifications.success(`Request saved successfully`)
|
||||||
if (dynamicVariables) {
|
if (dynamicVariables) {
|
||||||
datasource.config.dynamicVariables = rebuildVariables(saveId)
|
datasource.config.dynamicVariables = rebuildVariables(saveId)
|
||||||
datasource = await datasources.update({
|
datasource = await datasources.save({
|
||||||
integration: integrationInfo,
|
integration: integrationInfo,
|
||||||
datasource,
|
datasource,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1507,7 +1507,12 @@ export const updateReferencesInObject = ({
|
||||||
|
|
||||||
// Migrate references
|
// Migrate references
|
||||||
// Switch all bindings to reference their ids
|
// Switch all bindings to reference their ids
|
||||||
export const migrateReferencesInObject = ({ obj, label = "steps", steps }) => {
|
export const migrateReferencesInObject = ({
|
||||||
|
obj,
|
||||||
|
label = "steps",
|
||||||
|
steps,
|
||||||
|
originalIndex,
|
||||||
|
}) => {
|
||||||
const stepIndexRegex = new RegExp(`{{\\s*${label}\\.(\\d+)\\.`, "g")
|
const stepIndexRegex = new RegExp(`{{\\s*${label}\\.(\\d+)\\.`, "g")
|
||||||
const updateActionStep = (str, index, replaceWith) =>
|
const updateActionStep = (str, index, replaceWith) =>
|
||||||
str.replace(`{{ ${label}.${index}.`, `{{ ${label}.${replaceWith}.`)
|
str.replace(`{{ ${label}.${index}.`, `{{ ${label}.${replaceWith}.`)
|
||||||
|
@ -1528,6 +1533,7 @@ export const migrateReferencesInObject = ({ obj, label = "steps", steps }) => {
|
||||||
migrateReferencesInObject({
|
migrateReferencesInObject({
|
||||||
obj: obj[key],
|
obj: obj[key],
|
||||||
steps,
|
steps,
|
||||||
|
originalIndex,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -368,20 +368,22 @@
|
||||||
const payload = [
|
const payload = [
|
||||||
{
|
{
|
||||||
email: newUserEmail,
|
email: newUserEmail,
|
||||||
builder: {
|
userInfo: {
|
||||||
global: creationRoleType === Constants.BudibaseRoles.Admin,
|
builder: {
|
||||||
creator: creationRoleType === Constants.BudibaseRoles.Creator,
|
global: creationRoleType === Constants.BudibaseRoles.Admin,
|
||||||
|
creator: creationRoleType === Constants.BudibaseRoles.Creator,
|
||||||
|
},
|
||||||
|
admin: { global: creationRoleType === Constants.BudibaseRoles.Admin },
|
||||||
},
|
},
|
||||||
admin: { global: creationRoleType === Constants.BudibaseRoles.Admin },
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const notCreatingAdmin = creationRoleType !== Constants.BudibaseRoles.Admin
|
const notCreatingAdmin = creationRoleType !== Constants.BudibaseRoles.Admin
|
||||||
const isCreator = creationAccessType === Constants.Roles.CREATOR
|
const isCreator = creationAccessType === Constants.Roles.CREATOR
|
||||||
if (notCreatingAdmin && isCreator) {
|
if (notCreatingAdmin && isCreator) {
|
||||||
payload[0].builder.apps = [prodAppId]
|
payload[0].userInfo.builder.apps = [prodAppId]
|
||||||
} else if (notCreatingAdmin && !isCreator) {
|
} else if (notCreatingAdmin && !isCreator) {
|
||||||
payload[0].apps = { [prodAppId]: creationAccessType }
|
payload[0].userInfo.apps = { [prodAppId]: creationAccessType }
|
||||||
}
|
}
|
||||||
|
|
||||||
let userInviteResponse
|
let userInviteResponse
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
async function saveDatasource({ config, name }) {
|
async function saveDatasource({ config, name }) {
|
||||||
try {
|
try {
|
||||||
await datasources.update({
|
await datasources.save({
|
||||||
integration,
|
integration,
|
||||||
datasource: { ...datasource, config, name },
|
datasource: { ...datasource, config, name },
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
get(integrations),
|
get(integrations),
|
||||||
updatedDatasource
|
updatedDatasource
|
||||||
)
|
)
|
||||||
await datasources.update({ datasource: updatedDatasource, integration })
|
await datasources.save({ datasource: updatedDatasource, integration })
|
||||||
notifications.success(
|
notifications.success(
|
||||||
`Datasource ${updatedDatasource.name} updated successfully`
|
`Datasource ${updatedDatasource.name} updated successfully`
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,40 +1,22 @@
|
||||||
import { writable, Writable } from "svelte/store"
|
import { writable, Writable, Readable } from "svelte/store"
|
||||||
|
|
||||||
interface BudiStoreOpts {
|
interface BudiStoreOpts {
|
||||||
debug?: boolean
|
debug?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class BudiStore<T> implements Writable<T> {
|
export class BudiStore<T> {
|
||||||
store: Writable<T>
|
store: Writable<T>
|
||||||
subscribe: Writable<T>["subscribe"]
|
subscribe: Writable<T>["subscribe"]
|
||||||
update: Writable<T>["update"]
|
update: Writable<T>["update"]
|
||||||
set: Writable<T>["set"]
|
set: Writable<T>["set"]
|
||||||
|
|
||||||
constructor(init: T, opts?: BudiStoreOpts) {
|
constructor(init: T, opts?: BudiStoreOpts) {
|
||||||
const store = writable<T>(init)
|
this.store = writable<T>(init)
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal Svelte store
|
|
||||||
*/
|
|
||||||
this.store = store
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exposes the svelte subscribe fn to allow $ notation access
|
|
||||||
* @example
|
|
||||||
* $navigation.selectedScreenId
|
|
||||||
*/
|
|
||||||
this.subscribe = this.store.subscribe
|
this.subscribe = this.store.subscribe
|
||||||
|
|
||||||
/**
|
|
||||||
* Exposes the svelte update fn.
|
|
||||||
* *Store modification should be kept to a minimum
|
|
||||||
*/
|
|
||||||
this.update = this.store.update
|
this.update = this.store.update
|
||||||
this.set = this.store.set
|
this.set = this.store.set
|
||||||
|
|
||||||
/**
|
// Optional debug mode to output the store updates to console
|
||||||
* Optional debug mode to output the store updates to console
|
|
||||||
*/
|
|
||||||
if (opts?.debug) {
|
if (opts?.debug) {
|
||||||
this.subscribe(state => {
|
this.subscribe(state => {
|
||||||
console.warn(`${this.constructor.name}`, state)
|
console.warn(`${this.constructor.name}`, state)
|
||||||
|
@ -42,3 +24,18 @@ export default class BudiStore<T> implements Writable<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class DerivedBudiStore<T, DerivedT extends T> extends BudiStore<T> {
|
||||||
|
derivedStore: Readable<DerivedT>
|
||||||
|
subscribe: Readable<DerivedT>["subscribe"]
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
init: T,
|
||||||
|
makeDerivedStore: (store: Writable<T>) => Readable<DerivedT>,
|
||||||
|
opts?: BudiStoreOpts
|
||||||
|
) {
|
||||||
|
super(init, opts)
|
||||||
|
this.derivedStore = makeDerivedStore(this.store)
|
||||||
|
this.subscribe = this.derivedStore.subscribe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,54 @@
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import BudiStore from "../BudiStore"
|
import { BudiStore } from "../BudiStore"
|
||||||
|
import {
|
||||||
|
App,
|
||||||
|
AppFeatures,
|
||||||
|
AppIcon,
|
||||||
|
AutomationSettings,
|
||||||
|
Plugin,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
export const INITIAL_APP_META_STATE = {
|
interface ClientFeatures {
|
||||||
|
spectrumThemes: boolean
|
||||||
|
intelligentLoading: boolean
|
||||||
|
deviceAwareness: boolean
|
||||||
|
state: boolean
|
||||||
|
rowSelection: boolean
|
||||||
|
customThemes: boolean
|
||||||
|
devicePreview: boolean
|
||||||
|
messagePassing: boolean
|
||||||
|
continueIfAction: boolean
|
||||||
|
showNotificationAction: boolean
|
||||||
|
sidePanel: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TypeSupportPresets {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AppMetaState {
|
||||||
|
appId: string
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
libraries: string[]
|
||||||
|
clientFeatures: ClientFeatures
|
||||||
|
typeSupportPresets: TypeSupportPresets
|
||||||
|
features: AppFeatures
|
||||||
|
clientLibPath: string
|
||||||
|
hasLock: boolean
|
||||||
|
appInstance: { _id: string } | null
|
||||||
|
initialised: boolean
|
||||||
|
hasAppPackage: boolean
|
||||||
|
usedPlugins: Plugin[] | null
|
||||||
|
automations: AutomationSettings
|
||||||
|
routes: { [key: string]: any }
|
||||||
|
version?: string
|
||||||
|
revertableVersion?: string
|
||||||
|
upgradableVersion?: string
|
||||||
|
icon?: AppIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INITIAL_APP_META_STATE: AppMetaState = {
|
||||||
appId: "",
|
appId: "",
|
||||||
name: "",
|
name: "",
|
||||||
url: "",
|
url: "",
|
||||||
|
@ -34,23 +81,27 @@ export const INITIAL_APP_META_STATE = {
|
||||||
routes: {},
|
routes: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AppMetaStore extends BudiStore {
|
export class AppMetaStore extends BudiStore<AppMetaState> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(INITIAL_APP_META_STATE)
|
super(INITIAL_APP_META_STATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset(): void {
|
||||||
this.store.set({ ...INITIAL_APP_META_STATE })
|
this.store.set({ ...INITIAL_APP_META_STATE })
|
||||||
}
|
}
|
||||||
|
|
||||||
syncAppPackage(pkg) {
|
syncAppPackage(pkg: {
|
||||||
|
application: App
|
||||||
|
clientLibPath: string
|
||||||
|
hasLock: boolean
|
||||||
|
}): void {
|
||||||
const { application: app, clientLibPath, hasLock } = pkg
|
const { application: app, clientLibPath, hasLock } = pkg
|
||||||
|
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
name: app.name,
|
name: app.name,
|
||||||
appId: app.appId,
|
appId: app.appId,
|
||||||
url: app.url,
|
url: app.url || "",
|
||||||
hasLock,
|
hasLock,
|
||||||
clientLibPath,
|
clientLibPath,
|
||||||
libraries: app.componentLibraries,
|
libraries: app.componentLibraries,
|
||||||
|
@ -58,8 +109,8 @@ export class AppMetaStore extends BudiStore {
|
||||||
appInstance: app.instance,
|
appInstance: app.instance,
|
||||||
revertableVersion: app.revertableVersion,
|
revertableVersion: app.revertableVersion,
|
||||||
upgradableVersion: app.upgradableVersion,
|
upgradableVersion: app.upgradableVersion,
|
||||||
usedPlugins: app.usedPlugins,
|
usedPlugins: app.usedPlugins || null,
|
||||||
icon: app.icon || {},
|
icon: app.icon,
|
||||||
features: {
|
features: {
|
||||||
...INITIAL_APP_META_STATE.features,
|
...INITIAL_APP_META_STATE.features,
|
||||||
...app.features,
|
...app.features,
|
||||||
|
@ -70,7 +121,7 @@ export class AppMetaStore extends BudiStore {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
syncClientFeatures(features) {
|
syncClientFeatures(features: Partial<ClientFeatures>): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
clientFeatures: {
|
clientFeatures: {
|
||||||
|
@ -80,14 +131,14 @@ export class AppMetaStore extends BudiStore {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
syncClientTypeSupportPresets(typeSupportPresets) {
|
syncClientTypeSupportPresets(typeSupportPresets: TypeSupportPresets): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
typeSupportPresets,
|
typeSupportPresets,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncAppRoutes() {
|
async syncAppRoutes(): Promise<void> {
|
||||||
const resp = await API.fetchAppRoutes()
|
const resp = await API.fetchAppRoutes()
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
|
@ -96,7 +147,7 @@ export class AppMetaStore extends BudiStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returned from socket
|
// Returned from socket
|
||||||
syncMetadata(metadata) {
|
syncMetadata(metadata: { name: string; url: string; icon?: AppIcon }): void {
|
||||||
const { name, url, icon } = metadata
|
const { name, url, icon } = metadata
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
File diff suppressed because it is too large
Load Diff
|
@ -1,10 +1,28 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { createBuilderWebsocket } from "./websocket.js"
|
import { createBuilderWebsocket } from "./websocket.js"
|
||||||
|
import { Socket } from "socket.io-client"
|
||||||
import { BuilderSocketEvent } from "@budibase/shared-core"
|
import { BuilderSocketEvent } from "@budibase/shared-core"
|
||||||
import BudiStore from "../BudiStore.js"
|
import { BudiStore } from "../BudiStore.js"
|
||||||
import { TOUR_KEYS } from "@/components/portal/onboarding/tours.js"
|
import { TOUR_KEYS } from "@/components/portal/onboarding/tours.js"
|
||||||
|
import { App } from "@budibase/types"
|
||||||
|
|
||||||
export const INITIAL_BUILDER_STATE = {
|
interface BuilderState {
|
||||||
|
previousTopNavPath: Record<string, string>
|
||||||
|
highlightedSetting: {
|
||||||
|
key: string
|
||||||
|
type: "info" | string
|
||||||
|
} | null
|
||||||
|
propertyFocus: string | null
|
||||||
|
builderSidePanel: boolean
|
||||||
|
onboarding: boolean
|
||||||
|
tourNodes: Record<string, HTMLElement> | null
|
||||||
|
tourKey: string | null
|
||||||
|
tourStepKey: string | null
|
||||||
|
hoveredComponentId: string | null
|
||||||
|
websocket?: Socket
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INITIAL_BUILDER_STATE: BuilderState = {
|
||||||
previousTopNavPath: {},
|
previousTopNavPath: {},
|
||||||
highlightedSetting: null,
|
highlightedSetting: null,
|
||||||
propertyFocus: null,
|
propertyFocus: null,
|
||||||
|
@ -16,7 +34,9 @@ export const INITIAL_BUILDER_STATE = {
|
||||||
hoveredComponentId: null,
|
hoveredComponentId: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BuilderStore extends BudiStore {
|
export class BuilderStore extends BudiStore<BuilderState> {
|
||||||
|
websocket?: Socket
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({ ...INITIAL_BUILDER_STATE })
|
super({ ...INITIAL_BUILDER_STATE })
|
||||||
|
|
||||||
|
@ -32,11 +52,9 @@ export class BuilderStore extends BudiStore {
|
||||||
this.registerTourNode = this.registerTourNode.bind(this)
|
this.registerTourNode = this.registerTourNode.bind(this)
|
||||||
this.destroyTourNode = this.destroyTourNode.bind(this)
|
this.destroyTourNode = this.destroyTourNode.bind(this)
|
||||||
this.startBuilderOnboarding = this.startBuilderOnboarding.bind(this)
|
this.startBuilderOnboarding = this.startBuilderOnboarding.bind(this)
|
||||||
|
|
||||||
this.websocket
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init(app) {
|
init(app: App): void {
|
||||||
if (!app?.appId) {
|
if (!app?.appId) {
|
||||||
console.error("BuilderStore: No appId supplied for websocket")
|
console.error("BuilderStore: No appId supplied for websocket")
|
||||||
return
|
return
|
||||||
|
@ -46,45 +64,46 @@ export class BuilderStore extends BudiStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh(): void {
|
||||||
this.store.set(this.store.get())
|
const currentState = get(this.store)
|
||||||
|
this.store.set(currentState)
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset(): void {
|
||||||
this.store.set({ ...INITIAL_BUILDER_STATE })
|
this.store.set({ ...INITIAL_BUILDER_STATE })
|
||||||
this.websocket?.disconnect()
|
this.websocket?.disconnect()
|
||||||
this.websocket = null
|
this.websocket = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
highlightSetting(key, type) {
|
highlightSetting(key?: string, type?: string): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
highlightedSetting: key ? { key, type: type || "info" } : null,
|
highlightedSetting: key ? { key, type: type || "info" } : null,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
propertyFocus(key) {
|
propertyFocus(key: string | null): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
propertyFocus: key,
|
propertyFocus: key,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
showBuilderSidePanel() {
|
showBuilderSidePanel(): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
builderSidePanel: true,
|
builderSidePanel: true,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
hideBuilderSidePanel() {
|
hideBuilderSidePanel(): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
builderSidePanel: false,
|
builderSidePanel: false,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
setPreviousTopNavPath(route, url) {
|
setPreviousTopNavPath(route: string, url: string): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
previousTopNavPath: {
|
previousTopNavPath: {
|
||||||
|
@ -94,13 +113,13 @@ export class BuilderStore extends BudiStore {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
selectResource(id) {
|
selectResource(id: string): void {
|
||||||
this.websocket.emit(BuilderSocketEvent.SelectResource, {
|
this.websocket?.emit(BuilderSocketEvent.SelectResource, {
|
||||||
resourceId: id,
|
resourceId: id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
registerTourNode(tourStepKey, node) {
|
registerTourNode(tourStepKey: string, node: HTMLElement): void {
|
||||||
this.update(state => {
|
this.update(state => {
|
||||||
const update = {
|
const update = {
|
||||||
...state,
|
...state,
|
||||||
|
@ -113,7 +132,7 @@ export class BuilderStore extends BudiStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyTourNode(tourStepKey) {
|
destroyTourNode(tourStepKey: string): void {
|
||||||
const store = get(this.store)
|
const store = get(this.store)
|
||||||
if (store.tourNodes?.[tourStepKey]) {
|
if (store.tourNodes?.[tourStepKey]) {
|
||||||
const nodes = { ...store.tourNodes }
|
const nodes = { ...store.tourNodes }
|
||||||
|
@ -125,7 +144,7 @@ export class BuilderStore extends BudiStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startBuilderOnboarding() {
|
startBuilderOnboarding(): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
onboarding: true,
|
onboarding: true,
|
||||||
|
@ -133,19 +152,19 @@ export class BuilderStore extends BudiStore {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
endBuilderOnboarding() {
|
endBuilderOnboarding(): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
onboarding: false,
|
onboarding: false,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
setTour(tourKey) {
|
setTour(tourKey?: string | null): void {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
tourStepKey: null,
|
tourStepKey: null,
|
||||||
tourNodes: null,
|
tourNodes: null,
|
||||||
tourKey: tourKey,
|
tourKey: tourKey || null,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -28,7 +28,7 @@ import {
|
||||||
DB_TYPE_INTERNAL,
|
DB_TYPE_INTERNAL,
|
||||||
DB_TYPE_EXTERNAL,
|
DB_TYPE_EXTERNAL,
|
||||||
} from "@/constants/backend"
|
} from "@/constants/backend"
|
||||||
import BudiStore from "../BudiStore"
|
import { BudiStore } from "../BudiStore"
|
||||||
import { Utils } from "@budibase/frontend-core"
|
import { Utils } from "@budibase/frontend-core"
|
||||||
import { FieldType } from "@budibase/types"
|
import { FieldType } from "@budibase/types"
|
||||||
import { utils } from "@budibase/shared-core"
|
import { utils } from "@budibase/shared-core"
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { writable } from "svelte/store"
|
|
||||||
|
|
||||||
export const INITIAL_CONTEXT_MENU_STATE = {
|
|
||||||
id: null,
|
|
||||||
items: [],
|
|
||||||
position: { x: 0, y: 0 },
|
|
||||||
visible: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createViewsStore() {
|
|
||||||
const store = writable({ ...INITIAL_CONTEXT_MENU_STATE })
|
|
||||||
|
|
||||||
const open = (id, items, position) => {
|
|
||||||
store.set({ id, items, position, visible: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
store.set({ ...INITIAL_CONTEXT_MENU_STATE })
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe: store.subscribe,
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const contextMenuStore = createViewsStore()
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
|
interface Position {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MenuItem {
|
||||||
|
label: string
|
||||||
|
icon?: string
|
||||||
|
action: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextMenuState {
|
||||||
|
id: string | null
|
||||||
|
items: MenuItem[]
|
||||||
|
position: Position
|
||||||
|
visible: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INITIAL_CONTEXT_MENU_STATE: ContextMenuState = {
|
||||||
|
id: null,
|
||||||
|
items: [],
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
visible: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createViewsStore() {
|
||||||
|
const store = writable<ContextMenuState>({ ...INITIAL_CONTEXT_MENU_STATE })
|
||||||
|
|
||||||
|
const open = (id: string, items: MenuItem[], position: Position): void => {
|
||||||
|
store.set({ id, items, position, visible: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = (): void => {
|
||||||
|
store.set({ ...INITIAL_CONTEXT_MENU_STATE })
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const contextMenuStore = createViewsStore()
|
|
@ -1,4 +1,4 @@
|
||||||
import { writable, derived, get } from "svelte/store"
|
import { derived, get, Writable } from "svelte/store"
|
||||||
import {
|
import {
|
||||||
IntegrationTypes,
|
IntegrationTypes,
|
||||||
DEFAULT_BB_DATASOURCE_ID,
|
DEFAULT_BB_DATASOURCE_ID,
|
||||||
|
@ -16,11 +16,7 @@ import {
|
||||||
SourceName,
|
SourceName,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { TableNames } from "@/constants"
|
import { TableNames } from "@/constants"
|
||||||
|
import { DerivedBudiStore } from "@/stores/BudiStore"
|
||||||
// when building the internal DS - seems to represent it slightly differently to the backend typing of a DS
|
|
||||||
interface InternalDatasource extends Omit<Datasource, "entities"> {
|
|
||||||
entities: Table[]
|
|
||||||
}
|
|
||||||
|
|
||||||
class TableImportError extends Error {
|
class TableImportError extends Error {
|
||||||
errors: Record<string, string>
|
errors: Record<string, string>
|
||||||
|
@ -40,102 +36,138 @@ class TableImportError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DatasourceStore {
|
// when building the internal DS - seems to represent it slightly differently to the backend typing of a DS
|
||||||
list: Datasource[]
|
interface InternalDatasource extends Omit<Datasource, "entities"> {
|
||||||
|
entities: Table[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BuilderDatasourceStore {
|
||||||
|
rawList: Datasource[]
|
||||||
selectedDatasourceId: null | string
|
selectedDatasourceId: null | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDatasourcesStore() {
|
interface DerivedDatasourceStore extends BuilderDatasourceStore {
|
||||||
const store = writable<DatasourceStore>({
|
list: (Datasource | InternalDatasource)[]
|
||||||
list: [],
|
selected?: Datasource | InternalDatasource
|
||||||
selectedDatasourceId: null,
|
hasDefaultData: boolean
|
||||||
})
|
hasData: boolean
|
||||||
|
}
|
||||||
|
|
||||||
const derivedStore = derived([store, tables], ([$store, $tables]) => {
|
export class DatasourceStore extends DerivedBudiStore<
|
||||||
// Set the internal datasource entities from the table list, which we're
|
BuilderDatasourceStore,
|
||||||
// able to keep updated unlike the egress generated definition of the
|
DerivedDatasourceStore
|
||||||
// internal datasource
|
> {
|
||||||
let internalDS: Datasource | InternalDatasource | undefined =
|
constructor() {
|
||||||
$store.list?.find(ds => ds._id === BUDIBASE_INTERNAL_DB_ID)
|
const makeDerivedStore = (store: Writable<BuilderDatasourceStore>) => {
|
||||||
let otherDS = $store.list?.filter(ds => ds._id !== BUDIBASE_INTERNAL_DB_ID)
|
return derived([store, tables], ([$store, $tables]) => {
|
||||||
if (internalDS) {
|
// Set the internal datasource entities from the table list, which we're
|
||||||
const tables: Table[] = $tables.list?.filter((table: Table) => {
|
// able to keep updated unlike the egress generated definition of the
|
||||||
return (
|
// internal datasource
|
||||||
table.sourceId === BUDIBASE_INTERNAL_DB_ID &&
|
let internalDS: Datasource | InternalDatasource | undefined =
|
||||||
table._id !== TableNames.USERS
|
$store.rawList?.find(ds => ds._id === BUDIBASE_INTERNAL_DB_ID)
|
||||||
|
let otherDS = $store.rawList?.filter(
|
||||||
|
ds => ds._id !== BUDIBASE_INTERNAL_DB_ID
|
||||||
)
|
)
|
||||||
|
if (internalDS) {
|
||||||
|
const tables: Table[] = $tables.list?.filter((table: Table) => {
|
||||||
|
return (
|
||||||
|
table.sourceId === BUDIBASE_INTERNAL_DB_ID &&
|
||||||
|
table._id !== TableNames.USERS
|
||||||
|
)
|
||||||
|
})
|
||||||
|
internalDS = {
|
||||||
|
...internalDS,
|
||||||
|
entities: tables,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up enriched DS list
|
||||||
|
// Only add the internal DS if we have at least one non-users table
|
||||||
|
let list: (InternalDatasource | Datasource)[] = []
|
||||||
|
if (internalDS?.entities?.length) {
|
||||||
|
list.push(internalDS)
|
||||||
|
}
|
||||||
|
list = list.concat(otherDS || [])
|
||||||
|
|
||||||
|
return {
|
||||||
|
...$store,
|
||||||
|
list,
|
||||||
|
selected: list?.find(ds => ds._id === $store.selectedDatasourceId),
|
||||||
|
hasDefaultData: list?.some(ds => ds._id === DEFAULT_BB_DATASOURCE_ID),
|
||||||
|
hasData: list?.length > 0,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
internalDS = {
|
|
||||||
...internalDS,
|
|
||||||
entities: tables,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build up enriched DS list
|
super(
|
||||||
// Only add the internal DS if we have at least one non-users table
|
{
|
||||||
let list: (InternalDatasource | Datasource)[] = []
|
rawList: [],
|
||||||
if (internalDS?.entities?.length) {
|
selectedDatasourceId: null,
|
||||||
list.push(internalDS)
|
},
|
||||||
}
|
makeDerivedStore
|
||||||
list = list.concat(otherDS || [])
|
)
|
||||||
|
|
||||||
return {
|
this.fetch = this.fetch.bind(this)
|
||||||
...$store,
|
this.init = this.fetch.bind(this)
|
||||||
list,
|
this.select = this.select.bind(this)
|
||||||
selected: list?.find(ds => ds._id === $store.selectedDatasourceId),
|
this.updateSchema = this.updateSchema.bind(this)
|
||||||
hasDefaultData: list?.some(ds => ds._id === DEFAULT_BB_DATASOURCE_ID),
|
this.create = this.create.bind(this)
|
||||||
hasData: list?.length > 0,
|
this.delete = this.deleteDatasource.bind(this)
|
||||||
}
|
this.save = this.save.bind(this)
|
||||||
})
|
this.replaceDatasource = this.replaceDatasource.bind(this)
|
||||||
|
this.getTableNames = this.getTableNames.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
const fetch = async () => {
|
async fetch() {
|
||||||
const datasources = await API.getDatasources()
|
const datasources = await API.getDatasources()
|
||||||
store.update(state => ({
|
this.store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
list: datasources,
|
rawList: datasources,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const select = (id: string) => {
|
async init() {
|
||||||
store.update(state => ({
|
return this.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
select(id: string) {
|
||||||
|
this.store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
selectedDatasourceId: id,
|
selectedDatasourceId: id,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateDatasource = (
|
private updateDatasourceInStore(
|
||||||
response: { datasource: Datasource; errors?: Record<string, string> },
|
response: { datasource: Datasource; errors?: Record<string, string> },
|
||||||
{ ignoreErrors }: { ignoreErrors?: boolean } = {}
|
{ ignoreErrors }: { ignoreErrors?: boolean } = {}
|
||||||
) => {
|
) {
|
||||||
const { datasource, errors } = response
|
const { datasource, errors } = response
|
||||||
if (!ignoreErrors && errors && Object.keys(errors).length > 0) {
|
if (!ignoreErrors && errors && Object.keys(errors).length > 0) {
|
||||||
throw new TableImportError(errors)
|
throw new TableImportError(errors)
|
||||||
}
|
}
|
||||||
replaceDatasource(datasource._id!, datasource)
|
this.replaceDatasource(datasource._id!, datasource)
|
||||||
select(datasource._id!)
|
this.select(datasource._id!)
|
||||||
return datasource
|
return datasource
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateSchema = async (
|
async updateSchema(datasource: Datasource, tablesFilter: string[]) {
|
||||||
datasource: Datasource,
|
|
||||||
tablesFilter: string[]
|
|
||||||
) => {
|
|
||||||
const response = await API.buildDatasourceSchema(
|
const response = await API.buildDatasourceSchema(
|
||||||
datasource?._id!,
|
datasource?._id!,
|
||||||
tablesFilter
|
tablesFilter
|
||||||
)
|
)
|
||||||
updateDatasource(response)
|
this.updateDatasourceInStore(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceCount = (source: string) => {
|
sourceCount(source: string) {
|
||||||
return get(store).list.filter(datasource => datasource.source === source)
|
return get(this.store).rawList.filter(
|
||||||
.length
|
datasource => datasource.source === source
|
||||||
|
).length
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkDatasourceValidity = async (
|
async checkDatasourceValidity(
|
||||||
integration: Integration,
|
integration: Integration,
|
||||||
datasource: Datasource
|
datasource: Datasource
|
||||||
): Promise<{ valid: boolean; error?: string }> => {
|
): Promise<{ valid: boolean; error?: string }> {
|
||||||
if (integration.features?.[DatasourceFeature.CONNECTION_CHECKING]) {
|
if (integration.features?.[DatasourceFeature.CONNECTION_CHECKING]) {
|
||||||
const { connected, error } = await API.validateDatasource(datasource)
|
const { connected, error } = await API.validateDatasource(datasource)
|
||||||
if (connected) {
|
if (connected) {
|
||||||
|
@ -147,14 +179,14 @@ export function createDatasourcesStore() {
|
||||||
return { valid: true }
|
return { valid: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
const create = async ({
|
async create({
|
||||||
integration,
|
integration,
|
||||||
config,
|
config,
|
||||||
}: {
|
}: {
|
||||||
integration: UIIntegration
|
integration: UIIntegration
|
||||||
config: Record<string, any>
|
config: Record<string, any>
|
||||||
}) => {
|
}) {
|
||||||
const count = sourceCount(integration.name)
|
const count = this.sourceCount(integration.name)
|
||||||
const nameModifier = count === 0 ? "" : ` ${count + 1}`
|
const nameModifier = count === 0 ? "" : ` ${count + 1}`
|
||||||
|
|
||||||
const datasource: Datasource = {
|
const datasource: Datasource = {
|
||||||
|
@ -166,7 +198,7 @@ export function createDatasourcesStore() {
|
||||||
isSQL: integration.isSQL,
|
isSQL: integration.isSQL,
|
||||||
}
|
}
|
||||||
|
|
||||||
const { valid, error } = await checkDatasourceValidity(
|
const { valid, error } = await this.checkDatasourceValidity(
|
||||||
integration,
|
integration,
|
||||||
datasource
|
datasource
|
||||||
)
|
)
|
||||||
|
@ -179,43 +211,47 @@ export function createDatasourcesStore() {
|
||||||
fetchSchema: integration.plus,
|
fetchSchema: integration.plus,
|
||||||
})
|
})
|
||||||
|
|
||||||
return updateDatasource(response, { ignoreErrors: true })
|
return this.updateDatasourceInStore(response, { ignoreErrors: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
const update = async ({
|
async save({
|
||||||
integration,
|
integration,
|
||||||
datasource,
|
datasource,
|
||||||
}: {
|
}: {
|
||||||
integration: Integration
|
integration: Integration
|
||||||
datasource: Datasource
|
datasource: Datasource
|
||||||
}) => {
|
}) {
|
||||||
if (await checkDatasourceValidity(integration, datasource)) {
|
if (!(await this.checkDatasourceValidity(integration, datasource)).valid) {
|
||||||
throw new Error("Unable to connect")
|
throw new Error("Unable to connect")
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await API.updateDatasource(datasource)
|
const response = await API.updateDatasource(datasource)
|
||||||
|
|
||||||
return updateDatasource(response)
|
return this.updateDatasourceInStore(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteDatasource = async (datasource: Datasource) => {
|
async deleteDatasource(datasource: Datasource) {
|
||||||
if (!datasource?._id || !datasource?._rev) {
|
if (!datasource?._id || !datasource?._rev) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await API.deleteDatasource(datasource._id, datasource._rev)
|
await API.deleteDatasource(datasource._id, datasource._rev)
|
||||||
replaceDatasource(datasource._id)
|
this.replaceDatasource(datasource._id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const replaceDatasource = (datasourceId: string, datasource?: Datasource) => {
|
async delete(datasource: Datasource) {
|
||||||
|
return this.deleteDatasource(datasource)
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceDatasource(datasourceId: string, datasource?: Datasource) {
|
||||||
if (!datasourceId) {
|
if (!datasourceId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle deletion
|
// Handle deletion
|
||||||
if (!datasource) {
|
if (!datasource) {
|
||||||
store.update(state => ({
|
this.store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
list: state.list.filter(x => x._id !== datasourceId),
|
rawList: state.rawList.filter(x => x._id !== datasourceId),
|
||||||
}))
|
}))
|
||||||
tables.removeDatasourceTables(datasourceId)
|
tables.removeDatasourceTables(datasourceId)
|
||||||
queries.removeDatasourceQueries(datasourceId)
|
queries.removeDatasourceQueries(datasourceId)
|
||||||
|
@ -223,11 +259,13 @@ export function createDatasourcesStore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new datasource
|
// Add new datasource
|
||||||
const index = get(store).list.findIndex(x => x._id === datasource._id)
|
const index = get(this.store).rawList.findIndex(
|
||||||
|
x => x._id === datasource._id
|
||||||
|
)
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
store.update(state => ({
|
this.store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
list: [...state.list, datasource],
|
rawList: [...state.rawList, datasource],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// If this is a new datasource then we should refresh the tables list,
|
// If this is a new datasource then we should refresh the tables list,
|
||||||
|
@ -237,30 +275,17 @@ export function createDatasourcesStore() {
|
||||||
|
|
||||||
// Update existing datasource
|
// Update existing datasource
|
||||||
else if (datasource) {
|
else if (datasource) {
|
||||||
store.update(state => {
|
this.store.update(state => {
|
||||||
state.list[index] = datasource
|
state.rawList[index] = datasource
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTableNames = async (datasource: Datasource) => {
|
async getTableNames(datasource: Datasource) {
|
||||||
const info = await API.fetchInfoForDatasource(datasource)
|
const info = await API.fetchInfoForDatasource(datasource)
|
||||||
return info.tableNames || []
|
return info.tableNames || []
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe: derivedStore.subscribe,
|
|
||||||
fetch,
|
|
||||||
init: fetch,
|
|
||||||
select,
|
|
||||||
updateSchema,
|
|
||||||
create,
|
|
||||||
update,
|
|
||||||
delete: deleteDatasource,
|
|
||||||
replaceDatasource,
|
|
||||||
getTableNames,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const datasources = createDatasourcesStore()
|
export const datasources = new DatasourceStore()
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable, type Writable } from "svelte/store"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
import { DeploymentProgressResponse } from "@budibase/types"
|
||||||
|
|
||||||
export const createDeploymentStore = () => {
|
export const createDeploymentStore = () => {
|
||||||
let store = writable([])
|
let store: Writable<DeploymentProgressResponse[]> = writable([])
|
||||||
|
|
||||||
const load = async () => {
|
const load = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
store.set(await API.getAppDeployments())
|
store.set(await API.getAppDeployments())
|
||||||
} catch (err) {
|
} catch (err) {
|
|
@ -1,6 +1,6 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { previewStore } from "@/stores/builder"
|
import { previewStore } from "@/stores/builder"
|
||||||
import BudiStore from "../BudiStore"
|
import { BudiStore } from "../BudiStore"
|
||||||
|
|
||||||
export const INITIAL_HOVER_STATE = {
|
export const INITIAL_HOVER_STATE = {
|
||||||
componentId: null,
|
componentId: null,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { derived, get } from "svelte/store"
|
import { derived, get } from "svelte/store"
|
||||||
import { componentStore } from "@/stores/builder"
|
import { componentStore } from "@/stores/builder"
|
||||||
import BudiStore from "../BudiStore"
|
import { BudiStore } from "../BudiStore"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
|
|
||||||
export const INITIAL_LAYOUT_STATE = {
|
export const INITIAL_LAYOUT_STATE = {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { appStore } from "@/stores/builder"
|
import { appStore } from "@/stores/builder"
|
||||||
import BudiStore from "../BudiStore"
|
import { BudiStore } from "../BudiStore"
|
||||||
|
|
||||||
export const INITIAL_NAVIGATION_STATE = {
|
export const INITIAL_NAVIGATION_STATE = {
|
||||||
navigation: "Top",
|
navigation: "Top",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { get, derived } from "svelte/store"
|
import { get, derived } from "svelte/store"
|
||||||
import BudiStore from "@/stores/BudiStore"
|
import { BudiStore } from "@/stores/BudiStore"
|
||||||
import { tables } from "./tables"
|
import { tables } from "./tables"
|
||||||
import { viewsV2 } from "./viewsV2"
|
import { viewsV2 } from "./viewsV2"
|
||||||
import { automationStore } from "./automations"
|
import { automationStore } from "./automations"
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
} from "@/stores/builder"
|
} from "@/stores/builder"
|
||||||
import { createHistoryStore } from "@/stores/builder/history"
|
import { createHistoryStore } from "@/stores/builder/history"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import BudiStore from "../BudiStore"
|
import { BudiStore } from "../BudiStore"
|
||||||
|
|
||||||
export const INITIAL_SCREENS_STATE = {
|
export const INITIAL_SCREENS_STATE = {
|
||||||
screens: [],
|
screens: [],
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { integrations } from "./integrations"
|
||||||
import { derived } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
import { DatasourceTypes } from "@/constants/backend"
|
import { DatasourceTypes } from "@/constants/backend"
|
||||||
import { UIIntegration, Integration } from "@budibase/types"
|
import { UIIntegration, Integration } from "@budibase/types"
|
||||||
|
import { BudiStore } from "@/stores/BudiStore"
|
||||||
|
|
||||||
const getIntegrationOrder = (type: string | undefined) => {
|
const getIntegrationOrder = (type: string | undefined) => {
|
||||||
// if type is not known, sort to end
|
// if type is not known, sort to end
|
||||||
|
@ -17,29 +18,35 @@ const getIntegrationOrder = (type: string | undefined) => {
|
||||||
return type.charCodeAt(0) + 4
|
return type.charCodeAt(0) + 4
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createSortedIntegrationsStore = () => {
|
export class SortedIntegrationStore extends BudiStore<UIIntegration[]> {
|
||||||
return derived<typeof integrations, UIIntegration[]>(
|
constructor() {
|
||||||
integrations,
|
super([])
|
||||||
$integrations => {
|
|
||||||
const entries: [string, Integration][] = Object.entries($integrations)
|
|
||||||
const integrationsAsArray = entries.map(([name, integration]) => ({
|
|
||||||
name,
|
|
||||||
...integration,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return integrationsAsArray.sort((integrationA, integrationB) => {
|
const derivedStore = derived<typeof integrations, UIIntegration[]>(
|
||||||
const integrationASortOrder = getIntegrationOrder(integrationA.type)
|
integrations,
|
||||||
const integrationBSortOrder = getIntegrationOrder(integrationB.type)
|
$integrations => {
|
||||||
if (integrationASortOrder === integrationBSortOrder) {
|
const entries: [string, Integration][] = Object.entries($integrations)
|
||||||
return integrationA.friendlyName.localeCompare(
|
const integrationsAsArray = entries.map(([name, integration]) => ({
|
||||||
integrationB.friendlyName
|
name,
|
||||||
)
|
...integration,
|
||||||
}
|
}))
|
||||||
|
|
||||||
return integrationASortOrder < integrationBSortOrder ? -1 : 1
|
return integrationsAsArray.sort((integrationA, integrationB) => {
|
||||||
})
|
const integrationASortOrder = getIntegrationOrder(integrationA.type)
|
||||||
}
|
const integrationBSortOrder = getIntegrationOrder(integrationB.type)
|
||||||
)
|
if (integrationASortOrder === integrationBSortOrder) {
|
||||||
|
return integrationA.friendlyName.localeCompare(
|
||||||
|
integrationB.friendlyName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return integrationASortOrder < integrationBSortOrder ? -1 : 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
this.subscribe = derivedStore.subscribe
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sortedIntegrations = createSortedIntegrationsStore()
|
export const sortedIntegrations = new SortedIntegrationStore()
|
||||||
|
|
|
@ -1,30 +1,64 @@
|
||||||
import { FieldType } from "@budibase/types"
|
import {
|
||||||
|
FieldSchema,
|
||||||
|
FieldType,
|
||||||
|
SaveTableRequest,
|
||||||
|
Table,
|
||||||
|
} from "@budibase/types"
|
||||||
import { SWITCHABLE_TYPES } from "@budibase/shared-core"
|
import { SWITCHABLE_TYPES } from "@budibase/shared-core"
|
||||||
import { get, writable, derived } from "svelte/store"
|
import { get, derived, Writable } from "svelte/store"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
|
import { DerivedBudiStore } from "@/stores/BudiStore"
|
||||||
|
|
||||||
export function createTablesStore() {
|
interface BuilderTableStore {
|
||||||
const store = writable({
|
list: Table[]
|
||||||
list: [],
|
selectedTableId?: string
|
||||||
selectedTableId: null,
|
}
|
||||||
})
|
|
||||||
const derivedStore = derived(store, $store => ({
|
|
||||||
...$store,
|
|
||||||
selected: $store.list?.find(table => table._id === $store.selectedTableId),
|
|
||||||
}))
|
|
||||||
|
|
||||||
const fetch = async () => {
|
interface DerivedTableStore extends BuilderTableStore {
|
||||||
|
selected?: Table
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TableStore extends DerivedBudiStore<
|
||||||
|
BuilderTableStore,
|
||||||
|
DerivedTableStore
|
||||||
|
> {
|
||||||
|
constructor() {
|
||||||
|
const makeDerivedStore = (store: Writable<BuilderTableStore>) => {
|
||||||
|
return derived(store, $store => ({
|
||||||
|
...$store,
|
||||||
|
selected: $store.list?.find(
|
||||||
|
table => table._id === $store.selectedTableId
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
list: [],
|
||||||
|
selectedTableId: undefined,
|
||||||
|
},
|
||||||
|
makeDerivedStore
|
||||||
|
)
|
||||||
|
|
||||||
|
this.select = this.select.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
return this.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch() {
|
||||||
const tables = await API.getTables()
|
const tables = await API.getTables()
|
||||||
store.update(state => ({
|
this.store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
list: tables,
|
list: tables,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const singleFetch = async tableId => {
|
private async singleFetch(tableId: string) {
|
||||||
const table = await API.getTable(tableId)
|
const table = await API.getTable(tableId)
|
||||||
store.update(state => {
|
this.store.update(state => {
|
||||||
const list = []
|
const list = []
|
||||||
// update the list, keep order accurate
|
// update the list, keep order accurate
|
||||||
for (let tbl of state.list) {
|
for (let tbl of state.list) {
|
||||||
|
@ -39,16 +73,16 @@ export function createTablesStore() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const select = tableId => {
|
select(tableId: string | undefined) {
|
||||||
store.update(state => ({
|
this.store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
selectedTableId: tableId,
|
selectedTableId: tableId,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const save = async table => {
|
async save(table: Table) {
|
||||||
const updatedTable = cloneDeep(table)
|
const updatedTable: SaveTableRequest = cloneDeep(table)
|
||||||
const oldTable = get(store).list.filter(t => t._id === table._id)[0]
|
const oldTable = get(this.store).list.filter(t => t._id === table._id)[0]
|
||||||
|
|
||||||
const fieldNames = []
|
const fieldNames = []
|
||||||
// Update any renamed schema keys to reflect their names
|
// Update any renamed schema keys to reflect their names
|
||||||
|
@ -79,8 +113,8 @@ export function createTablesStore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedTable = await API.saveTable(updatedTable)
|
const savedTable = await API.saveTable(updatedTable)
|
||||||
replaceTable(savedTable._id, savedTable)
|
this.replaceTable(savedTable._id, savedTable)
|
||||||
select(savedTable._id)
|
this.select(savedTable._id)
|
||||||
// make sure tables up to date (related)
|
// make sure tables up to date (related)
|
||||||
let newTableIds = []
|
let newTableIds = []
|
||||||
for (let column of Object.values(updatedTable?.schema || {})) {
|
for (let column of Object.values(updatedTable?.schema || {})) {
|
||||||
|
@ -99,28 +133,30 @@ export function createTablesStore() {
|
||||||
const tableIdsToFetch = [...new Set([...newTableIds, ...oldTableIds])]
|
const tableIdsToFetch = [...new Set([...newTableIds, ...oldTableIds])]
|
||||||
// too many tables to fetch, just get all
|
// too many tables to fetch, just get all
|
||||||
if (tableIdsToFetch.length > 3) {
|
if (tableIdsToFetch.length > 3) {
|
||||||
await fetch()
|
await this.fetch()
|
||||||
} else {
|
} else {
|
||||||
await Promise.all(tableIdsToFetch.map(id => singleFetch(id)))
|
await Promise.all(tableIdsToFetch.map(id => this.singleFetch(id)))
|
||||||
}
|
}
|
||||||
return savedTable
|
return savedTable
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteTable = async table => {
|
async delete(table: { _id: string; _rev: string }) {
|
||||||
if (!table?._id) {
|
await API.deleteTable(table._id, table._rev)
|
||||||
return
|
this.replaceTable(table._id, null)
|
||||||
}
|
|
||||||
await API.deleteTable(table._id, table._rev || "rev")
|
|
||||||
replaceTable(table._id, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveField = async ({
|
async saveField({
|
||||||
originalName,
|
originalName,
|
||||||
field,
|
field,
|
||||||
primaryDisplay = false,
|
primaryDisplay = false,
|
||||||
indexes,
|
indexes,
|
||||||
}) => {
|
}: {
|
||||||
let draft = cloneDeep(get(derivedStore).selected)
|
originalName: string
|
||||||
|
field: FieldSchema
|
||||||
|
primaryDisplay: boolean
|
||||||
|
indexes: Record<string, any>
|
||||||
|
}) {
|
||||||
|
const draft: SaveTableRequest = cloneDeep(get(this.derivedStore).selected!)
|
||||||
|
|
||||||
// delete the original if renaming
|
// delete the original if renaming
|
||||||
// need to handle if the column had no name, empty string
|
// need to handle if the column had no name, empty string
|
||||||
|
@ -139,7 +175,7 @@ export function createTablesStore() {
|
||||||
const fields = Object.keys(draft.schema)
|
const fields = Object.keys(draft.schema)
|
||||||
// pick another display column randomly if unselecting
|
// pick another display column randomly if unselecting
|
||||||
draft.primaryDisplay = fields.filter(
|
draft.primaryDisplay = fields.filter(
|
||||||
name => name !== originalName || name !== field
|
name => name !== originalName || name !== field.name
|
||||||
)[0]
|
)[0]
|
||||||
}
|
}
|
||||||
if (indexes) {
|
if (indexes) {
|
||||||
|
@ -150,24 +186,24 @@ export function createTablesStore() {
|
||||||
[field.name]: cloneDeep(field),
|
[field.name]: cloneDeep(field),
|
||||||
}
|
}
|
||||||
|
|
||||||
await save(draft)
|
await this.save(draft)
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteField = async field => {
|
async deleteField(field: { name: string | number }) {
|
||||||
let draft = cloneDeep(get(derivedStore).selected)
|
let draft = cloneDeep(get(this.derivedStore).selected!)
|
||||||
delete draft.schema[field.name]
|
delete draft.schema[field.name]
|
||||||
await save(draft)
|
await this.save(draft)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles external updates of tables
|
// Handles external updates of tables
|
||||||
const replaceTable = (tableId, table) => {
|
replaceTable(tableId: string | undefined, table: Table | null) {
|
||||||
if (!tableId) {
|
if (!tableId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle deletion
|
// Handle deletion
|
||||||
if (!table) {
|
if (!table) {
|
||||||
store.update(state => ({
|
this.store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
list: state.list.filter(x => x._id !== tableId),
|
list: state.list.filter(x => x._id !== tableId),
|
||||||
}))
|
}))
|
||||||
|
@ -175,9 +211,9 @@ export function createTablesStore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new table
|
// Add new table
|
||||||
const index = get(store).list.findIndex(x => x._id === table._id)
|
const index = get(this.store).list.findIndex(x => x._id === table._id)
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
store.update(state => ({
|
this.store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
list: [...state.list, table],
|
list: [...state.list, table],
|
||||||
}))
|
}))
|
||||||
|
@ -188,7 +224,7 @@ export function createTablesStore() {
|
||||||
// This function has to merge state as there discrepancies with the table
|
// This function has to merge state as there discrepancies with the table
|
||||||
// API endpoints. The table list endpoint and get table endpoint use the
|
// API endpoints. The table list endpoint and get table endpoint use the
|
||||||
// "type" property to mean different things.
|
// "type" property to mean different things.
|
||||||
store.update(state => {
|
this.store.update(state => {
|
||||||
state.list[index] = {
|
state.list[index] = {
|
||||||
...table,
|
...table,
|
||||||
type: state.list[index].type,
|
type: state.list[index].type,
|
||||||
|
@ -198,26 +234,12 @@ export function createTablesStore() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeDatasourceTables = datasourceId => {
|
removeDatasourceTables(datasourceId: string) {
|
||||||
store.update(state => ({
|
this.store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
list: state.list.filter(table => table.sourceId !== datasourceId),
|
list: state.list.filter(table => table.sourceId !== datasourceId),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
...store,
|
|
||||||
subscribe: derivedStore.subscribe,
|
|
||||||
fetch,
|
|
||||||
init: fetch,
|
|
||||||
select,
|
|
||||||
save,
|
|
||||||
delete: deleteTable,
|
|
||||||
saveField,
|
|
||||||
deleteField,
|
|
||||||
replaceTable,
|
|
||||||
removeDatasourceTables,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tables = createTablesStore()
|
export const tables = new TableStore()
|
|
@ -65,7 +65,7 @@ describe("Builder store", () => {
|
||||||
ctx.test.builderStore.reset()
|
ctx.test.builderStore.reset()
|
||||||
expect(disconnected).toBe(true)
|
expect(disconnected).toBe(true)
|
||||||
expect(ctx.test.store).toStrictEqual(INITIAL_BUILDER_STATE)
|
expect(ctx.test.store).toStrictEqual(INITIAL_BUILDER_STATE)
|
||||||
expect(ctx.test.builderStore.websocket).toBeNull()
|
expect(ctx.test.builderStore.websocket).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Attempt to emit a resource select event to the websocket on select", ctx => {
|
it("Attempt to emit a resource select event to the websocket on select", ctx => {
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { it, expect, describe, beforeEach, vi } from "vitest"
|
import { it, expect, describe, beforeEach, vi } from "vitest"
|
||||||
import { createSortedIntegrationsStore } from "@/stores/builder/sortedIntegrations"
|
import { SortedIntegrationStore } from "@/stores/builder/sortedIntegrations"
|
||||||
import { DatasourceTypes } from "@/constants/backend"
|
import { DatasourceTypes } from "@/constants/backend"
|
||||||
|
|
||||||
import { derived } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
import { integrations } from "@/stores/builder/integrations"
|
import { integrations } from "@/stores/builder/integrations"
|
||||||
|
|
||||||
vi.mock("svelte/store", () => ({
|
vi.mock("svelte/store", () => ({
|
||||||
derived: vi.fn(),
|
derived: vi.fn(() => ({
|
||||||
|
subscribe: vi.fn(),
|
||||||
|
})),
|
||||||
writable: vi.fn(() => ({
|
writable: vi.fn(() => ({
|
||||||
subscribe: vi.fn(),
|
subscribe: vi.fn(),
|
||||||
})),
|
})),
|
||||||
|
@ -14,6 +15,8 @@ vi.mock("svelte/store", () => ({
|
||||||
|
|
||||||
vi.mock("@/stores/builder/integrations", () => ({ integrations: vi.fn() }))
|
vi.mock("@/stores/builder/integrations", () => ({ integrations: vi.fn() }))
|
||||||
|
|
||||||
|
const mockedDerived = vi.mocked(derived)
|
||||||
|
|
||||||
const inputA = {
|
const inputA = {
|
||||||
nonRelationalA: {
|
nonRelationalA: {
|
||||||
friendlyName: "non-relational A",
|
friendlyName: "non-relational A",
|
||||||
|
@ -104,25 +107,28 @@ const expectedOutput = [
|
||||||
]
|
]
|
||||||
|
|
||||||
describe("sorted integrations store", () => {
|
describe("sorted integrations store", () => {
|
||||||
beforeEach(ctx => {
|
interface LocalContext {
|
||||||
|
returnedStore: SortedIntegrationStore
|
||||||
|
derivedCallback: any
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach<LocalContext>(ctx => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
|
|
||||||
ctx.returnedStore = createSortedIntegrationsStore()
|
ctx.returnedStore = new SortedIntegrationStore()
|
||||||
|
ctx.derivedCallback = mockedDerived.mock.calls[0]?.[1]
|
||||||
ctx.derivedCallback = derived.mock.calls[0][1]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls derived with the correct parameters", () => {
|
it("calls derived with the correct parameters", () => {
|
||||||
expect(derived).toHaveBeenCalledTimes(1)
|
expect(mockedDerived).toHaveBeenCalledTimes(1)
|
||||||
expect(derived).toHaveBeenCalledWith(integrations, expect.toBeFunc())
|
expect(mockedDerived).toHaveBeenCalledWith(
|
||||||
|
integrations,
|
||||||
|
expect.any(Function)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("derived callback", () => {
|
describe("derived callback", () => {
|
||||||
it("When no integrations are loaded", ctx => {
|
it<LocalContext>("When integrations are present", ctx => {
|
||||||
expect(ctx.derivedCallback({})).toEqual([])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("When integrations are present", ctx => {
|
|
||||||
expect(ctx.derivedCallback(inputA)).toEqual(expectedOutput)
|
expect(ctx.derivedCallback(inputA)).toEqual(expectedOutput)
|
||||||
expect(ctx.derivedCallback(inputB)).toEqual(expectedOutput)
|
expect(ctx.derivedCallback(inputB)).toEqual(expectedOutput)
|
||||||
})
|
})
|
|
@ -1,102 +0,0 @@
|
||||||
import { writable, derived, get } from "svelte/store"
|
|
||||||
import { tables } from "./tables"
|
|
||||||
import { API } from "@/api"
|
|
||||||
|
|
||||||
export function createViewsV2Store() {
|
|
||||||
const store = writable({
|
|
||||||
selectedViewId: null,
|
|
||||||
})
|
|
||||||
const derivedStore = derived([store, tables], ([$store, $tables]) => {
|
|
||||||
let list = []
|
|
||||||
$tables.list?.forEach(table => {
|
|
||||||
const views = Object.values(table?.views || {}).filter(view => {
|
|
||||||
return view.version === 2
|
|
||||||
})
|
|
||||||
list = list.concat(views)
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
...$store,
|
|
||||||
list,
|
|
||||||
selected: list.find(view => view.id === $store.selectedViewId),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const select = id => {
|
|
||||||
store.update(state => ({
|
|
||||||
...state,
|
|
||||||
selectedViewId: id,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteView = async view => {
|
|
||||||
await API.viewV2.delete(view.id)
|
|
||||||
replaceView(view.id, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const create = async view => {
|
|
||||||
const savedViewResponse = await API.viewV2.create(view)
|
|
||||||
const savedView = savedViewResponse.data
|
|
||||||
replaceView(savedView.id, savedView)
|
|
||||||
return savedView
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = async view => {
|
|
||||||
const res = await API.viewV2.update(view)
|
|
||||||
const savedView = res?.data
|
|
||||||
replaceView(view.id, savedView)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handles external updates of tables
|
|
||||||
const replaceView = (viewId, view) => {
|
|
||||||
if (!viewId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const existingView = get(derivedStore).list.find(view => view.id === viewId)
|
|
||||||
const tableIndex = get(tables).list.findIndex(table => {
|
|
||||||
return table._id === view?.tableId || table._id === existingView?.tableId
|
|
||||||
})
|
|
||||||
if (tableIndex === -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle deletion
|
|
||||||
if (!view) {
|
|
||||||
tables.update(state => {
|
|
||||||
delete state.list[tableIndex].views[existingView.name]
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new view
|
|
||||||
if (!existingView) {
|
|
||||||
tables.update(state => {
|
|
||||||
state.list[tableIndex].views[view.name] = view
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update existing view
|
|
||||||
else {
|
|
||||||
tables.update(state => {
|
|
||||||
// Remove old view
|
|
||||||
delete state.list[tableIndex].views[existingView.name]
|
|
||||||
|
|
||||||
// Add new view
|
|
||||||
state.list[tableIndex].views[view.name] = view
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe: derivedStore.subscribe,
|
|
||||||
select,
|
|
||||||
delete: deleteView,
|
|
||||||
create,
|
|
||||||
save,
|
|
||||||
replaceView,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const viewsV2 = createViewsV2Store()
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
import { derived, get, Writable } from "svelte/store"
|
||||||
|
import { tables } from "./tables"
|
||||||
|
import { API } from "@/api"
|
||||||
|
import { DerivedBudiStore } from "@/stores/BudiStore"
|
||||||
|
import { CreateViewRequest, UpdateViewRequest, ViewV2 } from "@budibase/types"
|
||||||
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
|
||||||
|
interface BuilderViewV2Store {
|
||||||
|
selectedViewId: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DerivedViewV2Store extends BuilderViewV2Store {
|
||||||
|
list: ViewV2[]
|
||||||
|
selected?: ViewV2
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ViewV2Store extends DerivedBudiStore<
|
||||||
|
BuilderViewV2Store,
|
||||||
|
DerivedViewV2Store
|
||||||
|
> {
|
||||||
|
constructor() {
|
||||||
|
const makeDerivedStore = (store: Writable<BuilderViewV2Store>) => {
|
||||||
|
return derived(
|
||||||
|
[store, tables],
|
||||||
|
([$store, $tables]): DerivedViewV2Store => {
|
||||||
|
let list: ViewV2[] = []
|
||||||
|
$tables.list?.forEach(table => {
|
||||||
|
const views = Object.values(table?.views || {}).filter(
|
||||||
|
helpers.views.isV2
|
||||||
|
)
|
||||||
|
list = list.concat(views)
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
...$store,
|
||||||
|
list,
|
||||||
|
selected: list.find(view => view.id === $store.selectedViewId),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
selectedViewId: null,
|
||||||
|
},
|
||||||
|
makeDerivedStore
|
||||||
|
)
|
||||||
|
|
||||||
|
this.select = this.select.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
select(id: string) {
|
||||||
|
this.store.update(state => ({
|
||||||
|
...state,
|
||||||
|
selectedViewId: id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(view: { id: string }) {
|
||||||
|
await API.viewV2.delete(view.id)
|
||||||
|
this.replaceView(view.id, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(view: CreateViewRequest) {
|
||||||
|
const savedViewResponse = await API.viewV2.create(view)
|
||||||
|
const savedView = savedViewResponse.data
|
||||||
|
this.replaceView(savedView.id, savedView)
|
||||||
|
return savedView
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(view: UpdateViewRequest) {
|
||||||
|
const res = await API.viewV2.update(view)
|
||||||
|
const savedView = res?.data
|
||||||
|
this.replaceView(view.id, savedView)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles external updates of tables
|
||||||
|
replaceView(viewId: string, view: ViewV2 | null) {
|
||||||
|
const existingView = get(this.derivedStore).list.find(
|
||||||
|
view => view.id === viewId
|
||||||
|
)
|
||||||
|
const tableIndex = get(tables).list.findIndex(table => {
|
||||||
|
return table._id === view?.tableId || table._id === existingView?.tableId
|
||||||
|
})
|
||||||
|
if (tableIndex === -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle deletion
|
||||||
|
if (!view && existingView) {
|
||||||
|
tables.update(state => {
|
||||||
|
delete state.list[tableIndex].views![existingView.name]
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new view
|
||||||
|
else if (!existingView && view) {
|
||||||
|
tables.update(state => {
|
||||||
|
state.list[tableIndex].views ??= {}
|
||||||
|
state.list[tableIndex].views[view.name] = view
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update existing view
|
||||||
|
else if (existingView && view) {
|
||||||
|
tables.update(state => {
|
||||||
|
// Remove old view
|
||||||
|
state.list[tableIndex].views ??= {}
|
||||||
|
delete state.list[tableIndex].views[existingView.name]
|
||||||
|
|
||||||
|
// Add new view
|
||||||
|
state.list[tableIndex].views[view.name] = view
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const viewsV2 = new ViewV2Store()
|
|
@ -1,6 +1,5 @@
|
||||||
import { it, expect, describe, beforeEach, vi } from "vitest"
|
import { it, expect, describe, beforeEach, vi } from "vitest"
|
||||||
import { createAdminStore } from "./admin"
|
import { createAdminStore } from "./admin"
|
||||||
|
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { auth } from "@/stores/portal"
|
import { auth } from "@/stores/portal"
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { derived } from "svelte/store"
|
||||||
import { AppStatus } from "@/constants"
|
import { AppStatus } from "@/constants"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { auth } from "./auth"
|
import { auth } from "./auth"
|
||||||
import BudiStore from "../BudiStore"
|
import { BudiStore } from "../BudiStore"
|
||||||
import { App, UpdateAppRequest } from "@budibase/types"
|
import { App, UpdateAppRequest } from "@budibase/types"
|
||||||
|
|
||||||
interface AppIdentifierMetadata {
|
interface AppIdentifierMetadata {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { licensing } from "./licensing"
|
import { licensing } from "./licensing"
|
||||||
import BudiStore from "../BudiStore"
|
import { BudiStore } from "../BudiStore"
|
||||||
import {
|
import {
|
||||||
DownloadAuditLogsRequest,
|
DownloadAuditLogsRequest,
|
||||||
SearchAuditLogsRequest,
|
SearchAuditLogsRequest,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { get } from "svelte/store"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { admin } from "@/stores/portal"
|
import { admin } from "@/stores/portal"
|
||||||
import analytics from "@/analytics"
|
import analytics from "@/analytics"
|
||||||
import BudiStore from "@/stores/BudiStore"
|
import { BudiStore } from "@/stores/BudiStore"
|
||||||
import {
|
import {
|
||||||
isSSOUser,
|
isSSOUser,
|
||||||
SetInitInfoRequest,
|
SetInitInfoRequest,
|
||||||
|
|
|
@ -22,6 +22,7 @@ export const createLicensingStore = () => {
|
||||||
backupsEnabled: false,
|
backupsEnabled: false,
|
||||||
brandingEnabled: false,
|
brandingEnabled: false,
|
||||||
scimEnabled: false,
|
scimEnabled: false,
|
||||||
|
environmentVariablesEnabled: false,
|
||||||
budibaseAIEnabled: false,
|
budibaseAIEnabled: false,
|
||||||
customAIConfigsEnabled: false,
|
customAIConfigsEnabled: false,
|
||||||
auditLogsEnabled: false,
|
auditLogsEnabled: false,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": "./tsconfig.build.json",
|
"extends": "./tsconfig.build.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@budibase/*": [
|
"@budibase/*": [
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "./src/index.ts",
|
"svelte": "./src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"check:types": "yarn svelte-check"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "*",
|
"@budibase/bbui": "*",
|
||||||
"@budibase/shared-core": "*",
|
"@budibase/shared-core": "*",
|
||||||
|
@ -13,5 +16,8 @@
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"shortid": "2.2.15",
|
"shortid": "2.2.15",
|
||||||
"socket.io-client": "^4.7.5"
|
"socket.io-client": "^4.7.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"svelte-check": "^4.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@
|
||||||
.value {
|
.value {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: var(--content-lines);
|
-webkit-line-clamp: var(--content-lines);
|
||||||
|
line-clamp: var(--content-lines);
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
|
|
@ -93,6 +93,7 @@
|
||||||
.value {
|
.value {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: var(--content-lines);
|
-webkit-line-clamp: var(--content-lines);
|
||||||
|
line-clamp: var(--content-lines);
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
|
|
@ -74,12 +74,14 @@
|
||||||
.value {
|
.value {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: var(--content-lines);
|
-webkit-line-clamp: var(--content-lines);
|
||||||
|
line-clamp: var(--content-lines);
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
.number .value {
|
.number .value {
|
||||||
-webkit-line-clamp: 1;
|
-webkit-line-clamp: 1;
|
||||||
|
line-clamp: 1;
|
||||||
}
|
}
|
||||||
input {
|
input {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
@ -110,5 +112,6 @@
|
||||||
}
|
}
|
||||||
input[type="number"] {
|
input[type="number"] {
|
||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,8 +1,25 @@
|
||||||
import { derived, get, writable } from "svelte/store"
|
import { derived, get, Readable, Writable, writable } from "svelte/store"
|
||||||
import { DefaultColumnWidth, GutterWidth } from "../lib/constants"
|
import { DefaultColumnWidth, GutterWidth } from "../lib/constants"
|
||||||
|
import { UIColumn } from "@budibase/types"
|
||||||
|
import { Store as StoreContext } from "."
|
||||||
|
|
||||||
export const createStores = () => {
|
interface ColumnStore {
|
||||||
const columns = writable([])
|
columns: Writable<UIColumn[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DerivedColumnStore {
|
||||||
|
tableColumns: Readable<UIColumn[]>
|
||||||
|
displayColumn: Readable<UIColumn | undefined>
|
||||||
|
columnLookupMap: Readable<Record<string, UIColumn>>
|
||||||
|
visibleColumns: Readable<UIColumn[]>
|
||||||
|
scrollableColumns: Readable<UIColumn[]>
|
||||||
|
hasNonAutoColumn: Readable<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = ColumnStore & DerivedColumnStore
|
||||||
|
|
||||||
|
export const createStores = (): ColumnStore => {
|
||||||
|
const columns = writable<UIColumn[]>([])
|
||||||
|
|
||||||
// Enrich columns with metadata about their display position
|
// Enrich columns with metadata about their display position
|
||||||
const enrichedColumns = derived(columns, $columns => {
|
const enrichedColumns = derived(columns, $columns => {
|
||||||
|
@ -16,7 +33,7 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
if (col.visible) {
|
if (col.visible) {
|
||||||
idx++
|
idx++
|
||||||
offset += col.width
|
offset += col.width ?? 0
|
||||||
}
|
}
|
||||||
return enriched
|
return enriched
|
||||||
})
|
})
|
||||||
|
@ -30,12 +47,12 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = (context: StoreContext): DerivedColumnStore => {
|
||||||
const { columns } = context
|
const { columns } = context
|
||||||
|
|
||||||
// Derive a lookup map for all columns by name
|
// Derive a lookup map for all columns by name
|
||||||
const columnLookupMap = derived(columns, $columns => {
|
const columnLookupMap = derived(columns, $columns => {
|
||||||
let map = {}
|
let map: Record<string, UIColumn> = {}
|
||||||
$columns.forEach(column => {
|
$columns.forEach(column => {
|
||||||
map[column.name] = column
|
map[column.name] = column
|
||||||
})
|
})
|
||||||
|
@ -78,11 +95,11 @@ export const deriveStores = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = (context: StoreContext) => {
|
||||||
const { columns, datasource } = context
|
const { columns, datasource } = context
|
||||||
|
|
||||||
// Updates the width of all columns
|
// Updates the width of all columns
|
||||||
const changeAllColumnWidths = async width => {
|
const changeAllColumnWidths = async (width: number) => {
|
||||||
const $columns = get(columns)
|
const $columns = get(columns)
|
||||||
$columns.forEach(column => {
|
$columns.forEach(column => {
|
||||||
const { related } = column
|
const { related } = column
|
||||||
|
@ -101,7 +118,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a column is readonly
|
// Checks if a column is readonly
|
||||||
const isReadonly = column => {
|
const isReadonly = (column: UIColumn) => {
|
||||||
if (!column?.schema) {
|
if (!column?.schema) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -125,11 +142,11 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialise = context => {
|
export const initialise = (context: StoreContext) => {
|
||||||
const { definition, columns, displayColumn, enrichedSchema } = context
|
const { definition, columns, displayColumn, enrichedSchema } = context
|
||||||
|
|
||||||
// Merge new schema fields with existing schema in order to preserve widths
|
// Merge new schema fields with existing schema in order to preserve widths
|
||||||
const processColumns = $enrichedSchema => {
|
const processColumns = ($enrichedSchema: any) => {
|
||||||
if (!$enrichedSchema) {
|
if (!$enrichedSchema) {
|
||||||
columns.set([])
|
columns.set([])
|
||||||
return
|
return
|
||||||
|
@ -139,7 +156,7 @@ export const initialise = context => {
|
||||||
const $displayColumn = get(displayColumn)
|
const $displayColumn = get(displayColumn)
|
||||||
|
|
||||||
// Find primary display
|
// Find primary display
|
||||||
let primaryDisplay
|
let primaryDisplay: string
|
||||||
const candidatePD = $definition.primaryDisplay || $displayColumn?.name
|
const candidatePD = $definition.primaryDisplay || $displayColumn?.name
|
||||||
if (candidatePD && $enrichedSchema[candidatePD]) {
|
if (candidatePD && $enrichedSchema[candidatePD]) {
|
||||||
primaryDisplay = candidatePD
|
primaryDisplay = candidatePD
|
||||||
|
@ -151,7 +168,8 @@ export const initialise = context => {
|
||||||
.map(field => {
|
.map(field => {
|
||||||
const fieldSchema = $enrichedSchema[field]
|
const fieldSchema = $enrichedSchema[field]
|
||||||
const oldColumn = $columns?.find(col => col.name === field)
|
const oldColumn = $columns?.find(col => col.name === field)
|
||||||
const column = {
|
const column: UIColumn = {
|
||||||
|
type: fieldSchema.type,
|
||||||
name: field,
|
name: field,
|
||||||
label: fieldSchema.displayName || field,
|
label: fieldSchema.displayName || field,
|
||||||
schema: fieldSchema,
|
schema: fieldSchema,
|
|
@ -1,10 +1,54 @@
|
||||||
import { derived, get } from "svelte/store"
|
import { derived, get, Readable, Writable } from "svelte/store"
|
||||||
import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch"
|
import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch"
|
||||||
import { enrichSchemaWithRelColumns, memo } from "../../../utils"
|
import { enrichSchemaWithRelColumns, memo } from "../../../utils"
|
||||||
import { cloneDeep } from "lodash"
|
import { cloneDeep } from "lodash"
|
||||||
import { ViewV2Type } from "@budibase/types"
|
import {
|
||||||
|
Row,
|
||||||
|
SaveRowRequest,
|
||||||
|
SaveTableRequest,
|
||||||
|
UIDatasource,
|
||||||
|
UIFieldMutation,
|
||||||
|
UIFieldSchema,
|
||||||
|
UpdateViewRequest,
|
||||||
|
ViewV2Type,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import { Store as StoreContext } from "."
|
||||||
|
import { DatasourceActions } from "./datasources"
|
||||||
|
|
||||||
export const createStores = () => {
|
interface DatasourceStore {
|
||||||
|
definition: Writable<UIDatasource>
|
||||||
|
schemaMutations: Writable<Record<string, UIFieldMutation>>
|
||||||
|
subSchemaMutations: Writable<Record<string, Record<string, UIFieldMutation>>>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DerivedDatasourceStore {
|
||||||
|
schema: Readable<Record<string, UIFieldSchema> | null>
|
||||||
|
enrichedSchema: Readable<Record<string, UIFieldSchema> | null>
|
||||||
|
hasBudibaseIdentifiers: Readable<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionDatasourceStore {
|
||||||
|
datasource: DatasourceStore["definition"] & {
|
||||||
|
actions: DatasourceActions & {
|
||||||
|
refreshDefinition: () => Promise<void>
|
||||||
|
changePrimaryDisplay: (column: string) => Promise<void>
|
||||||
|
addSchemaMutation: (field: string, mutation: UIFieldMutation) => void
|
||||||
|
addSubSchemaMutation: (
|
||||||
|
field: string,
|
||||||
|
fromField: string,
|
||||||
|
mutation: UIFieldMutation
|
||||||
|
) => void
|
||||||
|
saveSchemaMutations: () => Promise<void>
|
||||||
|
resetSchemaMutations: () => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = DatasourceStore &
|
||||||
|
DerivedDatasourceStore &
|
||||||
|
ActionDatasourceStore
|
||||||
|
|
||||||
|
export const createStores = (): DatasourceStore => {
|
||||||
const definition = memo(null)
|
const definition = memo(null)
|
||||||
const schemaMutations = memo({})
|
const schemaMutations = memo({})
|
||||||
const subSchemaMutations = memo({})
|
const subSchemaMutations = memo({})
|
||||||
|
@ -16,7 +60,7 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = (context: StoreContext): DerivedDatasourceStore => {
|
||||||
const {
|
const {
|
||||||
API,
|
API,
|
||||||
definition,
|
definition,
|
||||||
|
@ -27,7 +71,7 @@ export const deriveStores = context => {
|
||||||
} = context
|
} = context
|
||||||
|
|
||||||
const schema = derived(definition, $definition => {
|
const schema = derived(definition, $definition => {
|
||||||
let schema = getDatasourceSchema({
|
let schema: Record<string, UIFieldSchema> = getDatasourceSchema({
|
||||||
API,
|
API,
|
||||||
datasource: get(datasource),
|
datasource: get(datasource),
|
||||||
definition: $definition,
|
definition: $definition,
|
||||||
|
@ -40,7 +84,7 @@ export const deriveStores = context => {
|
||||||
// Certain datasources like queries use primitives.
|
// Certain datasources like queries use primitives.
|
||||||
Object.keys(schema || {}).forEach(key => {
|
Object.keys(schema || {}).forEach(key => {
|
||||||
if (typeof schema[key] !== "object") {
|
if (typeof schema[key] !== "object") {
|
||||||
schema[key] = { type: schema[key] }
|
schema[key] = { name: key, type: schema[key] }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -58,19 +102,18 @@ export const deriveStores = context => {
|
||||||
|
|
||||||
const schemaWithRelatedColumns = enrichSchemaWithRelColumns($schema)
|
const schemaWithRelatedColumns = enrichSchemaWithRelColumns($schema)
|
||||||
|
|
||||||
const enrichedSchema = {}
|
const enrichedSchema: Record<string, UIFieldSchema> = {}
|
||||||
Object.keys(schemaWithRelatedColumns).forEach(field => {
|
Object.keys(schemaWithRelatedColumns || {}).forEach(field => {
|
||||||
enrichedSchema[field] = {
|
enrichedSchema[field] = {
|
||||||
...schemaWithRelatedColumns[field],
|
...schemaWithRelatedColumns?.[field],
|
||||||
...$schemaOverrides?.[field],
|
...$schemaOverrides?.[field],
|
||||||
...$schemaMutations[field],
|
...$schemaMutations[field],
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($subSchemaMutations[field]) {
|
if ($subSchemaMutations[field]) {
|
||||||
enrichedSchema[field].columns ??= {}
|
enrichedSchema[field].columns ??= {}
|
||||||
for (const [fieldName, mutation] of Object.entries(
|
for (const fieldName of Object.keys($subSchemaMutations[field])) {
|
||||||
$subSchemaMutations[field]
|
const mutation = $subSchemaMutations[field][fieldName]
|
||||||
)) {
|
|
||||||
enrichedSchema[field].columns[fieldName] = {
|
enrichedSchema[field].columns[fieldName] = {
|
||||||
...enrichedSchema[field].columns[fieldName],
|
...enrichedSchema[field].columns[fieldName],
|
||||||
...mutation,
|
...mutation,
|
||||||
|
@ -87,7 +130,7 @@ export const deriveStores = context => {
|
||||||
([$datasource, $definition]) => {
|
([$datasource, $definition]) => {
|
||||||
let type = $datasource?.type
|
let type = $datasource?.type
|
||||||
if (type === "provider") {
|
if (type === "provider") {
|
||||||
type = $datasource.value?.datasource?.type
|
type = ($datasource as any).value?.datasource?.type
|
||||||
}
|
}
|
||||||
// Handle calculation views
|
// Handle calculation views
|
||||||
if (type === "viewV2" && $definition?.type === ViewV2Type.CALCULATION) {
|
if (type === "viewV2" && $definition?.type === ViewV2Type.CALCULATION) {
|
||||||
|
@ -104,7 +147,7 @@ export const deriveStores = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = (context: StoreContext): ActionDatasourceStore => {
|
||||||
const {
|
const {
|
||||||
API,
|
API,
|
||||||
datasource,
|
datasource,
|
||||||
|
@ -147,21 +190,23 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saves the datasource definition
|
// Saves the datasource definition
|
||||||
const saveDefinition = async newDefinition => {
|
const saveDefinition = async (
|
||||||
|
newDefinition: SaveTableRequest | UpdateViewRequest
|
||||||
|
) => {
|
||||||
// Update local state
|
// Update local state
|
||||||
const originalDefinition = get(definition)
|
const originalDefinition = get(definition)
|
||||||
definition.set(newDefinition)
|
definition.set(newDefinition as UIDatasource)
|
||||||
|
|
||||||
// Update server
|
// Update server
|
||||||
if (get(config).canSaveSchema) {
|
if (get(config).canSaveSchema) {
|
||||||
try {
|
try {
|
||||||
await getAPI()?.actions.saveDefinition(newDefinition)
|
await getAPI()?.actions.saveDefinition(newDefinition as never)
|
||||||
|
|
||||||
// Broadcast change so external state can be updated, as this change
|
// Broadcast change so external state can be updated, as this change
|
||||||
// will not be received by the builder websocket because we caused it
|
// will not be received by the builder websocket because we caused it
|
||||||
// ourselves
|
// ourselves
|
||||||
dispatch("updatedatasource", newDefinition)
|
dispatch("updatedatasource", newDefinition)
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
const msg = error?.message || error || "Unknown error"
|
const msg = error?.message || error || "Unknown error"
|
||||||
get(notifications).error(`Error saving schema: ${msg}`)
|
get(notifications).error(`Error saving schema: ${msg}`)
|
||||||
|
|
||||||
|
@ -172,7 +217,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates the datasources primary display column
|
// Updates the datasources primary display column
|
||||||
const changePrimaryDisplay = async column => {
|
const changePrimaryDisplay = async (column: string) => {
|
||||||
let newDefinition = cloneDeep(get(definition))
|
let newDefinition = cloneDeep(get(definition))
|
||||||
|
|
||||||
// Update primary display
|
// Update primary display
|
||||||
|
@ -183,12 +228,14 @@ export const createActions = context => {
|
||||||
newDefinition.schema[column].constraints = {}
|
newDefinition.schema[column].constraints = {}
|
||||||
}
|
}
|
||||||
newDefinition.schema[column].constraints.presence = { allowEmpty: false }
|
newDefinition.schema[column].constraints.presence = { allowEmpty: false }
|
||||||
delete newDefinition.schema[column].default
|
if ("default" in newDefinition.schema[column]) {
|
||||||
return await saveDefinition(newDefinition)
|
delete newDefinition.schema[column].default
|
||||||
|
}
|
||||||
|
return await saveDefinition(newDefinition as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a schema mutation for a single field
|
// Adds a schema mutation for a single field
|
||||||
const addSchemaMutation = (field, mutation) => {
|
const addSchemaMutation = (field: string, mutation: UIFieldMutation) => {
|
||||||
if (!field || !mutation) {
|
if (!field || !mutation) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -204,7 +251,11 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a nested schema mutation for a single field
|
// Adds a nested schema mutation for a single field
|
||||||
const addSubSchemaMutation = (field, fromField, mutation) => {
|
const addSubSchemaMutation = (
|
||||||
|
field: string,
|
||||||
|
fromField: string,
|
||||||
|
mutation: UIFieldMutation
|
||||||
|
) => {
|
||||||
if (!field || !fromField || !mutation) {
|
if (!field || !fromField || !mutation) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -231,8 +282,8 @@ export const createActions = context => {
|
||||||
const $definition = get(definition)
|
const $definition = get(definition)
|
||||||
const $schemaMutations = get(schemaMutations)
|
const $schemaMutations = get(schemaMutations)
|
||||||
const $subSchemaMutations = get(subSchemaMutations)
|
const $subSchemaMutations = get(subSchemaMutations)
|
||||||
const $schema = get(schema)
|
const $schema = get(schema) || {}
|
||||||
let newSchema = {}
|
let newSchema: Record<string, UIFieldSchema> = {}
|
||||||
|
|
||||||
// Build new updated datasource schema
|
// Build new updated datasource schema
|
||||||
Object.keys($schema).forEach(column => {
|
Object.keys($schema).forEach(column => {
|
||||||
|
@ -242,9 +293,8 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
if ($subSchemaMutations[column]) {
|
if ($subSchemaMutations[column]) {
|
||||||
newSchema[column].columns ??= {}
|
newSchema[column].columns ??= {}
|
||||||
for (const [fieldName, mutation] of Object.entries(
|
for (const fieldName of Object.keys($subSchemaMutations[column])) {
|
||||||
$subSchemaMutations[column]
|
const mutation = $subSchemaMutations[column][fieldName]
|
||||||
)) {
|
|
||||||
newSchema[column].columns[fieldName] = {
|
newSchema[column].columns[fieldName] = {
|
||||||
...newSchema[column].columns[fieldName],
|
...newSchema[column].columns[fieldName],
|
||||||
...mutation,
|
...mutation,
|
||||||
|
@ -257,7 +307,7 @@ export const createActions = context => {
|
||||||
await saveDefinition({
|
await saveDefinition({
|
||||||
...$definition,
|
...$definition,
|
||||||
schema: newSchema,
|
schema: newSchema,
|
||||||
})
|
} as any)
|
||||||
resetSchemaMutations()
|
resetSchemaMutations()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,32 +317,32 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a row to the datasource
|
// Adds a row to the datasource
|
||||||
const addRow = async row => {
|
const addRow = async (row: SaveRowRequest) => {
|
||||||
return await getAPI()?.actions.addRow(row)
|
return await getAPI()?.actions.addRow(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates an existing row in the datasource
|
// Updates an existing row in the datasource
|
||||||
const updateRow = async row => {
|
const updateRow = async (row: SaveRowRequest) => {
|
||||||
return await getAPI()?.actions.updateRow(row)
|
return await getAPI()?.actions.updateRow(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deletes rows from the datasource
|
// Deletes rows from the datasource
|
||||||
const deleteRows = async rows => {
|
const deleteRows = async (rows: Row[]) => {
|
||||||
return await getAPI()?.actions.deleteRows(rows)
|
return await getAPI()?.actions.deleteRows(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets a single row from a datasource
|
// Gets a single row from a datasource
|
||||||
const getRow = async id => {
|
const getRow = async (id: string) => {
|
||||||
return await getAPI()?.actions.getRow(id)
|
return await getAPI()?.actions.getRow(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a certain datasource config is valid
|
// Checks if a certain datasource config is valid
|
||||||
const isDatasourceValid = datasource => {
|
const isDatasourceValid = (datasource: UIDatasource) => {
|
||||||
return getAPI()?.actions.isDatasourceValid(datasource)
|
return getAPI()?.actions.isDatasourceValid(datasource)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if this datasource can use a specific column by name
|
// Checks if this datasource can use a specific column by name
|
||||||
const canUseColumn = name => {
|
const canUseColumn = (name: string) => {
|
||||||
return getAPI()?.actions.canUseColumn(name)
|
return getAPI()?.actions.canUseColumn(name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import {
|
||||||
|
Row,
|
||||||
|
SaveRowRequest,
|
||||||
|
SaveTableRequest,
|
||||||
|
UIDatasource,
|
||||||
|
UpdateViewRequest,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
interface DatasourceBaseActions<
|
||||||
|
TSaveDefinitionRequest = UpdateViewRequest | SaveTableRequest
|
||||||
|
> {
|
||||||
|
saveDefinition: (newDefinition: TSaveDefinitionRequest) => Promise<void>
|
||||||
|
addRow: (row: SaveRowRequest) => Promise<Row | void>
|
||||||
|
updateRow: (row: SaveRowRequest) => Promise<Row | void>
|
||||||
|
deleteRows: (rows: Row[]) => Promise<void>
|
||||||
|
getRow: (id: string) => Promise<Row | void>
|
||||||
|
isDatasourceValid: (datasource: UIDatasource) => boolean | void
|
||||||
|
canUseColumn: (name: string) => boolean | void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatasourceTableActions
|
||||||
|
extends DatasourceBaseActions<SaveTableRequest> {}
|
||||||
|
|
||||||
|
export interface DatasourceViewActions
|
||||||
|
extends DatasourceBaseActions<UpdateViewRequest> {}
|
||||||
|
|
||||||
|
export interface DatasourceNonPlusActions
|
||||||
|
extends DatasourceBaseActions<never> {}
|
||||||
|
|
||||||
|
export type DatasourceActions =
|
||||||
|
| DatasourceTableActions & DatasourceViewActions & DatasourceNonPlusActions
|
|
@ -1,7 +1,17 @@
|
||||||
import { SortOrder } from "@budibase/types"
|
import { SortOrder, UIDatasource } from "@budibase/types"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
import { Store as StoreContext } from ".."
|
||||||
|
import { DatasourceNonPlusActions } from "."
|
||||||
|
|
||||||
export const createActions = context => {
|
interface NonPlusActions {
|
||||||
|
nonPlus: {
|
||||||
|
actions: DatasourceNonPlusActions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = NonPlusActions
|
||||||
|
|
||||||
|
export const createActions = (context: StoreContext): NonPlusActions => {
|
||||||
const { columns, table, viewV2 } = context
|
const { columns, table, viewV2 } = context
|
||||||
|
|
||||||
const saveDefinition = async () => {
|
const saveDefinition = async () => {
|
||||||
|
@ -20,7 +30,7 @@ export const createActions = context => {
|
||||||
throw "This datasource does not support fetching individual rows"
|
throw "This datasource does not support fetching individual rows"
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDatasourceValid = datasource => {
|
const isDatasourceValid = (datasource: UIDatasource) => {
|
||||||
// There are many different types and shapes of datasource, so we only
|
// There are many different types and shapes of datasource, so we only
|
||||||
// check that we aren't null
|
// check that we aren't null
|
||||||
return (
|
return (
|
||||||
|
@ -30,7 +40,7 @@ export const createActions = context => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const canUseColumn = name => {
|
const canUseColumn = (name: string) => {
|
||||||
return get(columns).some(col => col.name === name)
|
return get(columns).some(col => col.name === name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,11 +60,11 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small util to compare datasource definitions
|
// Small util to compare datasource definitions
|
||||||
const isSameDatasource = (a, b) => {
|
const isSameDatasource = (a: any, b: any) => {
|
||||||
return JSON.stringify(a) === JSON.stringify(b)
|
return JSON.stringify(a) === JSON.stringify(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialise = context => {
|
export const initialise = (context: StoreContext) => {
|
||||||
const {
|
const {
|
||||||
datasource,
|
datasource,
|
||||||
sort,
|
sort,
|
||||||
|
@ -69,7 +79,7 @@ export const initialise = context => {
|
||||||
} = context
|
} = context
|
||||||
// Keep a list of subscriptions so that we can clear them when the datasource
|
// Keep a list of subscriptions so that we can clear them when the datasource
|
||||||
// config changes
|
// config changes
|
||||||
let unsubscribers = []
|
let unsubscribers: any[] = []
|
||||||
|
|
||||||
// Observe datasource changes and apply logic for view V2 datasources
|
// Observe datasource changes and apply logic for view V2 datasources
|
||||||
datasource.subscribe($datasource => {
|
datasource.subscribe($datasource => {
|
|
@ -1,16 +1,32 @@
|
||||||
import { SortOrder } from "@budibase/types"
|
import {
|
||||||
|
Row,
|
||||||
|
SaveRowRequest,
|
||||||
|
SaveTableRequest,
|
||||||
|
SortOrder,
|
||||||
|
UIDatasource,
|
||||||
|
} from "@budibase/types"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
import { Store as StoreContext } from ".."
|
||||||
|
import { DatasourceTableActions } from "."
|
||||||
|
|
||||||
const SuppressErrors = true
|
const SuppressErrors = true
|
||||||
|
|
||||||
export const createActions = context => {
|
interface TableActions {
|
||||||
|
table: {
|
||||||
|
actions: DatasourceTableActions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = TableActions
|
||||||
|
|
||||||
|
export const createActions = (context: StoreContext): TableActions => {
|
||||||
const { API, datasource, columns } = context
|
const { API, datasource, columns } = context
|
||||||
|
|
||||||
const saveDefinition = async newDefinition => {
|
const saveDefinition = async (newDefinition: SaveTableRequest) => {
|
||||||
await API.saveTable(newDefinition)
|
await API.saveTable(newDefinition)
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveRow = async row => {
|
const saveRow = async (row: SaveRowRequest) => {
|
||||||
row = {
|
row = {
|
||||||
...row,
|
...row,
|
||||||
tableId: get(datasource)?.tableId,
|
tableId: get(datasource)?.tableId,
|
||||||
|
@ -18,15 +34,15 @@ export const createActions = context => {
|
||||||
return await API.saveRow(row, SuppressErrors)
|
return await API.saveRow(row, SuppressErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteRows = async rows => {
|
const deleteRows = async (rows: Row[]) => {
|
||||||
await API.deleteRows(get(datasource).tableId, rows)
|
await API.deleteRows(get(datasource).tableId, rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDatasourceValid = datasource => {
|
const isDatasourceValid = (datasource: UIDatasource) => {
|
||||||
return datasource?.type === "table" && datasource?.tableId
|
return datasource?.type === "table" && !!datasource?.tableId
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRow = async id => {
|
const getRow = async (id: any) => {
|
||||||
const res = await API.searchTable(get(datasource).tableId, {
|
const res = await API.searchTable(get(datasource).tableId, {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
query: {
|
query: {
|
||||||
|
@ -39,7 +55,7 @@ export const createActions = context => {
|
||||||
return res?.rows?.[0]
|
return res?.rows?.[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
const canUseColumn = name => {
|
const canUseColumn = (name: string) => {
|
||||||
return get(columns).some(col => col.name === name)
|
return get(columns).some(col => col.name === name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +74,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialise = context => {
|
export const initialise = (context: StoreContext) => {
|
||||||
const {
|
const {
|
||||||
datasource,
|
datasource,
|
||||||
fetch,
|
fetch,
|
||||||
|
@ -74,7 +90,7 @@ export const initialise = context => {
|
||||||
|
|
||||||
// Keep a list of subscriptions so that we can clear them when the datasource
|
// Keep a list of subscriptions so that we can clear them when the datasource
|
||||||
// config changes
|
// config changes
|
||||||
let unsubscribers = []
|
let unsubscribers: any[] = []
|
||||||
|
|
||||||
// Observe datasource changes and apply logic for table datasources
|
// Observe datasource changes and apply logic for table datasources
|
||||||
datasource.subscribe($datasource => {
|
datasource.subscribe($datasource => {
|
|
@ -1,16 +1,33 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { SortOrder } from "@budibase/types"
|
import {
|
||||||
|
Row,
|
||||||
|
SaveRowRequest,
|
||||||
|
SortOrder,
|
||||||
|
UIDatasource,
|
||||||
|
UIView,
|
||||||
|
UpdateViewRequest,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import { Store as StoreContext } from ".."
|
||||||
|
import { DatasourceViewActions } from "."
|
||||||
|
|
||||||
const SuppressErrors = true
|
const SuppressErrors = true
|
||||||
|
|
||||||
export const createActions = context => {
|
interface ViewActions {
|
||||||
|
viewV2: {
|
||||||
|
actions: DatasourceViewActions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = ViewActions
|
||||||
|
|
||||||
|
export const createActions = (context: StoreContext): ViewActions => {
|
||||||
const { API, datasource, columns } = context
|
const { API, datasource, columns } = context
|
||||||
|
|
||||||
const saveDefinition = async newDefinition => {
|
const saveDefinition = async (newDefinition: UpdateViewRequest) => {
|
||||||
await API.viewV2.update(newDefinition)
|
await API.viewV2.update(newDefinition)
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveRow = async row => {
|
const saveRow = async (row: SaveRowRequest) => {
|
||||||
const $datasource = get(datasource)
|
const $datasource = get(datasource)
|
||||||
row = {
|
row = {
|
||||||
...row,
|
...row,
|
||||||
|
@ -23,11 +40,11 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteRows = async rows => {
|
const deleteRows = async (rows: Row[]) => {
|
||||||
await API.deleteRows(get(datasource).id, rows)
|
await API.deleteRows(get(datasource).id, rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRow = async id => {
|
const getRow = async (id: string) => {
|
||||||
const res = await API.viewV2.fetch(get(datasource).id, {
|
const res = await API.viewV2.fetch(get(datasource).id, {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
query: {
|
query: {
|
||||||
|
@ -40,13 +57,13 @@ export const createActions = context => {
|
||||||
return res?.rows?.[0]
|
return res?.rows?.[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDatasourceValid = datasource => {
|
const isDatasourceValid = (datasource: UIDatasource) => {
|
||||||
return (
|
return (
|
||||||
datasource?.type === "viewV2" && datasource?.id && datasource?.tableId
|
datasource?.type === "viewV2" && !!datasource?.id && !!datasource?.tableId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const canUseColumn = name => {
|
const canUseColumn = (name: string) => {
|
||||||
return get(columns).some(col => col.name === name && col.visible)
|
return get(columns).some(col => col.name === name && col.visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +82,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialise = context => {
|
export const initialise = (context: StoreContext) => {
|
||||||
const {
|
const {
|
||||||
definition,
|
definition,
|
||||||
datasource,
|
datasource,
|
||||||
|
@ -85,7 +102,7 @@ export const initialise = context => {
|
||||||
|
|
||||||
// Keep a list of subscriptions so that we can clear them when the datasource
|
// Keep a list of subscriptions so that we can clear them when the datasource
|
||||||
// config changes
|
// config changes
|
||||||
let unsubscribers = []
|
let unsubscribers: any[] = []
|
||||||
|
|
||||||
// Observe datasource changes and apply logic for view V2 datasources
|
// Observe datasource changes and apply logic for view V2 datasources
|
||||||
datasource.subscribe($datasource => {
|
datasource.subscribe($datasource => {
|
||||||
|
@ -131,7 +148,7 @@ export const initialise = context => {
|
||||||
unsubscribers.push(
|
unsubscribers.push(
|
||||||
sort.subscribe(async $sort => {
|
sort.subscribe(async $sort => {
|
||||||
// Ensure we're updating the correct view
|
// Ensure we're updating the correct view
|
||||||
const $view = get(definition)
|
const $view = get(definition) as UIView
|
||||||
if ($view?.id !== $datasource.id) {
|
if ($view?.id !== $datasource.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -182,7 +199,7 @@ export const initialise = context => {
|
||||||
await datasource.actions.saveDefinition({
|
await datasource.actions.saveDefinition({
|
||||||
...$view,
|
...$view,
|
||||||
queryUI: $filter,
|
queryUI: $filter,
|
||||||
})
|
} as never as UpdateViewRequest)
|
||||||
|
|
||||||
// Refresh data since view definition changed
|
// Refresh data since view definition changed
|
||||||
await rows.actions.refreshData()
|
await rows.actions.refreshData()
|
|
@ -1,73 +0,0 @@
|
||||||
import * as Bounds from "./bounds"
|
|
||||||
import * as Columns from "./columns"
|
|
||||||
import * as Menu from "./menu"
|
|
||||||
import * as Pagination from "./pagination"
|
|
||||||
import * as Reorder from "./reorder"
|
|
||||||
import * as Resize from "./resize"
|
|
||||||
import * as Rows from "./rows"
|
|
||||||
import * as Scroll from "./scroll"
|
|
||||||
import * as UI from "./ui"
|
|
||||||
import * as Users from "./users"
|
|
||||||
import * as Validation from "./validation"
|
|
||||||
import * as Viewport from "./viewport"
|
|
||||||
import * as Clipboard from "./clipboard"
|
|
||||||
import * as Config from "./config"
|
|
||||||
import * as Sort from "./sort"
|
|
||||||
import * as Filter from "./filter"
|
|
||||||
import * as Notifications from "./notifications"
|
|
||||||
import * as Datasource from "./datasource"
|
|
||||||
import * as Table from "./datasources/table"
|
|
||||||
import * as ViewV2 from "./datasources/viewV2"
|
|
||||||
import * as NonPlus from "./datasources/nonPlus"
|
|
||||||
import * as Cache from "./cache"
|
|
||||||
import * as Conditions from "./conditions"
|
|
||||||
|
|
||||||
const DependencyOrderedStores = [
|
|
||||||
Sort,
|
|
||||||
Filter,
|
|
||||||
Bounds,
|
|
||||||
Table,
|
|
||||||
ViewV2,
|
|
||||||
NonPlus,
|
|
||||||
Datasource,
|
|
||||||
Columns,
|
|
||||||
Scroll,
|
|
||||||
Validation,
|
|
||||||
Rows,
|
|
||||||
Conditions,
|
|
||||||
UI,
|
|
||||||
Resize,
|
|
||||||
Viewport,
|
|
||||||
Reorder,
|
|
||||||
Users,
|
|
||||||
Menu,
|
|
||||||
Pagination,
|
|
||||||
Config,
|
|
||||||
Clipboard,
|
|
||||||
Notifications,
|
|
||||||
Cache,
|
|
||||||
]
|
|
||||||
|
|
||||||
export const attachStores = context => {
|
|
||||||
// Atomic store creation
|
|
||||||
for (let store of DependencyOrderedStores) {
|
|
||||||
context = { ...context, ...store.createStores?.(context) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Derived store creation
|
|
||||||
for (let store of DependencyOrderedStores) {
|
|
||||||
context = { ...context, ...store.deriveStores?.(context) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Action creation
|
|
||||||
for (let store of DependencyOrderedStores) {
|
|
||||||
context = { ...context, ...store.createActions?.(context) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialise any store logic
|
|
||||||
for (let store of DependencyOrderedStores) {
|
|
||||||
store.initialise?.(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
return context
|
|
||||||
}
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { Writable } from "svelte/store"
|
||||||
|
import type { APIClient } from "../../../api/types"
|
||||||
|
|
||||||
|
import * as Bounds from "./bounds"
|
||||||
|
import * as Columns from "./columns"
|
||||||
|
import * as Menu from "./menu"
|
||||||
|
import * as Pagination from "./pagination"
|
||||||
|
import * as Reorder from "./reorder"
|
||||||
|
import * as Resize from "./resize"
|
||||||
|
import * as Rows from "./rows"
|
||||||
|
import * as Scroll from "./scroll"
|
||||||
|
import * as UI from "./ui"
|
||||||
|
import * as Users from "./users"
|
||||||
|
import * as Validation from "./validation"
|
||||||
|
import * as Viewport from "./viewport"
|
||||||
|
import * as Clipboard from "./clipboard"
|
||||||
|
import * as Config from "./config"
|
||||||
|
import * as Sort from "./sort"
|
||||||
|
import * as Filter from "./filter"
|
||||||
|
import * as Notifications from "./notifications"
|
||||||
|
import * as Datasource from "./datasource"
|
||||||
|
import * as Table from "./datasources/table"
|
||||||
|
import * as ViewV2 from "./datasources/viewV2"
|
||||||
|
import * as NonPlus from "./datasources/nonPlus"
|
||||||
|
import * as Cache from "./cache"
|
||||||
|
import * as Conditions from "./conditions"
|
||||||
|
|
||||||
|
const DependencyOrderedStores = [
|
||||||
|
Sort,
|
||||||
|
Filter,
|
||||||
|
Bounds,
|
||||||
|
Table,
|
||||||
|
ViewV2,
|
||||||
|
NonPlus,
|
||||||
|
Datasource,
|
||||||
|
Columns,
|
||||||
|
Scroll,
|
||||||
|
Validation,
|
||||||
|
Rows,
|
||||||
|
Conditions,
|
||||||
|
UI,
|
||||||
|
Resize,
|
||||||
|
Viewport,
|
||||||
|
Reorder,
|
||||||
|
Users,
|
||||||
|
Menu,
|
||||||
|
Pagination,
|
||||||
|
Config as any,
|
||||||
|
Clipboard,
|
||||||
|
Notifications,
|
||||||
|
Cache,
|
||||||
|
]
|
||||||
|
|
||||||
|
export interface BaseStore {
|
||||||
|
API: APIClient
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = BaseStore &
|
||||||
|
Columns.Store &
|
||||||
|
Table.Store &
|
||||||
|
ViewV2.Store &
|
||||||
|
NonPlus.Store &
|
||||||
|
Datasource.Store &
|
||||||
|
Validation.Store &
|
||||||
|
Users.Store &
|
||||||
|
Menu.Store & {
|
||||||
|
// TODO while typing the rest of stores
|
||||||
|
fetch: Writable<any>
|
||||||
|
filter: Writable<any>
|
||||||
|
inlineFilters: Writable<any>
|
||||||
|
allFilters: Writable<any>
|
||||||
|
sort: Writable<any>
|
||||||
|
initialFilter: Writable<any>
|
||||||
|
initialSortColumn: Writable<any>
|
||||||
|
initialSortOrder: Writable<any>
|
||||||
|
rows: Writable<any> & { actions: any }
|
||||||
|
subscribe: any
|
||||||
|
config: Writable<any>
|
||||||
|
dispatch: (event: string, data: any) => any
|
||||||
|
notifications: Writable<any>
|
||||||
|
schemaOverrides: Writable<any>
|
||||||
|
focusedCellId: Writable<any>
|
||||||
|
previousFocusedRowId: Writable<string>
|
||||||
|
gridID: string
|
||||||
|
selectedRows: Writable<any>
|
||||||
|
selectedRowCount: Writable<any>
|
||||||
|
selectedCellMap: Writable<any>
|
||||||
|
selectedCellCount: Writable<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const attachStores = (context: Store): Store => {
|
||||||
|
// Atomic store creation
|
||||||
|
for (let store of DependencyOrderedStores) {
|
||||||
|
if ("createStores" in store) {
|
||||||
|
context = { ...context, ...store.createStores?.(context) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derived store creation
|
||||||
|
for (let store of DependencyOrderedStores) {
|
||||||
|
if ("deriveStores" in store) {
|
||||||
|
context = { ...context, ...store.deriveStores?.(context) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action creation
|
||||||
|
for (let store of DependencyOrderedStores) {
|
||||||
|
if ("createActions" in store) {
|
||||||
|
context = { ...context, ...store.createActions?.(context) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise any store logic
|
||||||
|
for (let store of DependencyOrderedStores) {
|
||||||
|
if ("initialise" in store) {
|
||||||
|
store.initialise?.(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context as Store
|
||||||
|
}
|
|
@ -1,8 +1,24 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get, Writable } from "svelte/store"
|
||||||
|
|
||||||
|
import { Store as StoreContext } from "."
|
||||||
import { parseCellID } from "../lib/utils"
|
import { parseCellID } from "../lib/utils"
|
||||||
|
|
||||||
|
interface MenuStoreData {
|
||||||
|
left: number
|
||||||
|
top: number
|
||||||
|
visible: boolean
|
||||||
|
multiRowMode: boolean
|
||||||
|
multiCellMode: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MenuStore {
|
||||||
|
menu: Writable<MenuStoreData>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = MenuStore
|
||||||
|
|
||||||
export const createStores = () => {
|
export const createStores = () => {
|
||||||
const menu = writable({
|
const menu = writable<MenuStoreData>({
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
visible: false,
|
visible: false,
|
||||||
|
@ -14,7 +30,7 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = (context: StoreContext) => {
|
||||||
const {
|
const {
|
||||||
menu,
|
menu,
|
||||||
focusedCellId,
|
focusedCellId,
|
||||||
|
@ -25,7 +41,7 @@ export const createActions = context => {
|
||||||
selectedCellCount,
|
selectedCellCount,
|
||||||
} = context
|
} = context
|
||||||
|
|
||||||
const open = (cellId, e) => {
|
const open = (cellId: string, e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
|
@ -37,7 +53,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute bounds of cell relative to outer data node
|
// Compute bounds of cell relative to outer data node
|
||||||
const targetBounds = e.target.getBoundingClientRect()
|
const targetBounds = (e.target as HTMLElement).getBoundingClientRect()
|
||||||
const dataBounds = dataNode.getBoundingClientRect()
|
const dataBounds = dataNode.getBoundingClientRect()
|
||||||
|
|
||||||
// Check if there are multiple rows selected, and if this is one of them
|
// Check if there are multiple rows selected, and if this is one of them
|
|
@ -1,11 +1,38 @@
|
||||||
import { writable, get, derived } from "svelte/store"
|
import { writable, get, derived, Writable, Readable } from "svelte/store"
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
import { Store as StoreContext } from "."
|
||||||
|
import { UIUser } from "@budibase/types"
|
||||||
|
|
||||||
export const createStores = () => {
|
interface UIEnrichedUser extends UIUser {
|
||||||
const users = writable([])
|
color: string
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UsersStore {
|
||||||
|
users: Writable<UIUser[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DerivedUsersStore {
|
||||||
|
userCellMap: Readable<Record<string, UIUser>>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionUserStore {
|
||||||
|
users: UsersStore["users"] &
|
||||||
|
Readable<UIEnrichedUser[]> & {
|
||||||
|
actions: {
|
||||||
|
updateUser: (user: UIUser) => void
|
||||||
|
removeUser: (sessionId: string) => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = DerivedUsersStore & ActionUserStore
|
||||||
|
|
||||||
|
export const createStores = (): UsersStore => {
|
||||||
|
const users = writable<UIUser[]>([])
|
||||||
|
|
||||||
const enrichedUsers = derived(users, $users => {
|
const enrichedUsers = derived(users, $users => {
|
||||||
return $users.map(user => ({
|
return $users.map<UIEnrichedUser>(user => ({
|
||||||
...user,
|
...user,
|
||||||
color: helpers.getUserColor(user),
|
color: helpers.getUserColor(user),
|
||||||
label: helpers.getUserLabel(user),
|
label: helpers.getUserLabel(user),
|
||||||
|
@ -20,7 +47,7 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = (context: StoreContext): DerivedUsersStore => {
|
||||||
const { users, focusedCellId } = context
|
const { users, focusedCellId } = context
|
||||||
|
|
||||||
// Generate a lookup map of cell ID to the user that has it selected, to make
|
// Generate a lookup map of cell ID to the user that has it selected, to make
|
||||||
|
@ -28,7 +55,7 @@ export const deriveStores = context => {
|
||||||
const userCellMap = derived(
|
const userCellMap = derived(
|
||||||
[users, focusedCellId],
|
[users, focusedCellId],
|
||||||
([$users, $focusedCellId]) => {
|
([$users, $focusedCellId]) => {
|
||||||
let map = {}
|
let map: Record<string, UIUser> = {}
|
||||||
$users.forEach(user => {
|
$users.forEach(user => {
|
||||||
const cellId = user.gridMetadata?.focusedCellId
|
const cellId = user.gridMetadata?.focusedCellId
|
||||||
if (cellId && cellId !== $focusedCellId) {
|
if (cellId && cellId !== $focusedCellId) {
|
||||||
|
@ -44,10 +71,10 @@ export const deriveStores = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = (context: StoreContext): ActionUserStore => {
|
||||||
const { users } = context
|
const { users } = context
|
||||||
|
|
||||||
const updateUser = user => {
|
const updateUser = (user: UIUser) => {
|
||||||
const $users = get(users)
|
const $users = get(users)
|
||||||
if (!$users.some(x => x.sessionId === user.sessionId)) {
|
if (!$users.some(x => x.sessionId === user.sessionId)) {
|
||||||
users.set([...$users, user])
|
users.set([...$users, user])
|
||||||
|
@ -60,7 +87,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeUser = sessionId => {
|
const removeUser = (sessionId: string) => {
|
||||||
users.update(state => {
|
users.update(state => {
|
||||||
return state.filter(x => x.sessionId !== sessionId)
|
return state.filter(x => x.sessionId !== sessionId)
|
||||||
})
|
})
|
|
@ -1,10 +1,21 @@
|
||||||
import { writable, get, derived } from "svelte/store"
|
import { writable, get, derived, Writable, Readable } from "svelte/store"
|
||||||
|
import { Store as StoreContext } from "."
|
||||||
import { parseCellID } from "../lib/utils"
|
import { parseCellID } from "../lib/utils"
|
||||||
|
|
||||||
|
interface ValidationStore {
|
||||||
|
validation: Writable<Record<string, string>>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DerivedValidationStore {
|
||||||
|
validationRowLookupMap: Readable<Record<string, string[]>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = ValidationStore & DerivedValidationStore
|
||||||
|
|
||||||
// Normally we would break out actions into the explicit "createActions"
|
// Normally we would break out actions into the explicit "createActions"
|
||||||
// function, but for validation all these actions are pure so can go into
|
// function, but for validation all these actions are pure so can go into
|
||||||
// "createStores" instead to make dependency ordering simpler
|
// "createStores" instead to make dependency ordering simpler
|
||||||
export const createStores = () => {
|
export const createStores = (): ValidationStore => {
|
||||||
const validation = writable({})
|
const validation = writable({})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -12,12 +23,12 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = (context: StoreContext): DerivedValidationStore => {
|
||||||
const { validation } = context
|
const { validation } = context
|
||||||
|
|
||||||
// Derive which rows have errors so that we can use that info later
|
// Derive which rows have errors so that we can use that info later
|
||||||
const validationRowLookupMap = derived(validation, $validation => {
|
const validationRowLookupMap = derived(validation, $validation => {
|
||||||
let map = {}
|
const map: Record<string, string[]> = {}
|
||||||
Object.entries($validation).forEach(([key, error]) => {
|
Object.entries($validation).forEach(([key, error]) => {
|
||||||
// Extract row ID from all errored cell IDs
|
// Extract row ID from all errored cell IDs
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -36,10 +47,10 @@ export const deriveStores = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = (context: StoreContext) => {
|
||||||
const { validation, focusedCellId, validationRowLookupMap } = context
|
const { validation, focusedCellId, validationRowLookupMap } = context
|
||||||
|
|
||||||
const setError = (cellId, error) => {
|
const setError = (cellId: string | undefined, error: string) => {
|
||||||
if (!cellId) {
|
if (!cellId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -49,11 +60,11 @@ export const createActions = context => {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const rowHasErrors = rowId => {
|
const rowHasErrors = (rowId: string) => {
|
||||||
return get(validationRowLookupMap)[rowId]?.length > 0
|
return get(validationRowLookupMap)[rowId]?.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const focusFirstRowError = rowId => {
|
const focusFirstRowError = (rowId: string) => {
|
||||||
const errorCells = get(validationRowLookupMap)[rowId]
|
const errorCells = get(validationRowLookupMap)[rowId]
|
||||||
const cellId = errorCells?.[0]
|
const cellId = errorCells?.[0]
|
||||||
if (cellId) {
|
if (cellId) {
|
||||||
|
@ -73,7 +84,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialise = context => {
|
export const initialise = (context: StoreContext) => {
|
||||||
const { validation, previousFocusedRowId, validationRowLookupMap } = context
|
const { validation, previousFocusedRowId, validationRowLookupMap } = context
|
||||||
|
|
||||||
// Remove validation errors when changing rows
|
// Remove validation errors when changing rows
|
|
@ -1,103 +0,0 @@
|
||||||
import { FieldType, RelationshipType } from "@budibase/types"
|
|
||||||
import { Helpers } from "@budibase/bbui"
|
|
||||||
|
|
||||||
const columnTypeManyTypeOverrides = {
|
|
||||||
[FieldType.DATETIME]: FieldType.STRING,
|
|
||||||
[FieldType.BOOLEAN]: FieldType.STRING,
|
|
||||||
[FieldType.SIGNATURE_SINGLE]: FieldType.ATTACHMENTS,
|
|
||||||
}
|
|
||||||
|
|
||||||
const columnTypeManyParser = {
|
|
||||||
[FieldType.DATETIME]: (value, field) => {
|
|
||||||
function parseDate(value) {
|
|
||||||
const { timeOnly, dateOnly, ignoreTimezones } = field || {}
|
|
||||||
const enableTime = !dateOnly
|
|
||||||
const parsedValue = Helpers.parseDate(value, {
|
|
||||||
timeOnly,
|
|
||||||
enableTime,
|
|
||||||
ignoreTimezones,
|
|
||||||
})
|
|
||||||
const parsed = Helpers.getDateDisplayValue(parsedValue, {
|
|
||||||
enableTime,
|
|
||||||
timeOnly,
|
|
||||||
})
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.map(v => parseDate(v))
|
|
||||||
},
|
|
||||||
[FieldType.BOOLEAN]: value => value.map(v => !!v),
|
|
||||||
[FieldType.BB_REFERENCE_SINGLE]: value => [
|
|
||||||
...new Map(value.map(i => [i._id, i])).values(),
|
|
||||||
],
|
|
||||||
[FieldType.BB_REFERENCE]: value => [
|
|
||||||
...new Map(value.map(i => [i._id, i])).values(),
|
|
||||||
],
|
|
||||||
[FieldType.ARRAY]: value => Array.from(new Set(value)),
|
|
||||||
}
|
|
||||||
|
|
||||||
export function enrichSchemaWithRelColumns(schema) {
|
|
||||||
if (!schema) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const result = Object.keys(schema).reduce((result, fieldName) => {
|
|
||||||
const field = schema[fieldName]
|
|
||||||
result[fieldName] = field
|
|
||||||
|
|
||||||
if (field.visible !== false && field.columns) {
|
|
||||||
const fromSingle =
|
|
||||||
field?.relationshipType === RelationshipType.ONE_TO_MANY
|
|
||||||
|
|
||||||
for (const relColumn of Object.keys(field.columns)) {
|
|
||||||
const relField = field.columns[relColumn]
|
|
||||||
if (!relField.visible) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const name = `${field.name}.${relColumn}`
|
|
||||||
result[name] = {
|
|
||||||
...relField,
|
|
||||||
name,
|
|
||||||
related: { field: fieldName, subField: relColumn },
|
|
||||||
cellRenderType:
|
|
||||||
(!fromSingle && columnTypeManyTypeOverrides[relField.type]) ||
|
|
||||||
relField.type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRelatedTableValues(row, field, fromField) {
|
|
||||||
const fromSingle =
|
|
||||||
fromField?.relationshipType === RelationshipType.ONE_TO_MANY
|
|
||||||
|
|
||||||
let result = ""
|
|
||||||
|
|
||||||
if (fromSingle) {
|
|
||||||
result = row[field.related.field]?.[0]?.[field.related.subField]
|
|
||||||
} else {
|
|
||||||
const parser = columnTypeManyParser[field.type] || (value => value)
|
|
||||||
const value = row[field.related.field]
|
|
||||||
?.flatMap(r => r[field.related.subField])
|
|
||||||
?.filter(i => i !== undefined && i !== null)
|
|
||||||
result = parser(value || [], field)
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
FieldType.STRING,
|
|
||||||
FieldType.NUMBER,
|
|
||||||
FieldType.BIGINT,
|
|
||||||
FieldType.BOOLEAN,
|
|
||||||
FieldType.DATETIME,
|
|
||||||
FieldType.LONGFORM,
|
|
||||||
FieldType.BARCODEQR,
|
|
||||||
].includes(field.type)
|
|
||||||
) {
|
|
||||||
result = result?.join(", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
import {
|
||||||
|
FieldType,
|
||||||
|
isRelationshipField,
|
||||||
|
RelationshipType,
|
||||||
|
Row,
|
||||||
|
UIFieldSchema,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
const columnTypeManyTypeOverrides: Partial<Record<FieldType, FieldType>> = {
|
||||||
|
[FieldType.DATETIME]: FieldType.STRING,
|
||||||
|
[FieldType.BOOLEAN]: FieldType.STRING,
|
||||||
|
[FieldType.SIGNATURE_SINGLE]: FieldType.ATTACHMENTS,
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnTypeManyParser = {
|
||||||
|
[FieldType.DATETIME]: (
|
||||||
|
value: any[],
|
||||||
|
field: {
|
||||||
|
timeOnly?: boolean
|
||||||
|
dateOnly?: boolean
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
function parseDate(value: any) {
|
||||||
|
const { timeOnly, dateOnly } = field || {}
|
||||||
|
const enableTime = !dateOnly
|
||||||
|
const parsedValue = Helpers.parseDate(value, { enableTime })
|
||||||
|
const parsed = Helpers.getDateDisplayValue(parsedValue, {
|
||||||
|
enableTime,
|
||||||
|
timeOnly,
|
||||||
|
})
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.map(v => parseDate(v))
|
||||||
|
},
|
||||||
|
[FieldType.BOOLEAN]: (value: any[]) => value.map(v => !!v),
|
||||||
|
[FieldType.BB_REFERENCE_SINGLE]: (value: any[]) => [
|
||||||
|
...new Map(value.map(i => [i._id, i])).values(),
|
||||||
|
],
|
||||||
|
[FieldType.BB_REFERENCE]: (value: any[]) => [
|
||||||
|
...new Map(value.map(i => [i._id, i])).values(),
|
||||||
|
],
|
||||||
|
[FieldType.ARRAY]: (value: any[]) => Array.from(new Set(value)),
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enrichSchemaWithRelColumns(
|
||||||
|
schema: Record<string, UIFieldSchema>
|
||||||
|
): Record<string, UIFieldSchema> | undefined {
|
||||||
|
if (!schema) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const result = Object.keys(schema).reduce<Record<string, UIFieldSchema>>(
|
||||||
|
(result, fieldName) => {
|
||||||
|
const field = schema[fieldName]
|
||||||
|
result[fieldName] = field
|
||||||
|
|
||||||
|
if (
|
||||||
|
field.visible !== false &&
|
||||||
|
isRelationshipField(field) &&
|
||||||
|
field.columns
|
||||||
|
) {
|
||||||
|
const fromSingle =
|
||||||
|
field?.relationshipType === RelationshipType.ONE_TO_MANY
|
||||||
|
|
||||||
|
for (const relColumn of Object.keys(field.columns)) {
|
||||||
|
const relField = field.columns[relColumn]
|
||||||
|
if (!relField.visible) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const name = `${field.name}.${relColumn}`
|
||||||
|
result[name] = {
|
||||||
|
...relField,
|
||||||
|
type: relField.type as any, // TODO
|
||||||
|
name,
|
||||||
|
related: { field: fieldName, subField: relColumn },
|
||||||
|
cellRenderType:
|
||||||
|
(!fromSingle && columnTypeManyTypeOverrides[relField.type]) ||
|
||||||
|
relField.type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRelatedTableValues(
|
||||||
|
row: Row,
|
||||||
|
field: UIFieldSchema & { related: { field: string; subField: string } },
|
||||||
|
fromField: UIFieldSchema
|
||||||
|
) {
|
||||||
|
const fromSingle =
|
||||||
|
isRelationshipField(fromField) &&
|
||||||
|
fromField?.relationshipType === RelationshipType.ONE_TO_MANY
|
||||||
|
|
||||||
|
let result = ""
|
||||||
|
|
||||||
|
if (fromSingle) {
|
||||||
|
result = row[field.related.field]?.[0]?.[field.related.subField]
|
||||||
|
} else {
|
||||||
|
const parser =
|
||||||
|
columnTypeManyParser[field.type as keyof typeof columnTypeManyParser] ||
|
||||||
|
((value: any) => value)
|
||||||
|
const value = row[field.related.field]
|
||||||
|
?.flatMap((r: Row) => r[field.related.subField])
|
||||||
|
?.filter((i: any) => i !== undefined && i !== null)
|
||||||
|
const parsed = parser(value || [], field as any)
|
||||||
|
result = parsed as any
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
FieldType.STRING,
|
||||||
|
FieldType.NUMBER,
|
||||||
|
FieldType.BIGINT,
|
||||||
|
FieldType.BOOLEAN,
|
||||||
|
FieldType.DATETIME,
|
||||||
|
FieldType.LONGFORM,
|
||||||
|
FieldType.BARCODEQR,
|
||||||
|
].includes(field.type)
|
||||||
|
) {
|
||||||
|
result = parsed?.join(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
{
|
{
|
||||||
|
"extends": "../../tsconfig.build.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
|
"module": "preserve",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
"outDir": "./dist",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"paths": {
|
"allowJs": true
|
||||||
"@budibase/types": ["../types/src"],
|
|
||||||
"@budibase/shared-core": ["../shared-core/src"],
|
|
||||||
"@budibase/bbui": ["../bbui/src"]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7fc699463b3957eb050351b983edef0d25a531ae
|
Subproject commit ae786121d923449b0ad5fcbd123d0a9fec28f65e
|
|
@ -37,6 +37,7 @@ import { jsonFromCsvString } from "../../../utilities/csv"
|
||||||
import { builderSocket } from "../../../websockets"
|
import { builderSocket } from "../../../websockets"
|
||||||
import { cloneDeep } from "lodash"
|
import { cloneDeep } from "lodash"
|
||||||
import {
|
import {
|
||||||
|
canBeDisplayColumn,
|
||||||
helpers,
|
helpers,
|
||||||
PROTECTED_EXTERNAL_COLUMNS,
|
PROTECTED_EXTERNAL_COLUMNS,
|
||||||
PROTECTED_INTERNAL_COLUMNS,
|
PROTECTED_INTERNAL_COLUMNS,
|
||||||
|
@ -67,6 +68,27 @@ function checkDefaultFields(table: Table) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function guardTable(table: Table, isCreate: boolean) {
|
||||||
|
checkDefaultFields(table)
|
||||||
|
|
||||||
|
if (
|
||||||
|
table.primaryDisplay &&
|
||||||
|
!canBeDisplayColumn(table.schema[table.primaryDisplay]?.type)
|
||||||
|
) {
|
||||||
|
// Prevent throwing errors from existing badly configured tables. Only throw for new tables or if this setting is being updated
|
||||||
|
if (
|
||||||
|
isCreate ||
|
||||||
|
(await sdk.tables.getTable(table._id!)).primaryDisplay !==
|
||||||
|
table.primaryDisplay
|
||||||
|
) {
|
||||||
|
throw new HTTPError(
|
||||||
|
`Column "${table.primaryDisplay}" cannot be used as a display type.`,
|
||||||
|
400
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// covers both internal and external
|
// covers both internal and external
|
||||||
export async function fetch(ctx: UserCtx<void, FetchTablesResponse>) {
|
export async function fetch(ctx: UserCtx<void, FetchTablesResponse>) {
|
||||||
const internal = await sdk.tables.getAllInternalTables()
|
const internal = await sdk.tables.getAllInternalTables()
|
||||||
|
@ -111,7 +133,7 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
|
||||||
|
|
||||||
const isCreate = !table._id
|
const isCreate = !table._id
|
||||||
|
|
||||||
checkDefaultFields(table)
|
await guardTable(table, isCreate)
|
||||||
|
|
||||||
let savedTable: Table
|
let savedTable: Table
|
||||||
if (isCreate) {
|
if (isCreate) {
|
||||||
|
|
|
@ -3399,7 +3399,7 @@ if (descriptions.length) {
|
||||||
type: FieldType.LINK,
|
type: FieldType.LINK,
|
||||||
relationshipType: RelationshipType.MANY_TO_ONE,
|
relationshipType: RelationshipType.MANY_TO_ONE,
|
||||||
tableId: toRelateTableId,
|
tableId: toRelateTableId,
|
||||||
fieldName: "link",
|
fieldName: "main",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -3408,7 +3408,7 @@ if (descriptions.length) {
|
||||||
)
|
)
|
||||||
await config.api.table.save({
|
await config.api.table.save({
|
||||||
...toRelateTable,
|
...toRelateTable,
|
||||||
primaryDisplay: "link",
|
primaryDisplay: "name",
|
||||||
})
|
})
|
||||||
const relatedRows = await Promise.all([
|
const relatedRows = await Promise.all([
|
||||||
config.api.row.save(toRelateTable._id!, {
|
config.api.row.save(toRelateTable._id!, {
|
||||||
|
|
|
@ -185,6 +185,62 @@ if (descriptions.length) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it("can set primary display", async () => {
|
||||||
|
const columnName = generator.word()
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
tableForDatasource(datasource, {
|
||||||
|
primaryDisplay: columnName,
|
||||||
|
schema: {
|
||||||
|
[columnName]: {
|
||||||
|
name: columnName,
|
||||||
|
type: FieldType.STRING,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(table.primaryDisplay).toEqual(columnName)
|
||||||
|
|
||||||
|
const res = await config.api.table.get(table._id!)
|
||||||
|
expect(res.primaryDisplay).toEqual(columnName)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("cannot use unexisting columns as primary display", async () => {
|
||||||
|
const columnName = generator.word()
|
||||||
|
await config.api.table.save(
|
||||||
|
tableForDatasource(datasource, {
|
||||||
|
primaryDisplay: columnName,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message: `Column "${columnName}" cannot be used as a display type.`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("cannot use invalid column types as display name", async () => {
|
||||||
|
const columnName = generator.word()
|
||||||
|
|
||||||
|
await config.api.table.save(
|
||||||
|
tableForDatasource(datasource, {
|
||||||
|
primaryDisplay: columnName,
|
||||||
|
schema: {
|
||||||
|
[columnName]: {
|
||||||
|
name: columnName,
|
||||||
|
type: FieldType.BOOLEAN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message: `Column "${columnName}" cannot be used as a display type.`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("permissions", () => {
|
describe("permissions", () => {
|
||||||
|
@ -603,6 +659,49 @@ if (descriptions.length) {
|
||||||
}
|
}
|
||||||
expect(response).toEqual(expectedResponse)
|
expect(response).toEqual(expectedResponse)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("cannot use unexisting columns as primary display", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
tableForDatasource(datasource)
|
||||||
|
)
|
||||||
|
|
||||||
|
const columnName = generator.word()
|
||||||
|
const tableRequest = {
|
||||||
|
...table,
|
||||||
|
primaryDisplay: columnName,
|
||||||
|
}
|
||||||
|
await config.api.table.save(tableRequest, {
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message: `Column "${columnName}" cannot be used as a display type.`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("cannot use invalid column types as display name", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
tableForDatasource(datasource)
|
||||||
|
)
|
||||||
|
const columnName = generator.word()
|
||||||
|
const tableRequest: SaveTableRequest = {
|
||||||
|
...table,
|
||||||
|
primaryDisplay: columnName,
|
||||||
|
schema: {
|
||||||
|
...table.schema,
|
||||||
|
[columnName]: {
|
||||||
|
name: columnName,
|
||||||
|
type: FieldType.BOOLEAN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
await config.api.table.save(tableRequest, {
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message: `Column "${columnName}" cannot be used as a display type.`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("import", () => {
|
describe("import", () => {
|
||||||
|
|
|
@ -12,6 +12,7 @@ interface FirebaseConfig {
|
||||||
email: string
|
email: string
|
||||||
privateKey: string
|
privateKey: string
|
||||||
projectId: string
|
projectId: string
|
||||||
|
databaseId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCHEMA: Integration = {
|
const SCHEMA: Integration = {
|
||||||
|
@ -30,12 +31,21 @@ const SCHEMA: Integration = {
|
||||||
},
|
},
|
||||||
privateKey: {
|
privateKey: {
|
||||||
type: DatasourceFieldType.STRING,
|
type: DatasourceFieldType.STRING,
|
||||||
|
display: "Private Key",
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
projectId: {
|
projectId: {
|
||||||
type: DatasourceFieldType.STRING,
|
type: DatasourceFieldType.STRING,
|
||||||
|
display: "Project ID",
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
databaseId: {
|
||||||
|
type: DatasourceFieldType.STRING,
|
||||||
|
display: "Database ID",
|
||||||
|
required: false,
|
||||||
|
default: "(default)",
|
||||||
|
placeholder: "(default)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
create: {
|
create: {
|
||||||
|
@ -97,6 +107,7 @@ class FirebaseIntegration implements IntegrationBase {
|
||||||
this.config = config
|
this.config = config
|
||||||
this.client = new Firestore({
|
this.client = new Firestore({
|
||||||
projectId: config.projectId,
|
projectId: config.projectId,
|
||||||
|
databaseId: config.databaseId || "(default)",
|
||||||
credentials: {
|
credentials: {
|
||||||
client_email: config.email,
|
client_email: config.email,
|
||||||
private_key: config.privateKey?.replace(/\\n/g, "\n"),
|
private_key: config.privateKey?.replace(/\\n/g, "\n"),
|
||||||
|
|
|
@ -324,8 +324,8 @@ export async function update(
|
||||||
return pickApi(tableId).update(tableId, view)
|
return pickApi(tableId).update(tableId, view)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isV2(view: View | ViewV2): view is ViewV2 {
|
export function isV2(view: View | ViewV2) {
|
||||||
return (view as ViewV2).version === 2
|
return helpers.views.isV2(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(viewId: string): Promise<ViewV2> {
|
export async function remove(viewId: string): Promise<ViewV2> {
|
||||||
|
|
|
@ -456,7 +456,7 @@ export function filterAutomation(appId: string, tableId?: string): Automation {
|
||||||
icon: "Icon",
|
icon: "Icon",
|
||||||
id: "a",
|
id: "a",
|
||||||
type: AutomationStepType.TRIGGER,
|
type: AutomationStepType.TRIGGER,
|
||||||
event: "row:save",
|
event: AutomationEventType.ROW_SAVE,
|
||||||
stepId: AutomationTriggerStepId.ROW_SAVED,
|
stepId: AutomationTriggerStepId.ROW_SAVED,
|
||||||
inputs: {
|
inputs: {
|
||||||
tableId: tableId!,
|
tableId: tableId!,
|
||||||
|
@ -498,7 +498,7 @@ export function updateRowAutomationWithFilters(
|
||||||
icon: "Icon",
|
icon: "Icon",
|
||||||
id: "a",
|
id: "a",
|
||||||
type: AutomationStepType.TRIGGER,
|
type: AutomationStepType.TRIGGER,
|
||||||
event: "row:update",
|
event: AutomationEventType.ROW_UPDATE,
|
||||||
stepId: AutomationTriggerStepId.ROW_UPDATED,
|
stepId: AutomationTriggerStepId.ROW_UPDATED,
|
||||||
inputs: { tableId },
|
inputs: { tableId },
|
||||||
schema: TRIGGER_DEFINITIONS.ROW_UPDATED.schema,
|
schema: TRIGGER_DEFINITIONS.ROW_UPDATED.schema,
|
||||||
|
@ -513,7 +513,7 @@ export function basicAutomationResults(
|
||||||
return {
|
return {
|
||||||
automationId,
|
automationId,
|
||||||
status: AutomationStatus.SUCCESS,
|
status: AutomationStatus.SUCCESS,
|
||||||
trigger: "trigger",
|
trigger: "trigger" as any,
|
||||||
steps: [
|
steps: [
|
||||||
{
|
{
|
||||||
stepId: AutomationActionStepId.SERVER_LOG,
|
stepId: AutomationActionStepId.SERVER_LOG,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
BasicViewFieldMetadata,
|
BasicViewFieldMetadata,
|
||||||
|
View,
|
||||||
ViewCalculationFieldMetadata,
|
ViewCalculationFieldMetadata,
|
||||||
ViewFieldMetadata,
|
ViewFieldMetadata,
|
||||||
ViewV2,
|
ViewV2,
|
||||||
|
@ -43,3 +44,7 @@ export function basicFields(view: UnsavedViewV2, opts?: { visible?: boolean }) {
|
||||||
return !isCalculationField(field) && (!visible || isVisible(field))
|
return !isCalculationField(field) && (!visible || isVisible(field))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isV2(view: View | ViewV2): view is ViewV2 {
|
||||||
|
return (view as ViewV2).version === 2
|
||||||
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ const allowDisplayColumnByType: Record<FieldType, boolean> = {
|
||||||
[FieldType.AUTO]: true,
|
[FieldType.AUTO]: true,
|
||||||
[FieldType.INTERNAL]: true,
|
[FieldType.INTERNAL]: true,
|
||||||
[FieldType.BARCODEQR]: true,
|
[FieldType.BARCODEQR]: true,
|
||||||
|
|
||||||
[FieldType.BIGINT]: true,
|
[FieldType.BIGINT]: true,
|
||||||
|
|
||||||
[FieldType.BOOLEAN]: false,
|
[FieldType.BOOLEAN]: false,
|
||||||
[FieldType.ARRAY]: false,
|
[FieldType.ARRAY]: false,
|
||||||
[FieldType.ATTACHMENTS]: false,
|
[FieldType.ATTACHMENTS]: false,
|
||||||
|
|
|
@ -148,6 +148,7 @@ export interface Automation extends Document {
|
||||||
|
|
||||||
interface BaseIOStructure {
|
interface BaseIOStructure {
|
||||||
type?: AutomationIOType
|
type?: AutomationIOType
|
||||||
|
subtype?: AutomationIOType
|
||||||
customType?: AutomationCustomIOType
|
customType?: AutomationCustomIOType
|
||||||
title?: string
|
title?: string
|
||||||
description?: string
|
description?: string
|
||||||
|
@ -192,7 +193,7 @@ export enum AutomationStoppedReason {
|
||||||
export interface AutomationResults {
|
export interface AutomationResults {
|
||||||
automationId?: string
|
automationId?: string
|
||||||
status?: AutomationStatus
|
status?: AutomationStatus
|
||||||
trigger?: any
|
trigger?: AutomationTrigger
|
||||||
steps: {
|
steps: {
|
||||||
stepId: AutomationTriggerStepId | AutomationActionStepId
|
stepId: AutomationTriggerStepId | AutomationActionStepId
|
||||||
inputs: {
|
inputs: {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
AutomationFeature,
|
AutomationFeature,
|
||||||
InputOutputBlock,
|
InputOutputBlock,
|
||||||
AutomationTriggerStepId,
|
AutomationTriggerStepId,
|
||||||
|
AutomationEventType,
|
||||||
} from "./automation"
|
} from "./automation"
|
||||||
import {
|
import {
|
||||||
CollectStepInputs,
|
CollectStepInputs,
|
||||||
|
@ -142,6 +143,7 @@ export type ActionImplementations<T extends Hosting> = {
|
||||||
export interface AutomationStepSchemaBase {
|
export interface AutomationStepSchemaBase {
|
||||||
name: string
|
name: string
|
||||||
stepTitle?: string
|
stepTitle?: string
|
||||||
|
event?: AutomationEventType
|
||||||
tagline: string
|
tagline: string
|
||||||
icon: string
|
icon: string
|
||||||
description: string
|
description: string
|
||||||
|
@ -344,7 +346,7 @@ export interface AutomationTriggerSchema<
|
||||||
> extends AutomationStepSchemaBase {
|
> extends AutomationStepSchemaBase {
|
||||||
id: string
|
id: string
|
||||||
type: AutomationStepType.TRIGGER
|
type: AutomationStepType.TRIGGER
|
||||||
event?: string
|
event?: AutomationEventType
|
||||||
cronJobId?: string
|
cronJobId?: string
|
||||||
stepId: TTrigger
|
stepId: TTrigger
|
||||||
inputs: AutomationTriggerInputs<TTrigger> & Record<string, any> // The record union to be removed once the types are fixed
|
inputs: AutomationTriggerInputs<TTrigger> & Record<string, any> // The record union to be removed once the types are fixed
|
||||||
|
|
|
@ -119,6 +119,7 @@ interface DatasourceBasicFieldConfig {
|
||||||
default?: any
|
default?: any
|
||||||
deprecated?: boolean
|
deprecated?: boolean
|
||||||
hidden?: string
|
hidden?: string
|
||||||
|
placeholder?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DatasourceSelectFieldConfig extends DatasourceBasicFieldConfig {
|
interface DatasourceSelectFieldConfig extends DatasourceBasicFieldConfig {
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
export interface BranchPath {
|
||||||
|
stepIdx: number
|
||||||
|
branchIdx: number
|
||||||
|
branchStepId: string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlockDefinitions {
|
||||||
|
TRIGGER: Record<string, any>
|
||||||
|
CREATABLE_TRIGGER: Record<string, any>
|
||||||
|
ACTION: Record<string, any>
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { CalculationType, FieldSchema, FieldType } from "@budibase/types"
|
||||||
|
|
||||||
|
export type UIColumn = FieldSchema & {
|
||||||
|
label: string
|
||||||
|
readonly: boolean
|
||||||
|
conditions: any
|
||||||
|
related?: {
|
||||||
|
field: string
|
||||||
|
subField: string
|
||||||
|
}
|
||||||
|
primaryDisplay?: boolean
|
||||||
|
schema?: {
|
||||||
|
disabled: boolean
|
||||||
|
type: FieldType
|
||||||
|
readonly: boolean
|
||||||
|
autocolumn: boolean
|
||||||
|
}
|
||||||
|
calculationType: CalculationType
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { UITable, UIView } from "@budibase/types"
|
||||||
|
|
||||||
|
export type UIDatasource = (UITable | UIView) & {
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UIFieldMutation {
|
||||||
|
visible?: boolean
|
||||||
|
readonly?: boolean
|
||||||
|
width?: number
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from "./columns"
|
||||||
|
export * from "./datasource"
|
||||||
|
export * from "./table"
|
||||||
|
export * from "./view"
|
||||||
|
export * from "./user"
|
|
@ -0,0 +1,34 @@
|
||||||
|
import {
|
||||||
|
BasicViewFieldMetadata,
|
||||||
|
FieldSchema,
|
||||||
|
FieldType,
|
||||||
|
RelationSchemaField,
|
||||||
|
SortOrder,
|
||||||
|
Table,
|
||||||
|
UISearchFilter,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
export interface UITable extends Omit<Table, "type"> {
|
||||||
|
name: string
|
||||||
|
id: string
|
||||||
|
type: string
|
||||||
|
tableId: string
|
||||||
|
primaryDisplay?: string
|
||||||
|
sort?: {
|
||||||
|
field: string
|
||||||
|
order: SortOrder
|
||||||
|
}
|
||||||
|
queryUI: UISearchFilter
|
||||||
|
schema: Record<string, UIFieldSchema>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UIFieldSchema = FieldSchema &
|
||||||
|
BasicViewFieldMetadata & {
|
||||||
|
related?: { field: string; subField: string }
|
||||||
|
columns?: Record<string, UIRelationSchemaField>
|
||||||
|
cellRenderType?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UIRelationSchemaField extends RelationSchemaField {
|
||||||
|
type: FieldType
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { User } from "@budibase/types"
|
||||||
|
|
||||||
|
export interface UIUser extends User {
|
||||||
|
sessionId: string
|
||||||
|
gridMetadata?: { focusedCellId?: string }
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { ViewV2 } from "@budibase/types"
|
||||||
|
import { UIFieldSchema } from "./table"
|
||||||
|
|
||||||
|
export interface UIView extends ViewV2 {
|
||||||
|
schema: Record<string, UIFieldSchema>
|
||||||
|
}
|
|
@ -1 +1,3 @@
|
||||||
export * from "./integration"
|
export * from "./integration"
|
||||||
|
export * from "./automations"
|
||||||
|
export * from "./grid"
|
||||||
|
|
36
yarn.lock
36
yarn.lock
|
@ -5946,6 +5946,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/shortid@^2.2.0":
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/shortid/-/shortid-2.2.0.tgz#905990fc4275f77e60ab0cd9f791b91a3d4bff04"
|
||||||
|
integrity sha512-jBG2FgBxcaSf0h662YloTGA32M8UtNbnTPekUr/eCmWXq0JWQXgNEQ/P5Gf05Cv66QZtE1Ttr83I1AJBPdzCBg==
|
||||||
|
|
||||||
"@types/ssh2-streams@*":
|
"@types/ssh2-streams@*":
|
||||||
version "0.1.12"
|
version "0.1.12"
|
||||||
resolved "https://registry.yarnpkg.com/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz#e68795ba2bf01c76b93f9c9809e1f42f0eaaec5f"
|
resolved "https://registry.yarnpkg.com/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz#e68795ba2bf01c76b93f9c9809e1f42f0eaaec5f"
|
||||||
|
@ -18639,16 +18644,7 @@ string-length@^4.0.1:
|
||||||
char-regex "^1.0.2"
|
char-regex "^1.0.2"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
|
||||||
dependencies:
|
|
||||||
emoji-regex "^8.0.0"
|
|
||||||
is-fullwidth-code-point "^3.0.0"
|
|
||||||
strip-ansi "^6.0.1"
|
|
||||||
|
|
||||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
@ -18740,7 +18736,7 @@ stringify-object@^3.2.1:
|
||||||
is-obj "^1.0.1"
|
is-obj "^1.0.1"
|
||||||
is-regexp "^1.0.0"
|
is-regexp "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
@ -18754,13 +18750,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^4.1.0"
|
ansi-regex "^4.1.0"
|
||||||
|
|
||||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
|
||||||
version "6.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
|
||||||
dependencies:
|
|
||||||
ansi-regex "^5.0.1"
|
|
||||||
|
|
||||||
strip-ansi@^7.0.1:
|
strip-ansi@^7.0.1:
|
||||||
version "7.0.1"
|
version "7.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
|
||||||
|
@ -20508,7 +20497,7 @@ worker-farm@1.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
errno "~0.1.7"
|
errno "~0.1.7"
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
@ -20526,15 +20515,6 @@ wrap-ansi@^5.1.0:
|
||||||
string-width "^3.0.0"
|
string-width "^3.0.0"
|
||||||
strip-ansi "^5.0.0"
|
strip-ansi "^5.0.0"
|
||||||
|
|
||||||
wrap-ansi@^7.0.0:
|
|
||||||
version "7.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^4.0.0"
|
|
||||||
string-width "^4.1.0"
|
|
||||||
strip-ansi "^6.0.0"
|
|
||||||
|
|
||||||
wrap-ansi@^8.1.0:
|
wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
|
|
Loading…
Reference in New Issue