Merge pull request #1190 from Budibase/bug/relationship-display-id
Bug/relationship display
This commit is contained in:
commit
45382bc40a
|
@ -11,7 +11,7 @@
|
|||
class:link={!!items.length}
|
||||
on:click={() => selectRelationship(row, columnName)}>
|
||||
{#each items as item}
|
||||
<div class="item">{item}</div>
|
||||
<div class="item">{item?.primaryDisplay ?? ''}</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import { backendUiStore } from "builderStore"
|
||||
import api from "builderStore/api"
|
||||
import { Select, Label, Multiselect } from "@budibase/bbui"
|
||||
|
@ -9,7 +8,9 @@
|
|||
export let linkedRows = []
|
||||
|
||||
let rows = []
|
||||
let linkedIds = (linkedRows || [])?.map(row => row?._id || row)
|
||||
|
||||
$: linkedRows = linkedIds
|
||||
$: label = capitalise(schema.name)
|
||||
$: linkedTableId = schema.tableId
|
||||
$: linkedTable = $backendUiStore.tables.find(
|
||||
|
@ -45,12 +46,12 @@
|
|||
<Select
|
||||
thin
|
||||
secondary
|
||||
on:change={e => (linkedRows = [e.target.value])}
|
||||
on:change={e => (linkedIds = e.target.value ? [e.target.value] : [])}
|
||||
name={label}
|
||||
{label}>
|
||||
<option value="">Choose an option</option>
|
||||
{#each rows as row}
|
||||
<option selected={row._id === linkedRows[0]} value={row._id}>
|
||||
<option selected={row._id === linkedIds[0]} value={row._id}>
|
||||
{getPrettyName(row)}
|
||||
</option>
|
||||
{/each}
|
||||
|
@ -58,7 +59,7 @@
|
|||
{:else}
|
||||
<Multiselect
|
||||
secondary
|
||||
bind:value={linkedRows}
|
||||
bind:value={linkedIds}
|
||||
{label}
|
||||
placeholder="Choose some options">
|
||||
{#each rows as row}
|
||||
|
|
|
@ -120,7 +120,11 @@ export const enrichRows = async (rows, tableId) => {
|
|||
const type = schema[key].type
|
||||
if (type === "link") {
|
||||
// Enrich row a string join of relationship fields
|
||||
row[`${key}_text`] = row[key]?.join(", ") || ""
|
||||
row[`${key}_text`] =
|
||||
row[key]
|
||||
?.map(option => option?.primaryDisplay)
|
||||
.filter(option => !!option)
|
||||
.join(", ") || ""
|
||||
} else if (type === "attachment") {
|
||||
// Enrich row with the first image URL for any attachment fields
|
||||
let url = null
|
||||
|
|
|
@ -15,6 +15,7 @@ const {
|
|||
} = require("../../utilities/rowProcessor")
|
||||
const { FieldTypes } = require("../../constants")
|
||||
const { isEqual } = require("lodash")
|
||||
const { cloneDeep } = require("lodash/fp")
|
||||
|
||||
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}`
|
||||
|
||||
|
@ -351,10 +352,15 @@ async function validate({ appId, tableId, row, table }) {
|
|||
}
|
||||
const errors = {}
|
||||
for (let fieldName of Object.keys(table.schema)) {
|
||||
const res = validateJs.single(
|
||||
row[fieldName],
|
||||
table.schema[fieldName].constraints
|
||||
)
|
||||
const constraints = cloneDeep(table.schema[fieldName].constraints)
|
||||
// special case for options, need to always allow unselected (null)
|
||||
if (
|
||||
table.schema[fieldName].type === FieldTypes.OPTIONS &&
|
||||
constraints.inclusion
|
||||
) {
|
||||
constraints.inclusion.push(null)
|
||||
}
|
||||
const res = validateJs.single(row[fieldName], constraints)
|
||||
if (res) errors[fieldName] = res
|
||||
}
|
||||
return { valid: Object.keys(errors).length === 0, errors }
|
||||
|
|
|
@ -89,16 +89,20 @@ exports.save = async function(ctx) {
|
|||
}
|
||||
|
||||
// update linked rows
|
||||
const linkResp = await linkRows.updateLinks({
|
||||
appId,
|
||||
eventType: oldTable
|
||||
? linkRows.EventType.TABLE_UPDATED
|
||||
: linkRows.EventType.TABLE_SAVE,
|
||||
table: tableToSave,
|
||||
oldTable: oldTable,
|
||||
})
|
||||
if (linkResp != null && linkResp._rev) {
|
||||
tableToSave._rev = linkResp._rev
|
||||
try {
|
||||
const linkResp = await linkRows.updateLinks({
|
||||
appId,
|
||||
eventType: oldTable
|
||||
? linkRows.EventType.TABLE_UPDATED
|
||||
: linkRows.EventType.TABLE_SAVE,
|
||||
table: tableToSave,
|
||||
oldTable: oldTable,
|
||||
})
|
||||
if (linkResp != null && linkResp._rev) {
|
||||
tableToSave._rev = linkResp._rev
|
||||
}
|
||||
} catch (err) {
|
||||
ctx.throw(400, err)
|
||||
}
|
||||
|
||||
// don't perform any updates until relationships have been
|
||||
|
|
|
@ -282,12 +282,13 @@ describe("/rows", () => {
|
|||
const secondRow = (await createRow({
|
||||
name: "Test 2",
|
||||
description: "og desc",
|
||||
link: [firstRow._id],
|
||||
link: [{_id: firstRow._id}],
|
||||
tableId: table._id,
|
||||
})).body
|
||||
const enriched = await outputProcessing(appId, table, [secondRow])
|
||||
expect(enriched[0].link.length).toBe(1)
|
||||
expect(enriched[0].link[0]).toBe("Test Contact")
|
||||
expect(enriched[0].link[0]._id).toBe(firstRow._id)
|
||||
expect(enriched[0].link[0].primaryDisplay).toBe("Test Contact")
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ const { IncludeDocs, getLinkDocuments } = require("./linkUtils")
|
|||
const { generateLinkID } = require("../utils")
|
||||
const Sentry = require("@sentry/node")
|
||||
const { FieldTypes, RelationshipTypes } = require("../../constants")
|
||||
const { isEqual } = require("lodash")
|
||||
|
||||
/**
|
||||
* Creates a new link document structure which can be put to the database. It is important to
|
||||
|
@ -113,6 +114,25 @@ class LinkController {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the passed in table schema contains valid relationship structures.
|
||||
*/
|
||||
validateTable(table) {
|
||||
const usedAlready = []
|
||||
for (let schema of Object.values(table.schema)) {
|
||||
if (schema.type !== FieldTypes.LINK) {
|
||||
continue
|
||||
}
|
||||
const unique = schema.tableId + schema.fieldName
|
||||
if (usedAlready.indexOf(unique) !== -1) {
|
||||
throw new Error(
|
||||
"Cannot re-use the linked column name for a linked table."
|
||||
)
|
||||
}
|
||||
usedAlready.push(unique)
|
||||
}
|
||||
}
|
||||
|
||||
// all operations here will assume that the table
|
||||
// this operation is related to has linked rows
|
||||
/**
|
||||
|
@ -268,6 +288,8 @@ class LinkController {
|
|||
*/
|
||||
async tableSaved() {
|
||||
const table = await this.table()
|
||||
// validate the table first
|
||||
this.validateTable(table)
|
||||
const schema = table.schema
|
||||
for (let fieldName of Object.keys(schema)) {
|
||||
const field = schema[fieldName]
|
||||
|
@ -291,6 +313,11 @@ class LinkController {
|
|||
if (field.autocolumn) {
|
||||
linkConfig.autocolumn = field.autocolumn
|
||||
}
|
||||
// check the linked table to make sure we aren't overwriting an existing column
|
||||
const existingSchema = linkedTable.schema[field.fieldName]
|
||||
if (existingSchema != null && !isEqual(existingSchema, linkConfig)) {
|
||||
throw new Error("Cannot overwrite existing column.")
|
||||
}
|
||||
// create the link field in the other table
|
||||
linkedTable.schema[field.fieldName] = linkConfig
|
||||
const response = await this._db.put(linkedTable)
|
||||
|
|
|
@ -175,11 +175,12 @@ exports.attachLinkedPrimaryDisplay = async (appId, table, rows) => {
|
|||
if (!linkedRow || !linkedTable) {
|
||||
continue
|
||||
}
|
||||
// need to handle an edge case where relationship just wasn't found
|
||||
const value = linkedRow[linkedTable.primaryDisplay] || linkedRow._id
|
||||
if (value) {
|
||||
row[link.fieldName].push(value)
|
||||
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)
|
||||
}
|
||||
}
|
||||
return rows
|
||||
|
|
|
@ -15,6 +15,9 @@ const TYPE_TRANSFORM_MAP = {
|
|||
[null]: [],
|
||||
[undefined]: undefined,
|
||||
parse: link => {
|
||||
if (Array.isArray(link) && typeof link[0] === "object") {
|
||||
return link.map(el => (el && el._id ? el._id : el))
|
||||
}
|
||||
if (typeof link === "string") {
|
||||
return [link]
|
||||
}
|
||||
|
@ -22,8 +25,8 @@ const TYPE_TRANSFORM_MAP = {
|
|||
},
|
||||
},
|
||||
[FieldTypes.OPTIONS]: {
|
||||
"": "",
|
||||
[null]: "",
|
||||
"": null,
|
||||
[null]: null,
|
||||
[undefined]: undefined,
|
||||
},
|
||||
[FieldTypes.STRING]: {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
const setFieldText = value => {
|
||||
if (fieldSchema?.relationshipType === "one-to-many") {
|
||||
if (value?.length && options?.length) {
|
||||
const row = options.find(row => row._id === value[0])
|
||||
const row = options.find(row => row._id === value[0]?._id)
|
||||
return getDisplayName(row)
|
||||
} else {
|
||||
return placeholder || "Choose an option"
|
||||
|
@ -60,27 +60,30 @@
|
|||
}
|
||||
|
||||
const getDisplayName = row => {
|
||||
return row[tableDefinition?.primaryDisplay || "_id"]
|
||||
return row?.[tableDefinition?.primaryDisplay || "_id"] || "-"
|
||||
}
|
||||
|
||||
const getValueLookupMap = value => {
|
||||
let map = {}
|
||||
if (value?.length) {
|
||||
value.forEach(option => {
|
||||
map[option] = true
|
||||
if (option?._id) {
|
||||
map[option._id] = true
|
||||
}
|
||||
})
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
const toggleOption = option => {
|
||||
if (fieldSchema.type === "one-to-many") {
|
||||
fieldApi.setValue([option])
|
||||
const toggleOption = id => {
|
||||
if (fieldSchema.relationshipType === "one-to-many") {
|
||||
fieldApi.setValue(id ? [{ _id: id }] : [])
|
||||
} else {
|
||||
if ($fieldState.value.includes(option)) {
|
||||
fieldApi.setValue($fieldState.value.filter(x => x !== option))
|
||||
if ($fieldState.value.find(option => option?._id === id)) {
|
||||
const filtered = $fieldState.value.filter(option => option?._id !== id)
|
||||
fieldApi.setValue(filtered)
|
||||
} else {
|
||||
fieldApi.setValue([...$fieldState.value, option])
|
||||
fieldApi.setValue([...$fieldState.value, { _id: id }])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<div class="container">
|
||||
{#each items as item}
|
||||
<div class="item">{item}</div>
|
||||
<div class="item">{item?.primaryDisplay ?? ''}</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
@ -5,7 +5,7 @@ import AttachmentCell from "./AttachmentCell/Button.svelte"
|
|||
import ViewDetails from "./ViewDetails/Cell.svelte"
|
||||
import Select from "./Select/Wrapper.svelte"
|
||||
import DatePicker from "./DateTime/Wrapper.svelte"
|
||||
import RelationshipCount from "./Relationship/RelationshipCount.svelte"
|
||||
import RelationshipLabel from "./Relationship/RelationshipLabel.svelte"
|
||||
|
||||
const renderers = new Map([
|
||||
["boolean", booleanRenderer],
|
||||
|
@ -127,7 +127,7 @@ function linkedRowRenderer(options, constraints, editable, SDK) {
|
|||
container.style.placeItems = "center"
|
||||
container.style.height = "100%"
|
||||
|
||||
new RelationshipCount({
|
||||
new RelationshipLabel({
|
||||
target: container,
|
||||
props: {
|
||||
row: params.data,
|
||||
|
|
Loading…
Reference in New Issue