Enrich datasources so that data can be correctly fetched reactively using only the datasource definition

This commit is contained in:
Andrew Kingston 2021-01-21 10:42:14 +00:00
parent ce6d89bc5c
commit 9056b0e49d
9 changed files with 61 additions and 50 deletions

View File

@ -37,6 +37,7 @@
$: queries = $backendUiStore.queries.map(query => ({ $: queries = $backendUiStore.queries.map(query => ({
label: query.name, label: query.name,
name: query.name, name: query.name,
tableId: query._id,
...query, ...query,
schema: query.schema, schema: query.schema,
parameters: query.parameters, parameters: query.parameters,
@ -59,9 +60,12 @@
providerId: property.providerId, providerId: property.providerId,
label: property.readableBinding, label: property.readableBinding,
fieldName: property.fieldSchema.name, fieldName: property.fieldSchema.name,
name: `all_${property.fieldSchema.tableId}`,
tableId: property.fieldSchema.tableId, tableId: property.fieldSchema.tableId,
type: "link", type: "link",
// These properties will be enriched by the client library and provide
// details of the parent row of the relationship field, from context
rowId: `{{ ${property.providerId}._id }}`,
rowTableId: `{{ ${property.providerId}.tableId }}`,
} }
}) })
@ -84,12 +88,12 @@
class="dropdownbutton" class="dropdownbutton"
bind:this={anchorRight} bind:this={anchorRight}
on:click={dropdownRight.show}> on:click={dropdownRight.show}>
<span>{value.label ? value.label : 'Table / View / Query'}</span> <span>{value?.label ? value.label : 'Choose option'}</span>
<Icon name="arrowdown" /> <Icon name="arrowdown" />
</div> </div>
{#if value.type === 'query'} {#if value?.type === 'query'}
<i class="ri-settings-5-line" on:click={drawer.show} /> <i class="ri-settings-5-line" on:click={drawer.show} />
<Drawer title={'Query'}> <Drawer title={'Query'} bind:this={drawer}>
<div slot="buttons"> <div slot="buttons">
<Button <Button
blue blue

View File

@ -1,16 +1,14 @@
import { get } from "svelte/store" import { cloneDeep } from "lodash/fp"
import { fetchTableData } from "./tables" import { fetchTableData } from "./tables"
import { fetchViewData } from "./views" import { fetchViewData } from "./views"
import { fetchRelationshipData } from "./relationships" import { fetchRelationshipData } from "./relationships"
import { executeQuery } from "./queries" import { executeQuery } from "./queries"
import { enrichRows } from "./rows" import { enrichRows } from "./rows"
import { enrichDataBindings } from "../utils/enrichDataBinding"
import { bindingStore } from "../store/binding"
/** /**
* Fetches all rows for a particular Budibase data source. * Fetches all rows for a particular Budibase data source.
*/ */
export const fetchDatasource = async (datasource, dataContext) => { export const fetchDatasource = async datasource => {
if (!datasource || !datasource.type) { if (!datasource || !datasource.type) {
return [] return []
} }
@ -23,28 +21,22 @@ export const fetchDatasource = async (datasource, dataContext) => {
} else if (type === "view") { } else if (type === "view") {
rows = await fetchViewData(datasource) rows = await fetchViewData(datasource)
} else if (type === "query") { } else if (type === "query") {
const bindings = get(bindingStore)
// Set the default query params // Set the default query params
let queryParams = datasource.queryParams || {} let parameters = cloneDeep(datasource.queryParams || {})
for (let param of datasource.parameters) { for (let param of datasource.parameters) {
if (!queryParams[param.name]) { if (!parameters[param.name]) {
queryParams[param.name] = param.default parameters[param.name] = param.default
} }
} }
const parameters = enrichDataBindings(queryParams, {
...bindings,
...dataContext,
})
return await executeQuery({ queryId: datasource._id, parameters }) return await executeQuery({ queryId: datasource._id, parameters })
} else if (type === "link") { } else if (type === "link") {
const row = dataContext[datasource.providerId]
rows = await fetchRelationshipData({ rows = await fetchRelationshipData({
rowId: row?._id, rowId: datasource.rowId,
tableId: row?.tableId, tableId: datasource.rowTableId,
fieldName, fieldName,
}) })
} }
// Enrich rows
// Enrich rows so they can displayed properly
return await enrichRows(rows, tableId) return await enrichRows(rows, tableId)
} }

View File

@ -1,3 +1,4 @@
import { cloneDeep } from "lodash/fp"
import mustache from "mustache" import mustache from "mustache"
// this is a much more liberal version of mustache's escape function // this is a much more liberal version of mustache's escape function
@ -35,12 +36,33 @@ export const enrichDataBinding = (input, context) => {
} }
/** /**
* Enriches each prop in a props object * Recursively enriches all props in a props object and returns the new props.
* Props are deeply cloned so that no mutation is done to the source object.
*/ */
export const enrichDataBindings = (props, context) => { export const enrichDataBindings = (props, context) => {
let enrichedProps = {} let clonedProps = cloneDeep(props)
Object.entries(props).forEach(([key, value]) => { recursiveEnrich(clonedProps, context)
enrichedProps[key] = enrichDataBinding(value, context) return clonedProps
}) }
return enrichedProps
/**
* Recurses through an object and enriches all string props found.
*/
const recursiveEnrich = (props, context) => {
if (typeof props !== "object") {
return
}
let keys = []
if (Array.isArray(props)) {
keys = Array.from(props.keys())
} else if (typeof props === "object") {
keys = Object.keys(props || {})
}
keys.forEach(key => {
if (typeof props[key] === "string") {
props[key] = enrichDataBinding(props[key], context)
} else {
recursiveEnrich(props[key], context)
}
})
} }

View File

@ -1,26 +1,21 @@
<script> <script>
import { getContext, onMount } from "svelte" import { getContext } from "svelte"
import { isEmpty } from "lodash/fp" import { isEmpty } from "lodash/fp"
const { API, styleable, DataProvider } = getContext("sdk") const { API, styleable, DataProvider } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
const dataContext = getContext("data")
export let datasource = [] export let datasource = []
let rows = [] let rows = []
$: datasource && fetchData() $: fetchData(datasource)
async function fetchData() { async function fetchData(datasource) {
rows = await API.fetchDatasource(datasource, $dataContext)
}
onMount(async () => {
if (!isEmpty(datasource)) { if (!isEmpty(datasource)) {
fetchData() rows = await API.fetchDatasource(datasource)
} }
}) }
</script> </script>
<div use:styleable={$component.styles}> <div use:styleable={$component.styles}>

View File

@ -35,7 +35,7 @@
// Fetch, filter and sort data // Fetch, filter and sort data
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
const result = await API.fetchDatasource(datasource, $dataContext) const result = await API.fetchDatasource(datasource)
const reducer = row => (valid, column) => valid && row[column] != null const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true) const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = result const data = result

View File

@ -33,7 +33,7 @@
// Fetch, filter and sort data // Fetch, filter and sort data
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
const result = await API.fetchDatasource(datasource, $dataContext) const result = await API.fetchDatasource(datasource)
const reducer = row => (valid, column) => valid && row[column] != null const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true) const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = result const data = result

View File

@ -41,7 +41,7 @@
// Fetch, filter and sort data // Fetch, filter and sort data
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
const result = await API.fetchDatasource(datasource, $dataContext) const result = await API.fetchDatasource(datasource)
const reducer = row => (valid, column) => valid && row[column] != null const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true) const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = result const data = result

View File

@ -31,7 +31,7 @@
// Fetch, filter and sort data // Fetch, filter and sort data
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
const result = await API.fetchDatasource(datasource, $dataContext) const result = await API.fetchDatasource(datasource)
const data = result const data = result
.filter(row => row[labelColumn] != null && row[valueColumn] != null) .filter(row => row[labelColumn] != null && row[valueColumn] != null)
.slice(0, 20) .slice(0, 20)

View File

@ -3,7 +3,7 @@
import { number } from "./valueSetters" import { number } from "./valueSetters"
import { getRenderer } from "./customRenderer" import { getRenderer } from "./customRenderer"
import { isEmpty } from "lodash/fp" import { isEmpty } from "lodash/fp"
import { getContext, onMount } from "svelte" import { getContext } from "svelte"
import AgGrid from "@budibase/svelte-ag-grid" import AgGrid from "@budibase/svelte-ag-grid"
import { import {
TextButton as DeleteButton, TextButton as DeleteButton,
@ -34,6 +34,7 @@
["--grid-height"]: `${height}px`, ["--grid-height"]: `${height}px`,
}, },
} }
$: fetchData(datasource)
// These can never change at runtime so don't need to be reactive // These can never change at runtime so don't need to be reactive
let canEdit = editable && datasource && datasource.type !== "view" let canEdit = editable && datasource && datasource.type !== "view"
@ -57,8 +58,11 @@
pagination, pagination,
} }
async function fetchData() { async function fetchData(datasource) {
data = await API.fetchDatasource(datasource, $dataContext) if (isEmpty(datasource)) {
return
}
data = await API.fetchDatasource(datasource)
let schema let schema
@ -115,12 +119,6 @@
dataLoaded = true dataLoaded = true
} }
$: datasource && fetchData()
onMount(() => {
if (!isEmpty(datasource)) fetchData()
})
const shouldHideField = name => { const shouldHideField = name => {
if (name.startsWith("_")) return true if (name.startsWith("_")) return true
// always 'row' // always 'row'