Add relationships as data source

This commit is contained in:
Andrew Kingston 2020-10-09 12:24:18 +01:00
parent f3d0104f41
commit d1f367ccff
8 changed files with 103 additions and 34 deletions

View File

@ -73,18 +73,32 @@ const componentInstanceToBindable = walkResult => i => {
const contextToBindables = (models, walkResult) => context => { const contextToBindables = (models, walkResult) => context => {
const contextParentPath = getParentPath(walkResult, context) const contextParentPath = getParentPath(walkResult, context)
const isModel = context.model?.isModel || typeof context.model === "string"
const modelId = let model, schema
typeof context.model === "string" ? context.model : context.model.modelId if (typeof context.model === "string" || context.model.type === "model") {
const model = models.find(model => model._id === modelId) const modelId =
typeof context.model === "string" ? context.model : context.model.modelId
model = models.find(model => model._id === modelId)
schema = model?.schema
} else if (context.model.type === "view") {
const modelId = context.model.modelId
model = models.find(model => model._id === modelId)
schema = model?.views?.[context.model.name]?.schema
} else if (context.model.type === "link") {
console.log(context.model)
const modelId = context.model.modelId
model = models.find(model => model._id === modelId)
schema = model?.schema
}
// Avoid crashing whenever no data source has been selected // Avoid crashing whenever no data source has been selected
if (model == null) { if (!schema) {
return [] return []
} }
const newBindable = key => ({ const newBindable = ([key, fieldSchema]) => ({
type: "context", type: "context",
fieldSchema,
instance: context.instance, instance: context.instance,
// how the binding expression persists, and is used in the app at runtime // how the binding expression persists, and is used in the app at runtime
runtimeBinding: `${contextParentPath}data.${key}`, runtimeBinding: `${contextParentPath}data.${key}`,
@ -92,15 +106,11 @@ const contextToBindables = (models, walkResult) => context => {
readableBinding: `${context.instance._instanceName}.${model.name}.${key}`, readableBinding: `${context.instance._instanceName}.${model.name}.${key}`,
}) })
// see ModelViewSelect.svelte for the format of context.model
// ... this allows us to bind to Model schemas, or View schemas
const schema = isModel ? model.schema : model.views[context.model.name].schema
return ( return (
Object.keys(schema) Object.entries(schema)
.map(newBindable) .map(newBindable)
// add _id and _rev fields - not part of schema, but always valid // add _id and _rev fields - not part of schema, but always valid
.concat([newBindable("_id"), newBindable("_rev")]) .concat([newBindable(["_id", "string"]), newBindable(["_rev", "string"])])
) )
} }

View File

@ -15,10 +15,12 @@
? models.find(m => m._id === componentInstance.datasource.modelId) ? models.find(m => m._id === componentInstance.datasource.modelId)
: null : null
$: type = componentInstance.datasource.type
$: if (model) { $: if (model) {
options = componentInstance.datasource.isModel options =
? Object.keys(model.schema) type === "model" || type === "link"
: Object.keys(model.views[componentInstance.datasource.name].schema) ? Object.keys(model.schema)
: Object.keys(model.views[componentInstance.datasource.name].schema)
} }
</script> </script>

View File

@ -1,7 +1,8 @@
<script> <script>
import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui" import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import fetchBindableProperties from "../../builderStore/fetchBindableProperties"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let anchorRight, dropdownRight let anchorRight, dropdownRight
@ -13,21 +14,39 @@
dropdownRight.hide() dropdownRight.hide()
} }
const models = $backendUiStore.models.map(m => ({ $: models = $backendUiStore.models.map(m => ({
label: m.name, label: m.name,
name: `all_${m._id}`, name: `all_${m._id}`,
modelId: m._id, modelId: m._id,
isModel: true, type: "model",
})) }))
const views = $backendUiStore.models.reduce((acc, cur) => { $: views = $backendUiStore.models.reduce((acc, cur) => {
let viewsArr = Object.entries(cur.views).map(([key, value]) => ({ let viewsArr = Object.entries(cur.views).map(([key, value]) => ({
label: key, label: key,
name: key, name: key,
...value, ...value,
type: "view",
})) }))
return [...acc, ...viewsArr] return [...acc, ...viewsArr]
}, []) }, [])
$: bindableProperties = fetchBindableProperties({
componentInstanceId: $store.currentComponentInfo._id,
components: $store.components,
screen: $store.currentPreviewItem,
models: $backendUiStore.models,
})
$: links = bindableProperties
.filter(x => x.fieldSchema.type === "link")
.map(property => ({
label: property.readableBinding,
fieldName: property.fieldSchema.name,
name: `all_${property.fieldSchema.modelId}`,
modelId: property.fieldSchema.modelId,
type: "link",
}))
</script> </script>
<div class="dropdownbutton" bind:this={anchorRight}> <div class="dropdownbutton" bind:this={anchorRight}>
@ -63,6 +82,19 @@
</li> </li>
{/each} {/each}
</ul> </ul>
<hr />
<div class="title">
<Heading extraSmall>Relationships</Heading>
</div>
<ul>
{#each links as link}
<li
class:selected={value === link}
on:click={() => handleSelected(link)}>
{link.label}
</li>
{/each}
</ul>
</div> </div>
</DropdownMenu> </DropdownMenu>

View File

@ -1,5 +1,5 @@
const fs = require("fs") const fs = require("fs")
const sharp = require("sharp") // const sharp = require("sharp")
const fsPromises = fs.promises const fsPromises = fs.promises
const FORMATS = { const FORMATS = {
@ -7,14 +7,14 @@ const FORMATS = {
} }
async function processImage(file) { async function processImage(file) {
const imgMeta = await sharp(file.path) // const imgMeta = await sharp(file.path)
.resize(300) // .resize(300)
.toFile(file.outputPath) // .toFile(file.outputPath)
//
return { // return {
...file, // ...file,
...imgMeta, // ...imgMeta,
} // }
} }
async function process(file) { async function process(file) {

View File

@ -39,7 +39,7 @@
onMount(async () => { onMount(async () => {
if (!isEmpty(datasource)) { if (!isEmpty(datasource)) {
data = await fetchData(datasource) data = await fetchData(datasource, _bb)
if (data && data.length) { if (data && data.length) {
await fetchModel(data[0].modelId) await fetchModel(data[0].modelId)
headers = Object.keys(schema).filter(shouldDisplayField) headers = Object.keys(schema).filter(shouldDisplayField)
@ -99,7 +99,7 @@
{#if schema[header].type === 'attachment'} {#if schema[header].type === 'attachment'}
<AttachmentList files={row[header]} /> <AttachmentList files={row[header]} />
{:else if schema[header].type === 'link'} {:else if schema[header].type === 'link'}
<td>{row[header] ? row[header].length : 0} related row(s)</td> <td>{row[header]} related row(s)</td>
{:else if row[header]} {:else if row[header]}
<td>{row[header]}</td> <td>{row[header]}</td>
{/if} {/if}

View File

@ -10,7 +10,7 @@
onMount(async () => { onMount(async () => {
if (!isEmpty(datasource)) { if (!isEmpty(datasource)) {
const data = await fetchData(datasource) const data = await fetchData(datasource, _bb)
_bb.attachChildren(target, { _bb.attachChildren(target, {
hydrate: false, hydrate: false,
context: data, context: data,

View File

@ -26,6 +26,10 @@
async function fetchData() { async function fetchData() {
const pathParts = window.location.pathname.split("/") const pathParts = window.location.pathname.split("/")
if (!model) {
return
}
let record let record
// if srcdoc, then we assume this is the builder preview // if srcdoc, then we assume this is the builder preview
if (pathParts.length === 0 || pathParts[0] === "srcdoc") { if (pathParts.length === 0 || pathParts[0] === "srcdoc") {

View File

@ -1,10 +1,17 @@
import api from "./api" import api from "./api"
export default async function fetchData(datasource) { export default async function fetchData(datasource, _bb) {
const { isModel, name } = datasource const { type, name } = datasource
if (name) { if (name) {
const records = isModel ? await fetchModelData() : await fetchViewData() let records
if (type === "model") {
records = await fetchModelData()
} else if (type === "view") {
records = await fetchViewData()
} else if (type === "link") {
records = await fetchLinkedRecordsData()
}
// Fetch model schema so we can check for linked records // Fetch model schema so we can check for linked records
if (records && records.length) { if (records && records.length) {
@ -53,4 +60,18 @@ export default async function fetchData(datasource) {
const response = await api.get(QUERY_VIEW_URL) const response = await api.get(QUERY_VIEW_URL)
return await response.json() return await response.json()
} }
async function fetchLinkedRecordsData() {
if (
!_bb.store.state ||
!_bb.store.state.data ||
!_bb.store.state.data._id
) {
return []
}
const QUERY_URL = `/api/${_bb.store.state.data.modelId}/${_bb.store.state.data._id}/enrich`
const response = await api.get(QUERY_URL)
const record = await response.json()
return record[datasource.fieldName]
}
} }