Adding handlebars formulas to the system, it is now possible to set a formula at a column level which will always be applied on the way out with a relationship depth of one.
This commit is contained in:
parent
e08df4110a
commit
a14c80bf6c
|
@ -1,7 +1,7 @@
|
|||
import { cloneDeep } from "lodash/fp"
|
||||
import { get } from "svelte/store"
|
||||
import { findComponent, findComponentPath } from "./storeUtils"
|
||||
import { store } from "builderStore"
|
||||
import {currentAssetId, store} from "builderStore"
|
||||
import { tables as tablesStore, queries as queriesStores } from "stores/backend"
|
||||
import { makePropSafe } from "@budibase/string-templates"
|
||||
import { TableNames } from "../constants"
|
||||
|
@ -329,6 +329,21 @@ export function removeBindings(obj) {
|
|||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* When converting from readable to runtime it can sometimes add too many square brackets,
|
||||
* this makes sure that doesn't happen.
|
||||
*/
|
||||
function shouldReplaceBinding(currentValue, from, convertTo) {
|
||||
if (!currentValue.includes(from)) {
|
||||
return false
|
||||
}
|
||||
if (convertTo === "readableBinding") {
|
||||
return true
|
||||
}
|
||||
const noSpaces = currentValue.replace(/\s+/g, "")
|
||||
return !noSpaces.includes(`[${from}]`)
|
||||
}
|
||||
|
||||
/**
|
||||
* utility function for the readableToRuntimeBinding and runtimeToReadableBinding.
|
||||
*/
|
||||
|
@ -348,7 +363,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
|
|||
for (let boundValue of boundValues) {
|
||||
let newBoundValue = boundValue
|
||||
for (let from of convertFromProps) {
|
||||
if (newBoundValue.includes(from)) {
|
||||
if (shouldReplaceBinding(newBoundValue, from, convertTo)) {
|
||||
const binding = bindableProperties.find(el => el[convertFrom] === from)
|
||||
newBoundValue = newBoundValue.replace(from, binding[convertTo])
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { automationStore } from "builderStore"
|
||||
import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
|
||||
import DrawerBindableInput from "../../common/DrawerBindableInput.svelte"
|
||||
import AutomationBindingPanel from "./AutomationBindingPanel.svelte"
|
||||
import AutomationBindingPanel from "../../common/ServerBindingPanel.svelte"
|
||||
|
||||
export let block
|
||||
export let webhookModal
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { tables } from "stores/backend"
|
||||
import { Select } from "@budibase/bbui"
|
||||
import DrawerBindableInput from "../../common/DrawerBindableInput.svelte"
|
||||
import AutomationBindingPanel from "./AutomationBindingPanel.svelte"
|
||||
import AutomationBindingPanel from "../../common/ServerBindingPanel.svelte"
|
||||
|
||||
export let value
|
||||
export let bindings
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import { FIELDS } from "constants/backend"
|
||||
import { tables } from "stores/backend"
|
||||
import { get as svelteGet } from "svelte/store"
|
||||
|
||||
// currently supported level of relationship depth (server side)
|
||||
const MAX_DEPTH = 1
|
||||
const TYPES_TO_SKIP = [
|
||||
FIELDS.FORMULA.type,
|
||||
FIELDS.LONGFORM.type,
|
||||
FIELDS.ATTACHMENT.type,
|
||||
]
|
||||
|
||||
export function getBindings({
|
||||
table,
|
||||
path = null,
|
||||
category = null,
|
||||
depth = 0,
|
||||
}) {
|
||||
let bindings = []
|
||||
if (!table) {
|
||||
return bindings
|
||||
}
|
||||
for (let [column, schema] of Object.entries(table.schema)) {
|
||||
const isRelationship = schema.type === FIELDS.LINK.type
|
||||
// skip relationships after a certain depth and types which
|
||||
// can't bind to
|
||||
if (
|
||||
TYPES_TO_SKIP.includes(schema.type) ||
|
||||
(isRelationship && depth >= MAX_DEPTH)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
category = category == null ? `${table.name} Fields` : category
|
||||
if (isRelationship && depth < MAX_DEPTH) {
|
||||
const relatedTable = svelteGet(tables).list.find(
|
||||
table => table._id === schema.tableId
|
||||
)
|
||||
const relatedBindings = bindings.concat(
|
||||
getBindings({
|
||||
table: relatedTable,
|
||||
path: column,
|
||||
category: `${column} Relationships`,
|
||||
depth: depth + 1,
|
||||
})
|
||||
)
|
||||
// remove the ones that have already been found
|
||||
bindings = bindings.concat(
|
||||
relatedBindings.filter(
|
||||
related => !bindings.find(binding => binding.path === related.path)
|
||||
)
|
||||
)
|
||||
}
|
||||
const field = Object.values(FIELDS).find(
|
||||
field => field.type === schema.type
|
||||
)
|
||||
const label = path == null ? column : `${path}.0.${column}`
|
||||
// only supply a description for relationship paths
|
||||
const description =
|
||||
path == null
|
||||
? undefined
|
||||
: `Update the "0" with the index of relationships or use array helpers`
|
||||
bindings.push({
|
||||
label: label,
|
||||
type: field.name === FIELDS.LINK.name ? "Array" : field.name,
|
||||
category,
|
||||
path: label,
|
||||
description,
|
||||
// don't include path, it messes things up, relationship path
|
||||
// will be replaced by the main array binding
|
||||
readableBinding: column,
|
||||
runtimeBinding: `[${column}]`,
|
||||
})
|
||||
}
|
||||
return bindings
|
||||
}
|
|
@ -8,23 +8,26 @@
|
|||
Toggle,
|
||||
Radio,
|
||||
} from "@budibase/bbui"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import { tables } from "stores/backend"
|
||||
import {cloneDeep} from "lodash/fp"
|
||||
import {tables} from "stores/backend"
|
||||
|
||||
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
|
||||
import {TableNames, UNEDITABLE_USER_FIELDS} from "constants"
|
||||
import {
|
||||
FIELDS,
|
||||
AUTO_COLUMN_SUB_TYPES,
|
||||
RelationshipTypes,
|
||||
} from "constants/backend"
|
||||
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import {getAutoColumnInformation, buildAutoColumn} from "builderStore/utils"
|
||||
import {notifier} from "builderStore/store/notifications"
|
||||
import ValuesList from "components/common/ValuesList.svelte"
|
||||
import DatePicker from "components/common/DatePicker.svelte"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { truncate } from "lodash"
|
||||
import {truncate} from "lodash"
|
||||
import ModalBindableInput from "components/common/ModalBindableInput.svelte"
|
||||
import {getBindings} from "components/backend/DataTable/formula"
|
||||
|
||||
const AUTO_COL = "auto"
|
||||
const AUTO_TYPE = "auto"
|
||||
const FORMULA_TYPE = FIELDS.FORMULA.type
|
||||
const LINK_TYPE = FIELDS.LINK.type
|
||||
let fieldDefinitions = cloneDeep(FIELDS)
|
||||
|
||||
|
@ -65,14 +68,17 @@
|
|||
$: canBeSearched =
|
||||
field.type !== LINK_TYPE &&
|
||||
field.subtype !== AUTO_COLUMN_SUB_TYPES.CREATED_BY &&
|
||||
field.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY
|
||||
$: canBeDisplay = field.type !== LINK_TYPE && field.type !== AUTO_COL
|
||||
field.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY &&
|
||||
field.type !== FORMULA_TYPE
|
||||
$: canBeDisplay = field.type !== LINK_TYPE &&
|
||||
field.type !== AUTO_TYPE &&
|
||||
field.type !== FORMULA_TYPE
|
||||
$: canBeRequired =
|
||||
field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_COL
|
||||
field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_TYPE
|
||||
$: relationshipOptions = getRelationshipOptions(field)
|
||||
|
||||
async function saveColumn() {
|
||||
if (field.type === AUTO_COL) {
|
||||
if (field.type === AUTO_TYPE) {
|
||||
field = buildAutoColumn($tables.draft.name, field.name, field.subtype)
|
||||
}
|
||||
tables.saveField({
|
||||
|
@ -115,7 +121,7 @@
|
|||
|
||||
function onChangeRequired(e) {
|
||||
const req = e.target.checked
|
||||
field.constraints.presence = req ? { allowEmpty: false } : false
|
||||
field.constraints.presence = req ? {allowEmpty: false} : false
|
||||
required = req
|
||||
}
|
||||
|
||||
|
@ -123,7 +129,7 @@
|
|||
const isPrimary = e.target.checked
|
||||
// primary display is always required
|
||||
if (isPrimary) {
|
||||
field.constraints.presence = { allowEmpty: false }
|
||||
field.constraints.presence = {allowEmpty: false}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,8 +163,8 @@
|
|||
if (!linkTable) {
|
||||
return null
|
||||
}
|
||||
const thisName = truncate(table.name, { length: 14 }),
|
||||
linkName = truncate(linkTable.name, { length: 14 })
|
||||
const thisName = truncate(table.name, {length: 14}),
|
||||
linkName = truncate(linkTable.name, {length: 14})
|
||||
return [
|
||||
{
|
||||
name: `Many ${thisName} rows → many ${linkName} rows`,
|
||||
|
@ -192,7 +198,7 @@
|
|||
{#each Object.values(fieldDefinitions) as field}
|
||||
<option value={field.type}>{field.name}</option>
|
||||
{/each}
|
||||
<option value={AUTO_COL}>Auto Column</option>
|
||||
<option value={AUTO_TYPE}>Auto Column</option>
|
||||
</Select>
|
||||
|
||||
{#if canBeRequired}
|
||||
|
@ -285,7 +291,15 @@
|
|||
label={`Column Name in Other Table`}
|
||||
thin
|
||||
bind:value={field.fieldName} />
|
||||
{:else if field.type === AUTO_COL}
|
||||
{:else if field.type === FORMULA_TYPE}
|
||||
<ModalBindableInput
|
||||
title="Handlebars Formula"
|
||||
label="Formula"
|
||||
value={field.formula}
|
||||
on:change={e => (field.formula = e.detail)}
|
||||
bindings={getBindings({ table })}
|
||||
serverSide=true />
|
||||
{:else if field.type === AUTO_TYPE}
|
||||
<Select label="Auto Column Type" thin secondary bind:value={field.subtype}>
|
||||
<option value="">Choose a subtype</option>
|
||||
{#each Object.entries(getAutoColumnInformation()) as [subtype, info]}
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
import * as api from "../api"
|
||||
import { ModalContent } from "@budibase/bbui"
|
||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||
import { FIELDS } from "constants/backend"
|
||||
|
||||
const FORMULA_TYPE = FIELDS.FORMULA.type
|
||||
|
||||
export let row = {}
|
||||
|
||||
|
@ -44,7 +47,7 @@
|
|||
onConfirm={saveRow}>
|
||||
<ErrorsBox {errors} />
|
||||
{#each tableSchema as [key, meta]}
|
||||
{#if !meta.autocolumn}
|
||||
{#if !meta.autocolumn && meta.type !== FORMULA_TYPE}
|
||||
<div>
|
||||
<RowFieldControl {meta} bind:value={row[key]} />
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<script>
|
||||
import { Icon, Input, Drawer, Label, Body, Button } from "@budibase/bbui"
|
||||
import { Icon, Input, Drawer, Body, Button } from "@budibase/bbui"
|
||||
import {
|
||||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "builderStore/dataBinding"
|
||||
import BindingPanel from "components/design/PropertiesPanel/BindingPanel.svelte"
|
||||
import ServerBindingPanel from "components/common/ServerBindingPanel.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let panel = BindingPanel
|
||||
export let serverSide = false
|
||||
export let panel = serverSide ? ServerBindingPanel : BindingPanel
|
||||
export let value = ""
|
||||
export let bindings = []
|
||||
export let thin = true
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<script>
|
||||
import { Icon, Input, Modal, Body, ModalContent } from "@budibase/bbui"
|
||||
import {
|
||||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "builderStore/dataBinding"
|
||||
import ServerBindingPanel from "components/common/ServerBindingPanel.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let panel = ServerBindingPanel
|
||||
export let value = ""
|
||||
export let bindings = []
|
||||
export let thin = true
|
||||
export let title = "Bindings"
|
||||
export let placeholder
|
||||
export let label
|
||||
|
||||
let bindingModal
|
||||
let validity = true
|
||||
|
||||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||
$: tempValue = readableValue
|
||||
$: invalid = !validity
|
||||
|
||||
const saveBinding = () => {
|
||||
onChange(tempValue)
|
||||
bindingModal.hide()
|
||||
}
|
||||
|
||||
const onChange = input => {
|
||||
dispatch("change", readableToRuntimeBinding(bindings, input))
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="control">
|
||||
<Input
|
||||
{label}
|
||||
{thin}
|
||||
value={readableValue}
|
||||
on:change={event => onChange(event.target.value)}
|
||||
{placeholder} />
|
||||
<div class="icon" on:click={bindingModal.show}>
|
||||
<Icon name="lightning" />
|
||||
</div>
|
||||
</div>
|
||||
<Modal bind:this={bindingModal} width="50%">
|
||||
<ModalContent
|
||||
{title}
|
||||
onConfirm={saveBinding}
|
||||
bind:disabled={invalid}>
|
||||
<Body extraSmall grey>
|
||||
Add the objects on the left to enrich your text.
|
||||
</Body>
|
||||
<svelte:component
|
||||
this={panel}
|
||||
serverSide
|
||||
value={readableValue}
|
||||
bind:validity={validity}
|
||||
on:update={event => (tempValue = event.detail)}
|
||||
bindableProperties={bindings} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.control {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon {
|
||||
right: 2px;
|
||||
bottom: 2px;
|
||||
position: absolute;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
padding-left: 7px;
|
||||
border-left: 1px solid var(--grey-4);
|
||||
background-color: var(--grey-2);
|
||||
border-top-right-radius: var(--border-radius-m);
|
||||
border-bottom-right-radius: var(--border-radius-m);
|
||||
color: var(--grey-7);
|
||||
font-size: 14px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
color: var(--ink);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
|
@ -4,18 +4,20 @@
|
|||
import { createEventDispatcher } from "svelte"
|
||||
import { isValid } from "@budibase/string-templates"
|
||||
import { handlebarsCompletions } from "constants/completions"
|
||||
import { readableToRuntimeBinding } from "../../builderStore/dataBinding"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
export let bindingDrawer
|
||||
export let bindingContainer
|
||||
export let bindableProperties = []
|
||||
export let validity = true
|
||||
export let value = ""
|
||||
|
||||
let hasReadable = bindableProperties[0].readableBinding != null
|
||||
let originalValue = value
|
||||
let helpers = handlebarsCompletions()
|
||||
let getCaretPosition
|
||||
let search = ""
|
||||
let validity = true
|
||||
|
||||
$: categories = Object.entries(groupBy("category", bindableProperties))
|
||||
$: value && checkValid()
|
||||
|
@ -23,8 +25,12 @@
|
|||
$: searchRgx = new RegExp(search, "ig")
|
||||
|
||||
function checkValid() {
|
||||
if (hasReadable) {
|
||||
validity = isValid(readableToRuntimeBinding(bindableProperties, value))
|
||||
} else {
|
||||
validity = isValid(value)
|
||||
}
|
||||
}
|
||||
|
||||
function addToText(binding) {
|
||||
const position = getCaretPosition()
|
||||
|
@ -40,7 +46,9 @@
|
|||
}
|
||||
export function cancel() {
|
||||
dispatch("update", originalValue)
|
||||
bindingDrawer.close()
|
||||
if (bindingContainer && bindingContainer.close) {
|
||||
bindingContainer.close()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -53,14 +61,16 @@
|
|||
{#each categories as [categoryName, bindings]}
|
||||
<Heading extraSmall>{categoryName}</Heading>
|
||||
<Spacer extraSmall />
|
||||
{#each bindableProperties.filter(binding =>
|
||||
{#each bindings.filter(binding =>
|
||||
binding.label.match(searchRgx)
|
||||
) as binding}
|
||||
<div class="binding" on:click={() => addToText(binding)}>
|
||||
<span class="binding__label">{binding.label}</span>
|
||||
<span class="binding__type">{binding.type}</span>
|
||||
<br />
|
||||
{#if binding.description}
|
||||
<div class="binding__description">{binding.description || ''}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
|
@ -129,8 +139,12 @@
|
|||
|
||||
.binding {
|
||||
font-size: 12px;
|
||||
padding: var(--spacing-s);
|
||||
border-radius: var(--border-radius-m);
|
||||
border: var(--border-light);
|
||||
border-width: 1px 0 0 0;
|
||||
padding: var(--spacing-m) 0;
|
||||
margin: auto 0;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.binding:hover {
|
||||
background-color: var(--grey-2);
|
|
@ -80,6 +80,15 @@ export const FIELDS = {
|
|||
presence: false,
|
||||
},
|
||||
},
|
||||
FORMULA: {
|
||||
name: "Formula",
|
||||
icon: "ri-braces-line",
|
||||
type: "formula",
|
||||
constraints: {
|
||||
type: "string",
|
||||
presence: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const AUTO_COLUMN_SUB_TYPES = {
|
||||
|
|
|
@ -88,6 +88,7 @@ exports.updateMetadata = async function(ctx) {
|
|||
})
|
||||
const metadata = {
|
||||
...globalUser,
|
||||
tableId: InternalTables.USER_METADATA,
|
||||
_id: user._id || generateUserMetadataID(globalUser._id),
|
||||
_rev: user._rev,
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ exports.FieldTypes = {
|
|||
DATETIME: "datetime",
|
||||
ATTACHMENT: "attachment",
|
||||
LINK: "link",
|
||||
FORMULA: "formula",
|
||||
AUTO: "auto",
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ const {
|
|||
} = require("./linkUtils")
|
||||
const { flatten } = require("lodash")
|
||||
const CouchDB = require("../../db")
|
||||
const { FieldTypes } = require("../../constants")
|
||||
const { getMultiIDParams } = require("../../db/utils")
|
||||
|
||||
/**
|
||||
|
@ -141,14 +142,14 @@ exports.attachLinkIDs = async (appId, rows) => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Given information about the table we can extract the display name from the linked rows, this
|
||||
* is what we do for showing the display name of each linked row when in a table format.
|
||||
* Given a table and a list of rows this will retrieve all of the attached docs and enrich them into the row.
|
||||
* This is required for formula fields, this may only be utilised internally (for now).
|
||||
* @param {string} appId The app in which the tables/rows/links exist.
|
||||
* @param {object} table The table from which the rows originated.
|
||||
* @param {array<object>} rows The rows which are to be enriched with the linked display names/IDs.
|
||||
* @returns {Promise<Array>} The enriched rows after having display names/IDs attached to the linked fields.
|
||||
* @param {array<object>} rows The rows which are to be enriched.
|
||||
* @return {Promise<*>} returns the rows with all of the enriched relationships on it.
|
||||
*/
|
||||
exports.attachLinkedPrimaryDisplay = async (appId, table, rows) => {
|
||||
exports.attachFullLinkedDocs = async (appId, table, rows) => {
|
||||
const linkedTableIds = getLinkedTableIDs(table)
|
||||
if (linkedTableIds.length === 0) {
|
||||
return rows
|
||||
|
@ -161,7 +162,6 @@ exports.attachLinkedPrimaryDisplay = async (appId, table, rows) => {
|
|||
const linked = (await db.allDocs(getMultiIDParams(linkedRowIds))).rows.map(
|
||||
row => row.doc
|
||||
)
|
||||
// will populate this as we find them
|
||||
const linkedTables = []
|
||||
for (let row of rows) {
|
||||
for (let link of links.filter(link => link.thisId === row._id)) {
|
||||
|
@ -175,13 +175,44 @@ exports.attachLinkedPrimaryDisplay = async (appId, table, rows) => {
|
|||
if (!linkedRow || !linkedTable) {
|
||||
continue
|
||||
}
|
||||
const obj = { _id: linkedRow._id }
|
||||
// if we know the display column, add it
|
||||
if (linkedRow[linkedTable.primaryDisplay] != null) {
|
||||
obj.primaryDisplay = linkedRow[linkedTable.primaryDisplay]
|
||||
}
|
||||
row[link.fieldName].push(obj)
|
||||
row[link.fieldName].push(linkedRow)
|
||||
}
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will take the given enriched rows and squash the links to only contain the primary display field.
|
||||
* @param {string} appId The app in which the tables/rows/links exist.
|
||||
* @param {object} table The table from which the rows originated.
|
||||
* @param {array<object>} enriched The pre-enriched rows (full docs) which are to be squashed.
|
||||
* @returns {Promise<Array>} The rows after having their links squashed to only contain the ID and primary display.
|
||||
*/
|
||||
exports.squashLinksToPrimaryDisplay = async (appId, table, enriched) => {
|
||||
const db = new CouchDB(appId)
|
||||
// will populate this as we find them
|
||||
const linkedTables = []
|
||||
for (let [column, schema] of Object.entries(table.schema)) {
|
||||
if (schema.type !== FieldTypes.LINK) {
|
||||
continue
|
||||
}
|
||||
for (let row of enriched) {
|
||||
if (!row[column] || !row[column].length) {
|
||||
continue
|
||||
}
|
||||
const newLinks = []
|
||||
for (let link of row[column]) {
|
||||
const linkTblId = link.tableId || getRelatedTableForField(table, column)
|
||||
// this only fetches the table if its not already in array
|
||||
const linkedTable = await getLinkedTable(db, linkTblId, linkedTables)
|
||||
const obj = { _id: link._id }
|
||||
if (link[linkedTable.primaryDisplay]) {
|
||||
obj.primaryDisplay = link[linkedTable.primaryDisplay]
|
||||
}
|
||||
newLinks.push(obj)
|
||||
}
|
||||
row[column] = newLinks
|
||||
}
|
||||
}
|
||||
return enriched
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ const { OBJ_STORE_DIRECTORY } = require("../constants")
|
|||
const linkRows = require("../db/linkedRows")
|
||||
const { cloneDeep } = require("lodash/fp")
|
||||
const { FieldTypes, AutoFieldSubTypes } = require("../constants")
|
||||
const { processStringSync } = require("@budibase/string-templates")
|
||||
|
||||
const BASE_AUTO_ID = 1
|
||||
|
||||
|
@ -34,6 +35,11 @@ const TYPE_TRANSFORM_MAP = {
|
|||
[null]: "",
|
||||
[undefined]: undefined,
|
||||
},
|
||||
[FieldTypes.FORMULA]: {
|
||||
"": "",
|
||||
[null]: "",
|
||||
[undefined]: undefined,
|
||||
},
|
||||
[FieldTypes.LONGFORM]: {
|
||||
"": "",
|
||||
[null]: "",
|
||||
|
@ -118,6 +124,41 @@ function processAutoColumn(user, table, row) {
|
|||
return { table, row }
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a set of rows and the table they came from this function will sort by auto ID or a custom
|
||||
* method if provided (not implemented yet).
|
||||
*/
|
||||
function sortRows(table, rows) {
|
||||
// sort based on auto ID (if found)
|
||||
let autoIDColumn = Object.entries(table.schema).find(
|
||||
schema => schema[1].subtype === AutoFieldSubTypes.AUTO_ID
|
||||
)
|
||||
// get the column name, this is the first element in the array (Object.entries)
|
||||
autoIDColumn = autoIDColumn && autoIDColumn.length ? autoIDColumn[0] : null
|
||||
if (autoIDColumn) {
|
||||
// sort in ascending order
|
||||
rows.sort((a, b) => a[autoIDColumn] - b[autoIDColumn])
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks through the rows provided and finds formulas - which it then processes.
|
||||
*/
|
||||
function processFormulas(table, rows) {
|
||||
for (let [column, schema] of Object.entries(table.schema)) {
|
||||
if (schema.type !== FieldTypes.FORMULA) {
|
||||
continue
|
||||
}
|
||||
// iterate through rows and process formula
|
||||
rows = rows.map(row => ({
|
||||
...row,
|
||||
[column]: processStringSync(schema.formula, row),
|
||||
}))
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
/**
|
||||
* This will coerce a value to the correct types based on the type transform map
|
||||
* @param {object} row The value to coerce
|
||||
|
@ -173,17 +214,19 @@ exports.outputProcessing = async (appId, table, rows) => {
|
|||
rows = [rows]
|
||||
wasArray = false
|
||||
}
|
||||
// sort by auto ID
|
||||
rows = sortRows(table, rows)
|
||||
// attach any linked row information
|
||||
const outputRows = await linkRows.attachLinkedPrimaryDisplay(
|
||||
appId,
|
||||
table,
|
||||
rows
|
||||
)
|
||||
let enriched = await linkRows.attachFullLinkedDocs(appId, table, rows)
|
||||
|
||||
// process formulas
|
||||
enriched = processFormulas(table, enriched)
|
||||
|
||||
// update the attachments URL depending on hosting
|
||||
if (env.isProd() && env.SELF_HOSTED) {
|
||||
for (let [property, column] of Object.entries(table.schema)) {
|
||||
if (column.type === FieldTypes.ATTACHMENT) {
|
||||
for (let row of outputRows) {
|
||||
for (let row of enriched) {
|
||||
if (row[property] == null || row[property].length === 0) {
|
||||
continue
|
||||
}
|
||||
|
@ -195,5 +238,6 @@ exports.outputProcessing = async (appId, table, rows) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
return wasArray ? outputRows : outputRows[0]
|
||||
enriched = await linkRows.squashLinksToPrimaryDisplay(appId, table, enriched)
|
||||
return wasArray ? enriched : enriched[0]
|
||||
}
|
||||
|
|
|
@ -122,12 +122,15 @@ exports.saveGlobalUser = async (ctx, appId, body) => {
|
|||
if (json.status !== 200 && response.status !== 200) {
|
||||
ctx.throw(400, "Unable to save global user.")
|
||||
}
|
||||
delete body.email
|
||||
delete body.password
|
||||
delete body.roleId
|
||||
delete body.status
|
||||
delete body.roles
|
||||
delete body.builder
|
||||
// TODO: for now these have been left in as they are
|
||||
// TODO: pretty important to keeping relationships working
|
||||
// TODO: however if user metadata is changed this should be removed
|
||||
// delete body.email
|
||||
// delete body.roleId
|
||||
// delete body.status
|
||||
return {
|
||||
...body,
|
||||
_id: json._id,
|
||||
|
|
Loading…
Reference in New Issue