Merge branch 'master' of github.com:Budibase/budibase into form-builder

This commit is contained in:
Andrew Kingston 2021-02-02 17:26:44 +00:00
commit 3206b8dc14
9 changed files with 155 additions and 34 deletions

View File

@ -9,7 +9,7 @@ const {
ViewNames, ViewNames,
} = require("../../db/utils") } = require("../../db/utils")
const usersController = require("./user") const usersController = require("./user")
const { coerceRowValues } = require("../../utilities") const { coerceRowValues, enrichRows } = require("../../utilities")
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}` const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}`
@ -190,7 +190,15 @@ exports.fetchView = async function(ctx) {
if (!calculation) { if (!calculation) {
response.rows = response.rows.map(row => row.doc) response.rows = response.rows.map(row => row.doc)
ctx.body = await linkRows.attachLinkInfo(appId, response.rows) let table
try {
table = await db.get(ctx.params.tableId)
} catch (err) {
table = {
schema: {},
}
}
ctx.body = await enrichRows(appId, table, response.rows)
} }
if (calculation === CALCULATION_TYPES.STATS) { if (calculation === CALCULATION_TYPES.STATS) {
@ -217,14 +225,15 @@ exports.fetchView = async function(ctx) {
exports.fetchTableRows = async function(ctx) { exports.fetchTableRows = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.user.appId
const db = new CouchDB(appId)
// special case for users, fetch through the user controller // special case for users, fetch through the user controller
let rows let rows,
table = await db.get(ctx.params.tableId)
if (ctx.params.tableId === ViewNames.USERS) { if (ctx.params.tableId === ViewNames.USERS) {
await usersController.fetch(ctx) await usersController.fetch(ctx)
rows = ctx.body rows = ctx.body
} else { } else {
const db = new CouchDB(appId)
const response = await db.allDocs( const response = await db.allDocs(
getRowParams(ctx.params.tableId, null, { getRowParams(ctx.params.tableId, null, {
include_docs: true, include_docs: true,
@ -232,15 +241,16 @@ exports.fetchTableRows = async function(ctx) {
) )
rows = response.rows.map(row => row.doc) rows = response.rows.map(row => row.doc)
} }
ctx.body = await linkRows.attachLinkInfo(appId, rows) ctx.body = await enrichRows(appId, table, rows)
} }
exports.find = async function(ctx) { exports.find = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.user.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
try { try {
const table = await db.get(ctx.params.tableId)
const row = await findRow(db, appId, ctx.params.tableId, ctx.params.rowId) const row = await findRow(db, appId, ctx.params.tableId, ctx.params.rowId)
ctx.body = await linkRows.attachLinkInfo(appId, row) ctx.body = await enrichRows(appId, table, row)
} catch (err) { } catch (err) {
ctx.throw(400, err) ctx.throw(400, err)
} }
@ -325,8 +335,9 @@ exports.fetchEnrichedRow = async function(ctx) {
keys: linkVals.map(linkVal => linkVal.id), keys: linkVals.map(linkVal => linkVal.id),
}) })
// need to include the IDs in these rows for any links they may have // need to include the IDs in these rows for any links they may have
let linkedRows = await linkRows.attachLinkInfo( let linkedRows = await enrichRows(
appId, appId,
table,
response.rows.map(row => row.doc) response.rows.map(row => row.doc)
) )
// insert the link rows in the correct place throughout the main row // insert the link rows in the correct place throughout the main row

View File

@ -17,11 +17,12 @@ const CouchDB = require("../../../db")
const setBuilderToken = require("../../../utilities/builder/setBuilderToken") const setBuilderToken = require("../../../utilities/builder/setBuilderToken")
const fileProcessor = require("../../../utilities/fileProcessor") const fileProcessor = require("../../../utilities/fileProcessor")
const env = require("../../../environment") const env = require("../../../environment")
const { OBJ_STORE_DIRECTORY } = require("../../../constants")
function objectStoreUrl() { function objectStoreUrl() {
if (env.SELF_HOSTED) { if (env.SELF_HOSTED) {
// can use a relative url for this as all goes through the proxy (this is hosted in minio) // can use a relative url for this as all goes through the proxy (this is hosted in minio)
return `/app-assets/assets` return OBJ_STORE_DIRECTORY
} else { } else {
return "https://cdn.app.budi.live/assets" return "https://cdn.app.budi.live/assets"
} }

View File

@ -108,7 +108,7 @@ exports.save = async function(ctx) {
} }
// update linked rows // update linked rows
await linkRows.updateLinks({ const linkResp = await linkRows.updateLinks({
appId, appId,
eventType: oldTable eventType: oldTable
? linkRows.EventType.TABLE_UPDATED ? linkRows.EventType.TABLE_UPDATED
@ -116,6 +116,9 @@ exports.save = async function(ctx) {
table: tableToSave, table: tableToSave,
oldTable: oldTable, oldTable: oldTable,
}) })
if (linkResp != null && linkResp._rev) {
tableToSave._rev = linkResp._rev
}
// don't perform any updates until relationships have been // don't perform any updates until relationships have been
// checked by the updateLinks function // checked by the updateLinks function

View File

@ -37,29 +37,31 @@ exports.defaultHeaders = appId => {
return headers return headers
} }
exports.createTable = async (request, appId, table) => { exports.BASE_TABLE = {
if (table != null && table._id) { name: "TestTable",
delete table._id type: "table",
} key: "name",
table = table || { schema: {
name: "TestTable", name: {
type: "table", type: "string",
key: "name", constraints: {
schema: {
name: {
type: "string", type: "string",
constraints: {
type: "string",
},
},
description: {
type: "string",
constraints: {
type: "string",
},
}, },
}, },
description: {
type: "string",
constraints: {
type: "string",
},
},
},
}
exports.createTable = async (request, appId, table, removeId = true) => {
if (removeId && table != null && table._id) {
delete table._id
} }
table = table || exports.BASE_TABLE
const res = await request const res = await request
.post(`/api/tables`) .post(`/api/tables`)
@ -68,6 +70,25 @@ exports.createTable = async (request, appId, table) => {
return res.body return res.body
} }
exports.createLinkedTable = async (request, appId) => {
// get the ID to link to
const table = await exports.createTable(request, appId)
table.schema.link = {
type: "link",
fieldName: "link",
tableId: table._id,
}
return exports.createTable(request, appId, table, false)
}
exports.createAttachmentTable = async (request, appId) => {
const table = await exports.createTable(request, appId)
table.schema.attachment = {
type: "attachment",
}
return exports.createTable(request, appId, table, false)
}
exports.getAllFromTable = async (request, appId, tableId) => { exports.getAllFromTable = async (request, appId, tableId) => {
const res = await request const res = await request
.get(`/api/${tableId}/rows`) .get(`/api/${tableId}/rows`)

View File

@ -3,7 +3,11 @@ const {
createTable, createTable,
supertest, supertest,
defaultHeaders, defaultHeaders,
createLinkedTable,
createAttachmentTable,
} = require("./couchTestUtils"); } = require("./couchTestUtils");
const { enrichRows } = require("../../../utilities")
const env = require("../../../environment")
describe("/rows", () => { describe("/rows", () => {
let request let request
@ -270,4 +274,44 @@ describe("/rows", () => {
}) })
}) })
describe("enrich row unit test", () => {
it("should allow enriching some linked rows", async () => {
const table = await createLinkedTable(request, appId)
const firstRow = (await createRow({
name: "Test Contact",
description: "original description",
tableId: table._id
})).body
const secondRow = (await createRow({
name: "Test 2",
description: "og desc",
link: [firstRow._id],
tableId: table._id,
})).body
const enriched = await enrichRows(appId, table, [secondRow])
expect(enriched[0].link.length).toBe(1)
expect(enriched[0].link[0]).toBe(firstRow._id)
})
})
it("should allow enriching attachment rows", async () => {
const table = await createAttachmentTable(request, appId)
const row = (await createRow({
name: "test",
description: "test",
attachment: [{
url: "/test/thing",
}],
tableId: table._id,
})).body
// the environment needs configured for this
env.CLOUD = 1
env.SELF_HOSTED = 1
const enriched = await enrichRows(appId, table, [row])
expect(enriched[0].attachment[0].url).toBe(`/app-assets/assets/${appId}/test/thing`)
// remove env config
env.CLOUD = undefined
env.SELF_HOSTED = undefined
})
}) })

View File

@ -43,3 +43,4 @@ exports.AuthTypes = AuthTypes
exports.USERS_TABLE_SCHEMA = USERS_TABLE_SCHEMA exports.USERS_TABLE_SCHEMA = USERS_TABLE_SCHEMA
exports.BUILDER_CONFIG_DB = "builder-config-db" exports.BUILDER_CONFIG_DB = "builder-config-db"
exports.HOSTING_DOC = "hosting-doc" exports.HOSTING_DOC = "hosting-doc"
exports.OBJ_STORE_DIRECTORY = "/app-assets/assets"

View File

@ -252,7 +252,11 @@ class LinkController {
tableId: table._id, tableId: table._id,
fieldName: fieldName, fieldName: fieldName,
} }
await this._db.put(linkedTable) const response = await this._db.put(linkedTable)
// special case for when linking back to self, make sure rev updated
if (linkedTable._id === table._id) {
table._rev = response.rev
}
} }
} }
return table return table

View File

@ -31,11 +31,14 @@ exports.createLinkView = async appId => {
thisId: doc1.rowId, thisId: doc1.rowId,
fieldName: doc1.fieldName, fieldName: doc1.fieldName,
}) })
emit([doc2.tableId, doc2.rowId], { // if linking to same table can't emit twice
id: doc1.rowId, if (doc1.tableId !== doc2.tableId) {
thisId: doc2.rowId, emit([doc2.tableId, doc2.rowId], {
fieldName: doc2.fieldName, id: doc1.rowId,
}) thisId: doc2.rowId,
fieldName: doc2.fieldName,
})
}
} }
}.toString(), }.toString(),
} }

View File

@ -3,6 +3,8 @@ const { DocumentTypes, SEPARATOR } = require("../db/utils")
const fs = require("fs") const fs = require("fs")
const { cloneDeep } = require("lodash/fp") const { cloneDeep } = require("lodash/fp")
const CouchDB = require("../db") const CouchDB = require("../db")
const { OBJ_STORE_DIRECTORY } = require("../constants")
const linkRows = require("../db/linkedRows")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR const APP_PREFIX = DocumentTypes.APP + SEPARATOR
@ -211,3 +213,34 @@ exports.getAllApps = async () => {
.map(({ value }) => value) .map(({ value }) => value)
} }
} }
/**
* This function "enriches" the input rows with anything they are supposed to contain, for example
* link records or attachment links.
* @param {string} appId the ID of the application for which rows are being enriched.
* @param {object} table the table from which these rows came from originally, this is used to determine
* the schema of the rows and then enrich.
* @param {object[]} rows the rows which are to be enriched.
* @returns {object[]} the enriched rows will be returned.
*/
exports.enrichRows = async (appId, table, rows) => {
// attach any linked row information
const enriched = await linkRows.attachLinkInfo(appId, rows)
// update the attachments URL depending on hosting
if (env.CLOUD && env.SELF_HOSTED) {
for (let [property, column] of Object.entries(table.schema)) {
if (column.type === "attachment") {
for (let row of enriched) {
if (row[property] == null || row[property].length === 0) {
continue
}
row[property].forEach(attachment => {
attachment.url = `${OBJ_STORE_DIRECTORY}/${appId}/${attachment.url}`
attachment.url = attachment.url.replace("//", "/")
})
}
}
}
}
return enriched
}