WIP - start of auto columns like autonumber, createdBy, createdAt, updatedBy etc.

This commit is contained in:
mike12345567 2021-02-10 16:10:39 +00:00
parent 352b3d43d9
commit feb7e2756f
6 changed files with 209 additions and 131 deletions

View File

@ -6,8 +6,6 @@
Input,
Label,
ModalContent,
Button,
Spacer,
Toggle,
} from "@budibase/bbui"
import TableDataImport from "../TableDataImport.svelte"
@ -28,6 +26,13 @@
let dataImport
let error = ""
let createAutoscreens = true
let autoColumns = {
createdBy: false,
createdAt: false,
updatedBy: false,
updatedAt: false,
autoNumber: false,
}
function checkValid(evt) {
const tableName = evt.target.value
@ -42,6 +47,7 @@
let newTable = {
name,
schema: dataImport.schema || {},
autoColumns,
dataImport,
}
@ -93,6 +99,30 @@
on:input={checkValid}
bind:value={name}
{error} />
<div class="autocolumns">
<label>Auto columns</label>
<div class="toggles">
<div class="toggle-1">
<Toggle
text="Created by"
bind:checked={autoColumns.createdBy} />
<Toggle
text="Created at"
bind:checked={autoColumns.createdAt} />
<Toggle
text="Autonumber"
bind:checked={autoColumns.autoNumber} />
</div>
<div class="toggle-2">
<Toggle
text="Updated by"
bind:checked={autoColumns.updatedBy} />
<Toggle
text="Updated at"
bind:checked={autoColumns.updatedAt} />
</div>
</div>
</div>
<Toggle
text="Generate screens in the design section"
bind:checked={createAutoscreens} />
@ -101,3 +131,25 @@
<TableDataImport bind:dataImport />
</div>
</ModalContent>
<style>
.autocolumns {
padding-bottom: 10px;
border-bottom: 3px solid var(--grey-1);
}
.toggles {
display: flex;
width: 100%;
margin-top: 6px;
margin-bottom: 10px;
}
.toggle-1 :global(> *) {
margin-top: 10px;
}
.toggle-2 :global(> *) {
margin-top: 10px;
margin-left: 10px;
}
</style>

View File

@ -9,7 +9,10 @@ const {
ViewNames,
} = require("../../db/utils")
const usersController = require("./user")
const { coerceRowValues, enrichRows } = require("../../utilities")
const {
inputProcessing,
outputProcessing,
} = require("../../utilities/rowProcessor")
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}`
@ -64,7 +67,7 @@ exports.patch = async function(ctx) {
row[key] = patchfields[key]
}
row = coerceRowValues(row, table)
row = inputProcessing(ctx.user, table, row)
const validateResult = await validate({
row,
@ -134,7 +137,7 @@ exports.save = async function(ctx) {
const table = await db.get(row.tableId)
row = coerceRowValues(row, table)
row = inputProcessing(ctx.user, table, row)
const validateResult = await validate({
row,
@ -204,7 +207,7 @@ exports.fetchView = async function(ctx) {
schema: {},
}
}
ctx.body = await enrichRows(appId, table, response.rows)
ctx.body = await outputProcessing(appId, table, response.rows)
}
if (calculation === CALCULATION_TYPES.STATS) {
@ -247,7 +250,7 @@ exports.fetchTableRows = async function(ctx) {
)
rows = response.rows.map(row => row.doc)
}
ctx.body = await enrichRows(appId, table, rows)
ctx.body = await outputProcessing(appId, table, rows)
}
exports.find = async function(ctx) {
@ -256,7 +259,7 @@ exports.find = async function(ctx) {
try {
const table = await db.get(ctx.params.tableId)
const row = await findRow(db, appId, ctx.params.tableId, ctx.params.rowId)
ctx.body = await enrichRows(appId, table, row)
ctx.body = await outputProcessing(appId, table, row)
} catch (err) {
ctx.throw(400, err)
}
@ -341,7 +344,7 @@ exports.fetchEnrichedRow = async function(ctx) {
keys: linkVals.map(linkVal => linkVal.id),
})
// need to include the IDs in these rows for any links they may have
let linkedRows = await enrichRows(
let linkedRows = await outputProcessing(
appId,
table,
response.rows.map(row => row.doc)

View File

@ -7,9 +7,31 @@ const {
PermissionLevels,
PermissionTypes,
} = require("../../utilities/security/permissions")
const joiValidator = require("../../middleware/joi-validator")
const Joi = require("joi")
const router = Router()
function generateSaveValidator() {
// prettier-ignore
return joiValidator.body(Joi.object({
_id: Joi.string(),
_rev: Joi.string(),
type: Joi.string().valid("table"),
primaryDisplay: Joi.string(),
schema: Joi.object().required(),
name: Joi.string().required(),
views: Joi.object(),
autoColumns: Joi.object({
createdBy: Joi.boolean(),
createdAt: Joi.boolean(),
updatedBy: Joi.boolean(),
updatedAt: Joi.boolean(),
}),
dataImport: Joi.object(),
}).unknown(true))
}
router
.get("/api/tables", authorized(BUILDER), tableController.fetch)
.get(
@ -23,6 +45,7 @@ router
// allows control over updating a table
bodyResource("_id"),
authorized(BUILDER),
generateSaveValidator(),
tableController.save
)
.post(

View File

@ -2,7 +2,7 @@ const CouchDB = require("../db")
const emitter = require("../events/index")
const InMemoryQueue = require("../utilities/queue/inMemoryQueue")
const { getAutomationParams } = require("../db/utils")
const { coerceValue } = require("../utilities")
const { coerce } = require("../utilities/rowProcessor")
let automationQueue = new InMemoryQueue("automationQueue")
@ -240,8 +240,8 @@ module.exports.externalTrigger = async function(automation, params) {
// values are likely to be submitted as strings, so we shall convert to correct type
const coercedFields = {}
const fields = automation.definition.trigger.inputs.fields
for (let key in fields) {
coercedFields[key] = coerceValue(params.fields[key], fields[key])
for (let key of Object.keys(fields)) {
coercedFields[key] = coerce(params.fields[key], fields[key])
}
params.fields = coercedFields
}

View File

@ -3,60 +3,9 @@ const { DocumentTypes, SEPARATOR } = require("../db/utils")
const fs = require("fs")
const { cloneDeep } = require("lodash/fp")
const CouchDB = require("../db")
const { OBJ_STORE_DIRECTORY } = require("../constants")
const linkRows = require("../db/linkedRows")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
/**
* A map of how we convert various properties in rows to each other based on the row type.
*/
const TYPE_TRANSFORM_MAP = {
link: {
"": [],
[null]: [],
[undefined]: undefined,
},
options: {
"": "",
[null]: "",
[undefined]: undefined,
},
string: {
"": "",
[null]: "",
[undefined]: undefined,
},
longform: {
"": "",
[null]: "",
[undefined]: undefined,
},
number: {
"": null,
[null]: null,
[undefined]: undefined,
parse: n => parseFloat(n),
},
datetime: {
"": null,
[undefined]: undefined,
[null]: null,
},
attachment: {
"": [],
[null]: [],
[undefined]: undefined,
},
boolean: {
"": null,
[null]: null,
[undefined]: undefined,
true: true,
false: false,
},
}
function confirmAppId(possibleAppId) {
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
? possibleAppId
@ -159,43 +108,6 @@ exports.walkDir = (dirPath, callback) => {
}
}
/**
* This will coerce a value to the correct types based on the type transform map
* @param {object} row The value to coerce
* @param {object} type The type fo coerce to
* @returns {object} The coerced value
*/
exports.coerceValue = (value, type) => {
// eslint-disable-next-line no-prototype-builtins
if (TYPE_TRANSFORM_MAP[type].hasOwnProperty(value)) {
return TYPE_TRANSFORM_MAP[type][value]
} else if (TYPE_TRANSFORM_MAP[type].parse) {
return TYPE_TRANSFORM_MAP[type].parse(value)
}
return value
}
/**
* This will coerce the values in a row to the correct types based on the type transform map and the
* table schema.
* @param {object} row The row which is to be coerced to correct values based on schema, this input
* row will not be updated.
* @param {object} table The table that has been retrieved from DB, this must contain the expected
* schema for the rows.
* @returns {object} The updated row will be returned with all values coerced.
*/
exports.coerceRowValues = (row, table) => {
const clonedRow = cloneDeep(row)
for (let [key, value] of Object.entries(clonedRow)) {
const field = table.schema[key]
if (!field) continue
clonedRow[key] = exports.coerceValue(value, field.type)
}
return clonedRow
}
exports.getLogoUrl = () => {
return "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg"
}
@ -213,34 +125,3 @@ exports.getAllApps = async () => {
.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
}

View File

@ -0,0 +1,119 @@
const env = require("../environment")
const { OBJ_STORE_DIRECTORY } = require("../constants")
const linkRows = require("../db/linkedRows")
const { cloneDeep } = require("lodash/fp")
/**
* A map of how we convert various properties in rows to each other based on the row type.
*/
const TYPE_TRANSFORM_MAP = {
link: {
"": [],
[null]: [],
[undefined]: undefined,
},
options: {
"": "",
[null]: "",
[undefined]: undefined,
},
string: {
"": "",
[null]: "",
[undefined]: undefined,
},
longform: {
"": "",
[null]: "",
[undefined]: undefined,
},
number: {
"": null,
[null]: null,
[undefined]: undefined,
parse: n => parseFloat(n),
},
datetime: {
"": null,
[undefined]: undefined,
[null]: null,
},
attachment: {
"": [],
[null]: [],
[undefined]: undefined,
},
boolean: {
"": null,
[null]: null,
[undefined]: undefined,
true: true,
false: false,
},
}
/**
* This will coerce a value to the correct types based on the type transform map
* @param {any} value The value to coerce
* @param {string} type The type fo coerce to
* @returns {any} The coerced value
*/
exports.coerce = (value, type) => {
// eslint-disable-next-line no-prototype-builtins
if (TYPE_TRANSFORM_MAP[type].hasOwnProperty(value)) {
return TYPE_TRANSFORM_MAP[type][value]
} else if (TYPE_TRANSFORM_MAP[type].parse) {
return TYPE_TRANSFORM_MAP[type].parse(value)
}
return value
}
/**
* Given an input route this function will apply all the necessary pre-processing to it, such as coercion
* of column values or adding auto-column values.
* @param {object} user the user which is performing the input.
* @param {object} row the row which is being created/updated.
* @param {object} table the table which the row is being saved to.
* @returns {object} the row which has been prepared to be written to the DB.
*/
exports.inputProcessing = (user, table, row) => {
const clonedRow = cloneDeep(row)
for (let [key, value] of Object.entries(clonedRow)) {
const field = table.schema[key]
if (!field) continue
clonedRow[key] = exports.coerce(value, field.type)
}
return clonedRow
}
/**
* 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.outputProcessing = async (appId, table, rows) => {
// attach any linked row information
const outputRows = 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 outputRows) {
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 outputRows
}