Add new custom datasource type
This commit is contained in:
parent
b75c78dae5
commit
43c30d877b
|
@ -9,6 +9,7 @@
|
||||||
Heading,
|
Heading,
|
||||||
Drawer,
|
Drawer,
|
||||||
DrawerContent,
|
DrawerContent,
|
||||||
|
Icon,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { store, currentAsset } from "builderStore"
|
import { store, currentAsset } from "builderStore"
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
import BindingBuilder from "components/integration/QueryBindingBuilder.svelte"
|
import BindingBuilder from "components/integration/QueryBindingBuilder.svelte"
|
||||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
||||||
|
|
||||||
export let value = {}
|
export let value = {}
|
||||||
export let otherSources
|
export let otherSources
|
||||||
|
@ -31,9 +33,12 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const arrayTypes = ["attachment", "array"]
|
const arrayTypes = ["attachment", "array"]
|
||||||
|
|
||||||
let anchorRight, dropdownRight
|
let anchorRight, dropdownRight
|
||||||
let drawer
|
let drawer
|
||||||
let tmpQueryParams
|
let tmpQueryParams
|
||||||
|
let tmpCustomData
|
||||||
|
let customDataValid = true
|
||||||
|
|
||||||
$: text = value?.label ?? "Choose an option"
|
$: text = value?.label ?? "Choose an option"
|
||||||
$: tables = $tablesStore.list.map(m => ({
|
$: tables = $tablesStore.list.map(m => ({
|
||||||
|
@ -125,6 +130,10 @@
|
||||||
value: `{{ literal ${runtimeBinding} }}`,
|
value: `{{ literal ${runtimeBinding} }}`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
$: custom = {
|
||||||
|
type: "custom",
|
||||||
|
label: "Custom",
|
||||||
|
}
|
||||||
|
|
||||||
const handleSelected = selected => {
|
const handleSelected = selected => {
|
||||||
dispatch("change", selected)
|
dispatch("change", selected)
|
||||||
|
@ -151,6 +160,11 @@
|
||||||
drawer.show()
|
drawer.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openCustomDrawer = () => {
|
||||||
|
tmpCustomData = value.data || ""
|
||||||
|
drawer.show()
|
||||||
|
}
|
||||||
|
|
||||||
const getQueryValue = queries => {
|
const getQueryValue = queries => {
|
||||||
return queries.find(q => q._id === value._id) || value
|
return queries.find(q => q._id === value._id) || value
|
||||||
}
|
}
|
||||||
|
@ -162,6 +176,14 @@
|
||||||
})
|
})
|
||||||
drawer.hide()
|
drawer.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const saveCustomData = () => {
|
||||||
|
handleSelected({
|
||||||
|
...value,
|
||||||
|
data: tmpCustomData,
|
||||||
|
})
|
||||||
|
drawer.hide()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container" bind:this={anchorRight}>
|
<div class="container" bind:this={anchorRight}>
|
||||||
|
@ -172,7 +194,9 @@
|
||||||
on:click={dropdownRight.show}
|
on:click={dropdownRight.show}
|
||||||
/>
|
/>
|
||||||
{#if value?.type === "query"}
|
{#if value?.type === "query"}
|
||||||
<i class="ri-settings-5-line" on:click={openQueryParamsDrawer} />
|
<div class="icon">
|
||||||
|
<Icon hoverable name="Settings" on:click={openQueryParamsDrawer} />
|
||||||
|
</div>
|
||||||
<Drawer title={"Query Bindings"} bind:this={drawer}>
|
<Drawer title={"Query Bindings"} bind:this={drawer}>
|
||||||
<Button slot="buttons" cta on:click={saveQueryParams}>Save</Button>
|
<Button slot="buttons" cta on:click={saveQueryParams}>Save</Button>
|
||||||
<DrawerContent slot="body">
|
<DrawerContent slot="body">
|
||||||
|
@ -198,6 +222,31 @@
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if value?.type === "custom"}
|
||||||
|
<div class="icon">
|
||||||
|
<Icon hoverable name="Settings" on:click={openCustomDrawer} />
|
||||||
|
</div>
|
||||||
|
<Drawer title="Custom data" bind:this={drawer}>
|
||||||
|
<Button
|
||||||
|
slot="buttons"
|
||||||
|
cta
|
||||||
|
on:click={saveCustomData}
|
||||||
|
disabled={!customDataValid}>Save</Button
|
||||||
|
>
|
||||||
|
<div slot="description">
|
||||||
|
Provide a JavaScript or JSON array to use as data
|
||||||
|
</div>
|
||||||
|
<ClientBindingPanel
|
||||||
|
slot="body"
|
||||||
|
bind:valid={customDataValid}
|
||||||
|
value={tmpCustomData}
|
||||||
|
on:change={event => (tmpCustomData = event.detail)}
|
||||||
|
{bindings}
|
||||||
|
allowJS
|
||||||
|
allowHelpers
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Popover bind:this={dropdownRight} anchor={anchorRight}>
|
<Popover bind:this={dropdownRight} anchor={anchorRight}>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
|
@ -285,17 +334,18 @@
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
{#if otherSources?.length}
|
<Divider />
|
||||||
<Divider />
|
<div class="title">
|
||||||
<div class="title">
|
<Heading size="XS">Other</Heading>
|
||||||
<Heading size="XS">Other</Heading>
|
</div>
|
||||||
</div>
|
<ul>
|
||||||
<ul>
|
<li on:click={() => handleSelected(custom)}>{custom.label}</li>
|
||||||
|
{#if otherSources?.length}
|
||||||
{#each otherSources as source}
|
{#each otherSources as source}
|
||||||
<li on:click={() => handleSelected(source)}>{source.label}</li>
|
<li on:click={() => handleSelected(source)}>{source.label}</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
{/if}
|
||||||
{/if}
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
|
@ -340,16 +390,7 @@
|
||||||
background-color: var(--spectrum-global-color-gray-200);
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
.icon {
|
||||||
margin-left: 5px;
|
margin-left: 8px;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
i:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export const getColor = (idx, opacity = 0.3) => {
|
export const getColor = (idx, opacity = 0.3) => {
|
||||||
if (idx == null || idx === -1) {
|
if (idx == null || idx === -1) {
|
||||||
return null
|
idx = 0
|
||||||
}
|
}
|
||||||
return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, ${opacity})`
|
return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, ${opacity})`
|
||||||
}
|
}
|
||||||
|
|
|
@ -420,7 +420,7 @@ export const createActions = context => {
|
||||||
// Ensure we have a unique _id.
|
// Ensure we have a unique _id.
|
||||||
// This means generating one for non DS+.
|
// This means generating one for non DS+.
|
||||||
if (!newRow._id) {
|
if (!newRow._id) {
|
||||||
newRow._id = `fake-${Helpers.hashString(JSON.stringify(newRow))}`
|
newRow._id = `fake-${Math.random()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rowCacheMap[newRow._id]) {
|
if (!rowCacheMap[newRow._id]) {
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
import DataFetch from "./DataFetch.js"
|
||||||
|
|
||||||
|
export default class CustomFetch extends DataFetch {
|
||||||
|
getType(value) {
|
||||||
|
if (value == null) {
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
const type = typeof value
|
||||||
|
if (type === "object") {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return "array"
|
||||||
|
}
|
||||||
|
return "json"
|
||||||
|
} else {
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses the custom data into an array format
|
||||||
|
parseCustomData(data) {
|
||||||
|
if (!data) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Happy path - already an array
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle string cases
|
||||||
|
if (typeof data === "string") {
|
||||||
|
// Try JSON parsing
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data)
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try a simple CSV
|
||||||
|
return data.split(",").map(x => x.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other cases we just assume it's a single object and wrap it
|
||||||
|
return [data]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enriches the custom data to ensure the structure and format is usable
|
||||||
|
enrichCustomData(data) {
|
||||||
|
if (!data?.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out any invalid values
|
||||||
|
data = data.filter(x => x != null && !Array.isArray(x))
|
||||||
|
|
||||||
|
// Ensure all values are packed into objects
|
||||||
|
return data.map(x => (typeof x === "object" ? x : { value: x }))
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomData(datasource) {
|
||||||
|
return this.enrichCustomData(this.parseCustomData(datasource?.data))
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDefinition(datasource) {
|
||||||
|
// Try and work out the schema from the array provided
|
||||||
|
let schema = {}
|
||||||
|
const data = this.getCustomData(datasource)
|
||||||
|
|
||||||
|
// Go through every object and extract all valid keys
|
||||||
|
for (let datum of data) {
|
||||||
|
for (let key of Object.keys(datum)) {
|
||||||
|
if (key === "_id") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!schema[key]) {
|
||||||
|
schema[key] = { type: this.getType(datum[key]) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
schema,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData() {
|
||||||
|
const { datasource } = this.options
|
||||||
|
return {
|
||||||
|
rows: this.getCustomData(datasource),
|
||||||
|
hasNextPage: false,
|
||||||
|
cursor: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import FieldFetch from "./FieldFetch.js"
|
||||||
import JSONArrayFetch from "./JSONArrayFetch.js"
|
import JSONArrayFetch from "./JSONArrayFetch.js"
|
||||||
import UserFetch from "./UserFetch.js"
|
import UserFetch from "./UserFetch.js"
|
||||||
import GroupUserFetch from "./GroupUserFetch.js"
|
import GroupUserFetch from "./GroupUserFetch.js"
|
||||||
|
import CustomFetch from "./CustomFetch.js"
|
||||||
|
|
||||||
const DataFetchMap = {
|
const DataFetchMap = {
|
||||||
table: TableFetch,
|
table: TableFetch,
|
||||||
|
@ -17,6 +18,7 @@ const DataFetchMap = {
|
||||||
link: RelationshipFetch,
|
link: RelationshipFetch,
|
||||||
user: UserFetch,
|
user: UserFetch,
|
||||||
groupUser: GroupUserFetch,
|
groupUser: GroupUserFetch,
|
||||||
|
custom: CustomFetch,
|
||||||
|
|
||||||
// Client specific datasource types
|
// Client specific datasource types
|
||||||
provider: NestedProviderFetch,
|
provider: NestedProviderFetch,
|
||||||
|
|
Loading…
Reference in New Issue