diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index d89dce4087..217deef587 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -137,6 +137,7 @@ exports.addPermission = async ( exports.createLinkedTable = async (request, appId) => { // get the ID to link to const table = await exports.createTable(request, appId) + table.displayName = "name" table.schema.link = { type: "link", fieldName: "link", diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index 0a97526c71..1c11369ae5 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -287,7 +287,7 @@ describe("/rows", () => { })).body const enriched = await outputProcessing(appId, table, [secondRow]) expect(enriched[0].link.length).toBe(1) - expect(enriched[0].link[0]._id).toBe(firstRow._id) + expect(enriched[0].link[0]).toBe("Test Contact") }) }) diff --git a/packages/server/src/db/linkedRows/index.js b/packages/server/src/db/linkedRows/index.js index c5279e99b0..6440da98d6 100644 --- a/packages/server/src/db/linkedRows/index.js +++ b/packages/server/src/db/linkedRows/index.js @@ -7,6 +7,8 @@ const { } = require("./linkUtils") const { flatten } = require("lodash") const CouchDB = require("../../db") +const { getMultiIDParams } = require("../../db/utils") +const { FieldTypes } = require("../../constants") /** * This functionality makes sure that when rows with links are created, updated or deleted they are processed @@ -28,10 +30,16 @@ exports.IncludeDocs = IncludeDocs exports.getLinkDocuments = getLinkDocuments exports.createLinkView = createLinkView +function getLinkedTableIDs(table) { + return Object.values(table.schema) + .filter(column => column.type === FieldTypes.LINK) + .map(column => column.tableId) +} + async function getLinksForRows(appId, rows) { - let tableIds = [...new Set(rows.map(el => el.tableId))] + const tableIds = [...new Set(rows.map(el => el.tableId))] // start by getting all the link values for performance reasons - let responses = flatten( + const responses = flatten( await Promise.all( tableIds.map(tableId => getLinkDocuments({ @@ -117,7 +125,7 @@ exports.updateLinks = async function({ * @returns {Promise} The updated row (this may be the same if no links were found). If an array was input * then an array will be output, object input -> object output. */ -exports.attachLinkInfo = async (appId, rows) => { +exports.attachLinkIDs = async (appId, rows) => { // handle a single row as well as multiple let wasArray = true if (!(rows instanceof Array)) { @@ -143,25 +151,37 @@ exports.attachLinkInfo = async (appId, rows) => { return wasArray ? rows : rows[0] } -exports.attachLinkedRows = 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. + * @param {string} appId The app in which the tables/rows/links exist. + * @param {object} table The table from which the rows originated. + * @param {array} rows The rows which are to be enriched with the linked display names/IDs. + * @returns {Promise} The enriched rows after having display names/IDs attached to the linked fields. + */ +exports.attachLinkedDisplayName = async (appId, table, rows) => { + const linkedTableIds = getLinkedTableIDs(table) + if (linkedTableIds.length === 0) { + return rows + } let wasArray = true if (!(rows instanceof Array)) { rows = [rows] wasArray = false } const db = new CouchDB(appId) + const linkedTables = (await db.find(getMultiIDParams(linkedTableIds))).docs const links = (await getLinksForRows(appId, rows)).filter(link => rows.some(row => row._id === link.thisId) ) - const linkedRows = ( - await db.find({ - selector: { - _id: { - $in: links.map(link => link.id), - }, - }, - }) - ).docs + const fields = [ + "_id", + ...linkedTables + .filter(table => table.displayName != null) + .map(table => table.displayName), + ] + const linkedRowIds = links.map(link => link.id) + const linked = (await db.find(getMultiIDParams(linkedRowIds, fields))).docs for (let row of rows) { links .filter(link => link.thisId === row._id) @@ -169,9 +189,15 @@ exports.attachLinkedRows = async (appId, rows) => { if (row[link.fieldName] == null) { row[link.fieldName] = [] } - const linkedRow = linkedRows.find(row => row._id === link.id) - if (linkedRow) { - row[link.fieldName].push(linkedRow) + const linkedTableId = table.schema[link.fieldName].tableId + const linkedRow = linked.find(row => row._id === link.id) + const linkedTable = linkedTables.find( + table => table._id === linkedTableId + ) + if (linkedRow && linkedTable) { + row[link.fieldName].push( + linkedRow[linkedTable.displayName] || linkedRow._id + ) } }) } diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 6ca55b6336..17f5f2f235 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -277,3 +277,20 @@ exports.getQueryParams = (datasourceId = null, otherProps = {}) => { otherProps ) } + +/** + * This can be used with the db.find functionality to get a list of IDs + */ +exports.getMultiIDParams = (ids, fields = null) => { + let config = { + selector: { + _id: { + $in: ids, + }, + }, + } + if (fields) { + config.fields = fields + } + return config +} diff --git a/packages/server/src/utilities/rowProcessor.js b/packages/server/src/utilities/rowProcessor.js index 145551a725..1f1aea7b23 100644 --- a/packages/server/src/utilities/rowProcessor.js +++ b/packages/server/src/utilities/rowProcessor.js @@ -167,7 +167,7 @@ exports.inputProcessing = async (user, table, row) => { */ exports.outputProcessing = async (appId, table, rows) => { // attach any linked row information - const outputRows = await linkRows.attachLinkedRows(appId, rows) + const outputRows = await linkRows.attachLinkedDisplayName(appId, table, rows) // update the attachments URL depending on hosting if (env.CLOUD && env.SELF_HOSTED) { for (let [property, column] of Object.entries(table.schema)) {