Add relationships as data source
This commit is contained in:
parent
f3d0104f41
commit
d1f367ccff
|
@ -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"
|
|
||||||
|
let model, schema
|
||||||
|
if (typeof context.model === "string" || context.model.type === "model") {
|
||||||
const modelId =
|
const modelId =
|
||||||
typeof context.model === "string" ? context.model : context.model.modelId
|
typeof context.model === "string" ? context.model : context.model.modelId
|
||||||
const model = models.find(model => model._id === 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"])])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
? 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 =
|
||||||
|
type === "model" || type === "link"
|
||||||
? Object.keys(model.schema)
|
? Object.keys(model.schema)
|
||||||
: Object.keys(model.views[componentInstance.datasource.name].schema)
|
: Object.keys(model.views[componentInstance.datasource.name].schema)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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") {
|
||||||
|
|
|
@ -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]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue